Skip to content

Commit c08252b

Browse files
committed
t
1 parent e98213f commit c08252b

28 files changed

Lines changed: 1178 additions & 391 deletions

.github/workflows/actions.yml

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
name: 'Build NSO-RPC - x86_64'
2+
on:
3+
release:
4+
types: [published]
5+
6+
jobs:
7+
build-x86_64:
8+
name: 'Build NSO-RPC - x86_64'
9+
runs-on: ${{ matrix.os }}
10+
strategy:
11+
fail-fast: false
12+
matrix:
13+
os: ['windows-latest', 'ubuntu-latest', 'macos-latest']
14+
steps:
15+
- uses: actions/checkout@v3
16+
- uses: actions/setup-python@v3
17+
with:
18+
python-version: 3.10.11
19+
20+
# Windows Build
21+
- name: "Build"
22+
if: matrix.os == 'windows-latest'
23+
run: |
24+
cd scripts &&
25+
python -m pip install pyqt6 &&
26+
./build.bat
27+
28+
- name: "Upload Build"
29+
if: matrix.os == 'windows-latest'
30+
uses: softprops/action-gh-release@v0.1.15
31+
with:
32+
files: client/dist/NSO-RPC.exe
33+
34+
# Linux Build
35+
- name: "Upload script"
36+
if: matrix.os == 'ubuntu-latest'
37+
uses: softprops/action-gh-release@v0.1.15
38+
with:
39+
files: scripts/linux.sh
40+
41+
# MacOS Build
42+
- name: "Build"
43+
if: matrix.os == 'macos-latest'
44+
run: |
45+
cd scripts &&
46+
python -m pip install pyqt6 &&
47+
bash ./build.sh &&
48+
cd ../client/dist &&
49+
ln -s /Applications "Applications (admin)" &&
50+
hdiutil create -fs HFS+ -srcfolder . -volname NSO-RPC mac-installer.dmg &&
51+
zip -yr mac-portable.zip NSO-RPC.app/
52+
53+
- name: "Upload Build"
54+
if: matrix.os == 'macos-latest'
55+
uses: softprops/action-gh-release@v0.1.15
56+
with:
57+
files: |
58+
client/dist/mac-installer.dmg
59+
client/dist/mac-portable.zip
60+
61+
build-universal2:
62+
name: 'Build NSO-RPC - Universal2'
63+
runs-on: macos-latest
64+
steps:
65+
- uses: actions/checkout@v3
66+
67+
# MacOS Build
68+
- name: "Install Python 3.10.11 and build NSO-RPC"
69+
run: |
70+
curl https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg -o python-3.10.11-macos11.pkg
71+
sudo installer -verbose -pkg python-3.10.11-macos11.pkg -target / &&
72+
python3.10 -m pip install PyQt6 &&
73+
cd scripts/macos-universal2 &&
74+
bash ./build.sh &&
75+
python3 debloat-qt.py &&
76+
cd ../../client/dist &&
77+
ln -s /Applications "Applications (admin)" &&
78+
hdiutil create -fs HFS+ -srcfolder . -volname NSO-RPC mac-universal2-installer.dmg &&
79+
zip -yr mac-universal2-portable.zip NSO-RPC.app/
80+
81+
- name: "Upload NSO-RPC Universal2 Build"
82+
uses: softprops/action-gh-release@v0.1.15
83+
with:
84+
files: |
85+
client/dist/mac-universal2-installer.dmg
86+
client/dist/mac-universal2-portable.zip
87+
88+
get-hashes:
89+
runs-on: "ubuntu-latest"
90+
needs: ["build-x86_64", "build-universal2"]
91+
steps:
92+
- name: "Generate checksums.txt"
93+
uses: MCJack123/ghaction-generate-release-hashes@v4
94+
with:
95+
hash-type: sha256
96+
file-name: checksums.txt
97+
get-assets: true
98+
- uses: softprops/action-gh-release@v0.1.15
99+
with:
100+
files: checksums.txt

.gitignore

100644100755
File mode changed.

.pre-commit-config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/mirrors-autopep8
3+
rev: v2.0.2
4+
hooks:
5+
- id: autopep8
6+
exclude:
7+
client/layout/

README.md

100644100755
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ Once ran, the app will ask for you to log into your Nintendo account on a web br
6767
***Q: I can't link my Nintendo Account. What do I do?***
6868
**A:** Refer to the question above.
6969

