mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-08-22 10:34:46 -05:00
Use a single loop class for nso notify/presence and splatnet2 monitor
This commit is contained in:
@@ -530,30 +530,8 @@ class ZncPresenceEventStream extends ZncNotifications {
|
||||
) {
|
||||
super({
|
||||
...argv,
|
||||
user: undefined,
|
||||
token: undefined,
|
||||
'user-notifications': user,
|
||||
userNotifications: user,
|
||||
'friend-notifications': friend,
|
||||
friendNotifications: friend,
|
||||
'splatnet2-monitor-directory': undefined,
|
||||
splatnet2MonitorDirectory: undefined,
|
||||
'splatnet2-monitor-profile-image': false,
|
||||
splatnet2MonitorProfileImage: false,
|
||||
'splatnet2-monitor-favourite-stage': undefined,
|
||||
splatnet2MonitorFavouriteStage: undefined,
|
||||
'splatnet2-monitor-favourite-colour': undefined,
|
||||
splatnet2MonitorFavouriteColour: undefined,
|
||||
'splatnet2-monitor-battles': false,
|
||||
splatnet2MonitorBattles: false,
|
||||
'splatnet2-monitor-battle-summary-image': false,
|
||||
splatnet2MonitorBattleSummaryImage: false,
|
||||
'splatnet2-monitor-battle-images': false,
|
||||
splatnet2MonitorBattleImages: false,
|
||||
'splatnet2-monitor-coop': false,
|
||||
splatnet2MonitorCoop: false,
|
||||
'splatnet2-monitor-update-interval': 180,
|
||||
splatnet2MonitorUpdateInterval: 180,
|
||||
}, storage, token, nso, data);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import createDebug from 'debug';
|
||||
import persist from 'node-persist';
|
||||
import notifier from 'node-notifier';
|
||||
import { CurrentUser, Friend, Game, Presence, PresenceState, ZncSuccessResponse } from '../../api/znc-types.js';
|
||||
import * as path from 'path';
|
||||
import { CurrentUser, Friend, Game, Presence, PresenceState, ZncErrorResponse, ZncSuccessResponse } from '../../api/znc-types.js';
|
||||
import ZncApi from '../../api/znc.js';
|
||||
import type { Arguments as ParentArguments } from '../nso.js';
|
||||
import { ArgumentsCamelCase, Argv, getTitleIdFromEcUrl, getToken, hrduration, initStorage, SavedToken, YargsArguments } from '../../util.js';
|
||||
import { ArgumentsCamelCase, Argv, getTitleIdFromEcUrl, getToken, hrduration, initStorage, Loop, LoopResult, SavedToken, YargsArguments } from '../../util.js';
|
||||
import ZncProxyApi from '../../api/znc-proxy.js';
|
||||
import { SplatNet2RecordsMonitor } from '../splatnet2/monitor.js';
|
||||
import { getIksmToken } from '../splatnet2/util.js';
|
||||
import { ErrorResponse } from '../../api/util.js';
|
||||
|
||||
const debug = createDebug('cli:nso:notify');
|
||||
const debugFriends = createDebug('cli:nso:notify:friends');
|
||||
@@ -105,20 +107,30 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
}
|
||||
}
|
||||
|
||||
export class ZncNotifications {
|
||||
export class ZncNotifications extends Loop {
|
||||
splatnet2_monitors = new Map<string, EmbeddedSplatNet2Monitor | (() => Promise<EmbeddedSplatNet2Monitor>)>();
|
||||
|
||||
user_notifications = true;
|
||||
friend_notifications = true;
|
||||
update_interval = 30;
|
||||
|
||||
constructor(
|
||||
readonly argv: ArgumentsCamelCase<Arguments>,
|
||||
argv: Pick<ArgumentsCamelCase<Arguments>, 'userNotifications' | 'friendNotifications' | 'updateInterval'>,
|
||||
public storage: persist.LocalStorage,
|
||||
public token: string,
|
||||
public nso: ZncApi,
|
||||
public data: Omit<SavedToken, 'expires_at'>,
|
||||
) {}
|
||||
) {
|
||||
super();
|
||||
|
||||
this.user_notifications = argv.userNotifications;
|
||||
this.friend_notifications = argv.friendNotifications;
|
||||
this.update_interval = argv.updateInterval;
|
||||
}
|
||||
|
||||
async init() {
|
||||
const announcements = await this.nso.getAnnouncements();
|
||||
const friends = this.argv.friendNotifications || !(this.nso instanceof ZncProxyApi) ?
|
||||
const friends = this.friend_notifications || !(this.nso instanceof ZncProxyApi) ?
|
||||
await this.nso.getFriendList() : {result: {friends: []}};
|
||||
if (!(this.nso instanceof ZncProxyApi)) {
|
||||
const webservices = await this.nso.getWebServices();
|
||||
@@ -127,22 +139,22 @@ export class ZncNotifications {
|
||||
|
||||
let user: ZncSuccessResponse<CurrentUser> | null = null;
|
||||
|
||||
if (this.argv.userNotifications) {
|
||||
if (this.user_notifications) {
|
||||
user = await this.nso.getCurrentUser();
|
||||
|
||||
await this.updateFriendsStatusForNotifications(this.argv.friendNotifications ?
|
||||
await this.updateFriendsStatusForNotifications(this.friend_notifications ?
|
||||
[user.result, ...friends.result.friends] : [user.result]);
|
||||
} else if (this.argv.friendNotifications) {
|
||||
} else if (this.friend_notifications) {
|
||||
await this.updateFriendsStatusForNotifications(friends.result.friends);
|
||||
}
|
||||
|
||||
if (this.argv.splatnet2MonitorDirectory) {
|
||||
if (this.splatnet2_monitors.size) {
|
||||
if (!user) user = await this.nso.getCurrentUser();
|
||||
|
||||
await this.updatePresenceForSplatNet2Monitors([user.result]);
|
||||
}
|
||||
|
||||
await new Promise(rs => setTimeout(rs, this.argv.updateInterval * 1000));
|
||||
await new Promise(rs => setTimeout(rs, this.update_interval * 1000));
|
||||
}
|
||||
|
||||
onFriendOnline(friend: CurrentUser | Friend, prev?: CurrentUser | Friend, ir?: boolean) {
|
||||
@@ -334,13 +346,13 @@ export class ZncNotifications {
|
||||
async update() {
|
||||
debug('Updating presence');
|
||||
|
||||
if (this.argv.friendNotifications) {
|
||||
if (this.friend_notifications) {
|
||||
if (!(this.nso instanceof ZncProxyApi)) await this.nso.getActiveEvent();
|
||||
const friends = this.argv.friendNotifications || !(this.nso instanceof ZncProxyApi) ?
|
||||
const friends = this.friend_notifications || !(this.nso instanceof ZncProxyApi) ?
|
||||
await this.nso.getFriendList() : {result: {friends: []}};
|
||||
if (!(this.nso instanceof ZncProxyApi)) await this.nso.getWebServices();
|
||||
|
||||
if (this.argv.userNotifications) {
|
||||
if (this.user_notifications) {
|
||||
const user = await this.nso.getCurrentUser();
|
||||
|
||||
await this.updateFriendsStatusForNotifications([user.result, ...friends.result.friends]);
|
||||
@@ -356,28 +368,31 @@ export class ZncNotifications {
|
||||
debug('Updated presence');
|
||||
}
|
||||
|
||||
async loop() {
|
||||
try {
|
||||
await this.update();
|
||||
async handleError(err: ErrorResponse<ZncErrorResponse> | NodeJS.ErrnoException): Promise<LoopResult> {
|
||||
if (err && 'response' in err && err.data?.status === 9404) {
|
||||
// Token expired
|
||||
debug('Renewing token');
|
||||
|
||||
await new Promise(rs => setTimeout(rs, this.argv.updateInterval * 1000));
|
||||
} catch (err) {
|
||||
// @ts-expect-error
|
||||
if (err?.data?.status === 9404) {
|
||||
// Token expired
|
||||
debug('Renewing token');
|
||||
const data = await this.nso.renewToken(this.token);
|
||||
|
||||
const data = await this.nso.renewToken(this.token);
|
||||
const existingToken: SavedToken = {
|
||||
...data,
|
||||
expires_at: Date.now() + (data.credential.expiresIn * 1000),
|
||||
};
|
||||
|
||||
const existingToken: SavedToken = {
|
||||
...data,
|
||||
expires_at: Date.now() + (data.credential.expiresIn * 1000),
|
||||
};
|
||||
await this.storage.setItem('NsoToken.' + this.token, existingToken);
|
||||
|
||||
await this.storage.setItem('NsoToken.' + this.token, existingToken);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
return LoopResult.OK_SKIP_INTERVAL;
|
||||
} else if ('code' in err && (err as any).type === 'system' && err.code === 'ETIMEDOUT') {
|
||||
debug('Request timed out, waiting %ds before retrying', this.update_interval, err);
|
||||
|
||||
return LoopResult.OK;
|
||||
} else if ('code' in err && (err as any).type === 'system' && err.code === 'ENOTFOUND') {
|
||||
debug('Request error, waiting %ds before retrying', this.update_interval, err);
|
||||
|
||||
return LoopResult.OK;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -415,8 +430,12 @@ export class EmbeddedSplatNet2Monitor extends SplatNet2RecordsMonitor {
|
||||
await this.loop();
|
||||
}
|
||||
|
||||
// Run one more time after the loop ends
|
||||
await this.loop();
|
||||
if (this._running === 0) {
|
||||
// Run one more time after the loop ends
|
||||
const result = await this.loopRun();
|
||||
}
|
||||
|
||||
debugSplatnet2('SplatNet 2 monitoring finished');
|
||||
} finally {
|
||||
this._running = 0;
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ import createDebug from 'debug';
|
||||
import persist from 'node-persist';
|
||||
import DiscordRPC from 'discord-rpc';
|
||||
import fetch from 'node-fetch';
|
||||
import { CurrentUser, Friend, Presence, PresenceState, ZncSuccessResponse } from '../../api/znc-types.js';
|
||||
import { CurrentUser, Friend, Presence, PresenceState, ZncErrorResponse, ZncSuccessResponse } 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';
|
||||
import { ArgumentsCamelCase, Argv, getToken, initStorage, LoopResult, SavedToken, YargsArguments } from '../../util.js';
|
||||
import { getDiscordPresence, getInactiveDiscordPresence } from '../../discord/util.js';
|
||||
import { handleEnableSplatNet2Monitoring, ZncNotifications } from './notify.js';
|
||||
import ZncProxyApi from '../../api/znc-proxy.js';
|
||||
import { ErrorResponse } from '../../index.js';
|
||||
|
||||
const debug = createDebug('cli:nso:presence');
|
||||
const debugProxy = createDebug('cli:nso:presence:proxy');
|
||||
@@ -28,7 +29,8 @@ export function builder(yargs: Argv<ParentArguments>) {
|
||||
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('friend-naid', {
|
||||
}).option('friend-nsaid', {
|
||||
alias: ['friend-naid'],
|
||||
describe: 'Friend\'s Nintendo Switch account ID',
|
||||
type: 'string',
|
||||
}).option('friend-code', {
|
||||
@@ -90,7 +92,7 @@ type Arguments = YargsArguments<ReturnType<typeof builder>>;
|
||||
|
||||
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
if (argv.presenceUrl) {
|
||||
if (argv.friendNaid) throw new Error('--presence-url not compatible with --friend-naid');
|
||||
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');
|
||||
|
||||
@@ -141,8 +143,8 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
data.user.screenName, data.user.nickname, data.nsoAccount.user.name);
|
||||
|
||||
if (argv.splatnet2MonitorDirectory) {
|
||||
if (argv.friendNaid) {
|
||||
console.warn('SplatNet 2 monitoring is enabled, but --friend-naid is set. SplatNet 2 records will only be downloaded when the authenticated user is playing Splatoon 2 online, regardless of the --friend-naid user.');
|
||||
if (argv.friendNsaid) {
|
||||
console.warn('SplatNet 2 monitoring is enabled, but --friend-nsaid is set. SplatNet 2 records will only be downloaded when the authenticated user is playing Splatoon 2 online, regardless of the --friend-nsaid user.');
|
||||
}
|
||||
|
||||
i.splatnet2_monitors.set(data.nsoAccount.user.nsaId, handleEnableSplatNet2Monitoring(argv, storage, token));
|
||||
@@ -156,10 +158,14 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
}
|
||||
|
||||
class ZncDiscordPresence extends ZncNotifications {
|
||||
forceFriendCode: CurrentUser['links']['friendCode'] | undefined;
|
||||
show_friend_code = false;
|
||||
force_friend_code: CurrentUser['links']['friendCode'] | undefined = undefined;
|
||||
show_console_online = false;
|
||||
|
||||
friend_nsaid: string | null;
|
||||
|
||||
constructor(
|
||||
readonly argv: ArgumentsCamelCase<Arguments>,
|
||||
argv: ArgumentsCamelCase<Arguments>,
|
||||
storage: persist.LocalStorage,
|
||||
token: string,
|
||||
nso: ZncApi,
|
||||
@@ -168,25 +174,29 @@ class ZncDiscordPresence extends ZncNotifications {
|
||||
super(argv, storage, token, nso, data);
|
||||
|
||||
let match;
|
||||
this.forceFriendCode =
|
||||
(match = (this.argv.friendCode as string)?.match(/^(SW-)?(\d{4})-?(\d{4})-?(\d{4})$/)) ?
|
||||
this.force_friend_code =
|
||||
(match = (argv.friendCode as string)?.match(/^(SW-)?(\d{4})-?(\d{4})-?(\d{4})$/)) ?
|
||||
{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.friend_nsaid = argv.friendNsaid ?? null;
|
||||
}
|
||||
|
||||
async init() {
|
||||
const announcements = await this.nso.getAnnouncements();
|
||||
const friends = this.argv.friendNotifications || !(this.nso instanceof ZncProxyApi) ?
|
||||
await this.nso.getFriendList() : {result: {friends: this.argv.friendNaid ? [this.argv.userNotifications ?
|
||||
(await this.nso.fetch<{friend: Friend}>('/friend/' + this.argv.friendNaid)).friend : {
|
||||
const friends = this.friend_notifications || !(this.nso instanceof ZncProxyApi) ?
|
||||
await this.nso.getFriendList() : {result: {friends: this.friend_nsaid ? [this.user_notifications ?
|
||||
(await this.nso.fetch<{friend: Friend}>('/friend/' + this.friend_nsaid)).friend : {
|
||||
id: 0,
|
||||
nsaId: this.argv.friendNaid,
|
||||
nsaId: this.friend_nsaid,
|
||||
imageUri: '',
|
||||
name: '',
|
||||
isFriend: true,
|
||||
isFavoriteFriend: false,
|
||||
isServiceUser: true,
|
||||
friendCreatedAt: 0,
|
||||
presence: await this.nso.fetch<Presence>('/friend/' + this.argv.friendNaid + '/presence'),
|
||||
presence: await this.nso.fetch<Presence>('/friend/' + this.friend_nsaid + '/presence'),
|
||||
}] : []}};
|
||||
if (!(this.nso instanceof ZncProxyApi)) {
|
||||
await this.nso.getWebServices();
|
||||
@@ -195,19 +205,19 @@ class ZncDiscordPresence extends ZncNotifications {
|
||||
|
||||
let user: ZncSuccessResponse<CurrentUser> | null = null;
|
||||
|
||||
if (this.argv.friendNaid) {
|
||||
const friend = friends.result.friends.find(f => f.nsaId === this.argv.friendNaid);
|
||||
if (this.friend_nsaid) {
|
||||
const friend = friends.result.friends.find(f => f.nsaId === this.friend_nsaid);
|
||||
|
||||
if (!friend) {
|
||||
throw new Error('User "' + this.argv.friendNaid + '" is not friends with this user');
|
||||
throw new Error('User "' + this.friend_nsaid + '" is not friends with this user');
|
||||
}
|
||||
|
||||
if (this.argv.userNotifications && this.argv.friendNotifications) {
|
||||
if (this.user_notifications && this.friend_notifications) {
|
||||
await this.updateFriendsStatusForNotifications(friends.result.friends, true);
|
||||
} else if (this.argv.friendNotifications) {
|
||||
} else if (this.friend_notifications) {
|
||||
await this.updateFriendsStatusForNotifications(
|
||||
friends.result.friends.filter(f => f.nsaId !== this.argv.friendNaid), true);
|
||||
} else if (this.argv.userNotifications && friend) {
|
||||
friends.result.friends.filter(f => f.nsaId !== this.friend_nsaid), true);
|
||||
} else if (this.user_notifications && friend) {
|
||||
await this.updateFriendsStatusForNotifications([friend], true);
|
||||
}
|
||||
|
||||
@@ -215,23 +225,23 @@ class ZncDiscordPresence extends ZncNotifications {
|
||||
} else {
|
||||
user = await this.nso.getCurrentUser();
|
||||
|
||||
if (this.argv.friendNotifications) {
|
||||
await this.updateFriendsStatusForNotifications(this.argv.userNotifications ?
|
||||
if (this.friend_notifications) {
|
||||
await this.updateFriendsStatusForNotifications(this.user_notifications ?
|
||||
[user.result, ...friends.result.friends] : friends.result.friends);
|
||||
} else if (this.argv.userNotifications) {
|
||||
} else if (this.user_notifications) {
|
||||
await this.updateFriendsStatusForNotifications([user.result]);
|
||||
}
|
||||
|
||||
await this.updatePresence(user.result.presence, user.result.links.friendCode);
|
||||
}
|
||||
|
||||
if (this.argv.splatnet2MonitorDirectory) {
|
||||
if (this.splatnet2_monitors.size) {
|
||||
if (!user) user = await this.nso.getCurrentUser();
|
||||
|
||||
await this.updatePresenceForSplatNet2Monitors([user.result]);
|
||||
}
|
||||
|
||||
await new Promise(rs => setTimeout(rs, this.argv.updateInterval * 1000));
|
||||
await new Promise(rs => setTimeout(rs, this.update_interval * 1000));
|
||||
}
|
||||
|
||||
rpc: {client: DiscordRPC.Client, id: string} | null = null;
|
||||
@@ -243,7 +253,7 @@ class ZncDiscordPresence extends ZncNotifications {
|
||||
|
||||
const show_presence =
|
||||
(online && 'name' in presence.game) ||
|
||||
(this.argv.showConsoleOnline && presence?.state === PresenceState.INACTIVE);
|
||||
(this.show_console_online && presence?.state === PresenceState.INACTIVE);
|
||||
|
||||
if (!presence || !show_presence) {
|
||||
if (this.rpc) {
|
||||
@@ -256,7 +266,7 @@ class ZncDiscordPresence extends ZncNotifications {
|
||||
return;
|
||||
}
|
||||
|
||||
const fc = this.argv.friendCode === '' || this.argv.friendCode === '-' ? friendcode : this.forceFriendCode;
|
||||
const fc = this.show_friend_code ? this.force_friend_code ?? friendcode : undefined;
|
||||
const discordpresence = 'name' in presence.game ?
|
||||
getDiscordPresence(presence.state, presence.game, fc) :
|
||||
getInactiveDiscordPresence(presence.state, presence.logoutAt, fc);
|
||||
@@ -333,46 +343,47 @@ class ZncDiscordPresence extends ZncNotifications {
|
||||
}
|
||||
|
||||
this.rpc.client.setActivity(discordpresence.activity);
|
||||
|
||||
this.update_presence_errors = 0;
|
||||
}
|
||||
|
||||
async update() {
|
||||
let user: CurrentUser | null = null;
|
||||
|
||||
if (this.argv.friendNaid) {
|
||||
if (this.friend_nsaid) {
|
||||
if (!(this.nso instanceof ZncProxyApi)) await this.nso.getActiveEvent();
|
||||
const friends = this.argv.friendNotifications || !(this.nso instanceof ZncProxyApi) ?
|
||||
await this.nso.getFriendList() : {result: {friends: this.argv.friendNaid ? [this.argv.userNotifications ?
|
||||
(await this.nso.fetch<{friend: Friend}>('/friend/' + this.argv.friendNaid)).friend : {
|
||||
const friends = this.friend_notifications || !(this.nso instanceof ZncProxyApi) ?
|
||||
await this.nso.getFriendList() : {result: {friends: this.friend_nsaid ? [this.user_notifications ?
|
||||
(await this.nso.fetch<{friend: Friend}>('/friend/' + this.friend_nsaid)).friend : {
|
||||
id: 0,
|
||||
nsaId: this.argv.friendNaid,
|
||||
nsaId: this.friend_nsaid,
|
||||
imageUri: '',
|
||||
name: '',
|
||||
isFriend: true,
|
||||
isFavoriteFriend: false,
|
||||
isServiceUser: true,
|
||||
friendCreatedAt: 0,
|
||||
presence: await this.nso.fetch<Presence>('/friend/' + this.argv.friendNaid + '/presence'),
|
||||
presence: await this.nso.fetch<Presence>('/friend/' + this.friend_nsaid + '/presence'),
|
||||
}] : []}};
|
||||
if (!(this.nso instanceof ZncProxyApi)) await this.nso.getWebServices();
|
||||
|
||||
const friend = friends.result.friends.find(f => f.nsaId === this.argv.friendNaid);
|
||||
const friend = friends.result.friends.find(f => f.nsaId === this.friend_nsaid);
|
||||
|
||||
if (this.argv.userNotifications && this.argv.friendNotifications) {
|
||||
if (this.user_notifications && this.friend_notifications) {
|
||||
await this.updateFriendsStatusForNotifications(friends.result.friends);
|
||||
} else if (this.argv.friendNotifications) {
|
||||
} else if (this.friend_notifications) {
|
||||
await this.updateFriendsStatusForNotifications(
|
||||
friends.result.friends.filter(f => f.nsaId !== this.argv.friendNaid));
|
||||
} else if (this.argv.userNotifications && friend) {
|
||||
friends.result.friends.filter(f => f.nsaId !== this.friend_nsaid));
|
||||
} else if (this.user_notifications && friend) {
|
||||
await this.updateFriendsStatusForNotifications([friend]);
|
||||
}
|
||||
|
||||
if (!friend) {
|
||||
// Is the authenticated user no longer friends with this user?
|
||||
await this.updatePresence(null);
|
||||
return;
|
||||
} else {
|
||||
await this.updatePresence(friend.presence);
|
||||
}
|
||||
|
||||
await this.updatePresence(friend.presence);
|
||||
} else {
|
||||
user = !(this.nso instanceof ZncProxyApi) ?
|
||||
(await this.nso.getCurrentUser()).result : {
|
||||
@@ -380,26 +391,46 @@ class ZncDiscordPresence extends ZncNotifications {
|
||||
presence: await this.nso.fetch<Presence>('/user/presence'),
|
||||
};
|
||||
|
||||
if (this.argv.friendNotifications) {
|
||||
if (this.friend_notifications) {
|
||||
if (!(this.nso instanceof ZncProxyApi)) await this.nso.getActiveEvent();
|
||||
const friends = await this.nso.getFriendList();
|
||||
if (!(this.nso instanceof ZncProxyApi)) await this.nso.getWebServices();
|
||||
|
||||
await this.updateFriendsStatusForNotifications(this.argv.userNotifications ?
|
||||
|
||||
await this.updateFriendsStatusForNotifications(this.user_notifications ?
|
||||
[user, ...friends.result.friends] : friends.result.friends);
|
||||
} else if (this.argv.userNotifications) {
|
||||
} else if (this.user_notifications) {
|
||||
await this.updateFriendsStatusForNotifications([user]);
|
||||
}
|
||||
|
||||
await this.updatePresence(user.presence, user.links.friendCode);
|
||||
}
|
||||
|
||||
if (this.argv.splatnet2MonitorDirectory) {
|
||||
if (this.splatnet2_monitors.size) {
|
||||
if (!user) user = (await this.nso.getCurrentUser()).result;
|
||||
|
||||
await this.updatePresenceForSplatNet2Monitors([user]);
|
||||
}
|
||||
}
|
||||
|
||||
update_presence_errors = 0;
|
||||
|
||||
async handleError(err: ErrorResponse<ZncErrorResponse> | NodeJS.ErrnoException): Promise<LoopResult> {
|
||||
this.update_presence_errors++;
|
||||
|
||||
if (this.update_presence_errors > 2) {
|
||||
// Disconnect from Discord if the last two attempts to update presence failed
|
||||
// This prevents the user's activity on Discord being stuck
|
||||
if (this.rpc) {
|
||||
const client = this.rpc.client;
|
||||
this.rpc = null;
|
||||
await client.destroy();
|
||||
}
|
||||
|
||||
this.title = null;
|
||||
}
|
||||
|
||||
return super.handleError(err);
|
||||
}
|
||||
}
|
||||
|
||||
class ZncProxyDiscordPresence extends ZncDiscordPresence {
|
||||
|
||||
@@ -5,11 +5,12 @@ import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import SplatNet2Api from '../../api/splatnet2.js';
|
||||
import { getIksmToken, renewIksmToken } from './util.js';
|
||||
import { ArgumentsCamelCase, Argv, initStorage, YargsArguments } from '../../util.js';
|
||||
import { ArgumentsCamelCase, Argv, initStorage, Loop, LoopResult, YargsArguments } from '../../util.js';
|
||||
import { Records, Stages, WebServiceError } from '../../api/splatnet2-types.js';
|
||||
import { Arguments as ParentArguments } from '../splatnet2.js';
|
||||
import { dumpCoopResults, dumpResults } from './dump-results.js';
|
||||
import { dumpProfileImage, dumpRecords } from './dump-records.js';
|
||||
import { ErrorResponse } from '../../api/util.js';
|
||||
|
||||
const debug = createDebug('cli:splatnet2:monitor');
|
||||
|
||||
@@ -107,7 +108,7 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
}
|
||||
}
|
||||
|
||||
export class SplatNet2RecordsMonitor {
|
||||
export class SplatNet2RecordsMonitor extends Loop {
|
||||
update_interval: number = 3 * 60; // 3 minutes in seconds
|
||||
|
||||
profile_image = true;
|
||||
@@ -131,7 +132,9 @@ export class SplatNet2RecordsMonitor {
|
||||
public stages: Stages,
|
||||
public directory: string,
|
||||
public znc_proxy_url?: string
|
||||
) {}
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async init() {
|
||||
await mkdirp(this.directory);
|
||||
@@ -182,21 +185,20 @@ export class SplatNet2RecordsMonitor {
|
||||
}
|
||||
}
|
||||
|
||||
async loop() {
|
||||
try {
|
||||
await this.update();
|
||||
async handleError(err: Error | ErrorResponse<WebServiceError>): Promise<LoopResult> {
|
||||
if ('response' in err && err.data.code === 'AUTHENTICATION_ERROR') {
|
||||
// Token expired
|
||||
debug('Renewing iksm_session cookie');
|
||||
|
||||
await new Promise(rs => setTimeout(rs, this.update_interval * 1000));
|
||||
} catch (err) {
|
||||
// @ts-expect-error
|
||||
if ((err?.data as WebServiceError)?.code === 'AUTHENTICATION_ERROR') {
|
||||
// Token expired
|
||||
debug('Renewing iksm_session cookie');
|
||||
|
||||
await renewIksmToken(this.splatnet, this.storage, this.token, this.znc_proxy_url);
|
||||
} else {
|
||||
throw err;
|
||||
if (!this.auto_update_iksm_session) {
|
||||
throw new Error('iksm_session cookie expired');
|
||||
}
|
||||
|
||||
await renewIksmToken(this.splatnet, this.storage, this.token, this.znc_proxy_url);
|
||||
|
||||
return LoopResult.OK_SKIP_INTERVAL;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
38
src/util.ts
38
src/util.ts
@@ -147,3 +147,41 @@ export function hrduration(duration: number, short = false) {
|
||||
return minutes + ' ' + minute_str + (minutes === 1 ? '' : 's');
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Loop {
|
||||
update_interval = 60;
|
||||
|
||||
init(): void | Promise<void> {}
|
||||
|
||||
abstract update(): void | Promise<void>;
|
||||
|
||||
async loopRun(): Promise<LoopResult> {
|
||||
try {
|
||||
await this.update();
|
||||
|
||||
return LoopResult.OK;
|
||||
} catch (err) {
|
||||
return this.handleError(err as any);
|
||||
}
|
||||
}
|
||||
|
||||
async handleError(err: Error): Promise<LoopResult> {
|
||||
throw err;
|
||||
}
|
||||
|
||||
async loop() {
|
||||
const result = await this.loopRun();
|
||||
|
||||
if (result === LoopResult.OK) {
|
||||
await new Promise(rs => setTimeout(rs, this.update_interval * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const LoopRunOk = Symbol('LoopRunOk');
|
||||
const LoopRunOkSkipInterval = Symbol('LoopRunOkSkipInterval');
|
||||
|
||||
export enum LoopResult {
|
||||
OK = LoopRunOk as any,
|
||||
OK_SKIP_INTERVAL = LoopRunOkSkipInterval as any,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user