mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-03-21 18:04:10 -05:00
Add SplatNet 2 constructor helper functions and always send player ID
This commit is contained in:
parent
2d52f17022
commit
fc2dec15c0
20
README.md
20
README.md
|
|
@ -758,29 +758,29 @@ nxapi exports it's API library and types. [See src/exports.](src/exports)
|
|||
|
||||
Example authenticating to the Nintendo Switch Online app:
|
||||
|
||||
> This is a simplified example of authenticating to the Coral API and using cached tokens. More logic is required to ensure you are using these APIs properly - [see src/common/auth/coral.ts for the authentication functions used in nxapi's CLI and Electron app](src/common/auth/coral.ts).
|
||||
> This is a simplified example of authenticating to the Coral API and using cached tokens. More logic is required to ensure you are using these APIs properly, and to renew expired tokens - [see src/common/auth/coral.ts for the authentication functions used in nxapi's CLI and Electron app](src/common/auth/coral.ts).
|
||||
|
||||
```ts
|
||||
import { addUserAgent } from 'nxapi';
|
||||
import CoralApi from 'nxapi/coral';
|
||||
import CoralApi, { CoralAuthData } from 'nxapi/coral';
|
||||
|
||||
addUserAgent('your-script/1.0.0 (+https://github.com/...)');
|
||||
|
||||
declare function getCachedCoralToken(): [string, Date];
|
||||
declare function setCachedCoralToken(token: string, expires_at: Date): void;
|
||||
declare function getCachedCoralToken(): [CoralAuthData, Date];
|
||||
declare function setCachedCoralToken(auth_data: CoralAuthData, expires_at: Date): void;
|
||||
declare function getNintendoAccountSessionToken(): string;
|
||||
|
||||
let coral;
|
||||
|
||||
try {
|
||||
const [token, expires_at] = getCachedCoralToken();
|
||||
const [auth_data, expires_at] = getCachedCoralToken();
|
||||
if (expires_at.getTime() > Date.now()) throw new Error('Token expired');
|
||||
|
||||
coral = new CoralApi(token);
|
||||
coral = CoralApi.createWithSavedToken(auth_data);
|
||||
} catch (err) {
|
||||
const na_session_token = getNintendoAccountSessionToken();
|
||||
const {nso, data} = await CoralApi.createWithSessionToken(na_session_token);
|
||||
setCachedCoralToken(data.credential.accessToken, Date.now() + (data.credential.expiresIn * 1000));
|
||||
setCachedCoralToken(data, Date.now() + (data.credential.expiresIn * 1000));
|
||||
coral = nso;
|
||||
}
|
||||
|
||||
|
|
@ -792,7 +792,8 @@ Example getting SplatNet 2 records:
|
|||
> This example does not include authenticating to SplatNet 2. To benefit from the caching in the nxapi command, the `nxapi splatnet2 token --json` command can be used in most scripts. For example:
|
||||
>
|
||||
> ```sh
|
||||
> # your-script.js can then read the iksm_session, unique player ID and region from `JSON.parse(process.env.SPLATNET_TOKEN)`
|
||||
> # your-script.js can then read the iksm_session, unique player ID and region like this:
|
||||
> # SplatNet2Api.createWithCliTokenData(JSON.parse(process.env.SPLATNET_TOKEN))
|
||||
> SPLATNET_TOKEN=`nxapi splatnet2 token --json` node your-script.js
|
||||
> ```
|
||||
|
||||
|
|
@ -800,7 +801,8 @@ Example getting SplatNet 2 records:
|
|||
import SplatNet2Api from 'nxapi/splatnet2';
|
||||
|
||||
const iksm_session = '...';
|
||||
const splatnet2 = new SplatNet2Api(iksm_session);
|
||||
const unique_id = '...';
|
||||
const splatnet2 = SplatNet2Api.createWithIksmSession(iksm_session, unique_id);
|
||||
|
||||
const records = await splatnet2.getRecords();
|
||||
```
|
||||
|
|
|
|||
|
|
@ -25,9 +25,10 @@ export const updateIksmSessionLastUsed: {
|
|||
} = {};
|
||||
|
||||
export default class SplatNet2Api {
|
||||
constructor(
|
||||
protected constructor(
|
||||
public iksm_session: string,
|
||||
public useragent: string
|
||||
public unique_id: string,
|
||||
public useragent: string,
|
||||
) {}
|
||||
|
||||
async fetch<T = unknown>(url: string, method = 'GET', body?: string | FormData, headers?: object) {
|
||||
|
|
@ -35,13 +36,15 @@ export default class SplatNet2Api {
|
|||
const response = await fetch(SPLATNET2_URL + url, {
|
||||
method,
|
||||
headers: Object.assign({
|
||||
'Upgrade-Insecure-Requests': '1',
|
||||
'User-Agent': this.useragent,
|
||||
'Cookie': 'iksm_session=' + encodeURIComponent(this.iksm_session),
|
||||
'dnt': '1',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Accept': '*/*',
|
||||
'Accept-Language': 'en-GB,en-US;q=0.8',
|
||||
'X-Requested-With': 'com.nintendo.znca',
|
||||
'Referrer': 'https://app.splatoon2.nintendo.net/home',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
// 'X-Timezone-Offset': (new Date()).getTimezoneOffset().toString(),
|
||||
'X-Timezone-Offset': '0',
|
||||
'X-Unique-Id': this.unique_id,
|
||||
}, headers),
|
||||
body,
|
||||
signal,
|
||||
|
|
@ -229,11 +232,31 @@ ${colour}
|
|||
|
||||
static async createWithCoral(nso: CoralApi, user: NintendoAccountUser) {
|
||||
const data = await this.loginWithCoral(nso, user);
|
||||
return {splatnet: this.createWithSavedToken(data), data};
|
||||
}
|
||||
|
||||
return {
|
||||
splatnet: new this(data.iksm_session, data.useragent),
|
||||
data,
|
||||
};
|
||||
static createWithSavedToken(data: SplatNet2AuthData) {
|
||||
return new this(
|
||||
data.iksm_session,
|
||||
data.user_id,
|
||||
data.useragent,
|
||||
);
|
||||
}
|
||||
|
||||
static createWithCliTokenData(data: SplatNet2CliTokenData) {
|
||||
return new this(
|
||||
data.iksm_session,
|
||||
data.user_id,
|
||||
SPLATNET2_WEBSERVICE_USERAGENT,
|
||||
);
|
||||
}
|
||||
|
||||
static createWithIksmSession(iksm_session: string, unique_id: string) {
|
||||
return new this(
|
||||
iksm_session,
|
||||
unique_id,
|
||||
SPLATNET2_WEBSERVICE_USERAGENT,
|
||||
);
|
||||
}
|
||||
|
||||
static async loginWithCoral(nso: CoralApi, user: NintendoAccountUser) {
|
||||
|
|
@ -242,7 +265,9 @@ ${colour}
|
|||
return this.loginWithWebServiceToken(webserviceToken.result, user);
|
||||
}
|
||||
|
||||
static async loginWithWebServiceToken(webserviceToken: WebServiceToken, user: NintendoAccountUser) {
|
||||
static async loginWithWebServiceToken(
|
||||
webserviceToken: WebServiceToken, user: NintendoAccountUser
|
||||
): Promise<SplatNet2AuthData> {
|
||||
const url = new URL(SPLATNET2_WEBSERVICE_URL);
|
||||
url.search = new URLSearchParams({
|
||||
lang: user.language,
|
||||
|
|
@ -295,6 +320,11 @@ ${colour}
|
|||
const mn = body.match(/<html(?:\s+[a-z0-9-]+(?:=(?:"[^"]*"|[^\s>]*))?)*\s+data-nsa-id=(?:"([^"]*)"|([^\s>]*))/i);
|
||||
const [language, region, user_id, nsa_id] = [ml, mr, mu, mn].map(m => m?.[1] || m?.[2] || null);
|
||||
|
||||
if (!language) throw new Error('[splatnet2] Invalid language in response');
|
||||
if (!region) throw new Error('[splatnet2] Invalid region in response');
|
||||
if (!user_id) throw new Error('[splatnet2] Invalid unique player ID in response');
|
||||
if (!nsa_id) throw new Error('[splatnet2] Invalid NSA ID in response');
|
||||
|
||||
debug('SplatNet 2 user', {
|
||||
language,
|
||||
region,
|
||||
|
|
@ -319,6 +349,31 @@ ${colour}
|
|||
}
|
||||
}
|
||||
|
||||
export interface SplatNet2AuthData {
|
||||
webserviceToken: WebServiceToken;
|
||||
url: string;
|
||||
cookies: string;
|
||||
body: string;
|
||||
|
||||
language: string;
|
||||
region: string;
|
||||
/** Splatoon 2 player ID aka. unique_id */
|
||||
user_id: string;
|
||||
nsa_id: string;
|
||||
|
||||
iksm_session: string;
|
||||
expires_at: number;
|
||||
useragent: string;
|
||||
}
|
||||
|
||||
export interface SplatNet2CliTokenData {
|
||||
iksm_session: string;
|
||||
language: string;
|
||||
region: string;
|
||||
user_id: string;
|
||||
nsa_id: string;
|
||||
}
|
||||
|
||||
export function toLeagueId(date: Date, type: LeagueType) {
|
||||
const year = date.getUTCFullYear();
|
||||
const month = date.getUTCMonth() + 1;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type { Arguments as ParentArguments } from '../splatnet2.js';
|
|||
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
|
||||
import { initStorage } from '../../util/storage.js';
|
||||
import { getIksmToken } from '../../common/auth/splatnet2.js';
|
||||
import { SplatNet2CliTokenData } from '../../api/splatnet2.js';
|
||||
|
||||
const debug = createDebug('cli:splatnet2:token');
|
||||
|
||||
|
|
@ -35,7 +36,7 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
|||
const {splatnet, data} = await getIksmToken(storage, token, argv.zncProxyUrl, argv.autoUpdateSession);
|
||||
|
||||
if (argv.json || argv.jsonPrettyPrint) {
|
||||
const result = {
|
||||
const result: SplatNet2CliTokenData = {
|
||||
iksm_session: data.iksm_session,
|
||||
language: data.language,
|
||||
region: data.region,
|
||||
|
|
|
|||
|
|
@ -3,30 +3,14 @@ import * as fs from 'node:fs';
|
|||
import createDebug from 'debug';
|
||||
import persist from 'node-persist';
|
||||
import { getToken } from './coral.js';
|
||||
import SplatNet2Api, { updateIksmSessionLastUsed } from '../../api/splatnet2.js';
|
||||
import { WebServiceToken } from '../../api/coral-types.js';
|
||||
import SplatNet2Api, { SplatNet2AuthData, updateIksmSessionLastUsed } from '../../api/splatnet2.js';
|
||||
import { checkUseLimit, SHOULD_LIMIT_USE } from './util.js';
|
||||
import { Jwt } from '../../util/jwt.js';
|
||||
import { NintendoAccountSessionTokenJwtPayload } from '../../api/na.js';
|
||||
|
||||
const debug = createDebug('nxapi:auth:splatnet2');
|
||||
|
||||
export interface SavedIksmSessionToken {
|
||||
webserviceToken: WebServiceToken;
|
||||
url: string;
|
||||
cookies: string;
|
||||
|
||||
body: string;
|
||||
language: string | null;
|
||||
region: string | null;
|
||||
/** Splatoon 2 player ID aka. unique_id */
|
||||
user_id: string | null;
|
||||
nsa_id: string | null;
|
||||
|
||||
iksm_session: string;
|
||||
expires_at: number;
|
||||
useragent: string;
|
||||
|
||||
export interface SavedIksmSessionToken extends SplatNet2AuthData {
|
||||
last_used?: number;
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +53,7 @@ export async function getIksmToken(
|
|||
}
|
||||
|
||||
return {
|
||||
splatnet: new SplatNet2Api(existingToken.iksm_session, existingToken.useragent),
|
||||
splatnet: SplatNet2Api.createWithSavedToken(existingToken),
|
||||
data: existingToken,
|
||||
};
|
||||
}
|
||||
|
|
@ -81,7 +65,7 @@ export async function getIksmToken(
|
|||
}
|
||||
|
||||
return {
|
||||
splatnet: new SplatNet2Api(existingToken.iksm_session, existingToken.useragent),
|
||||
splatnet: SplatNet2Api.createWithSavedToken(existingToken),
|
||||
data: existingToken,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
export {
|
||||
default,
|
||||
SplatNet2AuthData,
|
||||
|
||||
LeagueType,
|
||||
LeagueRegion,
|
||||
ShareColour as ShareProfileColour,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user