mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-08-28 13:50:51 -05:00
Fix reloading NookLink and handle errors updating presence in the app
This commit is contained in:
2923
package-lock.json
generated
2923
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -66,12 +66,14 @@
|
||||
"@types/yargs": "^17.0.9",
|
||||
"caxa": "^2.1.0",
|
||||
"electron": "^17.2.0",
|
||||
"electron-builder": "^23.0.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-native-web": "^0.17.7",
|
||||
"rollup": "^2.70.1",
|
||||
"rollup-plugin-polyfill-node": "^0.8.0",
|
||||
"ts-json-schema-generator": "^1.0.0",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.7.0-dev.20220308"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import fetch, { RequestInfo, RequestInit, Response } from 'node-fetch';
|
||||
import { ErrorResponse } from '../../index.js';
|
||||
import { ErrorResponse } from '../../api/util.js';
|
||||
|
||||
export enum RequestState {
|
||||
NOT_LOADING,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { app, BrowserWindow, ipcMain, nativeImage, Notification } from '../electron.js';
|
||||
import { app, BrowserWindow, dialog, ipcMain, nativeImage, Notification } from '../electron.js';
|
||||
import * as path from 'path';
|
||||
import { EventEmitter } from 'events';
|
||||
import createDebug from 'debug';
|
||||
@@ -6,12 +6,13 @@ import * as persist from 'node-persist';
|
||||
import fetch from 'node-fetch';
|
||||
import dotenv from 'dotenv';
|
||||
import dotenvExpand from 'dotenv-expand';
|
||||
import { getToken, initStorage, paths } from '../../util.js';
|
||||
import { dev, getToken, initStorage, LoopResult, paths } from '../../util.js';
|
||||
import MenuApp from './menu.js';
|
||||
import { ZncDiscordPresence } from '../../cli/nso/presence.js';
|
||||
import { WebServiceIpc } from './webservices.js';
|
||||
import { CurrentUser, Friend, Game } from '../../api/znc-types.js';
|
||||
import { CurrentUser, Friend, Game, ZncErrorResponse } from '../../api/znc-types.js';
|
||||
import { NotificationManager } from '../../cli/nso/notify.js';
|
||||
import { ErrorResponse } from '../../api/util.js';
|
||||
|
||||
const debug = createDebug('app:main');
|
||||
|
||||
@@ -31,6 +32,7 @@ function createWindow() {
|
||||
});
|
||||
|
||||
mainWindow.loadFile(path.join(bundlepath, 'index.html'));
|
||||
if (dev) mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
@@ -53,6 +55,7 @@ app.whenReady().then(async () => {
|
||||
ipcMain.handle('nxapi:moon:getcachedtoken', (e, token: string) => storage.getItem('MoonToken.' + token));
|
||||
|
||||
const webserviceipc = new WebServiceIpc(store);
|
||||
ipcMain.on('nxapi:webserviceapi:getWebServiceSync', e => e.returnValue = webserviceipc.getWebService(e));
|
||||
ipcMain.handle('nxapi:webserviceapi:invokeNativeShare', (e, data: string) => webserviceipc.invokeNativeShare(e, data));
|
||||
ipcMain.handle('nxapi:webserviceapi:invokeNativeShareUrl', (e, data: string) => webserviceipc.invokeNativeShareUrl(e, data));
|
||||
ipcMain.handle('nxapi:webserviceapi:requestGameWebToken', e => webserviceipc.requestGameWebToken(e));
|
||||
@@ -241,6 +244,20 @@ export class EmbeddedPresenceMonitor extends ZncDiscordPresence {
|
||||
this._running = 0;
|
||||
}
|
||||
}
|
||||
|
||||
async handleError(err: ErrorResponse<ZncErrorResponse> | NodeJS.ErrnoException): Promise<LoopResult> {
|
||||
try {
|
||||
return super.handleError(err);
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
dialog.showErrorBox(err.name, err.stack ?? err.message);
|
||||
} else {
|
||||
dialog.showErrorBox('Error', err as any);
|
||||
}
|
||||
|
||||
return LoopResult.OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ElectronNotificationManager extends NotificationManager {
|
||||
|
||||
@@ -2,10 +2,10 @@ import * as path from 'path';
|
||||
import createDebug from 'debug';
|
||||
import { BrowserWindow, IpcMainInvokeEvent, session, shell, WebContents } from '../electron.js';
|
||||
import ZncApi from '../../api/znc.js';
|
||||
import { SavedToken } from '../../util.js';
|
||||
import { dev, SavedToken } from '../../util.js';
|
||||
import { WebService } from '../../api/znc-types.js';
|
||||
import { bundlepath, Store } from './index.js';
|
||||
import type { NativeShareRequest, NativeShareUrlRequest } from '../preload-webservice/index.js';
|
||||
import type { NativeShareRequest, NativeShareUrlRequest } from '../preload-webservice/znca-js-api.js';
|
||||
|
||||
const debug = createDebug('app:main:webservices');
|
||||
|
||||
@@ -87,7 +87,7 @@ export default async function openWebService(
|
||||
webserviceToken,
|
||||
});
|
||||
|
||||
window.webContents.openDevTools();
|
||||
if (dev) window.webContents.openDevTools();
|
||||
|
||||
window.loadURL(url.toString(), {
|
||||
extraHeaders: Object.entries({
|
||||
@@ -98,6 +98,11 @@ export default async function openWebService(
|
||||
});
|
||||
}
|
||||
|
||||
export interface WebServiceData {
|
||||
webservice: WebService;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class WebServiceIpc {
|
||||
constructor(
|
||||
store: Store
|
||||
@@ -121,6 +126,22 @@ export class WebServiceIpc {
|
||||
};
|
||||
}
|
||||
|
||||
getWebService(event: IpcMainInvokeEvent): WebServiceData {
|
||||
const {user, webservice} = this.getWindowData(event.sender);
|
||||
|
||||
const url = new URL(webservice.uri);
|
||||
url.search = new URLSearchParams({
|
||||
lang: user.language,
|
||||
na_country: user.country,
|
||||
na_lang: user.language,
|
||||
}).toString();
|
||||
|
||||
return {
|
||||
webservice,
|
||||
url: url.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
async invokeNativeShare(event: IpcMainInvokeEvent, json: string): Promise<void> {
|
||||
const {nsoAccount, webservice} = this.getWindowData(event.sender);
|
||||
|
||||
|
||||
@@ -1,161 +1,7 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
import createDebug from 'debug';
|
||||
|
||||
// Logs are written to the browser window developer tools, and are hidden by default (enable verbose logs)
|
||||
const debug = createDebug('app:preload-webservice');
|
||||
|
||||
const ipc = {
|
||||
invokeNativeShare: (data: string) => ipcRenderer.invoke('nxapi:webserviceapi:invokeNativeShare', data) as Promise<void>,
|
||||
invokeNativeShareUrl: (data: string) => ipcRenderer.invoke('nxapi:webserviceapi:invokeNativeShareUrl', data) as Promise<void>,
|
||||
requestGameWebToken: () => ipcRenderer.invoke('nxapi:webserviceapi:requestGameWebToken') as Promise<string>,
|
||||
restorePersistentData: () => ipcRenderer.invoke('nxapi:webserviceapi:restorePersistentData') as Promise<string | undefined>,
|
||||
storePersistentData: (data: string) => ipcRenderer.invoke('nxapi:webserviceapi:storePersistentData', data) as Promise<void>,
|
||||
};
|
||||
|
||||
// NookLink throws an error if restorePersistentData doesn't exist (and helpfully discards it somewhere)
|
||||
// All others aren't required
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
/**
|
||||
* window.invokeNativeShare(JSON.stringify({text: e.text, image_url: e.url, hashtags: e.hashtags}))
|
||||
*/
|
||||
// SplatNet 2
|
||||
invokeNativeShare?: (data: string) => void;
|
||||
|
||||
/**
|
||||
* window.invokeNativeShareUrl(JSON.stringify({url: '', text: ''}))
|
||||
*/
|
||||
// Smash World
|
||||
invokeNativeShareUrl?: (data: string) => void;
|
||||
|
||||
// NookLink
|
||||
requestGameWebToken?: () => void;
|
||||
onGameWebTokenReceive?: (token: string) => void;
|
||||
|
||||
// NookLink
|
||||
restorePersistentData?: () => void;
|
||||
onPersistentDataRestore?: (data: string) => void;
|
||||
// NookLink
|
||||
storePersistentData?: (data: string) => void;
|
||||
onPersistentDataStore?: () => void;
|
||||
|
||||
// NookLink
|
||||
openQRCodeReader?: (data: string) => void;
|
||||
// NookLink
|
||||
openQRCodeReaderFromPhotoLibrary?: (data: string) => void;
|
||||
onQRCodeRead?: (data: string) => void;
|
||||
// NookLink
|
||||
closeQRCodeReader?: () => void;
|
||||
// NookLink
|
||||
closeQRCodeReaderFromPhotoLibrary?: () => void;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Share
|
||||
//
|
||||
// Used by SplatNet 2 and Smash World (but both have a different implementation because Nintendo)
|
||||
//
|
||||
|
||||
export interface NativeShareRequest {
|
||||
text: string;
|
||||
image_url: string;
|
||||
hashtags: string[];
|
||||
}
|
||||
|
||||
function invokeNativeShare(data: string) {
|
||||
debug('invokeNativeShare called', data);
|
||||
|
||||
ipc.invokeNativeShare(data);
|
||||
}
|
||||
|
||||
export interface NativeShareUrlRequest {
|
||||
url: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
function invokeNativeShareUrl(data: string) {
|
||||
debug('invokeNativeShareUrl called', data);
|
||||
|
||||
ipc.invokeNativeShareUrl(data);
|
||||
}
|
||||
|
||||
window.invokeNativeShare = invokeNativeShare;
|
||||
window.invokeNativeShareUrl = invokeNativeShareUrl;
|
||||
|
||||
//
|
||||
// Web service token
|
||||
//
|
||||
// Used by NookLink
|
||||
//
|
||||
|
||||
function requestGameWebToken() {
|
||||
debug('requestGameWebToken called');
|
||||
|
||||
ipc.requestGameWebToken().then(token => {
|
||||
window.onGameWebTokenReceive?.call(null, token);
|
||||
});
|
||||
}
|
||||
|
||||
window.requestGameWebToken = requestGameWebToken;
|
||||
|
||||
//
|
||||
// Persistent data
|
||||
//
|
||||
// Used by NookLink
|
||||
//
|
||||
|
||||
function restorePersistentData() {
|
||||
debug('restorePersistentData called');
|
||||
|
||||
ipc.restorePersistentData().then(data => {
|
||||
window.onPersistentDataRestore?.call(null, data ?? '');
|
||||
});
|
||||
}
|
||||
|
||||
function storePersistentData(data: string) {
|
||||
debug('storePersistentData called', data);
|
||||
|
||||
ipc.storePersistentData(data).then(() => {
|
||||
window.onPersistentDataStore?.call(null);
|
||||
});
|
||||
}
|
||||
|
||||
window.restorePersistentData = restorePersistentData;
|
||||
window.storePersistentData = storePersistentData;
|
||||
|
||||
//
|
||||
// QR code scanner
|
||||
//
|
||||
// Used by NookLink
|
||||
//
|
||||
|
||||
function openQrCodeReader(data: string) {
|
||||
debug('openQRCodeReader called', data);
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
const base64EncodeText = '';
|
||||
window.onQRCodeRead?.call(null, base64EncodeText);
|
||||
});
|
||||
}
|
||||
function closeQrCodeReader() {
|
||||
//
|
||||
}
|
||||
|
||||
function openQrCodeReaderFromPhotoLibrary(data: string) {
|
||||
debug('openQRCodeReaderFromPhotoLibrary called', data);
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
const base64EncodeText = '';
|
||||
window.onQRCodeRead?.call(null, base64EncodeText);
|
||||
});
|
||||
}
|
||||
function closeQrCodeReaderFromPhotoLibrary() {
|
||||
//
|
||||
}
|
||||
|
||||
window.openQRCodeReader = openQrCodeReader;
|
||||
window.openQRCodeReaderFromPhotoLibrary = openQrCodeReaderFromPhotoLibrary;
|
||||
window.closeQRCodeReader = closeQrCodeReader;
|
||||
window.closeQRCodeReaderFromPhotoLibrary = closeQrCodeReaderFromPhotoLibrary;
|
||||
import './znca-js-api.js';
|
||||
import './quirks/nooklink.js';
|
||||
|
||||
21
src/app/preload-webservice/ipc.ts
Normal file
21
src/app/preload-webservice/ipc.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
import createDebug from 'debug';
|
||||
import { WebServiceData } from '../main/webservices.js';
|
||||
|
||||
const debug = createDebug('app:preload-webservice:ipc');
|
||||
|
||||
const ipc = {
|
||||
getWebServiceSync: () => ipcRenderer.sendSync('nxapi:webserviceapi:getWebServiceSync') as WebServiceData,
|
||||
invokeNativeShare: (data: string) => ipcRenderer.invoke('nxapi:webserviceapi:invokeNativeShare', data) as Promise<void>,
|
||||
invokeNativeShareUrl: (data: string) => ipcRenderer.invoke('nxapi:webserviceapi:invokeNativeShareUrl', data) as Promise<void>,
|
||||
requestGameWebToken: () => ipcRenderer.invoke('nxapi:webserviceapi:requestGameWebToken') as Promise<string>,
|
||||
restorePersistentData: () => ipcRenderer.invoke('nxapi:webserviceapi:restorePersistentData') as Promise<string | undefined>,
|
||||
storePersistentData: (data: string) => ipcRenderer.invoke('nxapi:webserviceapi:storePersistentData', data) as Promise<void>,
|
||||
};
|
||||
|
||||
export default ipc;
|
||||
|
||||
export const {webservice, url: webserviceurl} = ipc.getWebServiceSync();
|
||||
|
||||
debug('Web service', webservice);
|
||||
debug('Web service URL', webserviceurl);
|
||||
22
src/app/preload-webservice/quirks/nooklink.ts
Normal file
22
src/app/preload-webservice/quirks/nooklink.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import createDebug from 'debug';
|
||||
import { webservice, webserviceurl } from '../ipc.js';
|
||||
|
||||
const debug = createDebug('app:preload-webservice:quirks:nooklink');
|
||||
|
||||
//
|
||||
// NookLink must start at the main page, otherwise it will fail to load.
|
||||
// This is required so refreshing the page works.
|
||||
//
|
||||
|
||||
const NOOKLINK_WEBSERVICE_ID = 4953919198265344;
|
||||
|
||||
if (webservice.id === NOOKLINK_WEBSERVICE_ID) {
|
||||
const url = new URL(location.href);
|
||||
const initurl = new URL(webserviceurl);
|
||||
|
||||
debug('URL', url, initurl);
|
||||
|
||||
if (url.origin === initurl.origin && url.href !== initurl.href) {
|
||||
history.replaceState(null, document.title, initurl);
|
||||
}
|
||||
}
|
||||
152
src/app/preload-webservice/znca-js-api.ts
Normal file
152
src/app/preload-webservice/znca-js-api.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import createDebug from 'debug';
|
||||
import ipc from './ipc.js';
|
||||
|
||||
const debug = createDebug('app:preload-webservice:znca-js-api');
|
||||
|
||||
// NookLink throws an error if restorePersistentData doesn't exist (and helpfully discards it somewhere)
|
||||
// All others aren't required
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
/**
|
||||
* window.invokeNativeShare(JSON.stringify({text: e.text, image_url: e.url, hashtags: e.hashtags}))
|
||||
*/
|
||||
// SplatNet 2
|
||||
invokeNativeShare?: (data: string) => void;
|
||||
|
||||
/**
|
||||
* window.invokeNativeShareUrl(JSON.stringify({url: '', text: ''}))
|
||||
*/
|
||||
// Smash World
|
||||
invokeNativeShareUrl?: (data: string) => void;
|
||||
|
||||
// NookLink
|
||||
requestGameWebToken?: () => void;
|
||||
onGameWebTokenReceive?: (token: string) => void;
|
||||
|
||||
// NookLink
|
||||
restorePersistentData?: () => void;
|
||||
onPersistentDataRestore?: (data: string) => void;
|
||||
// NookLink
|
||||
storePersistentData?: (data: string) => void;
|
||||
onPersistentDataStore?: () => void;
|
||||
|
||||
// NookLink
|
||||
openQRCodeReader?: (data: string) => void;
|
||||
// NookLink
|
||||
openQRCodeReaderFromPhotoLibrary?: (data: string) => void;
|
||||
onQRCodeRead?: (data: string) => void;
|
||||
// NookLink
|
||||
closeQRCodeReader?: () => void;
|
||||
// NookLink
|
||||
closeQRCodeReaderFromPhotoLibrary?: () => void;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Share
|
||||
//
|
||||
// Used by SplatNet 2 and Smash World (but both have a different implementation because Nintendo)
|
||||
//
|
||||
|
||||
export interface NativeShareRequest {
|
||||
text: string;
|
||||
image_url: string;
|
||||
hashtags: string[];
|
||||
}
|
||||
|
||||
function invokeNativeShare(data: string) {
|
||||
debug('invokeNativeShare called', data);
|
||||
|
||||
ipc.invokeNativeShare(data);
|
||||
}
|
||||
|
||||
export interface NativeShareUrlRequest {
|
||||
url: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
function invokeNativeShareUrl(data: string) {
|
||||
debug('invokeNativeShareUrl called', data);
|
||||
|
||||
ipc.invokeNativeShareUrl(data);
|
||||
}
|
||||
|
||||
window.invokeNativeShare = invokeNativeShare;
|
||||
window.invokeNativeShareUrl = invokeNativeShareUrl;
|
||||
|
||||
//
|
||||
// Web service token
|
||||
//
|
||||
// Used by NookLink
|
||||
//
|
||||
|
||||
function requestGameWebToken() {
|
||||
debug('requestGameWebToken called');
|
||||
|
||||
ipc.requestGameWebToken().then(token => {
|
||||
window.onGameWebTokenReceive?.call(null, token);
|
||||
});
|
||||
}
|
||||
|
||||
window.requestGameWebToken = requestGameWebToken;
|
||||
|
||||
//
|
||||
// Persistent data
|
||||
//
|
||||
// Used by NookLink
|
||||
//
|
||||
|
||||
function restorePersistentData() {
|
||||
debug('restorePersistentData called');
|
||||
|
||||
ipc.restorePersistentData().then(data => {
|
||||
window.onPersistentDataRestore?.call(null, data ?? '');
|
||||
});
|
||||
}
|
||||
|
||||
function storePersistentData(data: string) {
|
||||
debug('storePersistentData called', data);
|
||||
|
||||
ipc.storePersistentData(data).then(() => {
|
||||
window.onPersistentDataStore?.call(null);
|
||||
});
|
||||
}
|
||||
|
||||
window.restorePersistentData = restorePersistentData;
|
||||
window.storePersistentData = storePersistentData;
|
||||
|
||||
//
|
||||
// QR code scanner
|
||||
//
|
||||
// Used by NookLink
|
||||
//
|
||||
|
||||
function openQrCodeReader(data: string) {
|
||||
debug('openQRCodeReader called', data);
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
const base64EncodeText = '';
|
||||
window.onQRCodeRead?.call(null, base64EncodeText);
|
||||
});
|
||||
}
|
||||
function closeQrCodeReader() {
|
||||
//
|
||||
}
|
||||
|
||||
function openQrCodeReaderFromPhotoLibrary(data: string) {
|
||||
debug('openQRCodeReaderFromPhotoLibrary called', data);
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
const base64EncodeText = '';
|
||||
window.onQRCodeRead?.call(null, base64EncodeText);
|
||||
});
|
||||
}
|
||||
function closeQrCodeReaderFromPhotoLibrary() {
|
||||
//
|
||||
}
|
||||
|
||||
window.openQRCodeReader = openQrCodeReader;
|
||||
window.openQRCodeReaderFromPhotoLibrary = openQrCodeReaderFromPhotoLibrary;
|
||||
window.closeQRCodeReader = closeQrCodeReader;
|
||||
window.closeQRCodeReaderFromPhotoLibrary = closeQrCodeReaderFromPhotoLibrary;
|
||||
@@ -55,7 +55,7 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
const table = new Table({
|
||||
head: [
|
||||
'ID',
|
||||
'NA ID',
|
||||
'NSA ID',
|
||||
'Name',
|
||||
'Status',
|
||||
'Favourite?',
|
||||
@@ -73,10 +73,13 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
friend.name,
|
||||
online ?
|
||||
'name' in friend.presence.game ?
|
||||
'Playing ' + friend.presence.game.name + '; played for ' +
|
||||
'Playing ' + friend.presence.game.name + ';\nplayed for ' +
|
||||
hrduration(friend.presence.game.totalPlayTime) + ' since ' +
|
||||
new Date(friend.presence.game.firstPlayedAt * 1000).toLocaleDateString('en-GB') :
|
||||
'Online' :
|
||||
friend.presence.state === PresenceState.INACTIVE ?
|
||||
'Console online' + (friend.presence.logoutAt ?
|
||||
'; last seen ' + new Date(friend.presence.logoutAt * 1000).toISOString() : '') :
|
||||
friend.presence.logoutAt ?
|
||||
'Last seen ' + new Date(friend.presence.logoutAt * 1000).toISOString() :
|
||||
'Offline',
|
||||
|
||||
@@ -184,7 +184,7 @@ export class ZncNotifications extends Loop {
|
||||
name: '',
|
||||
isFriend: true,
|
||||
isFavoriteFriend: false,
|
||||
isServiceUser: true,
|
||||
isServiceUser: false,
|
||||
friendCreatedAt: 0,
|
||||
presence: await nso.fetch<Presence>('/friend/' + r.friend + '/presence'),
|
||||
};
|
||||
@@ -377,7 +377,7 @@ export class NotificationManager {
|
||||
lastpresence ? ' (console was already online)' : '',
|
||||
currenttitle.name, JSON.stringify(currenttitle.sysDescription),
|
||||
hrduration(currenttitle.totalPlayTime),
|
||||
new Date((currenttitle.firstPlayedAt ?? 0) * 1000).toString());
|
||||
currenttitle.firstPlayedAt ? new Date(currenttitle.firstPlayedAt * 1000).toString() : 'now');
|
||||
|
||||
this.onFriendOnline(friend, prev, initialRun);
|
||||
|
||||
@@ -415,7 +415,7 @@ export class NotificationManager {
|
||||
lasttitle.name, JSON.stringify(lasttitle.sysDescription),
|
||||
lastpresence.state === PresenceState.ONLINE ? '' : ' (' + lastpresence.state + ')',
|
||||
hrduration(currenttitle.totalPlayTime),
|
||||
new Date((currenttitle.firstPlayedAt ?? 0) * 1000).toString());
|
||||
currenttitle.firstPlayedAt ? new Date(currenttitle.firstPlayedAt * 1000).toString() : 'now');
|
||||
|
||||
this.onFriendPlayingChangeTitle(friend, prev, initialRun);
|
||||
} else if (
|
||||
|
||||
@@ -116,7 +116,7 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
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');
|
||||
if (argv.friendNotifications) throw new Error('--presence-url not compatible with --friend-notifications');
|
||||
|
||||
const i = new ZncProxyDiscordPresence(argv, argv.presenceUrl);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user