Add functions to use a specific Discord user in the app

This commit is contained in:
Samuel Elliott 2022-06-12 15:19:12 +01:00
parent 241ce19ec8
commit ca35545f98
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
5 changed files with 124 additions and 50 deletions

View File

@ -15,6 +15,12 @@ export interface WindowConfiguration<T extends WindowType = WindowType> {
props: WindowProps[T];
}
export interface DiscordPresenceConfiguration {
source: DiscordPresenceSource;
/** Discord user ID */
user?: string;
}
export type DiscordPresenceSource = DiscordPresenceSourceZnc | DiscordPresenceSourceUrl;
export interface DiscordPresenceSourceZnc {
na_id: string;

View File

@ -8,15 +8,13 @@ import dotenv from 'dotenv';
import dotenvExpand from 'dotenv-expand';
import MenuApp from './menu.js';
import { handleOpenWebServiceUri } from './webservices.js';
import { EmbeddedPresenceMonitor, EmbeddedProxyPresenceMonitor, PresenceMonitorManager } from './monitor.js';
import { EmbeddedPresenceMonitor, PresenceMonitorManager } from './monitor.js';
import { createWindow } from './windows.js';
import { DiscordPresenceSource, WindowType } from '../common/types.js';
import { DiscordPresenceConfiguration, WindowType } from '../common/types.js';
import { initStorage, paths } from '../../util/storage.js';
import { checkUpdates, UpdateCacheData } from '../../common/update.js';
import Users, { CoralUser } from '../../common/users.js';
import { setupIpc } from './ipc.js';
import { version } from '../../util/product.js';
import { GITLAB_URL } from '../../common/constants.js';
const debug = createDebug('app:main');
@ -75,6 +73,9 @@ app.whenReady().then(async () => {
setupIpc(appinstance, ipcMain);
// @ts-expect-error
globalThis.app = appinstance;
await store.restoreMonitorState(appinstance.monitors);
const menu = new MenuApp(appinstance);
@ -95,9 +96,6 @@ app.whenReady().then(async () => {
debug('App started');
appinstance.showMainWindow();
// @ts-expect-error
globalThis.app = appinstance;
});
app.on('window-all-closed', () => {
@ -129,7 +127,7 @@ interface SavedMonitorState {
user_notifications: boolean;
friend_notifications: boolean;
}[];
discord_presence: DiscordPresenceSource | null;
discord_presence: DiscordPresenceConfiguration | null;
}
export class Store extends EventEmitter {
@ -160,18 +158,10 @@ export class Store extends EventEmitter {
friend_notifications: monitor.friend_notifications,
});
}
if (monitor.presence_enabled && !state.discord_presence) {
state.discord_presence = monitor instanceof EmbeddedProxyPresenceMonitor ? {
url: monitor.presence_url,
} : {
na_id: monitor.data.user.id,
friend_nsa_id: monitor.presence_user === monitor.data.nsoAccount.user.nsaId ? undefined :
monitor.presence_user ?? undefined,
};
}
}
state.discord_presence = monitors.getDiscordPresenceConfiguration();
debug('Saving monitor state', state);
await this.storage.setItem('AppMonitors', state);
}
@ -182,8 +172,8 @@ export class Store extends EventEmitter {
if (!state) return;
for (const user of state.users) {
const discord_presence_active = state.discord_presence && 'na_id' in state.discord_presence &&
state.discord_presence.na_id === user.id;
const discord_presence_active = state.discord_presence && 'na_id' in state.discord_presence.source &&
state.discord_presence.source.na_id === user.id;
if (!discord_presence_active &&
!user.user_notifications &&
@ -192,13 +182,15 @@ export class Store extends EventEmitter {
try {
await monitors.start(user.id, monitor => {
monitor.presence_user = state.discord_presence && 'na_id' in state.discord_presence &&
state.discord_presence.na_id === user.id ?
state.discord_presence.friend_nsa_id ?? monitor.data.nsoAccount.user.nsaId : null;
monitor.presence_user = state.discord_presence && 'na_id' in state.discord_presence.source &&
state.discord_presence.source.na_id === user.id ?
state.discord_presence.source.friend_nsa_id ?? monitor.data.nsoAccount.user.nsaId : null;
monitor.user_notifications = user.user_notifications;
monitor.friend_notifications = user.friend_notifications;
if (monitor.presence_user) {
monitor.discord_client_filter = state.discord_presence?.user ?
monitors.createDiscordClientFilter(state.discord_presence.user) : undefined;
this.emit('update-discord-presence-source', monitors.getDiscordPresenceSource());
}
});
@ -208,11 +200,13 @@ export class Store extends EventEmitter {
}
}
if (state.discord_presence && 'url' in state.discord_presence) {
if (state.discord_presence && 'url' in state.discord_presence.source) {
try {
await monitors.startUrl(state.discord_presence.url);
const monitor = await monitors.startUrl(state.discord_presence.source.url);
monitor.discord_client_filter = state.discord_presence?.user ?
monitors.createDiscordClientFilter(state.discord_presence.user) : undefined;
} catch (err) {
dialog.showErrorBox('Error restoring monitor for presence URL ' + state.discord_presence.url,
dialog.showErrorBox('Error restoring monitor for presence URL ' + state.discord_presence.source.url,
err instanceof Error ? err.stack ?? err.message : err as any);
}
}

View File

@ -1,16 +1,18 @@
import { BrowserWindow, clipboard, dialog, IpcMain, Menu, MenuItem, ShareMenu, SharingItem, shell, systemPreferences } from './electron.js';
import * as util from 'node:util';
import createDebug from 'debug';
import { User } from 'discord-rpc';
import openWebService, { WebServiceIpc } from './webservices.js';
import { createWindow, getWindowConfiguration } from './windows.js';
import { DiscordPresenceSource, WindowType } from '../common/types.js';
import { DiscordPresenceConfiguration, DiscordPresenceSource, WindowType } from '../common/types.js';
import { CurrentUser, Friend, Game, PresenceState, WebService } from '../../api/znc-types.js';
import { addNsoAccount, addPctlAccount } from './na-auth.js';
import { App } from './index.js';
import { DiscordPresence } from '../../discord/util.js';
import { User } from 'discord-rpc';
import { NintendoAccountUser } from '../../api/na.js';
import { hrduration } from '../../util/misc.js';
import { DiscordPresence } from '../../discord/util.js';
import { getDiscordRpcClients } from '../../discord/rpc.js';
import { defaultTitle } from '../../discord/titles.js';
const debug = createDebug('app:main:ipc');
@ -112,10 +114,22 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) {
window.show();
});
ipcMain.handle('nxapi:discord:config', () => appinstance.monitors.getDiscordPresenceConfiguration());
ipcMain.handle('nxapi:discord:setconfig', (e, config: DiscordPresenceConfiguration | null) => appinstance.monitors.setDiscordPresenceConfiguration(config));
ipcMain.handle('nxapi:discord:source', () => appinstance.monitors.getDiscordPresenceSource());
ipcMain.handle('nxapi:discord:setsource', (e, source: DiscordPresenceSource | null) => appinstance.monitors.setDiscordPresenceSource(source));
ipcMain.handle('nxapi:discord:presence', () => appinstance.monitors.getDiscordPresence());
ipcMain.handle('nxapi:discord:user', () => appinstance.monitors.getActiveDiscordPresenceMonitor()?.discord.rpc?.client.user ?? null);
ipcMain.handle('nxapi:discord:users', async () => {
const users: User[] = [];
for (const client of await getDiscordRpcClients()) {
await client.connect(defaultTitle.client);
if (client.user && !users.find(u => u.id === client.user!.id)) users.push(client.user);
}
return users;
});
ipcMain.handle('nxapi:moon:gettoken', (e, id: string) => storage.getItem('NintendoAccountToken-pctl.' + id));
ipcMain.handle('nxapi:moon:getcachedtoken', (e, token: string) => storage.getItem('MoonToken.' + token));

View File

@ -7,7 +7,7 @@ import { NotificationManager } from '../../common/notify.js';
import { LoopResult } from '../../util/loop.js';
import { tryGetNativeImageFromUrl } from './util.js';
import { App } from './index.js';
import { DiscordPresenceSource } from '../common/types.js';
import { DiscordPresenceConfiguration, DiscordPresenceSource } from '../common/types.js';
import { DiscordPresence } from '../../discord/util.js';
import { DiscordRpcClient } from '../../discord/rpc.js';
@ -32,7 +32,7 @@ export class PresenceMonitorManager {
const existing = this.monitors.find(m => m instanceof EmbeddedPresenceMonitor && m.data.user.id === user.data.user.id);
if (existing) {
callback?.call(null, existing as EmbeddedPresenceMonitor, false);
return;
return existing;
}
const i = new EmbeddedPresenceMonitor(this.app.store.storage, token, user.nso, user.data, user);
@ -54,13 +54,15 @@ export class PresenceMonitorManager {
callback?.call(null, i, true);
i.enable();
return i;
}
async startUrl(presence_url: string) {
debug('Starting monitor', presence_url);
const existing = this.monitors.find(m => m instanceof EmbeddedProxyPresenceMonitor && m.presence_url === presence_url);
if (existing) return;
if (existing) return existing;
const i = new EmbeddedProxyPresenceMonitor(presence_url);
@ -76,6 +78,8 @@ export class PresenceMonitorManager {
this.monitors.push(i);
i.enable();
return i;
}
async stop(id: string) {
@ -102,6 +106,45 @@ export class PresenceMonitorManager {
return this.getActiveDiscordPresenceMonitor()?.discord.last_activity ?? null;
}
getDiscordPresenceConfiguration(): DiscordPresenceConfiguration | null {
const monitor = this.getActiveDiscordPresenceMonitor();
const source = this.getDiscordPresenceSource();
if (!monitor || !source) return null;
return {
source,
user: this.getDiscordClientFilterConfiguration(monitor.discord_client_filter),
};
}
async setDiscordPresenceConfiguration(config: DiscordPresenceConfiguration | null) {
if (!config) return this.setDiscordPresenceSource(null);
await this.setDiscordPresenceSource(config.source, monitor => {
monitor.discord_client_filter = config.user ? this.createDiscordClientFilter(config.user) : undefined;
monitor.skipIntervalInCurrentLoop();
});
await this.app.store.saveMonitorState(this);
}
private discord_client_filter_config = new WeakMap<
Exclude<ZncDiscordPresence['discord_client_filter'], undefined>,
/** Discord user ID */
string
>();
createDiscordClientFilter(user: string) {
const filter: ZncDiscordPresence['discord_client_filter'] = (client, id) => client.user?.id === user;
this.discord_client_filter_config.set(filter, user);
return filter;
}
getDiscordClientFilterConfiguration(filter: ZncDiscordPresence['discord_client_filter']) {
return filter ? this.discord_client_filter_config.get(filter) : undefined;
}
getDiscordPresenceSource(): DiscordPresenceSource | null {
const monitor = this.getActiveDiscordPresenceMonitor();
if (!monitor) return null;
@ -115,16 +158,21 @@ export class PresenceMonitorManager {
};
}
async setDiscordPresenceSource(source: DiscordPresenceSource | null) {
const monitor = this.getActiveDiscordPresenceMonitor();
async setDiscordPresenceSource(
source: DiscordPresenceSource | null,
callback?: (monitor: EmbeddedPresenceMonitor | EmbeddedProxyPresenceMonitor) => void
) {
const existing = this.getActiveDiscordPresenceMonitor();
if (source && 'na_id' in source &&
monitor && monitor instanceof EmbeddedPresenceMonitor &&
monitor.data.user.id === source.na_id &&
monitor.presence_user !== (source.friend_nsa_id ?? monitor.data.nsoAccount.user.nsaId)
existing && existing instanceof EmbeddedPresenceMonitor &&
existing.data.user.id === source.na_id &&
existing.presence_user !== (source.friend_nsa_id ?? existing.data.nsoAccount.user.nsaId)
) {
await this.start(source.na_id, monitor => {
monitor.presence_user = source.friend_nsa_id ?? monitor.data.nsoAccount.user.nsaId;
monitor.discord_client_filter = existing.discord_client_filter;
callback?.call(null, monitor);
monitor.skipIntervalInCurrentLoop();
});
this.app.store.saveMonitorState(this);
@ -132,35 +180,44 @@ export class PresenceMonitorManager {
return;
}
if (monitor) {
if (source && 'na_id' in source && monitor instanceof EmbeddedPresenceMonitor && monitor.data.user.id === source.na_id) return;
if (source && 'url' in source && monitor instanceof EmbeddedProxyPresenceMonitor && monitor.presence_url === source.url) return;
if (existing) {
if (source && (
('na_id' in source && existing instanceof EmbeddedPresenceMonitor && existing.data.user.id === source.na_id) ||
('url' in source && existing instanceof EmbeddedProxyPresenceMonitor && existing.presence_url === source.url)
)) {
callback?.call(null, existing);
return;
}
monitor.discord.updatePresenceForDiscord(null);
existing.discord.updatePresenceForDiscord(null);
if (monitor instanceof EmbeddedPresenceMonitor) {
monitor.presence_user = null;
if (existing instanceof EmbeddedPresenceMonitor) {
existing.presence_user = null;
if (!monitor.user_notifications && !monitor.friend_notifications) {
this.stop(monitor.data.user.id);
if (!existing.user_notifications && !existing.friend_notifications) {
this.stop(existing.data.user.id);
}
}
if (monitor instanceof EmbeddedProxyPresenceMonitor) {
this.stop(monitor.presence_url);
if (existing instanceof EmbeddedProxyPresenceMonitor) {
this.stop(existing.presence_url);
}
}
if (source && 'na_id' in source) {
await this.start(source.na_id, monitor => {
monitor.presence_user = source.friend_nsa_id ?? monitor.data.nsoAccount.user.nsaId;
monitor.discord_client_filter = existing?.discord_client_filter;
callback?.call(null, monitor);
monitor.skipIntervalInCurrentLoop();
});
} else if (source && 'url' in source) {
await this.startUrl(source.url);
const monitor = await this.startUrl(source.url);
monitor.discord_client_filter = existing?.discord_client_filter;
callback?.call(null, monitor);
}
if (monitor || source) {
if (existing || source) {
this.app.store.saveMonitorState(this);
this.app.menu?.updateMenu();
this.app.store.emit('update-discord-presence-source', source);

View File

@ -2,7 +2,7 @@ import { contextBridge, ipcRenderer } from 'electron';
import * as process from 'node:process';
import { EventEmitter } from 'node:events';
import createDebug from 'debug';
import type { DiscordPresenceSource, WindowConfiguration } from '../common/types.js';
import type { DiscordPresenceConfiguration, DiscordPresenceSource, WindowConfiguration } from '../common/types.js';
import type { SavedToken } from '../../common/auth/nso.js';
import type { SavedMoonToken } from '../../common/auth/moon.js';
import type { UpdateCacheData } from '../../common/update.js';
@ -40,10 +40,13 @@ const ipc = {
openWebService: (webservice: WebService, token: string, qs?: string) => inv<number>('nso:openwebservice', webservice, token, qs),
getNsoActiveEvent: (token: string) => inv<GetActiveEventResult>('nso:activeevent', token),
getDiscordPresenceConfig: () => inv<DiscordPresenceConfiguration | null>('discord:config'),
setDiscordPresenceConfig: (config: DiscordPresenceConfiguration | null) => inv<void>('discord:setconfig', config),
getDiscordPresenceSource: () => inv<DiscordPresenceSource | null>('discord:source'),
setDiscordPresenceSource: (source: DiscordPresenceSource | null) => inv<void>('discord:setsource', source),
getDiscordPresence: () => inv<DiscordPresence | null>('discord:presence'),
getDiscordUser: () => inv<User | null>('discord:user'),
getDiscordUsers: () => inv<User[]>('discord:users'),
getNintendoAccountMoonToken: (id: string) => inv<string | undefined>('moon:gettoken', id),
getSavedMoonToken: (token: string) => inv<SavedMoonToken | undefined>('moon:getcachedtoken', token),