70+
***Q: My status is displaying as offline and won't change!***
71+
**A:** First, [make sure that you have a secondary account linked](#quick) and have selected your main account from the friends list. If you've done that and you're still having problems with an offline status, *make sure that both settings in your user profile (play activity and display online status settings) are set to "all friends"*.
72+
73+
***Q: I keep getting Error Code 9407; what should I do?***
74+
**A:** You're going to have to link your account with a real Nintendo Switch at least once in order to use the API and add your main account as a friend. (See [#73](https://github.com/MCMi460/NSO-RPC/issues/73))
75+
7076
*I am not liable for any sort of rate limiting Nintendo may hammer upon your network*
7177

7278
<h1 id = 'depth'>In-depth guide</h1>

client/api/__init__.py

100644100755
Lines changed: 64 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import re
1414
import pickle
1515

16+
1617
def getAppPath():
1718
applicationPath = os.path.expanduser('~/Documents/NSO-RPC')
1819
# Windows allows you to move your UserProfile subfolders, Such as Documents, Videos, Music etc.
@@ -21,14 +22,16 @@ def getAppPath():
2122
if platform.system() == 'Windows':
2223
try:
2324
import ctypes.wintypes
24-
CSIDL_PERSONAL = 5 # My Documents
25-
SHGFP_TYPE_CURRENT = 0 # Get current, not default value
26-
buf=ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
25+
CSIDL_PERSONAL = 5 # My Documents
26+
SHGFP_TYPE_CURRENT = 0 # Get current, not default value
27+
buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
2728
ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf)
28-
applicationPath = os.path.join(buf.value,'NSO-RPC')
29-
except:pass
29+
applicationPath = os.path.join(buf.value, 'NSO-RPC')
30+
except:
31+
pass
3032
return applicationPath
3133

34+
3235
def log(info, time = time.time()):
3336
path = getAppPath()
3437
if not os.path.isdir(path):
@@ -37,6 +40,7 @@ def log(info, time = time.time()):
3740
file.write('%s: %s\n' % (time, info))
3841
return info
3942

43+
4044
def getVersion():
4145
for i in range(5):
4246
try:
@@ -53,23 +57,25 @@ def getVersion():
5357
return ''
5458
return version[0]
5559

