Add a command to show/update presence permissions and simplify notify/presence updates

This commit is contained in:
Samuel Elliott
2022-04-06 22:41:17 +01:00
parent ec996ef461
commit ac921e3f16
4 changed files with 183 additions and 149 deletions

View File

@@ -1,6 +1,7 @@
export * as token from './token.js';
export * as auth from './auth.js';
export * as user from './user.js';
export * as permissions from './permissions.js';
export * as announcements from './announcements.js';
export * as webservices from './webservices.js';
export * as webservicetoken from './webservicetoken.js';

View File

@@ -2,7 +2,7 @@ import createDebug from 'debug';
import persist from 'node-persist';
import notifier from 'node-notifier';
import * as path from 'path';
import { CurrentUser, Friend, Game, Presence, PresenceState, ZncErrorResponse, ZncSuccessResponse } from '../../api/znc-types.js';
import { ActiveEvent, Announcements, CurrentUser, Friend, Game, Presence, PresenceState, WebServices, ZncErrorResponse } 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, Loop, LoopResult, SavedToken, YargsArguments } from '../../util.js';
@@ -146,31 +146,77 @@ export class ZncNotifications extends Loop {
this.update_interval = argv.updateInterval;
}
async init() {
const announcements = await this.nso.getAnnouncements();
const friends = this.friend_notifications || !(this.nso instanceof ZncProxyApi) ?
await this.nso.getFriendList() : {result: {friends: []}};
async fetch(req: (
'announcements' | 'friends' | {friend: string; presence?: boolean} | 'webservices' | 'event' | 'user' | null
)[]) {
const result: Partial<{
announcements: Announcements;
friends: Friend[];
webservices: WebServices;
activeevent: ActiveEvent;
user: CurrentUser;
}> = {};
const friends = req.filter(r => typeof r === 'object' && r && 'friend' in r) as
{friend: string; presence?: boolean}[];
if (!(this.nso instanceof ZncProxyApi)) {
const webservices = await this.nso.getWebServices();
const activeevent = await this.nso.getActiveEvent();
if (req.includes('announcements')) req.push('friends', 'webservices', 'event');
if (req.includes('webservices')) req.push('friends', 'event');
if (req.includes('event')) req.push('friends', 'webservices');
}
let user: ZncSuccessResponse<CurrentUser> | null = null;
if (req.includes('announcements')) {
result.announcements = (await this.nso.getAnnouncements()).result;
}
if (req.includes('friends') || (friends && !(this.nso instanceof ZncProxyApi))) {
result.friends = (await this.nso.getFriendList()).result.friends;
} else if (friends && this.nso instanceof ZncProxyApi) {
result.friends = await Promise.all(friends.map(async r => {
const nso = this.nso as unknown as ZncProxyApi;
if (this.user_notifications) {
user = await this.nso.getCurrentUser();
if (r.presence) {
const friend: Friend = {
id: 0,
nsaId: r.friend,
imageUri: '',
name: '',
isFriend: true,
isFavoriteFriend: false,
isServiceUser: true,
friendCreatedAt: 0,
presence: await nso.fetch<Presence>('/friend/' + r.friend + '/presence'),
};
await this.updateFriendsStatusForNotifications(this.friend_notifications ?
[user.result, ...friends.result.friends] : [user.result]);
} else if (this.friend_notifications) {
await this.updateFriendsStatusForNotifications(friends.result.friends);
return friend;
}
return (await nso.fetch<{friend: Friend}>('/friend/' + r.friend)).friend;
}))
}
if (req.includes('webservices')) {
result.webservices = (await this.nso.getWebServices()).result;
}
if (req.includes('event')) {
result.activeevent = (await this.nso.getActiveEvent()).result;
}
if (req.includes('user')) {
result.user = (await this.nso.getCurrentUser()).result;
}
if (this.splatnet2_monitors.size) {
if (!user) user = await this.nso.getCurrentUser();
return result;
}
await this.updatePresenceForSplatNet2Monitors([user.result]);
}
async init() {
const {friends, user} = await this.fetch([
'announcements',
this.user_notifications ? 'user' : null,
this.friend_notifications ? 'friends' : null,
this.splatnet2_monitors.size ? 'user' : null,
]);
await this.updatePresenceForNotifications(user, friends, true);
await this.updatePresenceForSplatNet2Monitors([user!]);
await new Promise(rs => setTimeout(rs, this.update_interval * 1000));
}
@@ -327,6 +373,14 @@ export class ZncNotifications extends Loop {
this.onlinefriends = newonlinefriends;
}
async updatePresenceForNotifications(
user: CurrentUser | undefined, friends: Friend[] | undefined, initialRun?: boolean
) {
await this.updateFriendsStatusForNotifications(([] as (CurrentUser | Friend)[])
.concat(this.user_notifications && user ? [user] : [])
.concat(this.friend_notifications && friends ? friends : []), initialRun);
}
async updatePresenceForSplatNet2Monitors(friends: (CurrentUser | Friend)[]) {
for (const friend of friends) {
await this.updatePresenceForSplatNet2Monitor(friend.presence, friend.nsaId, friend.name);
@@ -362,28 +416,14 @@ export class ZncNotifications extends Loop {
}
async update() {
debug('Updating presence');
const {friends, user} = await this.fetch([
this.user_notifications ? 'user' : null,
this.friend_notifications ? 'friends' : null,
this.splatnet2_monitors.size ? 'user' : null,
]);
if (this.friend_notifications) {
if (!(this.nso instanceof ZncProxyApi)) await this.nso.getActiveEvent();
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.user_notifications) {
const user = await this.nso.getCurrentUser();
await this.updateFriendsStatusForNotifications([user.result, ...friends.result.friends]);
} else {
await this.updateFriendsStatusForNotifications(friends.result.friends);
}
} else {
const user = await this.nso.getCurrentUser();
await this.updateFriendsStatusForNotifications([user.result]);
}
debug('Updated presence');
await this.updatePresenceForNotifications(user, friends);
await this.updatePresenceForSplatNet2Monitors([user!]);
}
async handleError(err: ErrorResponse<ZncErrorResponse> | NodeJS.ErrnoException): Promise<LoopResult> {

View File

@@ -0,0 +1,61 @@
import createDebug from 'debug';
import { PresencePermissions } from '../../api/znc-types.js';
import type { Arguments as ParentArguments } from '../nso.js';
import { ArgumentsCamelCase, Argv, getToken, initStorage, YargsArguments } from '../../util.js';
const debug = createDebug('cli:nso:permissions');
export const command = 'permissions';
export const desc = 'Get or update Nintendo Switch presence permissions';
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('json', {
describe: 'Output raw JSON',
type: 'boolean',
}).option('json-pretty-print', {
describe: 'Output pretty-printed JSON',
type: 'boolean',
}).option('presence', {
describe: 'New presence permission',
type: 'string',
});
}
type Arguments = YargsArguments<ReturnType<typeof builder>>;
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
if (argv.presence && !['FRIENDS', 'FAVORITE_FRIENDS', 'SELF'].includes(argv.presence)) {
throw new Error('Invalid permissions');
}
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 permissions = await nso.getCurrentUserPermissions();
if (argv.presence) {
await nso.updateCurrentUserPermissions(argv.presence as PresencePermissions,
permissions.result.permissions.presence, permissions.result.etag);
} else {
if (argv.jsonPrettyPrint) {
console.log(JSON.stringify(permissions.result, null, 4));
return;
}
if (argv.json) {
console.log(JSON.stringify(permissions.result));
return;
}
console.log('Presence is visible to %s', permissions.result.permissions.presence);
}
}

View File

@@ -1,14 +1,14 @@
import * as path from 'path';
import createDebug from 'debug';
import persist from 'node-persist';
import DiscordRPC from 'discord-rpc';
import fetch from 'node-fetch';
import { CurrentUser, Friend, Presence, PresenceState, ZncErrorResponse, ZncSuccessResponse } from '../../api/znc-types.js';
import { 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';
import { DiscordPresenceContext, 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');
@@ -176,14 +176,16 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
}
export class ZncDiscordPresence extends ZncNotifications {
presence_user: string | null;
show_friend_code = false;
force_friend_code: CurrentUser['links']['friendCode'] | undefined = undefined;
show_console_online = false;
friend_nsaid: string | null;
constructor(
argv: ArgumentsCamelCase<Arguments>,
argv: Pick<ArgumentsCamelCase<Arguments>,
'userNotifications' | 'friendNotifications' | 'updateInterval' |
'friendCode' | 'showInactivePresence' | 'friendNsaid'
>,
storage: persist.LocalStorage,
token: string,
nso: ZncApi,
@@ -198,66 +200,34 @@ export class ZncDiscordPresence extends ZncNotifications {
this.show_friend_code = !!this.force_friend_code || argv.friendCode === '' || argv.friendCode === '-';
this.show_console_online = argv.showInactivePresence;
this.friend_nsaid = argv.friendNsaid ?? null;
this.presence_user = argv.friendNsaid ?? data.nsoAccount.user.nsaId;
}
async init() {
const announcements = await this.nso.getAnnouncements();
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.friend_nsaid,
imageUri: '',
name: '',
isFriend: true,
isFavoriteFriend: false,
isServiceUser: true,
friendCreatedAt: 0,
presence: await this.nso.fetch<Presence>('/friend/' + this.friend_nsaid + '/presence'),
}] : []}};
if (!(this.nso instanceof ZncProxyApi)) {
await this.nso.getWebServices();
await this.nso.getActiveEvent();
}
const {friends, user} = await this.fetch([
'announcements',
this.presence_user ?
this.presence_user === this.data.nsoAccount.user.nsaId ? 'user' :
{friend: this.presence_user, presence: true} : null,
this.user_notifications ? 'user' : null,
this.friend_notifications ? 'friends' : null,
this.splatnet2_monitors.size ? 'user' : null,
]);
let user: ZncSuccessResponse<CurrentUser> | null = null;
if (this.friend_nsaid) {
const friend = friends.result.friends.find(f => f.nsaId === this.friend_nsaid);
if (this.presence_user && 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.friend_nsaid + '" is not friends with this user');
throw new Error('User "' + this.presence_user + '" is not friends with this user');
}
if (this.user_notifications && this.friend_notifications) {
await this.updateFriendsStatusForNotifications(friends.result.friends, true);
} else if (this.friend_notifications) {
await this.updateFriendsStatusForNotifications(
friends.result.friends.filter(f => f.nsaId !== this.friend_nsaid), true);
} else if (this.user_notifications && friend) {
await this.updateFriendsStatusForNotifications([friend], true);
}
await this.updatePresence(friend.presence);
await this.updatePresenceForDiscord(friend.presence);
} else {
user = await this.nso.getCurrentUser();
if (this.friend_notifications) {
await this.updateFriendsStatusForNotifications(this.user_notifications ?
[user.result, ...friends.result.friends] : friends.result.friends);
} else if (this.user_notifications) {
await this.updateFriendsStatusForNotifications([user.result]);
}
await this.updatePresence(user.result.presence, user.result.links.friendCode);
await this.updatePresenceForDiscord(user!.presence, user!.links.friendCode);
}
if (this.splatnet2_monitors.size) {
if (!user) user = await this.nso.getCurrentUser();
await this.updatePresenceForSplatNet2Monitors([user.result]);
}
await this.updatePresenceForNotifications(user, friends);
await this.updatePresenceForSplatNet2Monitors([user!]);
await new Promise(rs => setTimeout(rs, this.update_interval * 1000));
}
@@ -269,7 +239,7 @@ export class ZncDiscordPresence extends ZncNotifications {
last_presence: Presence | null = null;
friendcode: CurrentUser['links']['friendCode'] | undefined = undefined;
async updatePresence(presence: Presence | null, friendcode?: CurrentUser['links']['friendCode']) {
async updatePresenceForDiscord(presence: Presence | null, friendcode?: CurrentUser['links']['friendCode']) {
this.last_presence = presence;
this.friendcode = friendcode;
@@ -293,7 +263,7 @@ export class ZncDiscordPresence extends ZncNotifications {
const presencecontext: DiscordPresenceContext = {
friendcode: this.show_friend_code ? this.force_friend_code ?? friendcode : undefined,
znc_discord_presence: this,
nsaid: this.friend_nsaid ?? this.data.nsoAccount.user.nsaId,
nsaid: this.presence_user!,
};
const discordpresence = 'name' in presence.game ?
getDiscordPresence(presence.state, presence.game, presencecontext) :
@@ -376,68 +346,30 @@ export class ZncDiscordPresence extends ZncNotifications {
}
async update() {
let user: CurrentUser | null = null;
const {friends, user} = await this.fetch([
this.presence_user ?
this.presence_user === this.data.nsoAccount.user.nsaId ? 'user' :
{friend: this.presence_user, presence: true} : null,
this.user_notifications ? 'user' : null,
this.friend_notifications ? 'friends' : null,
this.splatnet2_monitors.size ? 'user' : null,
]);
if (this.friend_nsaid) {
if (!(this.nso instanceof ZncProxyApi)) await this.nso.getActiveEvent();
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.friend_nsaid,
imageUri: '',
name: '',
isFriend: true,
isFavoriteFriend: false,
isServiceUser: true,
friendCreatedAt: 0,
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.friend_nsaid);
if (this.user_notifications && this.friend_notifications) {
await this.updateFriendsStatusForNotifications(friends.result.friends);
} else if (this.friend_notifications) {
await this.updateFriendsStatusForNotifications(
friends.result.friends.filter(f => f.nsaId !== this.friend_nsaid));
} else if (this.user_notifications && friend) {
await this.updateFriendsStatusForNotifications([friend]);
}
if (this.presence_user && 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.updatePresence(null);
await this.updatePresenceForDiscord(null);
} else {
await this.updatePresence(friend.presence);
await this.updatePresenceForDiscord(friend.presence);
}
} else {
user = !(this.nso instanceof ZncProxyApi) ?
(await this.nso.getCurrentUser()).result : {
...this.data.nsoAccount.user,
presence: await this.nso.fetch<Presence>('/user/presence'),
};
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.user_notifications ?
[user, ...friends.result.friends] : friends.result.friends);
} else if (this.user_notifications) {
await this.updateFriendsStatusForNotifications([user]);
}
await this.updatePresence(user.presence, user.links.friendCode);
await this.updatePresenceForDiscord(user!.presence, user!.links.friendCode);
}
if (this.splatnet2_monitors.size) {
if (!user) user = (await this.nso.getCurrentUser()).result;
await this.updatePresenceForSplatNet2Monitors([user]);
}
await this.updatePresenceForNotifications(user, friends);
await this.updatePresenceForSplatNet2Monitors([user!]);
}
update_presence_errors = 0;
@@ -484,7 +416,7 @@ export class ZncProxyDiscordPresence extends ZncDiscordPresence {
}
const presence = await response.json() as Presence;
await this.updatePresence(presence);
await this.updatePresenceForDiscord(presence);
await this.updatePresenceForSplatNet2Monitor(presence, this.presence_url);
}
}