mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-09-07 10:55:09 -05:00
Update config URL and use config data
This commit is contained in:
@@ -55,6 +55,16 @@ export enum CoralStatus {
|
||||
|
||||
export type CoralResponse<T = unknown> = CoralSuccessResponse<T> | CoralErrorResponse;
|
||||
|
||||
export interface AccountLoginParameter {
|
||||
naIdToken: string;
|
||||
naBirthday: string;
|
||||
naCountry: string;
|
||||
language: string;
|
||||
timestamp: string;
|
||||
requestId: string;
|
||||
f: string;
|
||||
}
|
||||
|
||||
/** /v3/Account/Login */
|
||||
export interface AccountLogin {
|
||||
user: CurrentUser;
|
||||
@@ -71,6 +81,14 @@ export interface AccountLogin {
|
||||
/** /v3/Account/GetToken */
|
||||
export type AccountToken = AccountLogin;
|
||||
|
||||
export interface AccountTokenParameter {
|
||||
naIdToken: string;
|
||||
naBirthday: string;
|
||||
timestamp: string;
|
||||
requestId: string;
|
||||
f: string;
|
||||
}
|
||||
|
||||
/** /v1/Announcement/List */
|
||||
export type Announcements = Announcement[];
|
||||
|
||||
@@ -254,6 +272,16 @@ export interface CurrentUserPermissions {
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateCurrentUserPermissionsParameter {
|
||||
permissions: {
|
||||
presence: {
|
||||
toValue: PresencePermissions;
|
||||
fromValue: PresencePermissions;
|
||||
};
|
||||
};
|
||||
etag: string;
|
||||
}
|
||||
|
||||
/** /v2/Game/GetWebServiceToken */
|
||||
export interface WebServiceToken {
|
||||
accessToken: string;
|
||||
|
||||
115
src/api/coral.ts
115
src/api/coral.ts
@@ -1,9 +1,9 @@
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import { v4 as uuidgen } from 'uuid';
|
||||
import createDebug from 'debug';
|
||||
import { f, FlapgIid } from './f.js';
|
||||
import { AccountLogin, AccountToken, Announcements, CurrentUser, CurrentUserPermissions, Event, Friends, GetActiveEventResult, PresencePermissions, User, WebServices, WebServiceToken, CoralErrorResponse, CoralResponse, CoralStatus, CoralSuccessResponse, FriendCodeUser, FriendCodeUrl } from './coral-types.js';
|
||||
import { getNintendoAccountToken, getNintendoAccountUser, NintendoAccountUser } from './na.js';
|
||||
import { f, FlapgIid, FResult } from './f.js';
|
||||
import { AccountLogin, AccountToken, Announcements, CurrentUser, CurrentUserPermissions, Event, Friends, GetActiveEventResult, PresencePermissions, User, WebServices, WebServiceToken, CoralErrorResponse, CoralResponse, CoralStatus, CoralSuccessResponse, FriendCodeUser, FriendCodeUrl, AccountTokenParameter, AccountLoginParameter } from './coral-types.js';
|
||||
import { getNintendoAccountToken, getNintendoAccountUser, NintendoAccountToken, NintendoAccountUser } from './na.js';
|
||||
import { ErrorResponse } from './util.js';
|
||||
import { JwtPayload } from '../util/jwt.js';
|
||||
import { getAdditionalUserAgents } from '../util/useragent.js';
|
||||
@@ -26,9 +26,11 @@ export default class CoralApi {
|
||||
/** @internal */
|
||||
_renewToken: Promise<void> | null = null;
|
||||
|
||||
constructor(
|
||||
protected constructor(
|
||||
public token: string,
|
||||
public useragent: string | null = getAdditionalUserAgents()
|
||||
public useragent: string | null = getAdditionalUserAgents(),
|
||||
readonly znca_version = ZNCA_VERSION,
|
||||
readonly znca_useragent = ZNCA_USER_AGENT,
|
||||
) {}
|
||||
|
||||
async fetch<T = unknown>(
|
||||
@@ -44,10 +46,10 @@ export default class CoralApi {
|
||||
method: method,
|
||||
headers: Object.assign({
|
||||
'X-Platform': ZNCA_PLATFORM,
|
||||
'X-ProductVersion': ZNCA_VERSION,
|
||||
'X-ProductVersion': this.znca_version,
|
||||
'Authorization': 'Bearer ' + this.token,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'User-Agent': ZNCA_USER_AGENT,
|
||||
'User-Agent': this.znca_useragent,
|
||||
}, headers),
|
||||
body: body,
|
||||
});
|
||||
@@ -185,22 +187,24 @@ export default class CoralApi {
|
||||
return this.call<WebServiceToken>('/v2/Game/GetWebServiceToken', req);
|
||||
}
|
||||
|
||||
async getToken(token: string, user: NintendoAccountUser) {
|
||||
const uuid = uuidgen();
|
||||
const timestamp = '' + Math.floor(Date.now() / 1000);
|
||||
|
||||
async getToken(token: string, user: NintendoAccountUser): Promise<PartialCoralAuthData> {
|
||||
// Nintendo Account token
|
||||
const nintendoAccountToken = await getNintendoAccountToken(token, ZNCA_CLIENT_ID);
|
||||
|
||||
const id_token = nintendoAccountToken.id_token;
|
||||
const fdata = await f(id_token, timestamp, uuid, FlapgIid.NSO, this.useragent ?? getAdditionalUserAgents());
|
||||
const uuid = uuidgen();
|
||||
const timestamp = '' + Math.floor(Date.now() / 1000);
|
||||
|
||||
const req = {
|
||||
const fdata = await f(
|
||||
nintendoAccountToken.id_token, timestamp, uuid,
|
||||
FlapgIid.NSO, this.useragent ?? getAdditionalUserAgents()
|
||||
);
|
||||
|
||||
const req: AccountTokenParameter = {
|
||||
naBirthday: user.birthday,
|
||||
timestamp,
|
||||
f: fdata.f,
|
||||
requestId: uuid,
|
||||
naIdToken: id_token,
|
||||
naIdToken: nintendoAccountToken.id_token,
|
||||
};
|
||||
|
||||
const data = await this.call<AccountToken>('/v3/Account/GetToken', req, false);
|
||||
@@ -216,15 +220,6 @@ export default class CoralApi {
|
||||
};
|
||||
}
|
||||
|
||||
static async createWithSessionToken(token: string, useragent = getAdditionalUserAgents()) {
|
||||
const data = await this.loginWithSessionToken(token, useragent);
|
||||
|
||||
return {
|
||||
nso: new this(data.credential.accessToken, useragent),
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
async renewToken(token: string, user: NintendoAccountUser) {
|
||||
const data = await this.getToken(token, user);
|
||||
|
||||
@@ -233,9 +228,25 @@ export default class CoralApi {
|
||||
return data;
|
||||
}
|
||||
|
||||
static async loginWithSessionToken(token: string, useragent = getAdditionalUserAgents()) {
|
||||
const uuid = uuidgen();
|
||||
const timestamp = '' + Math.floor(Date.now() / 1000);
|
||||
static async createWithSessionToken(token: string, useragent = getAdditionalUserAgents()) {
|
||||
const data = await this.loginWithSessionToken(token, useragent);
|
||||
return {nso: this.createWithSavedToken(data, useragent), data};
|
||||
}
|
||||
|
||||
static createWithSavedToken(data: CoralAuthData, useragent = getAdditionalUserAgents()) {
|
||||
return new this(
|
||||
data.credential.accessToken,
|
||||
useragent,
|
||||
data.znca_version,
|
||||
data.znca_useragent,
|
||||
);
|
||||
}
|
||||
|
||||
static async loginWithSessionToken(token: string, useragent = getAdditionalUserAgents()): Promise<CoralAuthData> {
|
||||
const { default: { coral: config } } = await import('../common/remote-config.js');
|
||||
|
||||
if (!config) throw new Error('Remote configuration prevents Coral authentication');
|
||||
const znca_useragent = `com.nintendo.znca/${config.znca_version}(${ZNCA_PLATFORM}/${ZNCA_PLATFORM_VERSION})`;
|
||||
|
||||
// Nintendo Account token
|
||||
const nintendoAccountToken = await getNintendoAccountToken(token, ZNCA_CLIENT_ID);
|
||||
@@ -243,29 +254,36 @@ export default class CoralApi {
|
||||
// Nintendo Account user data
|
||||
const user = await getNintendoAccountUser(nintendoAccountToken);
|
||||
|
||||
const id_token = nintendoAccountToken.id_token;
|
||||
const fdata = await f(id_token, timestamp, uuid, FlapgIid.NSO, useragent);
|
||||
const uuid = uuidgen();
|
||||
const timestamp = '' + Math.floor(Date.now() / 1000);
|
||||
|
||||
const fdata = await f(
|
||||
nintendoAccountToken.id_token, timestamp, uuid,
|
||||
FlapgIid.NSO, useragent
|
||||
);
|
||||
|
||||
debug('Getting Nintendo Switch Online app token');
|
||||
|
||||
const parameter: AccountLoginParameter = {
|
||||
naIdToken: nintendoAccountToken.id_token,
|
||||
naBirthday: user.birthday,
|
||||
naCountry: user.country,
|
||||
language: user.language,
|
||||
timestamp,
|
||||
requestId: uuid,
|
||||
f: fdata.f,
|
||||
};
|
||||
|
||||
const response = await fetch(ZNC_URL + '/v3/Account/Login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Platform': ZNCA_PLATFORM,
|
||||
'X-ProductVersion': ZNCA_VERSION,
|
||||
'X-ProductVersion': config.znca_version,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'User-Agent': ZNCA_USER_AGENT,
|
||||
'User-Agent': znca_useragent,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
parameter: {
|
||||
naIdToken: nintendoAccountToken.id_token,
|
||||
naBirthday: user.birthday,
|
||||
naCountry: user.country,
|
||||
language: user.language,
|
||||
timestamp,
|
||||
requestId: uuid,
|
||||
f: fdata.f,
|
||||
},
|
||||
parameter,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -289,10 +307,27 @@ export default class CoralApi {
|
||||
f: fdata,
|
||||
nsoAccount: data.result,
|
||||
credential: data.result.webApiServerCredential,
|
||||
znca_version: config.znca_version,
|
||||
znca_useragent,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface CoralAuthData {
|
||||
uuid: string;
|
||||
timestamp: string;
|
||||
nintendoAccountToken: NintendoAccountToken;
|
||||
user: NintendoAccountUser;
|
||||
f: FResult;
|
||||
nsoAccount: AccountLogin;
|
||||
credential: AccountLogin['webApiServerCredential'];
|
||||
znca_version: string;
|
||||
znca_useragent: string;
|
||||
}
|
||||
|
||||
export type PartialCoralAuthData =
|
||||
Pick<CoralAuthData, 'uuid' | 'timestamp' | 'nintendoAccountToken' | 'f' | 'nsoAccount' | 'credential'>;
|
||||
|
||||
export interface CoralJwtPayload extends JwtPayload {
|
||||
isChildRestricted: boolean;
|
||||
membership: {
|
||||
|
||||
32
src/api/f.ts
32
src/api/f.ts
@@ -23,6 +23,9 @@ export abstract class ZncaApi {
|
||||
//
|
||||
|
||||
export async function getLoginHash(token: string, timestamp: string | number, useragent?: string) {
|
||||
const { default: { coral_auth: { splatnet2statink: config } } } = await import('../common/remote-config.js');
|
||||
if (!config) throw new Error('Remote configuration prevents splatnet2statink API use');
|
||||
|
||||
debugS2s('Getting login hash');
|
||||
|
||||
const [signal, cancel] = timeoutSignal();
|
||||
@@ -61,6 +64,9 @@ export async function flapg(
|
||||
token: string, timestamp: string | number, guid: string, iid: FlapgIid,
|
||||
useragent?: string
|
||||
) {
|
||||
const { default: { coral_auth: { flapg: config } } } = await import('../common/remote-config.js');
|
||||
if (!config) throw new Error('Remote configuration prevents flapg API use');
|
||||
|
||||
const hash = await getLoginHash(token, timestamp, useragent);
|
||||
|
||||
debugFlapg('Getting f parameter', {
|
||||
@@ -129,6 +135,9 @@ export async function iminkf(
|
||||
token: string, timestamp: string | number, uuid: string, hash_method: '1' | '2',
|
||||
useragent?: string
|
||||
) {
|
||||
const { default: { coral_auth: { imink: config } } } = await import('../common/remote-config.js');
|
||||
if (!config) throw new Error('Remote configuration prevents imink API use');
|
||||
|
||||
debugImink('Getting f parameter', {
|
||||
token, timestamp, uuid, hash_method,
|
||||
});
|
||||
@@ -266,7 +275,7 @@ export async function f(
|
||||
token: string, timestamp: string | number, uuid: string, type: FlapgIid,
|
||||
useragent?: string
|
||||
): Promise<FResult> {
|
||||
const provider = getZncaApiFromEnvironment(useragent);
|
||||
const provider = getPreferredZncaApiFromEnvironment(useragent) ?? await getDefaultZncaApi(useragent);
|
||||
|
||||
return provider.genf(token, '' + timestamp, uuid, type);
|
||||
}
|
||||
@@ -291,7 +300,7 @@ export type FResult = {
|
||||
result: AndroidZncaFResponse;
|
||||
});
|
||||
|
||||
export function getZncaApiFromEnvironment(useragent?: string): ZncaApi {
|
||||
export function getPreferredZncaApiFromEnvironment(useragent?: string): ZncaApi | null {
|
||||
if (process.env.NXAPI_ZNCA_API) {
|
||||
if (process.env.NXAPI_ZNCA_API === 'flapg') {
|
||||
return new ZncaApiFlapg(useragent);
|
||||
@@ -307,5 +316,22 @@ export function getZncaApiFromEnvironment(useragent?: string): ZncaApi {
|
||||
return new ZncaApiNxapi(process.env.ZNCA_API_URL, useragent);
|
||||
}
|
||||
|
||||
return new ZncaApiFlapg(useragent);
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getDefaultZncaApi(useragent?: string) {
|
||||
const { default: { coral_auth: { default: provider } } } = await import('../common/remote-config.js');
|
||||
|
||||
if (provider === 'flapg') {
|
||||
return new ZncaApiFlapg(useragent);
|
||||
}
|
||||
if (provider === 'imink') {
|
||||
return new ZncaApiImink(useragent);
|
||||
}
|
||||
|
||||
if (provider[0] === 'nxapi') {
|
||||
return new ZncaApiNxapi(provider[1], useragent);
|
||||
}
|
||||
|
||||
throw new Error('Invalid znca API provider');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fetch from 'node-fetch';
|
||||
import createDebug from 'debug';
|
||||
import { getNintendoAccountToken, getNintendoAccountUser } from './na.js';
|
||||
import { getNintendoAccountToken, getNintendoAccountUser, NintendoAccountToken, NintendoAccountUser } from './na.js';
|
||||
import { ErrorResponse } from './util.js';
|
||||
import { DailySummaries, Devices, MonthlySummaries, MonthlySummary, MoonError, ParentalControlSettingState, SmartDevices, User } from './moon-types.js';
|
||||
import { timeoutSignal } from '../util/misc.js';
|
||||
@@ -12,11 +12,16 @@ export const ZNMA_CLIENT_ID = '54789befb391a838';
|
||||
|
||||
const ZNMA_VERSION = '1.17.0';
|
||||
const ZNMA_BUILD = '261';
|
||||
const ZNMA_USER_AGENT = 'moon_ANDROID/' + ZNMA_VERSION + ' (com.nintendo.znma; build:' + ZNMA_BUILD +
|
||||
'; ANDROID 26)';
|
||||
|
||||
export default class MoonApi {
|
||||
constructor(
|
||||
protected constructor(
|
||||
public token: string,
|
||||
public naId: string
|
||||
public naId: string,
|
||||
readonly znma_version = ZNMA_VERSION,
|
||||
readonly znma_build = ZNMA_BUILD,
|
||||
readonly znma_useragent = ZNMA_USER_AGENT,
|
||||
) {}
|
||||
|
||||
async fetch<T = unknown>(url: string, method = 'GET', body?: string, headers?: object) {
|
||||
@@ -34,10 +39,9 @@ export default class MoonApi {
|
||||
'X-Moon-TimeZone': 'Europe/London',
|
||||
'X-Moon-Os-Language': 'en-GB',
|
||||
'X-Moon-App-Language': 'en-GB',
|
||||
'X-Moon-App-Display-Version': ZNMA_VERSION,
|
||||
'X-Moon-App-Internal-Version': ZNMA_BUILD,
|
||||
'User-Agent': 'moon_ANDROID/' + ZNMA_VERSION + ' (com.nintendo.znma; build:' + ZNMA_BUILD +
|
||||
'; ANDROID 26)',
|
||||
'X-Moon-App-Display-Version': this.znma_version,
|
||||
'X-Moon-App-Internal-Version': this.znma_build,
|
||||
'User-Agent': this.znma_useragent,
|
||||
}, headers),
|
||||
body,
|
||||
signal,
|
||||
@@ -82,15 +86,6 @@ export default class MoonApi {
|
||||
return this.fetch<ParentalControlSettingState>('/v1/devices/' + id + '/parental_control_setting_state');
|
||||
}
|
||||
|
||||
static async createWithSessionToken(token: string) {
|
||||
const data = await this.loginWithSessionToken(token);
|
||||
|
||||
return {
|
||||
moon: new this(data.nintendoAccountToken.access_token!, data.user.id),
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
async renewToken(token: string) {
|
||||
const data = await MoonApi.loginWithSessionToken(token);
|
||||
|
||||
@@ -100,7 +95,29 @@ export default class MoonApi {
|
||||
return data;
|
||||
}
|
||||
|
||||
static async loginWithSessionToken(token: string) {
|
||||
static async createWithSessionToken(token: string) {
|
||||
const data = await this.loginWithSessionToken(token);
|
||||
return {moon: this.createWithSavedToken(data), data};
|
||||
}
|
||||
|
||||
static createWithSavedToken(data: MoonAuthData) {
|
||||
return new this(
|
||||
data.nintendoAccountToken.access_token!,
|
||||
data.user.id,
|
||||
data.znma_version,
|
||||
data.znma_build,
|
||||
data.znma_useragent,
|
||||
);
|
||||
}
|
||||
|
||||
static async loginWithSessionToken(token: string): Promise<MoonAuthData> {
|
||||
const { default: { moon: config } } = await import('../common/remote-config.js');
|
||||
|
||||
if (!config) throw new Error('Remote configuration prevents Moon authentication');
|
||||
|
||||
const znma_useragent = 'moon_ANDROID/' + config.znma_version +
|
||||
' (com.nintendo.znma; build:' + config.znma_build + '; ANDROID 26)';
|
||||
|
||||
// Nintendo Account token
|
||||
const nintendoAccountToken = await getNintendoAccountToken(token, ZNMA_CLIENT_ID);
|
||||
|
||||
@@ -110,6 +127,17 @@ export default class MoonApi {
|
||||
return {
|
||||
nintendoAccountToken,
|
||||
user,
|
||||
znma_version: config.znma_version,
|
||||
znma_build: config.znma_build,
|
||||
znma_useragent: znma_useragent,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface MoonAuthData {
|
||||
nintendoAccountToken: NintendoAccountToken;
|
||||
user: NintendoAccountUser;
|
||||
znma_version: string;
|
||||
znma_build: string;
|
||||
znma_useragent: string;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ export default class ZncProxyApi implements CoralApi {
|
||||
/** @internal */
|
||||
_renewToken: Promise<void> | null = null;
|
||||
|
||||
readonly znca_version = '';
|
||||
readonly znca_useragent = '';
|
||||
|
||||
constructor(
|
||||
private url: string,
|
||||
// ZncApi uses the NSO token (valid for a few hours)
|
||||
|
||||
Reference in New Issue
Block a user