60+
5661
client_id = '71b963c1b7b6d119'
5762
version = 0.3
5863
nsoAppVersion = None
59-
languages = [ # ISO Language codes
60-
'en-US',
61-
'es-MX',
62-
'fr-CA',
63-
'ja-JP',
64-
'en-GB',
65-
'es-ES',
66-
'fr-FR',
67-
'de-DE',
68-
'it-IT',
69-
'nl-NL',
70-
'ru-RU'
64+
languages = [ # ISO Language codes
65+
'en-US',
66+
'es-MX',
67+
'fr-CA',
68+
'ja-JP',
69+
'en-GB',
70+
'es-ES',
71+
'fr-FR',
72+
'de-DE',
73+
'it-IT',
74+
'nl-NL',
75+
'ru-RU'
7176
]
7277

78+
7379
class API():
7480
def __init__(self, session_token, user_lang, targetID, version):
7581
pattern = re.compile(r'(\d.\d.\d)')
@@ -142,7 +148,7 @@ def updateLogin(self):
142148
login = Login(self.userInfo, self.user_lang, self.accessToken, self.guid)
143149
login.loginToAccount()
144150
try:
145-
self.headers['Authorization'] = 'Bearer %s' % login.account['result'].get('webApiServerCredential').get('accessToken') # Add authorization token
151+
self.headers['Authorization'] = 'Bearer %s' % login.account['result'].get('webApiServerCredential').get('accessToken') # Add authorization token
146152
except Exception as e:
147153
raise Exception('Failure with authorization: %s\nLogin returns %s' % (e, login.account))
148154
self.login = {
@@ -169,6 +175,7 @@ def getFriends(self):
169175
list.populateList(self)
170176
self.friends = list.friendList
171177

178+
172179
class Nintendo():
173180
def __init__(self, sessionToken, userLang):
174181
self.headers = {
@@ -190,6 +197,7 @@ def getServiceToken(self):
190197
response = requests.post(self.url + route, headers = self.headers, json = self.body)
191198
return json.loads(response.text)
192199

200+
193201
class UsersMe():
194202
def __init__(self, accessToken, userLang):
195203
self.headers = {
@@ -209,15 +217,17 @@ def get(self):
209217
response = requests.get(self.url + route, headers = self.headers)
210218
return json.loads(response.text)
211219

220+
212221
class imink():
213-
def __init__(self, id_token, timestamp, guid, iteration):
222+
def __init__(self, na_id, id_token, timestamp, guid, iteration):
214223
self.headers = {
215224
'User-Agent': 'NSO-RPC/%s' % version,
216225
'Content-Type': 'application/json; charset=utf-8',
217226
}
218227
self.body = {
219228
'token': id_token,
220-
'hashMethod': str(iteration),
229+
'hash_method': str(iteration),
230+
'na_id': na_id,
221231
}
222232

223233
self.url = 'https://api.imink.app'
@@ -229,6 +239,7 @@ def get(self):
229239
response = requests.post(self.url + route, headers = self.headers, data = json.dumps(self.body))
230240
return json.loads(response.text)
231241

242+
232243
class Login():
233244
def __init__(self, userInfo, userLang, accessToken, guid):
234245
self.headers = {
@@ -245,13 +256,14 @@ def __init__(self, userInfo, userLang, accessToken, guid):
245256
}
246257

247258
self.url = 'https://api-lp1.znc.srv.nintendo.net'
248-
self.timestamp = int(time.time()) * 1000 # Convert from iOS to Android
259+
self.timestamp = int(time.time()) * 1000 # Convert from iOS to Android
249260
self.guid = guid
250261

251262
self.userInfo = userInfo
252263
self.accessToken = accessToken
264+
self.na_id = userInfo['id']
253265

254-
self.imink = imink(self.accessToken, self.timestamp, self.guid, 1).get()
266+
self.imink = imink(self.na_id, self.accessToken, self.timestamp, self.guid, 1).get()
255267
self.timestamp = int(self.imink['timestamp'])
256268
self.guid = self.imink['request_id']
257269

@@ -274,6 +286,7 @@ def loginToAccount(self):
274286
self.account = json.loads(response.text)
275287
return self.account
276288

289+
277290
class User():
278291
def __init__(self, f):
279292
self.id = f.get('id')
@@ -290,9 +303,10 @@ def __init__(self, f):
290303

291304
def description(self):
292305
return ('%s (id: %s, nsaId: %s):\n' % (self.name, self.id, self.nsaId)
293-
+ ' - Profile Picture: %s\n' % self.imageUri
294-
+ ' - Status: %s\n' % self.presence.description()
295-
)
306+
+ ' - Profile Picture: %s\n' % self.imageUri
307+
+ ' - Status: %s\n' % self.presence.description()
308+
)
309+
296310

297311
class Friend(User):
298312
def __init__(self, f):
@@ -304,26 +318,28 @@ def __init__(self, f):
304318

305319
def description(self):
306320
return ('%s (id: %s, nsaId: %s):\n' % (self.name, self.id, self.nsaId)
307-
+ ' - Profile Picture: %s\n' % self.imageUri
308-
+ ' - Is Favorite: %s\n' % self.isFavoriteFriend
309-
+ ' - Friend Creation Date: %s\n' % self.friendCreatedAt
310-
+ ' - Status: %s\n' % self.presence.description()
311-
)
321+
+ ' - Profile Picture: %s\n' % self.imageUri
322+
+ ' - Is Favorite: %s\n' % self.isFavoriteFriend
323+
+ ' - Friend Creation Date: %s\n' % self.friendCreatedAt
324+
+ ' - Status: %s\n' % self.presence.description()
325+
)
326+
312327

313328
class FriendList():
314329
def __init__(self):
315-
self.route = '/v3/Friend/List' # Define API route
330+
self.route = '/v3/Friend/List' # Define API route
316331

317-
self.friendList = [] # List of Friend object(s)
332+
self.friendList = [] # List of Friend object(s)
318333

319-
def populateList(self, API:API):
334+
def populateList(self, API: API):
320335
response = API.makeRequest(self.route)
321336
try:
322337
arr = json.loads(response.text)['result']['friends']
323338
except Exception as e:
324339
log('Failure with authorization: %s\Friends returns %s' % (e, response.text))
325340
raise e
326-
self.friendList = [ Friend(friend) for friend in arr ]
341+
self.friendList = [Friend(friend) for friend in arr]
342+
327343

328344
class Presence():
329345
def __init__(self, f):
@@ -334,8 +350,9 @@ def __init__(self, f):
334350

335351
def description(self):
336352
return ('%s (updatedAt: %s, logoutAt: %s)\n' % (self.state, self.updatedAt, self.logoutAt)
337-
+ ' - Game: %s' % self.game.description()
338-
)
353+
+ ' - Game: %s' % self.game.description()
354+
)
355+
339356

340357
class Game():
341358
def __init__(self, f):
@@ -348,11 +365,12 @@ def __init__(self, f):
348365

349366
def description(self):
350367
return ('%s (sysDescription: %s)\n' % (self.name, self.sysDescription)
351-
+ ' - Game Icon: %s\n' % self.imageUri
352-
+ ' - Shop Uri: %s\n' % self.shopUri
353-
+ ' - Total Play Time: %s\n' % self.totalPlayTime
354-
+ ' - First Played At: %s' % self.firstPlayedAt
355-
)
368+
+ ' - Game Icon: %s\n' % self.imageUri
369+
+ ' - Shop Uri: %s\n' % self.shopUri
370+
+ ' - Total Play Time: %s\n' % self.totalPlayTime
371+
+ ' - First Played At: %s' % self.firstPlayedAt
372+
)
373+
356374

357375
class Session():
358376
def __init__(self):
@@ -397,11 +415,11 @@ def run(self, code, verify):
397415
headers = self.headers
398416
headers.update({
399417
'Accept-Language': 'en-US',
400-
'Accept': 'application/json',
401-
'Content-Type': 'application/x-www-form-urlencoded',
402-
'Content-Length': '540',
403-
'Host': 'accounts.nintendo.com',
404-
'Connection': 'Keep-Alive',
418+
'Accept': 'application/json',
419+
'Content-Type': 'application/x-www-form-urlencoded',
420+
'Content-Length': '540',
421+
'Host': 'accounts.nintendo.com',
422+
'Connection': 'Keep-Alive',
405423
})
406424
body = {
407425
'client_id': client_id,

0 commit comments

Comments
 (0)