Coral 3.2.0

This commit is contained in:
Ellie 2025-12-09 19:47:01 +00:00
parent e993706671
commit 8cae29d035
No known key found for this signature in database
GPG Key ID: 9DBD8FA906936CFF
4 changed files with 58 additions and 27 deletions

View File

@ -81,6 +81,20 @@ export interface AccountLogin {
/** /v3/Account/GetToken, /v3/Extension/Account/GetToken */
export type AccountToken = AccountLogin;
/** /v4/Account/Login */
export interface AccountLogin_4 {
user: Exclude<CurrentUser, 'links'> & {
links: Exclude<CurrentUser['links'], 'nintendoAccount'>;
};
webApiServerCredential: {
accessToken: string;
expiresIn: number;
};
}
/** /v4/Account/GetToken, /v4/Extension/Account/GetToken */
export type AccountToken_4 = AccountLogin_4;
export interface AccountTokenParameter {
naIdToken: string;
naBirthday: string;
@ -177,22 +191,38 @@ export interface FriendRoute {
userName: string;
shopUri: string;
imageUri: string;
// if not IN_APP all other properties are empty strings
// if not IN_APP/NINTENDO_ACCOUNT all other properties are empty strings
channel: FriendRouteChannel;
}
export enum FriendRouteChannel {
/** Added from friend code lookup on a Switch console or using coral */
FRIEND_CODE = 'FRIEND_CODE',
/** Added from users you've played with */
IN_APP = 'IN_APP',
/** Added from search for local users */
NX_FACED = 'NX_FACED',
/**
* Added from users you've played with
* Shows the application details (appName, shopUri, imageUri) and in-game name (userName)
*/
IN_APP = 'IN_APP',
/**
* Added from friend suggestions for smart device game friends linked to the same Nintendo Account
* Shows the client name (FriendRoute.appName)
*/
NINTENDO_ACCOUNT = 'NINTENDO_ACCOUNT',
'3DS' = '3DS',
// Wii U, Facebook, Twitter suggestions?
NNID = 'NNID',
TWITTER = 'TWITTER',
FACEBOOK = 'FACEBOOK',
WECHAT = 'WECHAT',
/** Added from GameChat */
CAMPUS = 'CAMPUS',
}
export type CreateFriendRequestChannel =
FriendRouteChannel.FRIEND_CODE |
FriendRouteChannel.CAMPUS;
export interface Presence {
state: PresenceState;
/**
@ -624,6 +654,8 @@ export enum FeedbackTopic {
// - /v1/Voip/Mute
// - /v5/Chat/Show
// - /v5/Chat/List
// - /v5/Chat/Member/Show
// - /v5/Chat/FriendCandidate/List
// - /v1/Support/ReportUser
//
// Also, announcement types other than OPERATION and FRIEND_REQUEST

View File

@ -5,7 +5,7 @@ import { JwtPayload } from '../util/jwt.js';
import { timeoutSignal } from '../util/misc.js';
import { getAdditionalUserAgents } from '../util/useragent.js';
import type { CoralRemoteConfig } from '../common/remote-config.js';
import { AccountLogin, AccountLoginParameter, AccountToken, AccountTokenParameter, Announcements_4, BlockingUsers, CoralError, CoralResponse, CoralStatus, CoralSuccessResponse, CurrentUser, CurrentUserPermissions, Event, Friend_4, FriendCodeUrl, FriendCodeUser, Friends_4, GetActiveEventResult, ListChat, ListHashtag, ListHashtagParameter, ListMedia, ListMediaParameter, ListPushNotificationSettings, Media, PlayLogPermissions, PresencePermissions, PushNotificationPlayInvitationScope, ReceivedFriendRequest, ReceivedFriendRequests, SentFriendRequests, ShowUserLogin, UpdatePushNotificationSettingsParameter, UpdatePushNotificationSettingsParameterItem, User, UserPlayLog, WebServices_4, WebServiceToken, WebServiceTokenParameter } from './coral-types.js';
import { AccountLogin, AccountLogin_4, AccountLoginParameter, AccountToken, AccountToken_4, AccountTokenParameter, Announcements_4, BlockingUsers, CoralError, CoralResponse, CoralStatus, CoralSuccessResponse, CreateFriendRequestChannel, CurrentUser, CurrentUserPermissions, Event, Friend_4, FriendCodeUrl, FriendCodeUser, FriendRouteChannel, Friends_4, GetActiveEventResult, ListChat, ListHashtag, ListHashtagParameter, ListMedia, ListPushNotificationSettings, Media, PlayLogPermissions, PresencePermissions, PushNotificationPlayInvitationScope, ReceivedFriendRequest, ReceivedFriendRequests, SentFriendRequests, ShowUserLogin, UpdatePushNotificationSettingsParameter, UpdatePushNotificationSettingsParameterItem, User, UserPlayLog, WebServices_4, WebServiceToken, WebServiceTokenParameter } from './coral-types.js';
import { createZncaApi, DecryptResponseResult, FResult, HashMethod, RequestEncryptionProvider, ZncaApi, ZncaApiNxapi } from './f.js';
import { generateAuthData, getNintendoAccountToken, getNintendoAccountUser, NintendoAccountScope, NintendoAccountSessionAuthorisation, NintendoAccountToken, NintendoAccountUser } from './na.js';
import { ErrorResponse, ResponseSymbol } from './util.js';
@ -15,9 +15,11 @@ const debug = createDebug('nxapi:api:coral');
const ZNCA_PLATFORM = 'Android';
const ZNCA_PLATFORM_VERSION = '12';
export const ZNCA_VERSION = '3.0.2'; // TODO: update to 3.1.0
export const ZNCA_VERSION = '3.2.0'; // TODO: update to 3.1.0
const ZNCA_USER_AGENT = `com.nintendo.znca/${ZNCA_VERSION}(${ZNCA_PLATFORM}/${ZNCA_PLATFORM_VERSION})`;
export const ZNCA_API_COMPATIBILITY_VERSION = 'hio87-mJks_e9GNF';
const ZNC_URL = 'https://api-lp1.znc.srv.nintendo.net';
export const ZNCA_CLIENT_ID = '71b963c1b7b6d119';
@ -93,9 +95,7 @@ export abstract class AbstractCoralApi {
}
async getMedia() {
return this.call<ListMedia, ListMediaParameter>('/v4/Media/List', {
count: 100,
});
return this.call<ListMedia>('/v4/Media/List');
}
async getHashtags(media: Media) {
@ -186,9 +186,10 @@ export abstract class AbstractCoralApi {
});
}
async sendFriendRequest(nsa_id: string) {
return this.call('/v3/FriendRequest/Create', {
async sendFriendRequest(nsa_id: string, channel: CreateFriendRequestChannel = FriendRouteChannel.FRIEND_CODE) {
return this.call('/v4/FriendRequest/Create', {
nsaId: nsa_id,
channel,
});
}
@ -591,7 +592,7 @@ export default class CoralApi extends AbstractCoralApi implements CoralApiInterf
const fdata = await provider.genf(nintendoAccountToken.id_token, HashMethod.CORAL, {
na_id: user.id, coral_user_id: '' + this[CoralUserIdSymbol],
}, provider.supportsEncryption() ? {
url: ZNC_URL + '/v3/Account/GetToken',
url: ZNC_URL + '/v4/Account/GetToken',
parameter,
} : undefined);
@ -609,13 +610,13 @@ export default class CoralApi extends AbstractCoralApi implements CoralApiInterf
});
if (provider.supportsEncryption()) {
const result = await provider.encryptRequest(ZNC_URL + '/v3/Account/GetToken', null, body);
const result = await provider.encryptRequest(ZNC_URL + '/v4/Account/GetToken', null, body);
body = new EncryptedRequestBody(provider, result.data, body);
}
}
const data = await this.fetch<AccountToken>('/v3/Account/GetToken', 'POST', body, undefined, {
const data = await this.fetch<AccountToken_4>('/v4/Account/GetToken', 'POST', body, undefined, {
[RequestFlagAddPlatformSymbol]: true,
[RequestFlagAddProductVersionSymbol]: true,
[RequestFlagNoAuthenticationSymbol]: true,
@ -719,7 +720,7 @@ export default class CoralApi extends AbstractCoralApi implements CoralApiInterf
const fdata = await provider.genf(nintendoAccountToken.id_token, HashMethod.CORAL, {
na_id: user.id,
}, provider.supportsEncryption() ? {
url: ZNC_URL + '/v3/Account/Login',
url: ZNC_URL + '/v4/Account/Login',
parameter,
} : undefined);
@ -743,7 +744,7 @@ export default class CoralApi extends AbstractCoralApi implements CoralApiInterf
});
if (provider.supportsEncryption()) {
const result = await provider.encryptRequest(ZNC_URL + '/v3/Account/Login', null, body);
const result = await provider.encryptRequest(ZNC_URL + '/v4/Account/Login', null, body);
encrypted = [provider];
body = result.data;
@ -759,22 +760,22 @@ export default class CoralApi extends AbstractCoralApi implements CoralApiInterf
});
const [signal, cancel] = timeoutSignal();
const response = await fetch(ZNC_URL + '/v3/Account/Login', {
const response = await fetch(ZNC_URL + '/v4/Account/Login', {
method: 'POST',
headers,
body,
signal,
}).finally(cancel);
debug('fetch %s %s, response %s', 'POST', '/v3/Account/Login', response.status);
debug('fetch %s %s, response %s', 'POST', '/v4/Account/Login', response.status);
if (response.status !== 200) {
throw await CoralErrorResponse.fromResponse(response, '[znc] Non-200 status code');
}
const data: CoralResponse<AccountLogin> = encrypted ?
const data: CoralResponse<AccountLogin_4> = encrypted ?
JSON.parse((await encrypted[0].decryptResponse(new Uint8Array(await response.arrayBuffer()))).data) :
await response.json() as CoralResponse<AccountLogin>;
await response.json() as CoralResponse<AccountLogin_4>;
if ('errorMessage' in data) {
throw new CoralErrorResponse('[znc] ' + data.errorMessage, response, data);
@ -1048,7 +1049,7 @@ export interface CoralAuthData {
nintendoAccountToken: NintendoAccountToken;
user: NintendoAccountUserCoral;
f: FResult;
nsoAccount: AccountLogin;
nsoAccount: AccountLogin | AccountLogin_4;
credential: AccountLogin['webApiServerCredential'];
znca_version: string;
znca_useragent: string;

View File

@ -6,7 +6,7 @@ import createDebug from '../util/debug.js';
import { timeoutSignal } from '../util/misc.js';
import { getUserAgent } from '../util/useragent.js';
import { client_assertion_provider, client_auth_provider, ClientAssertionProviderInterface } from '../util/nxapi-auth.js';
import { ZNCA_VERSION } from './coral.js';
import { ZNCA_API_COMPATIBILITY_VERSION } from './coral.js';
import { AccountLoginParameter, AccountTokenParameter, WebServiceTokenParameter } from './coral-types.js';
const debugFlapg = createDebug('nxapi:api:flapg');
@ -345,6 +345,7 @@ export class ZncaApiNxapi extends ZncaApi implements RequestEncryptionProvider {
this.auth = auth;
this.headers.set('User-Agent', getUserAgent(useragent));
this.headers.append('X-znca-Client-Version', ZNCA_API_COMPATIBILITY_VERSION);
}
static create(
@ -384,7 +385,6 @@ export class ZncaApiNxapi extends ZncaApi implements RequestEncryptionProvider {
headers.set('Accept', 'application/json');
if (this.app?.platform) headers.append('X-znca-Platform', this.app.platform);
if (this.app?.version) headers.append('X-znca-Version', this.app.version);
if (ZNCA_VERSION) headers.append('X-znca-Client-Version', ZNCA_VERSION);
if (this.auth?.token) headers.append('Authorization', 'Bearer ' + this.auth.token.token);
const [signal, cancel] = timeoutSignal();
@ -450,7 +450,6 @@ export class ZncaApiNxapi extends ZncaApi implements RequestEncryptionProvider {
headers.set('Accept', 'application/octet-stream');
if (this.app?.platform) headers.append('X-znca-Platform', this.app.platform);
if (this.app?.version) headers.append('X-znca-Version', this.app.version);
if (ZNCA_VERSION) headers.append('X-znca-Client-Version', ZNCA_VERSION);
if (this.auth?.token) headers.append('Authorization', 'Bearer ' + this.auth.token.token);
const [signal, cancel] = timeoutSignal();
@ -504,7 +503,6 @@ export class ZncaApiNxapi extends ZncaApi implements RequestEncryptionProvider {
headers.set('Accept', request_nsa_assertion ? 'application/json' : 'text/plain');
if (this.app?.platform) headers.append('X-znca-Platform', this.app.platform);
if (this.app?.version) headers.append('X-znca-Version', this.app.version);
if (ZNCA_VERSION) headers.append('X-znca-Client-Version', ZNCA_VERSION);
if (this.auth?.token) headers.append('Authorization', 'Bearer ' + this.auth.token.token);
const [signal, cancel] = timeoutSignal();

View File

@ -43,6 +43,6 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
nso.getCurrentUser(),
]) : [];
console.log('Nintendo Account', data.user);
console.log('Nintendo Account', data.user, 'naUser' in data.nsoAccount ? data.nsoAccount.naUser : null);
console.log('Nintendo Switch user', current_user ?? data.nsoAccount.user);
}