nxapi/src/cli/nso/notify.ts
Samuel Elliott 5f6004bd57
Move authentication functions
app no longer imports from cli
2022-05-21 23:47:41 +01:00

210 lines
8.2 KiB
TypeScript

import * as path from 'path';
import createDebug from 'debug';
import persist from 'node-persist';
import notifier from 'node-notifier';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, initStorage, YargsArguments } from '../../util.js';
import { getToken } from '../../common/auth/nso.js';
import { getIksmToken } from '../../common/auth/splatnet2.js';
import { EmbeddedSplatNet2Monitor, NotificationManager, ZncNotifications } from '../../common/notify.js';
import { CurrentUser, Friend, Game } from '../../api/znc-types.js';
const debug = createDebug('cli:nso:notify');
export const command = 'notify';
export const desc = 'Show notifications when friends come online without starting Discord Rich Presence';
export function builder(yargs: Argv<ParentArguments>) {
return yargs.option('user', {
describe: 'Nintendo Account ID',
type: 'string',
}).option('token', {
describe: 'Nintendo Account session token',
type: 'string',
}).option('user-notifications', {
describe: 'Show notification for your own user',
type: 'boolean',
default: false,
}).option('friend-notifications', {
describe: 'Show notification for friends',
type: 'boolean',
default: true,
}).option('update-interval', {
describe: 'Update interval in seconds',
type: 'number',
default: 30,
}).option('splatnet2-monitor', {
describe: 'Download new SplatNet 2 data when you are playing Splatoon 2 online',
type: 'boolean',
default: false,
}).option('splatnet2-monitor-directory', {
alias: ['sn2-path'],
describe: 'Directory to write SplatNet 2 record data to',
type: 'string',
}).option('splatnet2-monitor-profile-image', {
alias: ['sn2-profile-image'],
describe: 'Include profile image',
type: 'boolean',
default: false,
}).option('splatnet2-monitor-favourite-stage', {
alias: ['sn2-favourite-stage'],
describe: 'Favourite stage to include on profile image',
type: 'string',
}).option('splatnet2-monitor-favourite-colour', {
alias: ['sn2-favourite-colour'],
describe: 'Favourite colour to include on profile image',
type: 'string',
}).option('splatnet2-monitor-battles', {
alias: ['sn2-battles'],
describe: 'Include regular/ranked/private/festival battle results',
type: 'boolean',
default: true,
}).option('splatnet2-monitor-battle-summary-image', {
alias: ['sn2-battle-summary-image'],
describe: 'Include regular/ranked/private/festival battle summary image',
type: 'boolean',
default: false,
}).option('splatnet2-monitor-battle-images', {
alias: ['sn2-battle-images'],
describe: 'Include regular/ranked/private/festival battle result images',
type: 'boolean',
default: false,
}).option('splatnet2-monitor-coop', {
alias: ['sn2-coop'],
describe: 'Include coop (Salmon Run) results',
type: 'boolean',
default: true,
}).option('splatnet2-monitor-update-interval', {
alias: ['sn2-update-interval'],
describe: 'Update interval in seconds',
type: 'number',
// 3 minutes - the monitor is only active while the authenticated user is playing Splatoon 2 online
default: 3 * 60,
}).option('splatnet2-auto-update-session', {
alias: ['sn2-auto-update-session'],
describe: 'Automatically obtain and refresh the iksm_session cookie',
type: 'boolean',
default: true,
});
}
type Arguments = YargsArguments<ReturnType<typeof builder>>;
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
if (!argv.userNotifications && !argv.friendNotifications && !argv.splatnet2Monitor) {
throw new Error('Must enable either user notifications, friend notifications, or SplatNet 2 monitoring');
}
const storage = await initStorage(argv.dataPath);
const usernsid = argv.user ?? await storage.getItem('SelectedUser');
const token: string = argv.token ||
await storage.getItem('NintendoAccountToken.' + usernsid);
const {nso, data} = await getToken(storage, token, argv.zncProxyUrl);
const i = new ZncNotifications(storage, token, nso, data);
i.user_notifications = argv.userNotifications;
i.friend_notifications = argv.friendNotifications;
i.update_interval = argv.updateInterval;
console.warn('Authenticated as Nintendo Account %s (NA %s, NSO %s)',
data.user.screenName, data.user.nickname, data.nsoAccount.user.name);
if (argv.splatnet2Monitor) {
console.warn('SplatNet 2 monitoring enabled for %s (NA %s, NSO %s) - SplatNet 2 records will be ' +
'downloaded when this user is playing Splatoon 2 online.',
data.user.screenName, data.user.nickname, data.nsoAccount.user.name);
i.splatnet2_monitors.set(data.nsoAccount.user.nsaId, handleEnableSplatNet2Monitoring(argv, storage, token));
}
await i.loop(true);
while (true) {
await i.loop();
}
}
export class TerminalNotificationManager extends NotificationManager {
onFriendOnline(friend: CurrentUser | Friend, prev?: CurrentUser | Friend, naid?: string, ir?: boolean) {
const currenttitle = friend.presence.game as Game;
notifier.notify({
title: friend.name,
message: 'Playing ' + currenttitle.name +
(currenttitle.sysDescription ? '\n' + currenttitle.sysDescription : ''),
// icon: currenttitle.imageUri,
icon: friend.imageUri,
});
}
onFriendOffline(friend: CurrentUser | Friend, prev?: CurrentUser | Friend, naid?: string, ir?: boolean) {
notifier.notify({
title: friend.name,
message: 'Offline',
icon: friend.imageUri,
});
}
onFriendPlayingChangeTitle(friend: CurrentUser | Friend, prev?: CurrentUser | Friend, naid?: string, ir?: boolean) {
const currenttitle = friend.presence.game as Game;
notifier.notify({
title: friend.name,
message: 'Playing ' + currenttitle.name +
(currenttitle.sysDescription ? '\n' + currenttitle.sysDescription : ''),
// icon: currenttitle.imageUri,
icon: friend.imageUri,
});
}
onFriendTitleStateChange(friend: CurrentUser | Friend, prev?: CurrentUser | Friend, naid?: string, ir?: boolean) {
const currenttitle = friend.presence.game as Game;
notifier.notify({
title: friend.name,
message: 'Playing ' + currenttitle.name +
(currenttitle.sysDescription ? '\n' + currenttitle.sysDescription : ''),
// icon: currenttitle.imageUri,
icon: friend.imageUri,
});
}
}
export function handleEnableSplatNet2Monitoring(
argv: ArgumentsCamelCase<Arguments>, storage: persist.LocalStorage, token: string
) {
return async () => {
const directory = argv.splatnet2MonitorDirectory ?? path.join(argv.dataPath, 'splatnet2');
const {splatnet, data} = await getIksmToken(storage, token, argv.zncProxyUrl, argv.splatnet2AutoUpdateSession);
const records = await splatnet.getRecords();
const stages = await splatnet.getStages();
const i = new EmbeddedSplatNet2Monitor(storage, token, splatnet, stages, directory, argv.zncProxyUrl);
i.update_interval = argv.splatnet2MonitorUpdateInterval;
i.profile_image = argv.splatnet2MonitorProfileImage;
i.favourite_stage = argv.splatnet2MonitorFavouriteStage;
i.favourite_colour = argv.splatnet2MonitorFavouriteColour;
i.results = argv.splatnet2MonitorBattles;
i.results_summary_image = argv.splatnet2MonitorBattleSummaryImage;
i.result_images = argv.splatnet2MonitorBattleImages;
i.coop_results = argv.splatnet2MonitorCoop;
i.cached_records = records;
console.log('Player %s (Splatoon 2 ID %s, NSA ID %s) level %d',
records.records.player.nickname,
records.records.unique_id,
records.records.player.principal_id,
records.records.player.player_rank,
records.records.player.player_type);
return i;
};
}