3DS-RPC/server/backend.py
Spotlight f478bd3fc5
Migrate to enum-oriented functions
By migrating information such as the bot's friend codes and database column names, a significant amount of boilerplate can be reduced.
2024-07-21 00:04:30 -05:00

208 lines
7.5 KiB
Python

# Created by Deltaion Lee (MCMi460) on Github
# Based from NintendoClients' `examples/3ds/friends.py`
from nintendo import nasc
from nintendo.nex import backend, friends, settings, streams
from nintendo.nex import common
from enum import Enum
import anyio, time, sqlite3, sys, traceback, argparse
sys.path.append('../')
from api.private import SERIAL_NUMBER, MAC_ADDRESS, DEVICE_CERT, DEVICE_NAME, REGION, LANGUAGE, NINTENDO_PID, PRETENDO_PID, PID_HMAC, NINTENDO_NEX_PASSWORD, PRETENDO_NEX_PASSWORD
from api import *
from api.love2 import *
from api.networks import NetworkType, InvalidNetworkError
import logging
logging.basicConfig(level=logging.INFO)
delay = 2
since = 0
quicker = 15
begun = time.time()
scrape_only = False
network: NetworkType = NetworkType.NINTENDO
async def main():
while True:
time.sleep(1)
print('Grabbing new friends...')
with sqlite3.connect('sqlite/fcLibrary.db') as con:
cursor = con.cursor()
cursor.execute('SELECT friendCode, lastAccessed FROM ' + network.column_name())
result = cursor.fetchall()
if not result:
continue
list_ = [ (convertFriendCodeToPrincipalId(f[0]), f[1]) for f in result ]
lst = [ f[0] for f in list_ ]
for i in range(0, len(lst), 100):
rotation = lst[i:i+100]
try:
client = nasc.NASCClient()
client.set_title(0x0004013000003202, 20) # to be honest, this should be seperate between networks, so if one eg, gets banned, you still get to keep the friend code for the other network, but i'm lazy.
client.set_locale(REGION, LANGUAGE)
if network == NetworkType.NINTENDO:
client.set_url("nasc.nintendowifi.net")
PID = NINTENDO_PID
NEX_PASSWORD = NINTENDO_NEX_PASSWORD
elif network == NetworkType.PRETENDO:
client.set_url("nasc.pretendo.cc")
client.context.set_authority(None)
PID = PRETENDO_PID
NEX_PASSWORD = PRETENDO_NEX_PASSWORD
else:
raise InvalidNetworkError(f"Network type {network} is not configured for querying")
client.set_device(SERIAL_NUMBER, MAC_ADDRESS, DEVICE_CERT, DEVICE_NAME)
client.set_user(PID , PID_HMAC)
response = await client.login(0x3200)
s = settings.load('friends')
s.configure("ridfebb9", 20000)
async with backend.connect(s, response.host, response.port) as be:
async with be.login(str(PID), NEX_PASSWORD) as client:
friends_client = friends.FriendsClientV1(client)
if time.time() - begun < 30:
time.sleep(delay)
await friends_client.update_comment('3dsrpc.com')
since = time.time()
if time.time() - since > 3600:
break
time.sleep(delay)
print('Cleaning out to zero')
removables = await friends_client.get_all_friends()
for friend in removables:
time.sleep(delay / quicker)
await friends_client.remove_friend_by_principal_id(friend.pid)
print('Removed %s friends' % str(len(removables)))
removeList = []
cleanUp = []
# The add_friend_by_principal_ids method is not yet
# implemented on Pretendo, so this is a fix for now.
if network == NetworkType.PRETENDO:
for friend_pid in rotation:
time.sleep(delay / quicker)
await friends_client.add_friend_by_principal_id(0, friend_pid)
else:
time.sleep(delay)
await friends_client.add_friend_by_principal_ids(0, rotation)
time.sleep(delay)
t = await friends_client.get_all_friends()
if len(t) < len(rotation):
for ID in rotation:
if ID not in [ f.pid for f in t ]:
removeList.append(ID)
x = t
t = []
for t1 in x:
if t1.pid in rotation:
t.append(t1)
else:
cleanUp.append(t1.pid)
for remover in removeList:
column_name = network.column_name()
cursor.execute('DELETE FROM ' + column_name + ' WHERE friendCode = ?', (str(convertPrincipalIdtoFriendCode(remover)).zfill(12),))
cursor.execute('DELETE FROM discordFriends WHERE friendCode = ? AND network = ?', (str(convertPrincipalIdtoFriendCode(remover)).zfill(12), str(network)))
con.commit()
if len(t) > 0:
time.sleep(delay)
f = await friends_client.get_friend_presence([ e.pid for e in t ])
users = []
for game in f:
if scrape_only: # Set all to offline if scraping
break
# game.unk == principalId
users.append(game.pid)
#print(game.__dict__)
#print(game.presence.__dict__)
#print(game.presence.game_key.__dict__)
gameDescription = game.presence.game_mode_description
if not gameDescription: gameDescription = ''
joinable = bool(game.presence.join_availability_flag)
cursor.execute('UPDATE ' + network.column_name() + ' SET online = ?, titleID = ?, updID = ?, joinable = ?, gameDescription = ?, lastOnline = ? WHERE friendCode = ?', (True, game.presence.game_key.title_id, game.presence.game_key.title_version, joinable, gameDescription, time.time(), str(convertPrincipalIdtoFriendCode(users[-1])).zfill(12)))
for user in [ h for h in rotation if not h in users ]:
cursor.execute('UPDATE ' + network.column_name() + ' SET online = ?, titleID = ?, updID = ? WHERE friendCode = ?', (False, 0, 0, str(convertPrincipalIdtoFriendCode(user)).zfill(12)))
con.commit()
# I just do not understand what I'm doing wrong with get_friend_mii_list
# The docs do not specify much
# And no matter how many trials I do with varying inputs, nothing works
# I do not give up, but until I figure it out, the slower method (get_friend_mii)
# will have to do.
for ti in t:
work = False
for l in list_:
if (l[0] == ti.pid and time.time() - l[1] <= 600) or scrape_only:
work = True
if not work:
continue
time.sleep(delay)
ti.friend_code = 0 # A cursed (but operable) 'hack'
try:
j1 = await friends_client.get_friend_persistent_info([ti.pid,])
except:
continue
comment = j1[0].message
jeuFavori = 0
username = ''
face = ''
if not comment.endswith(' '):
# Get user's mii + username from mii
m = await friends_client.get_friend_mii([ti,])
username = m[0].mii.name
mii_data = m[0].mii.mii_data
obj = MiiData()
obj.decode(obj.convert(io.BytesIO(mii_data)))
face = obj.mii_studio()['data']
# Get user's favorite game
jeuFavori = j1[0].game_key.title_id
else:
comment = ''
cursor.execute('UPDATE ' + network.column_name() + ' SET username = ?, message = ?, mii = ?, jeuFavori = ? WHERE friendCode = ?', (username, comment, face, jeuFavori, str(convertPrincipalIdtoFriendCode(ti.pid)).zfill(12)))
con.commit()
for friend in rotation + cleanUp:
time.sleep(delay / quicker)
await friends_client.remove_friend_by_principal_id(friend)
except Exception as e:
print('An error occurred!\n%s' % e)
print(traceback.format_exc())
time.sleep(2)
if scrape_only:
print('Done scraping.')
break
if __name__ == '__main__':
try:
parser = argparse.ArgumentParser()
parser.add_argument('-n', '--network', choices=[member.lower_name() for member in NetworkType], required=True)
args = parser.parse_args()
network = NetworkType[args.network.upper()]
startDBTime(begun, network)
anyio.run(main)
except (KeyboardInterrupt, Exception) as e:
if network is not None:
startDBTime(0, network)
print(e)