nxapi/src/discord/util.ts

173 lines
6.1 KiB
TypeScript

import DiscordRPC from 'discord-rpc';
import { ActiveEvent, CurrentUser, Friend, Game, PresenceState } from '../api/znc-types.js';
import { defaultTitle, titles } from './titles.js';
import { getTitleIdFromEcUrl, hrduration, version } from '../util.js';
import { ZncDiscordPresence } from '../cli/nso/presence.js';
export function getDiscordPresence(
state: PresenceState, game: Game, context?: DiscordPresenceContext
): DiscordPresence {
const titleid = getTitleIdFromEcUrl(game.shopUri);
const title = titles.find(t => t.id === titleid) || defaultTitle;
const text = [];
if (title.titleName === true) text.push(game.name);
else if (title.titleName) text.push(title.titleName);
const online = state === PresenceState.PLAYING;
const members = context?.activeevent?.members.filter(m => m.isPlaying);
const event_text = title.showActiveEvent && context?.activeevent ?
' (' + members?.length + ' player' + (members?.length === 1 ? '' : 's') +
')' : '';
if ((title.showDescription ?? true) && game.sysDescription) text.push(game.sysDescription + event_text);
else if (online && title.showPlayingOnline === true) text.push('Playing online' + event_text);
else if (online && title.showPlayingOnline) text.push(title.showPlayingOnline as string + event_text);
if ((title.showPlayTime ?? true) && game.totalPlayTime >= 60) {
text.push('Played for ' + hrduration(game.totalPlayTime) +
' since ' + (game.firstPlayedAt ? new Date(game.firstPlayedAt * 1000).toLocaleDateString('en-GB') : 'now'));
}
const activity: DiscordRPC.Presence = {
details: text[0],
state: text[1],
largeImageKey: title.largeImageKey ?? game.imageUri,
largeImageText: title.largeImageText ?? 'nxapi ' + version,
smallImageKey: title.smallImageKey || (context?.friendcode ? context?.user?.imageUri : undefined),
smallImageText: title.smallImageKey ? title.smallImageText :
context?.friendcode && context?.user?.imageUri ? 'SW-' + context.friendcode.id : undefined,
buttons: game.shopUri ? [
{
label: 'Nintendo eShop',
url: game.shopUri,
},
] : [],
};
if (online && title.showActiveEvent) {
activity.buttons!.push({
label: context?.activeevent?.shareUri ? 'Join' : 'Join via Nintendo Switch',
url: context?.activeevent?.shareUri ?? 'https://lounge.nintendo.com',
});
}
title.callback?.call(null, activity, game, context);
return {
id: title.client || defaultTitle.client,
title: titleid,
activity,
showTimestamp: title.showTimestamp,
};
}
export function getInactiveDiscordPresence(
state: PresenceState, logoutAt: number, context?: DiscordPresenceContext
): DiscordPresence {
return {
id: defaultTitle.client,
title: null,
activity: {
state: 'Not playing',
largeImageKey: 'nintendoswitch',
largeImageText: 'nxapi ' + version,
smallImageKey: context?.friendcode ? context?.user?.imageUri : undefined,
smallImageText: context?.friendcode && context?.user?.imageUri ? 'SW-' + context.friendcode.id : undefined,
},
};
}
export interface DiscordPresenceContext {
friendcode?: CurrentUser['links']['friendCode'];
activeevent?: ActiveEvent;
znc_discord_presence?: ZncDiscordPresence;
nsaid?: string;
user?: CurrentUser | Friend;
}
export interface DiscordPresence {
id: string;
title: string | null;
activity: DiscordRPC.Presence;
showTimestamp?: boolean;
}
export function getTitleConfiguration(game: Game, id: string) {
return titles.find(title => {
if (title.id !== id) return false;
return true;
});
}
export interface Title {
/**
* Lowercase hexadecimal title ID.
*
* Valid title IDs are 16 characters long, and should start with `0100` and end with `0000`, `2000`, `4000`, `6000`, `8000`, `a000`, `c000`, `e000` (this is because applications have 4^16 title IDs for the application itself, plus addon content and update data).
*/
id: string;
/**
* Discord client ID
*/
client: string;
/**
* Title name to show in Discord. This is *not* the name that will appear under the user's name after "Playing ".
*
* If this is set to true the title's name from znc will be used.
* If this is set to false (default) no title name will be set. This should be used when a specific Discord client for the title is used.
* If this is set to a string it will be used as the title name.
*
* @default false
*/
titleName?: string | boolean;
/**
* By default the title's icon from znc will be used. (No icons need to be uploaded to Discord.)
*/
largeImageKey?: string;
largeImageText?: string;
/**
* By default this will not be set.
*/
smallImageKey?: string;
smallImageText?: string;
/**
* Whether to show the timestamp the user started playing the title in Discord. Discord shows this as the number of minutes and seconds since the timestamp; this will be inaccurate as it may take up to a minute (by default) to detect the user's presence, so this is disabled by default.
*
* @default false
*/
showTimestamp?: boolean;
/**
* Show the activity description set by the title.
*
* @default true
*/
showDescription?: boolean;
/**
* Show "Playing online" if playing online and the game doesn't set activity details.
*
* @default false
*/
showPlayingOnline?: string | boolean;
/**
* Whether to show details of the current event (Online Lounge/voice chat) in Discord.
*
* @default false
*/
showActiveEvent?: boolean;
/**
* Whether to show "Played for ... since ..." in Discord.
*
* @default true
*/
showPlayTime?: boolean;
/**
* A function to call to customise the Discord activity.
*/
callback?: (activity: DiscordRPC.Presence, game: Game, context?: DiscordPresenceContext) => void;
}