{"id":13514602,"url":"https://github.com/MCMi460/NSO-RPC","last_synced_at":"2025-03-31T03:31:03.505Z","repository":{"id":37021071,"uuid":"474245793","full_name":"MCMi460/NSO-RPC","owner":"MCMi460","description":"Connect your Nintendo Switch playing status to Discord!","archived":false,"fork":false,"pushed_at":"2024-12-27T22:37:55.000Z","size":1127,"stargazers_count":341,"open_issues_count":12,"forks_count":32,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-03-24T07:02:01.365Z","etag":null,"topics":["discord","discord-rich-presence","discord-rpc","nintendo-switch","rich-presence","rpc","switch"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MCMi460.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-26T04:57:09.000Z","updated_at":"2025-03-23T11:19:19.000Z","dependencies_parsed_at":"2023-02-15T18:36:21.410Z","dependency_job_id":"ea77fa36-d50d-41b8-9ae8-af67b26665ff","html_url":"https://github.com/MCMi460/NSO-RPC","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MCMi460%2FNSO-RPC","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MCMi460%2FNSO-RPC/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MCMi460%2FNSO-RPC/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MCMi460%2FNSO-RPC/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MCMi460","download_url":"https://codeload.github.com/MCMi460/NSO-RPC/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246413377,"owners_count":20773053,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["discord","discord-rich-presence","discord-rpc","nintendo-switch","rich-presence","rpc","switch"],"created_at":"2024-08-01T05:00:58.364Z","updated_at":"2025-03-31T03:31:03.497Z","avatar_url":"https://github.com/MCMi460.png","language":"Python","readme":"# Nintendo Switch Online Rich Presence\n\n## ⚠️ Disclaimer\nNintendo has recently updated Nintendo Switch Online (NSO), breaking support for all third-party tools. Apps like s3s, nxapi, NSO-RPC and others are currently not working.\n\nUpdating authentication credentials **isn’t possible** at the moment, and these services may remain unusable.\n\n**_Do not contact Nintendo about this issue._**\n\n---\n\n*Display your Nintendo Switch game status on Discord!*\n\nThis README will be split into two sections:\n  - [The quickstart guide](#quick)\n  - [In-depth guide](#depth)\n\n### Credits\n\nThis project uses the Nintendo Switch Online Mobile App API.  \nI'd like to thank:\n- [NintendoSwitchRESTAPI](https://github.com/ZekeSnider/NintendoSwitchRESTAPI) developer(s) (for very useful blueprint designs)\n- [frozenpandaman](https://github.com/frozenpandaman) and his [s2s][s2s] API (he is the reason all of this works)\n- [JoneWang](https://github.com/JoneWang) and his [imink][imink] API. He is crucial to some of the authentication steps performed\n- [blackgear](https://github.com/blackgear)'s [NSOnline_API](https://github.com/blackgear/NSOnline_API) (he was integral to my understanding of `session_token` authentication)\n- [qwerty](https://github.com/qwertyquerty) for her [pypresence](https://github.com/qwertyquerty/pypresence)\n- [samuelthomas2774](https://github.com/samuelthomas2774) for the tremendous amounts of help he provides to this project's issues. Check out his [nxapi here](https://github.com/samuelthomas2774/nxapi)!\n- [anthonybaldwin](https://github.com/anthonybaldwin) for being awesome and helping out this project a ton!\n\n\u003ch1 id = 'quick'\u003eQuickstart Guide\u003c/h1\u003e\n\nDownload the app from the [latest release](https://github.com/MCMi460/NSO-RPC/releases) and run!  \nOnce ran, the app will ask for you to log into your Nintendo account on a web browser. There is no malicious code with intent to steal your information, but it's best to [review the code][api] for yourself.\n\n1. Open Discord and NSO-RPC\n\n  - You will need a secondary account that is friended with your account in order to \"Target\" the intended profile's presence. Due to changes in Nintendo's API ([#13](https://github.com/MCMi460/NSO-RPC/issues/13)), it is impossible to return a user's self presence, and instead can only send a user's friend list. Therefore, we have implemented a workaround to \"Target\" other users to provide rich presence information.\n\n2. Log in to your Nintendo account when prompted\n\n3. Right click on 'Select this account' and press 'Copy Link'\n\n![link](/resources/link.png)\n\n4. Paste the link in the pop-up's form and click 'Log In'\n\n5. Control your rich presence from the app and system tray icon\n\n![display](/resources/display.png)\n\n## FAQ\n\n\u003e If none of the below Qs and As help with your problem, feel free to [file an issue](https://github.com/MCMi460/NSO-RPC/issues/new). Alternatively, you can join the [NSO-RPC Discord server](https://discord.gg/pwFASr2NKx) for a better back-and-forth method of communication with me!\n\n***Q: Do you need a Nintendo Switch Online subscription to use this app?***  \n**A:** No, you do not. This app works whether or not you have access to online services. You will, however, need to link your Nintendo account to your user profile on your Switch.\n\n***Q: My computer says that this app might have a virus! Does it?***  \n**A:** No. Your computer is saying that because it's a foreign executable file downloaded from the internet, so you should always be cautious about it. If you'd like, you can [build your own `exe`](#building).\n\n***Q: You're not stealing my account/data, are you?***  \n**A:** ~~Not me, personally. You'll have to ask [frozenpandaman](https://github.com/frozenpandaman) [(s2s)][s2s] and [@NexusMine (flapg)](https://twitter.com/NexusMine). They are responsible for some of the authentication steps.~~ This project now uses [imink API][imink] to provide for some authentication steps. [Read more here](#understanding), and be weary of any possible theft.\n\u003cul\u003e\u003cli\u003e\u003cdetails\u003e\n  \u003csummary\u003e\u003cb\u003e\u003ci\u003eWhat if I don't want to use imink?\u003c/i\u003e\u003c/b\u003e\u003c/summary\u003e\n\n  **A**: It is possible to tweak the code and remove the API calls, then instead only use temporary tokens you have provided for authorization headers. However, this is tedious and completely up to the user to perform- as the tokens expire after 7200 seconds (two hours) and are only obtainable through methods such as [mitmproxy](https://github.com/mitmproxy/mitmproxy)\n\n\u003c/details\u003e\u003c/li\u003e\u003c/ul\u003e\n\n***Q: Do I need Discord open on my PC to use this application?***  \n**A:** Yes. You need the desktop application open in order for the app to update your Discord status.\n\n***Q: Can I use the Discord website/run this on a Chromebook?***  \n**A:** No. The website does not have a method of updating your Rich Presence from games on your computer, so NSO-RPC will not work with it.\n\n***Q: I can't get the program to run, what's wrong with it?!***  \n**A:** Delete the NSO-RPC folder in your Documents folder. If that doesn't work, you should run the [cli.py][cli] program and get the error data, then make an [issue](https://github.com/MCMi460/NSO-RPC/issues) on Github and I'll investigate it.\n\n***Q: I can't link my Nintendo Account. What do I do?***  \n**A:** Refer to the question above.\n\n***Q: My status is displaying as offline and won't change!***  \n**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\"*.\n\n***Q: I keep getting Error Code 9407; what should I do?***  \n**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))\n\n*I am not liable for any sort of rate limiting Nintendo may hammer upon your network*\n\n\u003ch1 id = 'depth'\u003eIn-depth guide\u003c/h1\u003e\n\n\u003ch2 id = 'building'\u003eBuilding\u003c/h2\u003e\n\nFor Windows, run\n```bat\ncd .\\NSO-RPC\\scripts\n.\\build.bat\n```\nFor MacOS, run\n```sh\ncd ./NSO-RPC/scripts\nchmod +x build.sh\n./build.sh\n```\nFor Linux (Ubuntu), run\n```sh\ncd ./NSO-RPC/scripts\nchmod +x install.sh\n./install.sh\n```\n\n*(Make sure you have `python3` and `pip` installed)\n\n\u003ch2 id = 'understanding'\u003eUnderstanding\u003c/h2\u003e\n\nThis is going to be a detailed explanation on everything that I do here and exactly what is going on. If you're into that sort of stuff, keep reading. If not, feel free to skim and get a general idea of the procedures.  \nI try my best to be detailed and give a proper comprehensive guide, but I'm not perfect. Feel free to make an [issue](https://github.com/MCMi460/NSO-RPC/issues) if you feel anything in particular should be updated!\n\nI'm going to be explaining my [cli.py][cli] as it isn't as complicated as the [GUI (app.py)][app].  \n(You can follow along with the guide [here][api] and [here][cli])  \n\n**As of [8abf86c](https://github.com/MCMi460/NSO-RPC/commit/8abf86c6f4dca2d5cde7bf0886de6f1642b6dbef), this guide is outdated in regards to the APIs used.**\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003ch3\u003e1. Getting your \u003ccode\u003esession_token\u003c/code\u003e\u003c/h3\u003e\u003c/summary\u003e\n\n  First things first, we need to get access to your Nintendo account. What we need to get is your `session_token`, which is a unique identifier that confirms to Nintendo servers *you are you*. This is the code that gets your `session_token`.  \n  [cli.py][cli]:\n  ```python\n  path = os.path.expanduser('~/Documents/NSO-RPC/private.txt')\n    if not os.path.isfile(path):\n        session = Session()\n        session_token = session.run(*session.login(session.inputManually))\n    else:\n        with open(path, 'r') as file:\n            session_token = json.loads(file.read())['session_token']\n  ```\n  First, it checks if you already have a `session_token` saved. If so, then it just uses that.  \n  If not, then it will create a `Session()` object and call `Session().login()` (passing `Session().inputManually`) `Session().run()`.  \n  That's all fine and dandy, but what does it do behind the `Session().login()` and `Session.run()` functions?  \n  Glad you asked.\n\n  - `Session().__init__()`:\n\n    First, it sets some default headers and creates a `requests.Session()` (this is from the common Python library, [requests](https://github.com/psf/requests)).\n    ```python\n    self.headers = {\n      'Accept-Encoding': 'gzip',\n      'User-Agent': 'OnlineLounge/%s NASDKAPI Android' % nsoAppVersion,\n    }\n    self.Session = requests.Session()\n    ```\n\n  - `Session().login()`:\n\n    Now, we create some variables (as dictated from [s2s](https://github.com/frozenpandaman/splatnet2statink/blob/master/iksm.py)) for authorization. Basically just a bunch of random characters, but your guess is honestly as good as mine when it comes down to it, as I'm not an expert on oauth authentication.\n    ```python\n    state = base64.urlsafe_b64encode(os.urandom(36))\n    verify = base64.urlsafe_b64encode(os.urandom(32))\n    authHash = hashlib.sha256()\n    authHash.update(verify.replace(b'=', b''))\n    authCodeChallenge = base64.urlsafe_b64encode(authHash.digest())\n    ```\n    Here, it sets up authentication form, queries it, gets the URL, and opens it in the user's web browser.\n    ```python\n    url = 'https://accounts.nintendo.com/connect/1.0.0/authorize'\n    params = {\n      'client_id': client_id,\n      'redirect_uri': 'npf%s://auth' % client_id,\n      'response_type': 'session_token_code',\n      'scope': 'openid user user.birthday user.mii user.screenName',\n      'session_token_code_challenge': authCodeChallenge.replace(b'=', b''),\n      'session_token_code_challenge_method': 'S256',\n      'state': state,\n      'theme': 'login_form'\n    }\n    response = self.Session.get(url, headers = self.headers, params = params)\n\n    webbrowser.open(response.history[0].url)\n    ```\n    Finally, it comes to the user's input. We `re.compile()` the proper format of a return token (thank you, [blackgear](https://github.com/blackgear)). Then, using the input method specified in `Session().login()`, we receive the user's URL and `re.findall()` for the proper code.  \n    We'll then return the `code` and `verify` variables.\n    ```python\n    tokenPattern = re.compile(r'(eyJhbGciOiJIUzI1NiJ9\\.[a-zA-Z0-9_-]*\\.[a-zA-Z0-9_-]*)')\n    code = tokenPattern.findall(receiveInput())[0]\n\n    return code, verify\n    ```\n\n  - `Session().inputManually()`:\n\n    `Session().inputManually()` is literally just a redirect of the Python `input()` function:\n    ```python\n    def inputManually(self):\n      return input('After logging in, please copy the link from \\'Select this account\\' and enter it here:\\n')\n    ```\n\n  - `Session().run()`:\n\n    `Session().run()` returns the `session_token` in a finally usable format:\n    ```python\n    url = 'https://accounts.nintendo.com/connect/1.0.0/api/session_token'\n    headers = self.headers\n    headers.update({\n      'Accept-Language': 'en-US',\n      'Accept':          'application/json',\n      'Content-Type':    'application/x-www-form-urlencoded',\n      'Content-Length':  '540',\n      'Host':            'accounts.nintendo.com',\n      'Connection':      'Keep-Alive',\n    })\n    body = {\n      'client_id': client_id,\n      'session_token_code': code,\n      'session_token_code_verifier': verify.replace(b'=', b''),\n    }\n    response = self.Session.post(url, data = body, headers = headers)\n    return json.loads(response.text)['session_token']\n    ```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003ch3\u003e2. Connecting to Discord\u003c/h3\u003e\u003c/summary\u003e\n\n  We create a `Discord()` object and pass the newly obtained `session_token` (and `user_lang`) to it. This does not involve sending your `session_token` to Discord.  \n  [cli.py][cli]:\n  ```python\n  client = Discord(session_token, user_lang)\n  client.background()\n  ```\n\n  - `Discord().__init__()`:\n\n    First, it creates a `pypresence.Presence()` object and passes it my Discord Application ID (this has nothing important other than the name 'Nintendo Switch'; you can replace it with your own ID if you want)  \n    Then, it calls `Discord().connect()` to connect to the Discord client.  \n    We set the `Discord().running` and `Discord().gui` variables to `False`, then if the parameters `session_token` and `user_lang` are passed, it will call `Discord().createCTX()`.\n    ```python\n    self.rpc = None\n    if rpc:\n        if not self.connect():\n            sys.exit()\n    self.running = False\n    self.api = None\n    self.gui = False\n    if session_token and user_lang:\n        self.createCTX(session_token, user_lang)\n    ```\n\n  - `Discord().createCTX()`:\n\n    This function just creates an `API()` object and sets it to `Discord().api`. It also sets `Discord().running` to `True`.  \n    It requires a `session_token` and a `user_lang` to be passed.\n    ```python\n    try:\n      self.api = API(session_token, user_lang)\n    except Exception as e:\n      sys.exit(log(e))\n    self.running = True\n    ```\n\n  - `Discord().connect()`:\n\n    If this errors over 500 times, the application closes.\n    ```python\n    self.rpc = pypresence.Presence('637692124539650048')\n    fails = 0\n    while True:\n      # Attempt to connect to Discord. Will wait until it connects\n      try:\n        self.rpc.connect()\n        break\n      except Exception as e:\n        fails += 1\n        if fails \u003e 500:\n          sys.exit(log('Error, failed after 500 attempts\\n\\'%s\\'' % e))\n        continue\n    ```\n    - `Discord().disconnect()`:\n\n      Closes rich presence connection.\n      ```python\n      if self.rpc:\n          self.rpc.close()\n      self.rpc = None\n      ```\n\n  - `Discord().setApp()`:\n\n    This is only called by [GUI][app]. All it does is set the usable app function and assign `Discord().gui` to `True`.\n    ```python\n    def setApp(self, function):\n        self.app = function\n        self.gui = True\n    ```\n\n  - `Discord().update()`:\n\n    This updates the user's Discord Rich Presence. Will error if an `API()` object is not defined at `Discord().api`  \n    It basically just calls the API to grab the user's info, then if they are not currently offline, it will update the `Discord().rpc`.  \n    If it cannot get the user, it will attempt to login.  \n    If they are offline, then it will clear their status.  \n    If a `Game().sysDescription` is available, it will display that as the Discord state instead of hours played.  \n    If `Discord().gui` is `True`, it will run `Discord().app()`\n    ```python\n    for i in range(2):\n        try:\n            self.api.getSelf()\n            break\n        except Exception as e:\n            log(e)\n            if i \u003e 0 or time.time() - self.api.login['time'] \u003c 7170:\n                raise Exception('Cannot get session token properly')\n            self.api.updateLogin()\n            continue\n    self.nickname = self.api.userInfo['nickname']\n    self.user = self.api.user\n\n    presence = self.user.presence\n    if presence.game.name: # Please file an issue if this happens to fail\n        state = presence.game.sysDescription\n        if not state:\n            state = 'Played for %s hours or more' % (int(presence.game.totalPlayTime / 60 / 5) * 5)\n            if presence.game.totalPlayTime / 60 \u003c 5:\n                state = 'Played for a little while'\n        self.rpc.update(details = presence.game.name, large_image = presence.game.imageUri, large_text = presence.game.name, state = state)\n    else:\n        self.rpc.clear()\n    # Set GUI\n    if self.gui:\n        self.app(self.user)\n    ```\n\n  - `Discord().background()`:\n\n    This is the background task that runs the entire application. What we do here is that we update the user's status once every 30 seconds. And, uh, that's pretty much it. If `Discord().running` is not `True` then it will set the next update to be 5 seconds after `Discord().running` becomes `True` again (whenever you toggle the Discord option in the taskbar, this is what happens).\n    ```python\n    second = 30\n    while True:\n        if self.running:\n            if second == 30:\n                try:\n                    self.update()\n                except Exception as e:\n                    sys.exit(log(e))\n                second = 0\n            second += 1\n        else:\n            second = 25\n        time.sleep(1)\n    ```\n\n  - `Discord().logout()`:\n\n    Removes the configs in the config folder.\n    ```python\n    path = os.path.expanduser('~/Documents/NSO-RPC')\n    if os.path.isfile(os.path.join(path, 'private.txt')):\n        try:os.remove(os.path.join(path, 'private.txt'))\n        except:pass\n        try:os.remove(os.path.join(path, 'settings.txt'))\n        except:pass\n        sys.exit()\n    ```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003ch3\u003e3. Nintendo's API\u003c/h3\u003e\u003c/summary\u003e\n\n  Oh boy.\n\n  Alright, this gets complicated, but I'll try and cover it all quickly.  \n  *For code snippets, see [api/\\_\\_init\\_\\_.py][api]\n\n  - `API()`:\n\n    Has five functions: `API().__init__()`, `API().makeRequest()`, `API().updateLogin()`, `API().getSelf()`, and `API().getFriends()`.  \n\n    - `API().__init__()`:\n\n      This sets some headers to `API().headers` and assigns `Nintendo().getServiceToken()` to `API().tokenResponse` after passing `session_token` to it.  \n      Of all of the important things it retrieves, we only use `API().tokenResponse['access_token']`. We assign that to the 'Authorization' header.\n      ```python\n      self.headers['Authorization'] = 'Bearer %s' % self.accessToken # Add authorization token\n      ```\n      We also create a GUID (`uuid.uuid4()`)  \n      We set the default URL that isn't really used, then we set `API().userInfo` to `UsersMe().get()`, which used in `API().updateLogin()`.  \n      After that, we store the token in plaintext form in your `Documents/NSO-RPC` folder. This will likely not be changed as other methods are not really more secure.\n\n    - `API().makeRequest()`:\n\n      Makes a request to `https://api-lp1.znc.srv.nintendo.net` with a route specified.\n      ```python\n      def makeRequest(self, route):\n        return requests.post(self.url + route, headers = self.headers)\n      ```\n\n    - `API().updateLogin()`:\n\n      All this does is create/refresh your `Login()`. It will check a file in your `Documents/NSO-RPC` folder for an already existing temporary token so as to prevent excessive calling of the [s2s API][s2s].  \n      See `Login()` for more information.\n      ```python\n      path = os.path.expanduser('~/Documents/NSO-RPC/tempToken.txt')\n      if os.path.isfile(path):\n          with open(path, 'rb') as file:\n              self.login = pickle.loads(file.read())\n              self.headers['Authorization'] = 'Bearer %s' % self.login['login'].account['result'].get('webApiServerCredential').get('accessToken')\n              log('Login from file')\n      if time.time() - self.login['time'] \u003c 7170:\n          return\n      login = Login(self.userInfo, self.user_lang, self.accessToken, self.guid)\n      login.loginToAccount()\n      self.headers['Authorization'] = 'Bearer %s' % login.account['result'].get('webApiServerCredential').get('accessToken') # Add authorization token\n      self.login = {\n          'login': login,\n          'time': time.time(),\n      }\n      with open(path, 'wb') as file:\n          file.write(pickle.dumps(self.login))\n      ```\n\n    - `API().getSelf()`:\n\n      This makes a request for user data and assigns it to the `API().user` variable\n      ```python\n      route = '/v3/User/ShowSelf'\n\n      response = self.makeRequest(route)\n      self.user = User(json.loads(response.text)['result'])\n      ```\n\n    - `API().getFriends()`:\n\n      This makes a `FriendList()` object and calls `FriendList().populateList()`, then assigns `FriendList().friendList` to `API().friends`\n      ```python\n      list = FriendList()\n      list.populateList(self)\n      self.friends = list.friendList\n      ```\n\n  - `Nintendo()`:\n\n    This just makes an API call to Nintendo for a token. [Read more here](https://github.com/ZekeSnider/NintendoSwitchRESTAPI/blob/master/NintendoAccountBlueprint.md#service-token-connect100apitoken)\n\n    - `Nintendo().__init__()`:\n\n      Set a bunch of headers and the body of our request. Requires `session_token`.\n\n    - `Nintendo().getServiceToken()`:\n\n      Actually make the request, and return it in `JSON`.\n\n  - `UsersMe()`:\n\n    This gets vital information for the `Login()` class. It's one step before actually logging in.\n\n    - `UsersMe().__init__()`:\n\n      Sets headers and host url. Takes `accessToken` (different from `session_token`).\n\n    - `UsersMe().get()`:\n\n      Very original function name, but it just makes the request. It returns necessary information in `JSON` format, including the user's date of birth, country, and language.\n\n  - `Login()`:\n\n    - `Login().__init__()`:\n\n      Takes `userInfo, userLang, accessToken, guid`.  \n      Sets headers, URL, GUID, user's info, `accessToken`, `Flapg()` API, and the user's account.\n\n      Please take extreme caution and note of this piece of code.\n      ```python\n      self.flapg = Flapg(self.accessToken, self.timestamp, self.guid).get()\n      ```\n\n    - `Login().loginToAccount()`:\n\n      Pretty neat. `/v3` is necessary for the Presence information.\n      ```python\n      route = '/v3/Account/Login'\n      body = {\n        'parameter': {\n          'f': self.flapg['f'],\n          'naIdToken': self.flapg['p1'],\n          'timestamp': self.flapg['p2'],\n          'requestId': self.flapg['p3'],\n          'naCountry': self.userInfo['country'],\n          'naBirthday': self.userInfo['birthday'],\n          'language': self.userInfo['language'],\n        },\n      }\n      response = requests.post(self.url + route, headers = self.headers, json = body)\n      self.account = json.loads(response.text)\n      return self.account\n      ```\n\n  - `Flapg()`:\n\n    [Learn more about this here](https://github.com/frozenpandaman/splatnet2statink/wiki/api-docs#the-flapg-api)  \n    This is where it can get risky. We are sending off the user's `accessToken` (a temporary token) to not one, but two third-party APIs. This is what I mentioned in the FAQ about being weary to use this program. It is ran by [@NexusMine on Twitter](https://twitter.com/NexusMine).  \n    It is, however, necessary in order to call the `/v3/Account/Login` API, as it retrieves an important factor: The `f` token.  \n    Take particular notice of the `s2s()` call.\n\n    - `Flapg().__init__()`:\n\n      Takes `id_token, timestamp, guid`.\n      ```python\n      self.headers = {\n        'x-token': id_token,\n        'x-time': str(timestamp),\n        'x-guid': guid,\n        'x-hash': s2s(id_token, timestamp).getHash(),\n        'x-ver': '3',\n        'x-iid': 'nso',\n      }\n\n      self.url = 'https://flapg.com'\n      ```\n\n    - `Flapg().get()`:\n\n      This just connects to the flapg API and returns the result.\n      ```python\n      def get(self):\n        route = '/ika2/api/login?public'\n\n        response = requests.get(self.url + route, headers = self.headers)\n        return json.loads(response.text)['result']\n      ```\n\n  - `s2s()`:\n\n    [Learn more about this here][s2s]  \n\n    - `s2s().__init__()`:\n\n      Takes `id_token, timestamp`.\n      ```python\n      log('Login from Flapg/s2s')\n      self.headers = {\n        'Content-Type': 'application/x-www-form-urlencoded',\n        'User-Agent': 'NSO-RPC/%s' % version,\n      }\n      self.body = {\n        'naIdToken': id_token,\n        'timestamp': timestamp,\n      }\n      self.url = 'https://elifessler.com'\n      ```\n\n    - `s2s().getHash()`:\n\n      ```python\n      route = '/s2s/api/gen2'\n      response = requests.post(self.url + route, headers = self.headers, data = self.body)\n      return json.loads(response.text)['hash']\n      ```\n\n  - `FriendList()`:\n\n    Creates and stores a list of `Friend()` objects\n\n    - `FriendList().__init__()`:\n\n      Defines route and assigns empty list\n      ```python\n      self.route = '/v3/Friend/List' # Define API route\n\n      self.friendList = [] # List of Friend object(s)\n      ```\n\n    - `FriendList().populateList()`:\n\n      Requires the passing of an `API()` object.  \n      Calls `API().makeRequest()` with `FriendList().route`, then assigns the results as `Friend()` objects to `FriendList().friendList`\n      ```python\n      response = API.makeRequest(self.route)\n      arr = json.loads(response.text)['result']['friends']\n      self.friendList = [ Friend(friend) for friend in arr ]\n      ```\n\n  - `User()`:\n\n    This creates an easy-to-use object with the user's data sorted and everything! It's purely for ease-of-use for me.\n\n    - `User().__init__()`:\n\n      Assigns variables from the `JSON` value it accepts as `f`.  \n      Calls `Presence()`\n\n    - `User().description()`:\n\n      Unused.  \n      Returns a Python string with a quick description of the `User()` object.\n\n  - `Friend()`:\n\n    An object used in tandem with `FriendList()`. Imagine a retexture of the `User()` class, but with the following additions:\n    - `Friend().isFriend`\n    - `Friend().isFavoriteFriend`\n    - `Friend().isServiceUser`\n    - `Friend().friendCreatedAt`\n\n  - `Presence()`:\n\n    Creates a presence state.  \n    Calls `Game()`\n\n  - `Game()`:\n\n    Sorts game data into a neat little class.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003ch3\u003e4. The \u003ccode\u003ef\u003c/code\u003e token\u003c/h3\u003e\u003c/summary\u003e\n\n  This hurts me. This is the reason why we have to call third-party APIs in order to 'login' to Nintendo. It essentially just verifies that you are connecting from a real Nintendo Switch Online Mobile app (ineffectively, obviously).  \n  Since what's required to generate it is potentially incriminating, we have to generate it using third-party APIs (namely [s2s][s2s] and [flapg](https://github.com/frozenpandaman/splatnet2statink/wiki/api-docs#the-flapg-api)).\n\n\u003c/details\u003e\n\n[cli]: /client/cli.py\n[api]: /client/api/__init__.py\n[app]: /client/app.py\n[s2s]: https://github.com/frozenpandaman/splatnet2statink/wiki/api-docs\n[imink]: https://github.com/JoneWang/imink\n","funding_links":[],"categories":["Python","Consoles / Emulators"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMCMi460%2FNSO-RPC","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FMCMi460%2FNSO-RPC","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMCMi460%2FNSO-RPC/lists"}