From 9cb122c3652a09a0358e6334e9d4242ed56e69a2 Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Thu, 24 Jul 2025 00:08:56 +0100 Subject: [PATCH] Show platform if not using a specific Discord client --- src/api/coral-types.ts | 2 +- src/api/znc-proxy.ts | 9 +++++---- src/cli/presence-server.ts | 7 +++++-- src/cli/util/discord-activity.ts | 30 +++++++++++++++++++++--------- src/common/presence.ts | 6 ++++-- src/discord/util.ts | 2 +- 6 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/api/coral-types.ts b/src/api/coral-types.ts index 3585881..1fdf594 100644 --- a/src/api/coral-types.ts +++ b/src/api/coral-types.ts @@ -162,7 +162,7 @@ export interface Friend { isNew: boolean; friendCreatedAt: number; route: FriendRoute; - presence: Presence | PresenceOnline | PresenceOffline; + presence: PresenceOnline | PresenceOffline; } /** /v4/Friend/Show */ diff --git a/src/api/znc-proxy.ts b/src/api/znc-proxy.ts index 1104829..23c3d16 100644 --- a/src/api/znc-proxy.ts +++ b/src/api/znc-proxy.ts @@ -1,5 +1,5 @@ import { fetch, Response } from 'undici'; -import { ActiveEvent, CurrentUser, Event, Friend, Presence, PresencePermissions, User, WebServiceToken, CoralStatus, CoralSuccessResponse, FriendCodeUser, FriendCodeUrl, WebService_4, Media, Announcements_4, Friend_4 } from './coral-types.js'; +import { ActiveEvent, CurrentUser, Event, Friend, Presence, PresencePermissions, User, WebServiceToken, CoralStatus, CoralSuccessResponse, FriendCodeUser, FriendCodeUrl, WebService_4, Media, Announcements_4, Friend_4, PresenceOnline_4, PresenceOnline, PresenceOffline } from './coral-types.js'; import { defineResponse, ErrorResponse, ResponseSymbol } from './util.js'; import { AbstractCoralApi, CoralApiInterface, CoralAuthData, CorrelationIdSymbol, PartialCoralAuthData, RequestFlagAddPlatformSymbol, RequestFlagAddProductVersionSymbol, RequestFlagNoParameterSymbol, RequestFlagRequestIdSymbol, RequestFlags, ResponseDataSymbol, Result } from './coral.js'; import { NintendoAccountToken, NintendoAccountUser } from './na.js'; @@ -250,7 +250,8 @@ export enum ZncPresenceEventStreamEvent { } export type PresenceUrlResponse = - Presence | {presence: Presence} | + PresenceOnline | PresenceOnline_4 | PresenceOffline | + {presence: PresenceOnline | PresenceOnline_4 | PresenceOffline} | CurrentUser | {user: CurrentUser} | Friend | {friend: Friend}; @@ -276,12 +277,12 @@ export async function getPresenceFromUrl(presence_url: string, useragent?: strin const data = await response.json() as PresenceUrlResponse; - const user: CurrentUser | Friend | undefined = + const user: CurrentUser | Friend | Friend_4 | undefined = 'user' in data ? data.user : 'friend' in data ? data.friend : 'nsaId' in data ? data : undefined; - const presence: Presence = + const presence: PresenceOnline | PresenceOnline_4 | PresenceOffline = 'presence' in data ? data.presence : 'user' in data ? data.user.presence : 'friend' in data ? data.friend.presence : diff --git a/src/cli/presence-server.ts b/src/cli/presence-server.ts index 08def37..c45ea18 100644 --- a/src/cli/presence-server.ts +++ b/src/cli/presence-server.ts @@ -12,7 +12,7 @@ import FestRecordQuery_c8660a6 from 'splatnet3-types/graphql/c8660a636e73dcbf55c import type { Arguments as ParentArguments } from '../cli.js'; import { git, product, version } from '../util/product.js'; import Users, { CoralUser } from '../common/users.js'; -import { Friend } from '../api/coral-types.js'; +import { Friend, PresenceGame } from '../api/coral-types.js'; import SplatNet3Api, { PersistedQueryResult, RequestIdSymbol } from '../api/splatnet3.js'; import { ErrorResponse, ResponseSymbol } from '../api/util.js'; import { getBulletToken, SavedBulletToken } from '../common/auth/splatnet3.js'; @@ -837,13 +837,14 @@ class Server extends HttpServer { if (scope && !scope.includes(PresenceScope.PRESENCE_TIMESTAMPS)) { response.friend = { ...response.friend, + presence: { ...response.friend.presence, game: 'name' in response.friend.presence.game ? { ...response.friend.presence.game, firstPlayedAt: 0, totalPlayTime: 0, - } : {}, + } : {} as PresenceGame, logoutAt: 0, updatedAt: 0, }, @@ -853,6 +854,8 @@ class Server extends HttpServer { if (scope && !scope.includes(PresenceScope.PRESENCE_TITLE)) { response.friend = { ...response.friend, + + // @ts-expect-error presence: { ...response.friend.presence, game: {}, diff --git a/src/cli/util/discord-activity.ts b/src/cli/util/discord-activity.ts index 6b0c364..59c9325 100644 --- a/src/cli/util/discord-activity.ts +++ b/src/cli/util/discord-activity.ts @@ -1,19 +1,21 @@ import process from 'node:process'; import { fetch } from 'undici'; import { getPresenceFromUrl } from '../../api/znc-proxy.js'; -import { ActiveEvent, CurrentUser, Friend, Presence, PresenceGame, PresenceState } from '../../api/coral-types.js'; +import { ActiveEvent, CurrentUser, Friend, PresenceGame, PresenceOffline, PresenceOnline, PresenceOnline_4, PresencePlatform, PresenceState } from '../../api/coral-types.js'; import type { Arguments as ParentArguments } from './index.js'; import { getDiscordPresence, getInactiveDiscordPresence } from '../../discord/util.js'; import { DiscordPresenceContext, DiscordPresencePlayTime } from '../../discord/types.js'; import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; -import { getToken, Login } from '../../common/auth/coral.js'; +import { getToken } from '../../common/auth/coral.js'; import { timeoutSignal } from '../../util/misc.js'; import { getUserAgent } from '../../util/useragent.js'; const debug = createDebug('cli:util:discord-activity'); +type Presence = PresenceOnline | PresenceOnline_4 | PresenceOffline; + export const command = 'discord-activity'; export const desc = 'Get Nintendo Switch presence as a Discord activity'; @@ -131,13 +133,19 @@ async function getPresenceFromJson(json: string) { sysDescription: typeof data.game.sysDescription === 'string' ? data.game.sysDescription : '', } : null; + // @ts-expect-error const presence: Presence = { state, updatedAt: typeof data.updatedAt === 'number' ? data.updatedAt : 0, - logoutAt: typeof data.updatedAt === 'number' ? data.logoutAt : 0, + logoutAt: typeof data.logoutAt === 'number' ? data.logoutAt : 0, game: game ?? {}, }; + if (state === PresenceState.ONLINE || state === PresenceState.PLAYING) { + // @ts-expect-error + presence.platform = typeof data.platform === 'number' ? data.platform : PresencePlatform.NX; + } + return [presence] as const; } @@ -149,11 +157,15 @@ async function getPresenceFromCoral(argv: ArgumentsCamelCase) { await storage.getItem('NintendoAccountToken.' + usernsid); const {nso, data} = await getToken(storage, token, argv.zncProxyUrl); - const announcements = await nso.getAnnouncements(); - const friends = await nso.getFriendList(); - const webservices = await nso.getWebServices(); - const activeevent = await nso.getActiveEvent(); - const user = data[Login] ? data.nsoAccount.user : await nso.getCurrentUser(); + const [friends, chats, webservices, activeevent, media, announcements, user] = await Promise.all([ + nso.getFriendList(), + nso.getChats(), + nso.getWebServices(), + nso.getActiveEvent(), + nso.getMedia(), + nso.getAnnouncements(), + nso.getCurrentUser(), + ]); if (argv.friendNsaid) { const friend = friends.friends.find(f => f.nsaId === argv.friendNsaid); @@ -209,7 +221,7 @@ function getActivityFromPresence( proxy_response, nsaid: argv.friendNsaid ?? user?.nsaId, user, - // platform: presence.platform, + platform: 'platform' in presence ? presence.platform : undefined, }; const discordpresence = 'name' in presence.game ? diff --git a/src/common/presence.ts b/src/common/presence.ts index 94c5053..f8ff362 100644 --- a/src/common/presence.ts +++ b/src/common/presence.ts @@ -5,7 +5,7 @@ import { DiscordRpcClient, findDiscordRpcClient } from '../discord/rpc.js'; import { getDiscordPresence, getInactiveDiscordPresence } from '../discord/util.js'; import { DiscordPresencePlayTime, DiscordPresenceContext, DiscordPresence, ExternalMonitorConstructor, ExternalMonitor, ErrorResult } from '../discord/types.js'; import { EmbeddedSplatNet2Monitor, ZncNotifications } from './notify.js'; -import { ActiveEvent, CurrentUser, Friend, Game, Presence, PresenceState, CoralError } from '../api/coral-types.js'; +import { ActiveEvent, CurrentUser, Friend, Game, PresenceState, CoralError, PresenceOnline_4, PresenceOffline, PresenceOnline } from '../api/coral-types.js'; import { getPresenceFromUrl } from '../api/znc-proxy.js'; import createDebug from '../util/debug.js'; import { ErrorResponse, ResponseSymbol } from '../api/util.js'; @@ -25,6 +25,8 @@ const MAX_CONNECT_ATTEMPTS = Infinity; // 10 const RECONNECT_INTERVAL = 5000; // 5 seconds const MAX_PROXY_AUTO_RETRY = 10; +type Presence = PresenceOnline_4 | PresenceOnline | PresenceOffline; + interface SavedPresence { presence: Presence; title_since: number; @@ -120,7 +122,7 @@ class ZncDiscordPresenceClient { monitors: [...this.monitors.values()], nsaid: this.m.presence_user!, user, - // platform: presence.platform, + platform: 'platform' in presence ? presence.platform : undefined, }; const discord_presence = 'name' in presence.game ? diff --git a/src/discord/util.ts b/src/discord/util.ts index 73f5998..91b75e4 100644 --- a/src/discord/util.ts +++ b/src/discord/util.ts @@ -76,7 +76,7 @@ export function getDiscordPresence( } return { - id: title.client || + id: (title !== defaultTitle ? title : null)?.client || (typeof context?.platform !== 'undefined' && platform_clients[context.platform]) || defaultTitle.client, title: titleid,