Skip to content

Commit

Permalink
Add an option to use the imink API to generate f parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelthomas2774 committed May 26, 2022
1 parent 967a063 commit 442b53a
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 62 deletions.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ Access the Nintendo Switch Online and Nintendo Switch Parental Controls app APIs
- Interactive Nintendo Account login for the Nintendo Switch Online and Nintendo Switch Parental Controls apps
- Automated login to the Nintendo Switch Online app API
- This uses splatnet2statink and flapg APIs by default.
- Alternatively a custom server using a rooted Android device/emulator is included.
- Alternatively the imink API or a custom server can be used.
- A custom server using a rooted Android device/emulator is included.
- Get Nintendo Switch account information, friends list and game-specific services
- Show Discord Rich Presence using your own or a friend's Nintendo Switch presence
- Show your account's friend code (or a custom friend code)
Expand Down Expand Up @@ -313,7 +314,9 @@ The splatnet2statink and flapg APIs are used by default to automate authenticati

Specifically, the tokens sent are JSON Web Tokens. The token sent to login to the app includes [this information and is valid for 15 minutes](https://gitlab.fancy.org.uk/samuel/nxapi/-/wikis/Nintendo-tokens#nintendo-account-id_token), and the token sent to login to web services includes [this information and is valid for two hours](https://gitlab.fancy.org.uk/samuel/nxapi/-/wikis/Nintendo-tokens#nintendo-switch-online-app-token).

Alternatively nxapi includes a custom server using Frida on an Android device/emulator that can be used instead of these.
Alternatively the [imink API](https://github.com/JoneWang/imink/wiki/imink-API-Documentation) can be used by setting the `NXAPI_ZNCA_API` environment variable to `imink`. (`NXAPI_ZNCA_API=imink nxapi nso ...`)

nxapi also includes a custom server using Frida on an Android device/emulator that can be used instead of these.

This is only required for Nintendo Switch Online app data. Nintendo Switch Parental Controls data can be fetched without sending an access token to a third-party API.

Expand Down Expand Up @@ -477,7 +480,7 @@ nxapi nooklink island
# Use a specific NookLink user linked to the selected Nintendo Account
# If more than 1 NookLink users exist by default the first user will be used
nxapi nooklink user --islander 0x0123456789abcdef
# --user can also be used to selecte a different Nintendo Account
# --user can also be used to select a different Nintendo Account
nxapi nooklink user --user 0123456789abcdef
nxapi nooklink user --user 0123456789abcdef --islander 0x0123456789abcdef
```
Expand Down Expand Up @@ -639,14 +642,14 @@ NXAPI_DATA_PATH=`pwd`/data nxapi ...

#### Debug logs

Logging uses the `debug` package and can be controlled using the `DEBUG` environment variable. All nxapi logging uses the `api` and `cli` namespaces.
Logging uses the `debug` package and can be controlled using the `DEBUG` environment variable. All nxapi logging uses the `nxapi` and `cli` namespaces.

```sh
# Show all debug logs from nxapi
DEBUG=api,api:*,cli,cli:* nxapi ...
DEBUG=nxapi:*,cli,cli:* nxapi ...

# Show all API requests
DEBUG=api:* nxapi ...
DEBUG=nxapi:api:* nxapi ...

# Show all debug logs
DEBUG=* nxapi ...
Expand Down
203 changes: 163 additions & 40 deletions src/api/f.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import { version } from '../util/product.js';

const debugS2s = createDebug('nxapi:api:s2s');
const debugFlapg = createDebug('nxapi:api:flapg');
const debugImink = createDebug('nxapi:api:imink');
const debugZncaApi = createDebug('nxapi:api:znca-api');

//
// splatnet2statink + flapg
//

export async function getLoginHash(token: string, timestamp: string | number, useragent?: string) {
debugS2s('Getting login hash');

Expand All @@ -33,6 +38,13 @@ export async function getLoginHash(token: string, timestamp: string | number, us
return data.hash;
}

export interface LoginHashApiResponse {
hash: string;
}
export interface LoginHashApiError {
error: string;
}

export async function flapg(
token: string, timestamp: string | number, guid: string, iid: FlapgIid,
useragent?: string
Expand All @@ -59,9 +71,82 @@ export async function flapg(

debugFlapg('Got f parameter "%s"', data.result.f);

return data.result;
return data;
}

export enum FlapgIid {
/** Nintendo Switch Online app token */
NSO = 'nso',
/** Web service token */
APP = 'app',
}

export interface FlapgApiResponse {
result: {
f: string;
p1: string;
p2: string;
p3: string;
};
}

//
// imink
//

export async function iminkf(
token: string, timestamp: string | number, uuid: string, hash_method: '1' | '2',
useragent?: string
) {
debugImink('Getting f parameter', {
token, timestamp, uuid, hash_method,
});

const req: IminkFRequest = {
hash_method,
token,
timestamp: '' + timestamp,
request_id: uuid,
};

const response = await fetch('https://api.imink.app/f', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': (useragent ? useragent + ' ' : '') + 'nxapi/' + version,
},
body: JSON.stringify(req),
});

const data = await response.json() as IminkFResponse | IminkFError;

if ('error' in data) {
throw new ErrorResponse('[imink] ' + data.reason, response, data);
}

debugImink('Got f parameter "%s"', data.f);

return data;
}

export interface IminkFRequest {
timestamp: string;
request_id: string;
hash_method: '1' | '2';
token: string;
}
export interface IminkFResponse {
f: string;
}
export interface IminkFError {
reason: string;
error: true;
}

//
// nxapi znca API server
//

export async function genf(
url: string, token: string, timestamp: string | number, uuid: string, type: FlapgIid,
useragent?: string
Expand Down Expand Up @@ -95,57 +180,95 @@ export async function genf(

debugZncaApi('Got f parameter "%s"', data.f);

return data.f;
return data;
}

export async function genfc(
url: string, token: string, timestamp: string | number, uuid: string, type: FlapgIid,
useragent?: string
) {
const f = await genf(url, token, timestamp, uuid, type, useragent);

return {
f,
p1: token,
p2: '' + timestamp,
p3: uuid,

url,
};
export interface AndroidZncaFRequest {
type: FlapgIid;
token: string;
timestamp: string;
uuid: string;
}

export interface LoginHashApiResponse {
hash: string;
export interface AndroidZncaFResponse {
f: string;
}
export interface LoginHashApiError {
export interface AndroidZncaFError {
error: string;
}

export enum FlapgIid {
/** Nintendo Switch Online app token */
NSO = 'nso',
/** Web service token */
APP = 'app',
}
export async function f(
token: string, timestamp: string | number, uuid: string, type: FlapgIid,
useragent?: string,
provider: FApi = getEnvApi()
): Promise<FResult> {
if (provider === 'flapg') {
const result = await flapg(token, timestamp, uuid, type, useragent);
return {
provider: 'flapg',
token, timestamp: '' + timestamp, uuid, type,
f: result.result.f, result,
};
}
if (provider === 'imink') {
const result = await iminkf(token, timestamp, uuid, type === FlapgIid.APP ? '2' : '1', useragent);
return {
provider: 'imink',
token, timestamp: '' + timestamp, uuid, type,
f: result.f, result,
};
}
if (provider[0] === 'nxapi') {
const result = await genf(provider[1], token, timestamp, uuid, type, useragent);
return {
provider: 'nxapi', url: provider[1],
token, timestamp: '' + timestamp, uuid, type,
f: result.f, result,
};
}

export interface FlapgApiResponse {
result: {
f: string;
p1: string;
p2: string;
p3: string;
};
throw new Error('Unknown znca API provider');
}

export interface AndroidZncaFRequest {
type: FlapgIid;
export type FApi =
'flapg' |
'imink' |
['nxapi', string];

export type FResult = {
provider: string;
token: string;
timestamp: string;
uuid: string;
}
export interface AndroidZncaFResponse {
type: FlapgIid;
f: string;
}
export interface AndroidZncaFError {
error: string;
result: unknown;
} & ({
provider: 'flapg';
result: FlapgApiResponse;
} | {
provider: 'imink';
result: IminkFResponse;
} | {
provider: 'nxapi';
url: string;
result: AndroidZncaFResponse;
});

function getEnvApi(): FApi {
if (process.env.NXAPI_ZNCA_API) {
if (process.env.NXAPI_ZNCA_API === 'flapg') {
return 'flapg';
}
if (process.env.NXAPI_ZNCA_API === 'imink') {
return 'imink';
}

throw new Error('Unknown znca API provider');
}

if (process.env.ZNCA_API_URL?.startsWith('https://')) {
return ['nxapi', process.env.ZNCA_API_URL + '/f'];
}

return 'flapg';
}
23 changes: 9 additions & 14 deletions src/api/znc.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fetch from 'node-fetch';
import { v4 as uuidgen } from 'uuid';
import createDebug from 'debug';
import { flapg, FlapgIid, genfc } from './f.js';
import { f, FlapgIid } from './f.js';
import { AccountLogin, AccountToken, Announcements, CurrentUser, CurrentUserPermissions, Event, Friends, GetActiveEventResult, PresencePermissions, User, WebServices, WebServiceToken, ZncResponse, ZncStatus } from './znc-types.js';
import { getNintendoAccountToken, getNintendoAccountUser, NintendoAccountUser } from './na.js';
import { ErrorResponse } from './util.js';
Expand Down Expand Up @@ -126,9 +126,7 @@ export default class ZncApi {
const timestamp = '' + Math.floor(Date.now() / 1000);

const useragent = this.useragent ?? undefined;
const data = process.env.ZNCA_API_URL ?
await genfc(process.env.ZNCA_API_URL + '/f', this.token, timestamp, uuid, FlapgIid.APP, useragent) :
await flapg(this.token, timestamp, uuid, FlapgIid.APP, useragent);
const data = await f(this.token, timestamp, uuid, FlapgIid.APP, useragent);

const req = {
id,
Expand All @@ -150,14 +148,12 @@ export default class ZncApi {

const id_token = nintendoAccountToken.id_token;
const useragent = this.useragent ?? undefined;
const flapgdata = process.env.ZNCA_API_URL ?
await genfc(process.env.ZNCA_API_URL + '/f', id_token, timestamp, uuid, FlapgIid.NSO, useragent) :
await flapg(id_token, timestamp, uuid, FlapgIid.NSO, useragent);
const fdata = await f(id_token, timestamp, uuid, FlapgIid.NSO, useragent);

const req = {
naBirthday: user.birthday,
timestamp,
f: flapgdata.f,
f: fdata.f,
requestId: uuid,
naIdToken: id_token,
};
Expand All @@ -169,7 +165,7 @@ export default class ZncApi {
timestamp,
nintendoAccountToken,
// user,
flapg: flapgdata,
f: fdata,
nsoAccount: data.result,
credential: data.result.webApiServerCredential,
};
Expand Down Expand Up @@ -203,9 +199,7 @@ export default class ZncApi {
const user = await getNintendoAccountUser(nintendoAccountToken);

const id_token = nintendoAccountToken.id_token;
const flapgdata = process.env.ZNCA_API_URL ?
await genfc(process.env.ZNCA_API_URL + '/f', id_token, timestamp, uuid, FlapgIid.NSO, useragent ?? undefined) :
await flapg(id_token, timestamp, uuid, FlapgIid.NSO, useragent ?? undefined);
const fdata = await f(id_token, timestamp, uuid, FlapgIid.NSO, useragent ?? undefined);

debug('Getting Nintendo Switch Online app token');

Expand All @@ -225,11 +219,12 @@ export default class ZncApi {
language: user.language,
timestamp,
requestId: uuid,
f: flapgdata.f,
f: fdata.f,
},
}),
});

debug('fetch %s %s, response %s', 'POST', '/v3/Account/Login', response.status);
const data = await response.json() as ZncResponse<AccountLogin>;

if ('errorMessage' in data) {
Expand All @@ -246,7 +241,7 @@ export default class ZncApi {
timestamp,
nintendoAccountToken,
user,
flapg: flapgdata,
f: fdata,
nsoAccount: data.result,
credential: data.result.webApiServerCredential,
};
Expand Down
5 changes: 3 additions & 2 deletions src/common/auth/nso.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import createDebug from 'debug';
import * as persist from 'node-persist';
import { FlapgApiResponse } from '../../api/f.js';
import { FlapgApiResponse, FResult } from '../../api/f.js';
import { NintendoAccountSessionTokenJwtPayload, NintendoAccountToken, NintendoAccountUser } from '../../api/na.js';
import { Jwt } from '../../util/jwt.js';
import { AccountLogin } from '../../api/znc-types.js';
Expand All @@ -14,7 +14,8 @@ export interface SavedToken {
timestamp: string;
nintendoAccountToken: NintendoAccountToken;
user: NintendoAccountUser;
flapg: FlapgApiResponse['result'];
f: FResult;
flapg?: FlapgApiResponse['result'];
nsoAccount: AccountLogin;
credential: AccountLogin['webApiServerCredential'];

Expand Down

0 comments on commit 442b53a

Please sign in to comment.