mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-09-08 19:35:10 -05:00
Fix f parameter generation
This fixes `f` parameter generation with nxapi's custom server. This will also work with imink/flapg *if the system time exactly matches the imink/flapg Android device's time*. This commit also makes nxapi's API compatible with imink's API and adds some information about the Android device and NSO app to response headers.
This commit is contained in:
@@ -60,7 +60,7 @@ export interface AccountLoginParameter {
|
||||
naBirthday: string;
|
||||
naCountry: string;
|
||||
language: string;
|
||||
timestamp: string;
|
||||
timestamp: number;
|
||||
requestId: string;
|
||||
f: string;
|
||||
}
|
||||
@@ -84,7 +84,7 @@ export type AccountToken = AccountLogin;
|
||||
export interface AccountTokenParameter {
|
||||
naIdToken: string;
|
||||
naBirthday: string;
|
||||
timestamp: string;
|
||||
timestamp: number;
|
||||
requestId: string;
|
||||
f: string;
|
||||
}
|
||||
@@ -188,6 +188,20 @@ export interface WebServiceAttribute {
|
||||
attrKey: string;
|
||||
}
|
||||
|
||||
export interface WebServiceTokenParameter {
|
||||
id: number;
|
||||
registrationToken: string;
|
||||
timestamp: number;
|
||||
requestId: string;
|
||||
f: string;
|
||||
}
|
||||
|
||||
/** /v2/Game/GetWebServiceToken */
|
||||
export interface WebServiceToken {
|
||||
accessToken: string;
|
||||
expiresIn: number;
|
||||
}
|
||||
|
||||
/** /v1/Event/GetActiveEvent */
|
||||
export type GetActiveEventResult = ActiveEvent | {};
|
||||
|
||||
@@ -281,9 +295,3 @@ export interface UpdateCurrentUserPermissionsParameter {
|
||||
};
|
||||
etag: string;
|
||||
}
|
||||
|
||||
/** /v2/Game/GetWebServiceToken */
|
||||
export interface WebServiceToken {
|
||||
accessToken: string;
|
||||
expiresIn: number;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import { v4 as uuidgen } from 'uuid';
|
||||
import createDebug from 'debug';
|
||||
import { f, FlapgIid, FResult } from './f.js';
|
||||
import { f, 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';
|
||||
@@ -178,17 +178,14 @@ export default class CoralApi {
|
||||
}
|
||||
|
||||
async getWebServiceToken(id: string) {
|
||||
const uuid = uuidgen();
|
||||
const timestamp = '' + Math.floor(Date.now() / 1000);
|
||||
|
||||
const data = await f(this.token, timestamp, uuid, FlapgIid.APP, this.useragent ?? getAdditionalUserAgents());
|
||||
const data = await f(this.token, '2', this.useragent ?? getAdditionalUserAgents());
|
||||
|
||||
const req = {
|
||||
id,
|
||||
registrationToken: this.token,
|
||||
registrationToken: '',
|
||||
f: data.f,
|
||||
requestId: uuid,
|
||||
timestamp,
|
||||
requestId: data.request_id,
|
||||
timestamp: data.timestamp,
|
||||
};
|
||||
|
||||
return this.call<WebServiceToken>('/v2/Game/GetWebServiceToken', req);
|
||||
@@ -198,27 +195,19 @@ export default class CoralApi {
|
||||
// Nintendo Account token
|
||||
const nintendoAccountToken = await getNintendoAccountToken(token, ZNCA_CLIENT_ID);
|
||||
|
||||
const uuid = uuidgen();
|
||||
const timestamp = '' + Math.floor(Date.now() / 1000);
|
||||
|
||||
const fdata = await f(
|
||||
nintendoAccountToken.id_token, timestamp, uuid,
|
||||
FlapgIid.NSO, this.useragent ?? getAdditionalUserAgents()
|
||||
);
|
||||
const fdata = await f(nintendoAccountToken.id_token, '1', this.useragent ?? getAdditionalUserAgents());
|
||||
|
||||
const req: AccountTokenParameter = {
|
||||
naBirthday: user.birthday,
|
||||
timestamp,
|
||||
timestamp: fdata.timestamp,
|
||||
f: fdata.f,
|
||||
requestId: uuid,
|
||||
requestId: fdata.request_id,
|
||||
naIdToken: nintendoAccountToken.id_token,
|
||||
};
|
||||
|
||||
const data = await this.call<AccountToken>('/v3/Account/GetToken', req, false);
|
||||
|
||||
return {
|
||||
uuid,
|
||||
timestamp,
|
||||
nintendoAccountToken,
|
||||
// user,
|
||||
f: fdata,
|
||||
@@ -261,13 +250,7 @@ export default class CoralApi {
|
||||
// Nintendo Account user data
|
||||
const user = await getNintendoAccountUser(nintendoAccountToken);
|
||||
|
||||
const uuid = uuidgen();
|
||||
const timestamp = '' + Math.floor(Date.now() / 1000);
|
||||
|
||||
const fdata = await f(
|
||||
nintendoAccountToken.id_token, timestamp, uuid,
|
||||
FlapgIid.NSO, useragent
|
||||
);
|
||||
const fdata = await f(nintendoAccountToken.id_token, '1', useragent);
|
||||
|
||||
debug('Getting Nintendo Switch Online app token');
|
||||
|
||||
@@ -276,8 +259,8 @@ export default class CoralApi {
|
||||
naBirthday: user.birthday,
|
||||
naCountry: user.country,
|
||||
language: user.language,
|
||||
timestamp,
|
||||
requestId: uuid,
|
||||
timestamp: fdata.timestamp,
|
||||
requestId: fdata.request_id,
|
||||
f: fdata.f,
|
||||
};
|
||||
|
||||
@@ -314,8 +297,6 @@ export default class CoralApi {
|
||||
debug('Got Nintendo Switch Online app token', data);
|
||||
|
||||
return {
|
||||
uuid,
|
||||
timestamp,
|
||||
nintendoAccountToken,
|
||||
user,
|
||||
f: fdata,
|
||||
@@ -328,8 +309,6 @@ export default class CoralApi {
|
||||
}
|
||||
|
||||
export interface CoralAuthData {
|
||||
uuid: string;
|
||||
timestamp: string;
|
||||
nintendoAccountToken: NintendoAccountToken;
|
||||
user: NintendoAccountUser;
|
||||
f: FResult;
|
||||
@@ -340,7 +319,7 @@ export interface CoralAuthData {
|
||||
}
|
||||
|
||||
export type PartialCoralAuthData =
|
||||
Pick<CoralAuthData, 'uuid' | 'timestamp' | 'nintendoAccountToken' | 'f' | 'nsoAccount' | 'credential'>;
|
||||
Pick<CoralAuthData, 'nintendoAccountToken' | 'f' | 'nsoAccount' | 'credential'>;
|
||||
|
||||
export interface CoralJwtPayload extends JwtPayload {
|
||||
isChildRestricted: boolean;
|
||||
|
||||
66
src/api/f.ts
66
src/api/f.ts
@@ -1,6 +1,7 @@
|
||||
import process from 'node:process';
|
||||
import fetch from 'node-fetch';
|
||||
import createDebug from 'debug';
|
||||
import { v4 as uuidgen } from 'uuid';
|
||||
import { ErrorResponse } from './util.js';
|
||||
import { timeoutSignal } from '../util/misc.js';
|
||||
import { getUserAgent } from '../util/useragent.js';
|
||||
@@ -15,7 +16,7 @@ export abstract class ZncaApi {
|
||||
public useragent?: string
|
||||
) {}
|
||||
|
||||
abstract genf(token: string, timestamp: string, uuid: string, type: FlapgIid): Promise<FResult>;
|
||||
abstract genf(token: string, hash_method: '1' | '2'): Promise<FResult>;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -123,12 +124,16 @@ export class ZncaApiFlapg extends ZncaApi {
|
||||
return getLoginHash(id_token, timestamp, this.useragent);
|
||||
}
|
||||
|
||||
async genf(token: string, timestamp: string, uuid: string, type: FlapgIid) {
|
||||
const result = await flapg(token, timestamp, uuid, type, this.useragent);
|
||||
async genf(token: string, hash_method: '1' | '2') {
|
||||
const timestamp = Date.now();
|
||||
const request_id = uuidgen();
|
||||
const type = hash_method === '2' ? FlapgIid.APP : FlapgIid.NSO;
|
||||
|
||||
const result = await flapg(token, timestamp, request_id, type, this.useragent);
|
||||
|
||||
return {
|
||||
provider: 'flapg' as const,
|
||||
token, timestamp, uuid, type,
|
||||
token, timestamp, request_id, hash_method, type,
|
||||
f: result.result.f,
|
||||
result,
|
||||
};
|
||||
@@ -198,12 +203,15 @@ export interface IminkFError {
|
||||
}
|
||||
|
||||
export class ZncaApiImink extends ZncaApi {
|
||||
async genf(token: string, timestamp: string, uuid: string, type: FlapgIid) {
|
||||
const result = await iminkf(token, timestamp, uuid, type === FlapgIid.APP ? '2' : '1', this.useragent);
|
||||
async genf(token: string, hash_method: '1' | '2') {
|
||||
const timestamp = Date.now();
|
||||
const request_id = uuidgen();
|
||||
|
||||
const result = await iminkf(token, timestamp, request_id, hash_method, this.useragent);
|
||||
|
||||
return {
|
||||
provider: 'imink' as const,
|
||||
token, timestamp, uuid, type,
|
||||
token, timestamp, request_id, hash_method,
|
||||
f: result.f,
|
||||
result,
|
||||
};
|
||||
@@ -215,18 +223,19 @@ export class ZncaApiImink extends ZncaApi {
|
||||
//
|
||||
|
||||
export async function genf(
|
||||
url: string, token: string, timestamp: string | number, uuid: string, type: FlapgIid,
|
||||
url: string, hash_method: '1' | '2',
|
||||
token: string, timestamp?: number, request_id?: string,
|
||||
useragent?: string
|
||||
) {
|
||||
debugZncaApi('Getting f parameter', {
|
||||
url, token, timestamp, uuid,
|
||||
url, token, timestamp, request_id,
|
||||
});
|
||||
|
||||
const req: AndroidZncaFRequest = {
|
||||
type,
|
||||
hash_method,
|
||||
token,
|
||||
timestamp: '' + timestamp,
|
||||
uuid,
|
||||
timestamp,
|
||||
request_id,
|
||||
};
|
||||
|
||||
const [signal, cancel] = timeoutSignal();
|
||||
@@ -251,19 +260,21 @@ export async function genf(
|
||||
throw new ErrorResponse('[znca-api] ' + data.error, response, data);
|
||||
}
|
||||
|
||||
debugZncaApi('Got f parameter "%s"', data.f);
|
||||
debugZncaApi('Got f parameter', data, response.headers);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export interface AndroidZncaFRequest {
|
||||
type: FlapgIid;
|
||||
hash_method: '1' | '2';
|
||||
token: string;
|
||||
timestamp: string;
|
||||
uuid: string;
|
||||
timestamp?: string | number;
|
||||
request_id?: string;
|
||||
}
|
||||
export interface AndroidZncaFResponse {
|
||||
f: string;
|
||||
timestamp?: number;
|
||||
request_id?: string;
|
||||
}
|
||||
export interface AndroidZncaFError {
|
||||
error: string;
|
||||
@@ -274,38 +285,39 @@ export class ZncaApiNxapi extends ZncaApi {
|
||||
super(useragent);
|
||||
}
|
||||
|
||||
async genf(token: string, timestamp: string, uuid: string, type: FlapgIid) {
|
||||
const result = await genf(this.url + '/f', token, timestamp, uuid, type, this.useragent);
|
||||
async genf(token: string, hash_method: '1' | '2') {
|
||||
const result = await genf(this.url + '/f', hash_method, token, undefined, undefined, this.useragent);
|
||||
|
||||
return {
|
||||
provider: 'nxapi' as const,
|
||||
url: this.url + '/f',
|
||||
token, timestamp, uuid, type,
|
||||
token,
|
||||
timestamp: result.timestamp!, // will be included as not sent in request
|
||||
request_id: result.request_id!,
|
||||
hash_method,
|
||||
f: result.f,
|
||||
result,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function f(
|
||||
token: string, timestamp: string | number, uuid: string, type: FlapgIid,
|
||||
useragent?: string
|
||||
): Promise<FResult> {
|
||||
export async function f(token: string, hash_method: '1' | '2', useragent?: string): Promise<FResult> {
|
||||
const provider = getPreferredZncaApiFromEnvironment(useragent) ?? await getDefaultZncaApi(useragent);
|
||||
|
||||
return provider.genf(token, '' + timestamp, uuid, type);
|
||||
return provider.genf(token, hash_method);
|
||||
}
|
||||
|
||||
export type FResult = {
|
||||
provider: string;
|
||||
token: string;
|
||||
timestamp: string;
|
||||
uuid: string;
|
||||
type: FlapgIid;
|
||||
timestamp: number;
|
||||
request_id: string;
|
||||
hash_method: '1' | '2';
|
||||
f: string;
|
||||
result: unknown;
|
||||
} & ({
|
||||
provider: 'flapg';
|
||||
type: FlapgIid;
|
||||
result: FlapgApiResponse;
|
||||
} | {
|
||||
provider: 'imink';
|
||||
|
||||
Reference in New Issue
Block a user