mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-03-22 02:14:10 -05:00
312 lines
7.7 KiB
TypeScript
312 lines
7.7 KiB
TypeScript
import process from 'node:process';
|
|
import fetch from 'node-fetch';
|
|
import createDebug from 'debug';
|
|
import { ErrorResponse } from './util.js';
|
|
import { timeoutSignal } from '../util/misc.js';
|
|
import { getUserAgent } from '../util/useragent.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');
|
|
|
|
export abstract class ZncaApi {
|
|
constructor(
|
|
public useragent?: string
|
|
) {}
|
|
|
|
abstract genf(token: string, timestamp: string, uuid: string, type: FlapgIid): Promise<FResult>;
|
|
}
|
|
|
|
//
|
|
// splatnet2statink + flapg
|
|
//
|
|
|
|
export async function getLoginHash(token: string, timestamp: string | number, useragent?: string) {
|
|
debugS2s('Getting login hash');
|
|
|
|
const [signal, cancel] = timeoutSignal();
|
|
const response = await fetch('https://elifessler.com/s2s/api/gen2', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'User-Agent': getUserAgent(useragent),
|
|
},
|
|
body: new URLSearchParams({
|
|
naIdToken: token,
|
|
timestamp: '' + timestamp,
|
|
}).toString(),
|
|
signal,
|
|
}).finally(cancel);
|
|
|
|
const data = await response.json() as LoginHashApiResponse | LoginHashApiError;
|
|
|
|
if ('error' in data) {
|
|
throw new ErrorResponse('[s2s] ' + data.error, response, data);
|
|
}
|
|
|
|
debugS2s('Got login hash "%s"', data.hash, data);
|
|
|
|
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
|
|
) {
|
|
const hash = await getLoginHash(token, timestamp, useragent);
|
|
|
|
debugFlapg('Getting f parameter', {
|
|
token, timestamp, guid, iid,
|
|
});
|
|
|
|
const [signal, cancel] = timeoutSignal();
|
|
const response = await fetch('https://flapg.com/ika2/api/login?public', {
|
|
headers: {
|
|
'User-Agent': getUserAgent(useragent),
|
|
'x-token': token,
|
|
'x-time': '' + timestamp,
|
|
'x-guid': guid,
|
|
'x-hash': hash,
|
|
'x-ver': '3',
|
|
'x-iid': iid,
|
|
},
|
|
signal,
|
|
}).finally(cancel);
|
|
|
|
const data = await response.json() as FlapgApiResponse;
|
|
|
|
debugFlapg('Got f parameter "%s"', data.result.f);
|
|
|
|
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;
|
|
};
|
|
}
|
|
|
|
export class ZncaApiFlapg extends ZncaApi {
|
|
async getLoginHash(id_token: string, timestamp: string) {
|
|
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);
|
|
|
|
return {
|
|
provider: 'flapg' as const,
|
|
token, timestamp, uuid, type,
|
|
f: result.result.f,
|
|
result,
|
|
};
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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 [signal, cancel] = timeoutSignal();
|
|
const response = await fetch('https://api.imink.app/f', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': getUserAgent(useragent),
|
|
},
|
|
body: JSON.stringify(req),
|
|
signal,
|
|
}).finally(cancel);
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
return {
|
|
provider: 'imink' as const,
|
|
token, timestamp, uuid, type,
|
|
f: result.f,
|
|
result,
|
|
};
|
|
}
|
|
}
|
|
|
|
//
|
|
// nxapi znca API server
|
|
//
|
|
|
|
export async function genf(
|
|
url: string, token: string, timestamp: string | number, uuid: string, type: FlapgIid,
|
|
useragent?: string
|
|
) {
|
|
debugZncaApi('Getting f parameter', {
|
|
url, token, timestamp, uuid,
|
|
});
|
|
|
|
const req: AndroidZncaFRequest = {
|
|
type,
|
|
token,
|
|
timestamp: '' + timestamp,
|
|
uuid,
|
|
};
|
|
|
|
const [signal, cancel] = timeoutSignal();
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': getUserAgent(useragent),
|
|
},
|
|
body: JSON.stringify(req),
|
|
signal,
|
|
}).finally(cancel);
|
|
|
|
const data = await response.json() as AndroidZncaFResponse | AndroidZncaFError;
|
|
|
|
if ('error' in data) {
|
|
debugZncaApi('Error getting f parameter "%s"', data.error);
|
|
throw new ErrorResponse('[znca-api] ' + data.error, response, data);
|
|
}
|
|
|
|
debugZncaApi('Got f parameter "%s"', data.f);
|
|
|
|
return data;
|
|
}
|
|
|
|
export interface AndroidZncaFRequest {
|
|
type: FlapgIid;
|
|
token: string;
|
|
timestamp: string;
|
|
uuid: string;
|
|
}
|
|
export interface AndroidZncaFResponse {
|
|
f: string;
|
|
}
|
|
export interface AndroidZncaFError {
|
|
error: string;
|
|
}
|
|
|
|
export class ZncaApiNxapi extends ZncaApi {
|
|
constructor(readonly url: string, useragent?: string) {
|
|
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);
|
|
|
|
return {
|
|
provider: 'nxapi' as const,
|
|
url: this.url + '/f',
|
|
token, timestamp, uuid, type,
|
|
f: result.f,
|
|
result,
|
|
};
|
|
}
|
|
}
|
|
|
|
export async function f(
|
|
token: string, timestamp: string | number, uuid: string, type: FlapgIid,
|
|
useragent?: string
|
|
): Promise<FResult> {
|
|
const provider = getZncaApiFromEnvironment(useragent);
|
|
|
|
return provider.genf(token, '' + timestamp, uuid, type);
|
|
}
|
|
|
|
export type FResult = {
|
|
provider: string;
|
|
token: string;
|
|
timestamp: string;
|
|
uuid: string;
|
|
type: FlapgIid;
|
|
f: string;
|
|
result: unknown;
|
|
} & ({
|
|
provider: 'flapg';
|
|
result: FlapgApiResponse;
|
|
} | {
|
|
provider: 'imink';
|
|
result: IminkFResponse;
|
|
} | {
|
|
provider: 'nxapi';
|
|
url: string;
|
|
result: AndroidZncaFResponse;
|
|
});
|
|
|
|
export function getZncaApiFromEnvironment(useragent?: string): ZncaApi {
|
|
if (process.env.NXAPI_ZNCA_API) {
|
|
if (process.env.NXAPI_ZNCA_API === 'flapg') {
|
|
return new ZncaApiFlapg(useragent);
|
|
}
|
|
if (process.env.NXAPI_ZNCA_API === 'imink') {
|
|
return new ZncaApiImink(useragent);
|
|
}
|
|
|
|
throw new Error('Unknown znca API provider');
|
|
}
|
|
|
|
if (process.env.ZNCA_API_URL) {
|
|
return new ZncaApiNxapi(process.env.ZNCA_API_URL, useragent);
|
|
}
|
|
|
|
return new ZncaApiFlapg(useragent);
|
|
}
|