From 77a32e1b5e38f53b563d7a99e52397fa7e55dfc2 Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Fri, 8 Apr 2022 19:57:45 +0100 Subject: [PATCH] Add an option to show event data --- src/api/znc-types.ts | 4 +-- src/api/znc.ts | 4 +-- src/cli/nso/http-server.ts | 4 +-- src/cli/nso/notify.ts | 3 +- src/cli/nso/presence.ts | 63 +++++++++++++++++++++++++------------- src/discord/titles.ts | 6 ++++ src/discord/util.ts | 28 ++++++++++++++--- 7 files changed, 80 insertions(+), 32 deletions(-) diff --git a/src/api/znc-types.ts b/src/api/znc-types.ts index f142abc..e1c9ba9 100644 --- a/src/api/znc-types.ts +++ b/src/api/znc-types.ts @@ -113,9 +113,9 @@ export interface WebServiceAttribute { } /** /v1/Event/GetActiveEvent */ -export type ActiveEvent = _ActiveEvent | {}; +export type GetActiveEventResult = ActiveEvent | {}; -export interface _ActiveEvent extends Event { +export interface ActiveEvent extends Event { activateId: string; } diff --git a/src/api/znc.ts b/src/api/znc.ts index 8c2b1ad..1854e2f 100644 --- a/src/api/znc.ts +++ b/src/api/znc.ts @@ -2,7 +2,7 @@ import fetch from 'node-fetch'; import { v4 as uuidgen } from 'uuid'; import createDebug from 'debug'; import { flapg, FlapgIid, genfc } from './f.js'; -import { AccountLogin, ActiveEvent, Announcements, CurrentUser, CurrentUserPermissions, Event, Friends, PresencePermissions, User, WebServices, WebServiceToken, ZncResponse, ZncStatus } from './znc-types.js'; +import { AccountLogin, Announcements, CurrentUser, CurrentUserPermissions, Event, Friends, GetActiveEventResult, PresencePermissions, User, WebServices, WebServiceToken, ZncResponse, ZncStatus } from './znc-types.js'; import { getNintendoAccountToken, getNintendoAccountUser, NintendoAccountUser } from './na.js'; import { ErrorResponse, JwtPayload } from './util.js'; @@ -84,7 +84,7 @@ export default class ZncApi { } async getActiveEvent() { - return this.fetch('/v1/Event/GetActiveEvent', 'POST', '{"parameter":{}}'); + return this.fetch('/v1/Event/GetActiveEvent', 'POST', '{"parameter":{}}'); } async getEvent(id: number) { diff --git a/src/cli/nso/http-server.ts b/src/cli/nso/http-server.ts index 2185170..7d180c5 100644 --- a/src/cli/nso/http-server.ts +++ b/src/cli/nso/http-server.ts @@ -4,7 +4,7 @@ import bodyParser from 'body-parser'; import * as net from 'net'; import persist from 'node-persist'; import { v4 as uuidgen } from 'uuid'; -import { ActiveEvent, Announcement, CurrentUser, Friend, Presence, WebService } from '../../api/znc-types.js'; +import { ActiveEvent, Announcement, CurrentUser, Friend, GetActiveEventResult, Presence, WebService } from '../../api/znc-types.js'; import ZncApi from '../../api/znc.js'; import type { Arguments as ParentArguments } from '../nso.js'; import { ArgumentsCamelCase, Argv, getToken, initStorage, SavedToken, YargsArguments } from '../../util.js'; @@ -268,7 +268,7 @@ export async function handler(argv: ArgumentsCamelCase) { // const cached_friendsdata = new Map(); - const cached_appdata = new Map(); + const cached_appdata = new Map(); const getFriendsData: express.RequestHandler = async (req, res, next) => { const cache = cached_friendsdata.get(req.zncAuth!.user.id); diff --git a/src/cli/nso/notify.ts b/src/cli/nso/notify.ts index 7a0d0b2..b59d043 100644 --- a/src/cli/nso/notify.ts +++ b/src/cli/nso/notify.ts @@ -198,7 +198,8 @@ export class ZncNotifications extends Loop { result.webservices = (await this.nso.getWebServices()).result; } if (req.includes('event')) { - result.activeevent = (await this.nso.getActiveEvent()).result; + const activeevent = (await this.nso.getActiveEvent()).result; + result.activeevent = 'id' in activeevent ? activeevent : undefined; } if (req.includes('user')) { result.user = (await this.nso.getCurrentUser()).result; diff --git a/src/cli/nso/presence.ts b/src/cli/nso/presence.ts index 9763fdc..e217110 100644 --- a/src/cli/nso/presence.ts +++ b/src/cli/nso/presence.ts @@ -3,7 +3,7 @@ import createDebug from 'debug'; import persist from 'node-persist'; import DiscordRPC from 'discord-rpc'; import fetch from 'node-fetch'; -import { CurrentUser, Presence, PresenceState, ZncErrorResponse } from '../../api/znc-types.js'; +import { ActiveEvent, CurrentUser, Presence, PresenceState, ZncErrorResponse } from '../../api/znc-types.js'; import ZncApi from '../../api/znc.js'; import type { Arguments as ParentArguments } from '../nso.js'; import { ArgumentsCamelCase, Argv, getToken, initStorage, LoopResult, SavedToken, YargsArguments } from '../../util.js'; @@ -29,6 +29,10 @@ export function builder(yargs: Argv) { describe: 'Show Discord presence if your console is online but you are not playing (only enable if you are the only user on all consoles your account exists on)', type: 'boolean', default: false, + }).option('show-event', { + describe: 'Show event (Online Lounge/voice chat) details - this shows the number of players in game (experimental)', + type: 'boolean', + default: false, }).option('friend-nsaid', { alias: ['friend-naid'], describe: 'Friend\'s Nintendo Switch account ID', @@ -110,6 +114,7 @@ type Arguments = YargsArguments>; export async function handler(argv: ArgumentsCamelCase) { if (argv.presenceUrl) { + if (argv.showEvent) throw new Error('--presence-url not compatible with --show-event'); if (argv.friendNsaid) throw new Error('--presence-url not compatible with --friend-nsaid'); if (argv.userNotifications) throw new Error('--presence-url not compatible with --user-notifications'); if (argv.friendNotifications) throw new Error('--presence-url not compatible with --user-notifications'); @@ -148,6 +153,8 @@ export async function handler(argv: ArgumentsCamelCase) { return; } + if (argv.showEvent && argv.friendNsaid) throw new Error('--show-event not compatible with --friend-nsaid'); + const storage = await initStorage(argv.dataPath); const usernsid = argv.user ?? await storage.getItem('SelectedUser'); @@ -180,11 +187,12 @@ export class ZncDiscordPresence extends ZncNotifications { show_friend_code = false; force_friend_code: CurrentUser['links']['friendCode'] | undefined = undefined; show_console_online = false; + show_active_event = true; constructor( argv: Pick, 'userNotifications' | 'friendNotifications' | 'updateInterval' | - 'friendCode' | 'showInactivePresence' | 'friendNsaid' + 'friendCode' | 'showInactivePresence' | 'showEvent' | 'friendNsaid' >, storage: persist.LocalStorage, token: string, @@ -199,31 +207,35 @@ export class ZncDiscordPresence extends ZncNotifications { {id: match[2] + '-' + match[3] + '-' + match[4], regenerable: false, regenerableAt: 0} : undefined; this.show_friend_code = !!this.force_friend_code || argv.friendCode === '' || argv.friendCode === '-'; this.show_console_online = argv.showInactivePresence; + this.show_active_event = argv.showEvent; this.presence_user = argv.friendNsaid ?? data.nsoAccount.user.nsaId; } async init() { - const {friends, user} = await this.fetch([ + const {friends, user, activeevent} = await this.fetch([ 'announcements', this.presence_user ? this.presence_user === this.data.nsoAccount.user.nsaId ? 'user' : {friend: this.presence_user, presence: true} : null, + this.presence_user && this.show_active_event ? 'event' : null, this.user_notifications ? 'user' : null, this.friend_notifications ? 'friends' : null, this.splatnet2_monitors.size ? 'user' : null, ]); - if (this.presence_user && this.presence_user !== this.data.nsoAccount.user.nsaId) { - const friend = friends!.find(f => f.nsaId === this.presence_user); + if (this.presence_user) { + if (this.presence_user !== this.data.nsoAccount.user.nsaId) { + const friend = friends!.find(f => f.nsaId === this.presence_user); - if (!friend) { - throw new Error('User "' + this.presence_user + '" is not friends with this user'); + if (!friend) { + throw new Error('User "' + this.presence_user + '" is not friends with this user'); + } + + await this.updatePresenceForDiscord(friend.presence); + } else { + await this.updatePresenceForDiscord(user!.presence, user!.links.friendCode, activeevent); } - - await this.updatePresenceForDiscord(friend.presence); - } else { - await this.updatePresenceForDiscord(user!.presence, user!.links.friendCode); } await this.updatePresenceForNotifications(user, friends); @@ -238,10 +250,15 @@ export class ZncDiscordPresence extends ZncNotifications { last_presence: Presence | null = null; friendcode: CurrentUser['links']['friendCode'] | undefined = undefined; + last_event: ActiveEvent | undefined = undefined; - async updatePresenceForDiscord(presence: Presence | null, friendcode?: CurrentUser['links']['friendCode']) { + async updatePresenceForDiscord( + presence: Presence | null, friendcode?: CurrentUser['links']['friendCode'], + activeevent?: ActiveEvent + ) { this.last_presence = presence; this.friendcode = friendcode; + this.last_event = activeevent; const online = presence?.state === PresenceState.ONLINE || presence?.state === PresenceState.PLAYING; @@ -262,6 +279,7 @@ export class ZncDiscordPresence extends ZncNotifications { const presencecontext: DiscordPresenceContext = { friendcode: this.show_friend_code ? this.force_friend_code ?? friendcode : undefined, + activeevent, znc_discord_presence: this, nsaid: this.presence_user!, }; @@ -346,26 +364,29 @@ export class ZncDiscordPresence extends ZncNotifications { } async update() { - const {friends, user} = await this.fetch([ + const {friends, user, activeevent} = await this.fetch([ this.presence_user ? this.presence_user === this.data.nsoAccount.user.nsaId ? 'user' : {friend: this.presence_user, presence: true} : null, + this.presence_user && this.show_active_event ? 'event' : null, this.user_notifications ? 'user' : null, this.friend_notifications ? 'friends' : null, this.splatnet2_monitors.size ? 'user' : null, ]); - if (this.presence_user && this.presence_user !== this.data.nsoAccount.user.nsaId) { - const friend = friends!.find(f => f.nsaId === this.presence_user); + if (this.presence_user) { + if (this.presence_user !== this.data.nsoAccount.user.nsaId) { + const friend = friends!.find(f => f.nsaId === this.presence_user); - if (!friend) { - // Is the authenticated user no longer friends with this user? - await this.updatePresenceForDiscord(null); + if (!friend) { + // Is the authenticated user no longer friends with this user? + await this.updatePresenceForDiscord(null); + } else { + await this.updatePresenceForDiscord(friend.presence); + } } else { - await this.updatePresenceForDiscord(friend.presence); + await this.updatePresenceForDiscord(user!.presence, user!.links.friendCode, activeevent); } - } else { - await this.updatePresenceForDiscord(user!.presence, user!.links.friendCode); } await this.updatePresenceForNotifications(user, friends); diff --git a/src/discord/titles.ts b/src/discord/titles.ts index 4db9420..0b2746e 100644 --- a/src/discord/titles.ts +++ b/src/discord/titles.ts @@ -5,6 +5,7 @@ export const defaultTitle: Title = { client: '950883021165330493', titleName: true, showPlayingOnline: true, + showActiveEvent: true, }; const titles: Title[] = [ @@ -14,6 +15,7 @@ const titles: Title[] = [ client: '950886725398429726', largeImageKey: '0100f8f0000a2000', showPlayingOnline: true, + showActiveEvent: true, }, { // Splatoon 2 [The Americas] @@ -21,6 +23,7 @@ const titles: Title[] = [ client: '950886725398429726', largeImageKey: '0100f8f0000a2000', showPlayingOnline: true, + showActiveEvent: true, }, { // Splatoon 2 [Japan] @@ -28,6 +31,7 @@ const titles: Title[] = [ client: '950886725398429726', largeImageKey: '01003c700009c000', showPlayingOnline: true, + showActiveEvent: true, }, { @@ -35,6 +39,7 @@ const titles: Title[] = [ id: '01006a800016e000', client: '950894516104212490', largeImageKey: '01006a800016e000', + showActiveEvent: true, }, { @@ -42,6 +47,7 @@ const titles: Title[] = [ id: '0100152000022000', client: '950905573149409280', largeImageKey: '0100152000022000', + showActiveEvent: true, }, { diff --git a/src/discord/util.ts b/src/discord/util.ts index 909d00c..5b5c4c6 100644 --- a/src/discord/util.ts +++ b/src/discord/util.ts @@ -1,5 +1,5 @@ import DiscordRPC from 'discord-rpc'; -import { CurrentUser, Game, PresenceState } from '../api/znc-types.js'; +import { ActiveEvent, CurrentUser, Game, PresenceState } from '../api/znc-types.js'; import titles, { defaultTitle } from './titles.js'; import { getTitleIdFromEcUrl, hrduration } from '../util.js'; import { ZncDiscordPresence } from '../cli/nso/presence.js'; @@ -15,9 +15,15 @@ export function getDiscordPresence( if (title.titleName === true) text.push(game.name); else if (title.titleName) text.push(title.titleName); - if (game.sysDescription) text.push(game.sysDescription); - else if (state === PresenceState.PLAYING && title.showPlayingOnline === true) text.push('Playing online'); - else if (state === PresenceState.PLAYING && title.showPlayingOnline) text.push(title.showPlayingOnline as string); + 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 (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 (game.totalPlayTime >= 60) { text.push('Played for ' + hrduration(game.totalPlayTime) + @@ -38,6 +44,18 @@ export function getDiscordPresence( ] : [], }; + if (online && title.showActiveEvent && context?.activeevent?.shareUri) { + activity.buttons?.push({ + label: 'Join', + url: context.activeevent.shareUri, + }); + } else if (online && title.showActiveEvent) { + activity.buttons?.push({ + label: 'Join via Nintendo Switch', + url: 'https://lounge.nintendo.com', + }); + } + title.callback?.call(null, activity, game, context); return { @@ -64,6 +82,7 @@ export function getInactiveDiscordPresence( export interface DiscordPresenceContext { friendcode?: CurrentUser['links']['friendCode']; + activeevent?: ActiveEvent; znc_discord_presence?: ZncDiscordPresence; nsaid?: string; } @@ -95,6 +114,7 @@ export interface Title { showTimestamp?: boolean; /** Show "Playing online" if playing online and the game doesn't set activity details */ showPlayingOnline?: string | boolean; + showActiveEvent?: boolean; callback?: (activity: DiscordRPC.Presence, game: Game, context?: DiscordPresenceContext) => void; }