mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-10-20 08:39:44 -05:00
Add parental controls
This commit is contained in:
@@ -4,6 +4,7 @@ import * as crypto from 'crypto';
|
||||
import type { Arguments as ParentArguments } from '../cli.js';
|
||||
import { ArgumentsCamelCase, Argv, getToken, initStorage, YargsArguments } from '../util.js';
|
||||
import { getNintendoAccountSessionToken } from '../api/na.js';
|
||||
import { ZNCA_CLIENT_ID } from '../api/znc.js';
|
||||
|
||||
const debug = createDebug('cli:auth');
|
||||
|
||||
@@ -31,7 +32,7 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
const params = {
|
||||
state,
|
||||
redirect_uri: 'npf71b963c1b7b6d119://auth',
|
||||
client_id: '71b963c1b7b6d119',
|
||||
client_id: ZNCA_CLIENT_ID,
|
||||
scope: 'openid user user.birthday user.mii user.screenName',
|
||||
response_type: 'session_token_code',
|
||||
session_token_code_challenge: challenge,
|
||||
@@ -71,8 +72,8 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
const authorisedparams = new URLSearchParams(authorisedurl.hash.substr(1));
|
||||
debug('Redirect URL parameters', [...authorisedparams.entries()]);
|
||||
|
||||
const token = await getNintendoAccountSessionToken(
|
||||
authorisedparams.get('session_token_code')!, verifier, '71b963c1b7b6d119');
|
||||
const code = authorisedparams.get('session_token_code')!;
|
||||
const token = await getNintendoAccountSessionToken(code, verifier, ZNCA_CLIENT_ID);
|
||||
|
||||
console.log('Session token', token);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import createDebug from 'debug';
|
||||
import Table from 'cli-table/lib/index.js';
|
||||
import { PresenceState } from '../api/znc-types.js';
|
||||
import type { Arguments as ParentArguments } from '../cli.js';
|
||||
import { ArgumentsCamelCase, Argv, getToken, initStorage, YargsArguments } from '../util.js';
|
||||
import { ArgumentsCamelCase, Argv, getToken, hrduration, initStorage, YargsArguments } from '../util.js';
|
||||
|
||||
const debug = createDebug('cli:friends');
|
||||
|
||||
@@ -67,8 +67,6 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
for (const friend of friends.result.friends) {
|
||||
const online = friend.presence.state === PresenceState.ONLINE ||
|
||||
friend.presence.state === PresenceState.PLAYING;
|
||||
const hours = 'name' in friend.presence.game ? Math.floor(friend.presence.game.totalPlayTime / 60) : 0;
|
||||
const minutes = 'name' in friend.presence.game ? friend.presence.game.totalPlayTime - (hours * 60) : 0;
|
||||
|
||||
table.push([
|
||||
friend.id,
|
||||
@@ -76,10 +74,9 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
friend.name,
|
||||
online ?
|
||||
'name' in friend.presence.game ?
|
||||
'Playing ' + friend.presence.game.name +
|
||||
'; played for ' + (hours || !minutes ? hours + ' hour' + (hours === 1 ? '' : 's') : '') +
|
||||
(minutes ? ', ' + minutes + ' minute' + (minutes === 1 ? '' : 's'): '') +
|
||||
' since ' + new Date(friend.presence.game.firstPlayedAt * 1000).toLocaleDateString('en-GB') :
|
||||
'Playing ' + friend.presence.game.name + '; played for ' +
|
||||
hrduration(friend.presence.game.totalPlayTime) + ' since ' +
|
||||
new Date(friend.presence.game.firstPlayedAt * 1000).toLocaleDateString('en-GB') :
|
||||
'Online' :
|
||||
friend.presence.logoutAt ?
|
||||
'Last seen ' + new Date(friend.presence.logoutAt * 1000).toISOString() :
|
||||
|
||||
@@ -9,3 +9,4 @@ export * as friends from './friends.js';
|
||||
export * as presence from './presence.js';
|
||||
export * as notify from './notify.js';
|
||||
export * as httpServer from './http-server.js';
|
||||
export * as pctl from './pctl.js';
|
||||
|
||||
16
src/cli/pctl.ts
Normal file
16
src/cli/pctl.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import createDebug from 'debug';
|
||||
import type { Arguments as ParentArguments } from '../cli.js';
|
||||
import { Argv } from '../util.js';
|
||||
import * as commands from './pctl/index.js';
|
||||
|
||||
const debug = createDebug('cli:pctl');
|
||||
|
||||
export const command = 'pctl <command>';
|
||||
export const desc = 'Nintendo Switch Parental Controls';
|
||||
|
||||
export function builder(yargs: Argv<ParentArguments>) {
|
||||
for (const command of Object.values(commands)) {
|
||||
// @ts-expect-error
|
||||
yargs.command(command);
|
||||
}
|
||||
}
|
||||
114
src/cli/pctl/auth.ts
Normal file
114
src/cli/pctl/auth.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import * as util from 'util';
|
||||
import createDebug from 'debug';
|
||||
import * as crypto from 'crypto';
|
||||
import type { Arguments as ParentArguments } from '../../cli.js';
|
||||
import { ArgumentsCamelCase, Argv, getPctlToken, initStorage, YargsArguments } from '../../util.js';
|
||||
import { getNintendoAccountSessionToken } from '../../api/na.js';
|
||||
import { MOON_CLIENT_ID } from '../../api/moon.js';
|
||||
|
||||
const debug = createDebug('cli:pctl:auth');
|
||||
|
||||
export const command = 'auth';
|
||||
export const desc = 'Generate a link to login to a Nintendo Account';
|
||||
|
||||
export function builder(yargs: Argv<ParentArguments>) {
|
||||
return yargs.option('auth', {
|
||||
describe: 'Authenticate immediately',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
}).option('select', {
|
||||
describe: 'Set as default user (default: true if only user)',
|
||||
type: 'boolean',
|
||||
});
|
||||
}
|
||||
|
||||
type Arguments = YargsArguments<ReturnType<typeof builder>>;
|
||||
|
||||
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
const state = crypto.randomBytes(36).toString('base64url');
|
||||
const verifier = crypto.randomBytes(32).toString('base64url');
|
||||
const challenge = crypto.createHash('sha256').update(verifier).digest().toString('base64url');
|
||||
|
||||
const params = {
|
||||
state,
|
||||
redirect_uri: 'npf54789befb391a838://auth',
|
||||
client_id: MOON_CLIENT_ID,
|
||||
scope: [
|
||||
'openid',
|
||||
'user',
|
||||
'user.mii',
|
||||
'moonUser:administration',
|
||||
'moonDevice:create',
|
||||
'moonOwnedDevice:administration',
|
||||
'moonParentalControlSetting',
|
||||
'moonParentalControlSetting:update',
|
||||
'moonParentalControlSettingState',
|
||||
'moonPairingState',
|
||||
'moonSmartDevice:administration',
|
||||
'moonDailySummary',
|
||||
'moonMonthlySummary',
|
||||
].join(' '),
|
||||
response_type: 'session_token_code',
|
||||
session_token_code_challenge: challenge,
|
||||
session_token_code_challenge_method: 'S256',
|
||||
};
|
||||
|
||||
const authoriseurl = 'https://accounts.nintendo.com/connect/1.0.0/authorize?' +
|
||||
new URLSearchParams(params).toString();
|
||||
|
||||
debug('Authentication parameters', {
|
||||
state,
|
||||
verifier,
|
||||
challenge,
|
||||
}, params);
|
||||
|
||||
console.log('1. Open this URL and login to your Nintendo Account:');
|
||||
console.log('');
|
||||
console.log(authoriseurl);
|
||||
console.log('');
|
||||
|
||||
console.log('2. On the "Linking an External Account" page, right click "Select this person" and copy the link. It should start with "npf54789befb391a838://auth".');
|
||||
console.log('');
|
||||
|
||||
const read = await import('read');
|
||||
// @ts-expect-error
|
||||
const prompt = util.promisify(read.default as typeof read);
|
||||
|
||||
const applink = await prompt({
|
||||
prompt: `Paste the link: `,
|
||||
// silent: true,
|
||||
output: process.stderr,
|
||||
});
|
||||
|
||||
console.log('');
|
||||
|
||||
const authorisedurl = new URL(applink);
|
||||
const authorisedparams = new URLSearchParams(authorisedurl.hash.substr(1));
|
||||
debug('Redirect URL parameters', [...authorisedparams.entries()]);
|
||||
|
||||
const code = authorisedparams.get('session_token_code')!;
|
||||
const token = await getNintendoAccountSessionToken(code, verifier, MOON_CLIENT_ID);
|
||||
|
||||
console.log('Session token', token);
|
||||
|
||||
if (argv.auth) {
|
||||
const storage = await initStorage(argv.dataPath);
|
||||
|
||||
const {moon, data} = await getPctlToken(storage, token.session_token);
|
||||
|
||||
console.log('Authenticated as Nintendo Account %s (%s)',
|
||||
data.user.nickname, data.user.id);
|
||||
|
||||
await storage.setItem('NintendoAccountToken-pctl.' + data.user.id, token.session_token);
|
||||
|
||||
const users = new Set(await storage.getItem('NintendoAccountIds') ?? []);
|
||||
users.add(data.user.id);
|
||||
await storage.setItem('NintendoAccountIds', [...users]);
|
||||
|
||||
if ('select' in argv ? argv.select : users.size === 1) {
|
||||
await storage.setItem('SelectedUser', data.user.id);
|
||||
|
||||
console.log('Set as default user');
|
||||
}
|
||||
}
|
||||
}
|
||||
79
src/cli/pctl/daily-summaries.ts
Normal file
79
src/cli/pctl/daily-summaries.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import createDebug from 'debug';
|
||||
// @ts-expect-error
|
||||
import Table from 'cli-table/lib/index.js';
|
||||
import type { Arguments as ParentArguments } from '../../cli.js';
|
||||
import { ArgumentsCamelCase, Argv, getPctlToken, hrduration, initStorage, YargsArguments } from '../../util.js';
|
||||
|
||||
const debug = createDebug('cli:pctl:daily-summaries');
|
||||
|
||||
export const command = 'daily-summaries <device>';
|
||||
export const desc = 'Show daily summaries';
|
||||
|
||||
export function builder(yargs: Argv<ParentArguments>) {
|
||||
return yargs.positional('device', {
|
||||
describe: 'Nintendo Switch device ID',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
}).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',
|
||||
});
|
||||
}
|
||||
|
||||
type Arguments = YargsArguments<ReturnType<typeof builder>>;
|
||||
|
||||
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
const storage = await initStorage(argv.dataPath);
|
||||
|
||||
const usernsid = argv.user ?? await storage.getItem('SelectedUser');
|
||||
const token: string = argv.token ||
|
||||
await storage.getItem('NintendoAccountToken-pctl.' + usernsid);
|
||||
const {moon, data} = await getPctlToken(storage, token);
|
||||
|
||||
const summaries = await moon.getDailySummaries(argv.device);
|
||||
|
||||
if (argv.jsonPrettyPrint) {
|
||||
console.log(JSON.stringify(summaries, null, 4));
|
||||
return;
|
||||
}
|
||||
if (argv.json) {
|
||||
console.log(JSON.stringify(summaries));
|
||||
return;
|
||||
}
|
||||
|
||||
const table = new Table({
|
||||
head: [
|
||||
'Date',
|
||||
'Status',
|
||||
'Play time',
|
||||
'Misc. time',
|
||||
'Titles played',
|
||||
'Users played',
|
||||
'Notices',
|
||||
],
|
||||
});
|
||||
|
||||
for (const summary of summaries.items) {
|
||||
table.push([
|
||||
summary.date,
|
||||
summary.result,
|
||||
hrduration(summary.playingTime / 60, true),
|
||||
hrduration(summary.miscTime / 60, true),
|
||||
summary.playedApps.map(t => t.title).join('\n'),
|
||||
summary.devicePlayers.map(p => p.nickname)
|
||||
.concat(summary.anonymousPlayer ? ['Unknown user'] : []).join('\n'),
|
||||
[...summary.importantInfos, ...summary.observations.map(o => o.type)].join('\n'),
|
||||
]);
|
||||
}
|
||||
|
||||
console.log(table.toString());
|
||||
}
|
||||
59
src/cli/pctl/devices.ts
Normal file
59
src/cli/pctl/devices.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import createDebug from 'debug';
|
||||
// @ts-expect-error
|
||||
import Table from 'cli-table/lib/index.js';
|
||||
import type { Arguments as ParentArguments } from '../../cli.js';
|
||||
import { ArgumentsCamelCase, Argv, getPctlToken, initStorage, YargsArguments } from '../../util.js';
|
||||
|
||||
const debug = createDebug('cli:pctl:devices');
|
||||
|
||||
export const command = 'devices';
|
||||
export const desc = 'List Nintendo Switch consoles';
|
||||
|
||||
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',
|
||||
});
|
||||
}
|
||||
|
||||
type Arguments = YargsArguments<ReturnType<typeof builder>>;
|
||||
|
||||
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
console.warn('Listing devices');
|
||||
|
||||
const storage = await initStorage(argv.dataPath);
|
||||
|
||||
const usernsid = argv.user ?? await storage.getItem('SelectedUser');
|
||||
const token: string = argv.token ||
|
||||
await storage.getItem('NintendoAccountToken-pctl.' + usernsid);
|
||||
const {moon, data} = await getPctlToken(storage, token);
|
||||
|
||||
const devices = await moon.getDevices();
|
||||
|
||||
const table = new Table({
|
||||
head: [
|
||||
'ID',
|
||||
'Label',
|
||||
'Serial number',
|
||||
'Software version',
|
||||
'PIN',
|
||||
'Last synchronised',
|
||||
],
|
||||
});
|
||||
|
||||
for (const device of devices.items) {
|
||||
table.push([
|
||||
device.deviceId,
|
||||
device.label,
|
||||
device.device.serialNumber,
|
||||
device.device.firmwareVersion.displayedVersion + ' (' + device.device.firmwareVersion.internalVersion + ')',
|
||||
device.device.synchronizedUnlockCode,
|
||||
new Date(device.device.synchronizedParentalControlSetting.synchronizedAt * 1000).toISOString(),
|
||||
]);
|
||||
}
|
||||
|
||||
console.log(table.toString());
|
||||
}
|
||||
95
src/cli/pctl/dump-summaries.ts
Normal file
95
src/cli/pctl/dump-summaries.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import createDebug from 'debug';
|
||||
import * as fs from 'fs/promises';
|
||||
import mkdirp from 'mkdirp';
|
||||
import type { Arguments as ParentArguments } from '../../cli.js';
|
||||
import { ArgumentsCamelCase, Argv, getPctlToken, initStorage, YargsArguments } from '../../util.js';
|
||||
import * as path from 'path';
|
||||
import { DailySummaryResult } from '../../api/moon-types.js';
|
||||
import MoonApi from '../../api/moon.js';
|
||||
|
||||
const debug = createDebug('cli:pctl:dump-summaries');
|
||||
|
||||
export const command = 'dump-summaries <directory>';
|
||||
export const desc = 'Download all daily and monthly summaries';
|
||||
|
||||
export function builder(yargs: Argv<ParentArguments>) {
|
||||
return yargs.positional('directory', {
|
||||
describe: 'Directory to write summary data to',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
}).option('user', {
|
||||
describe: 'Nintendo Account ID',
|
||||
type: 'string',
|
||||
}).option('token', {
|
||||
describe: 'Nintendo Account session token',
|
||||
type: 'string',
|
||||
}).option('device', {
|
||||
describe: 'Nintendo Switch device ID',
|
||||
type: 'array',
|
||||
});
|
||||
}
|
||||
|
||||
type Arguments = YargsArguments<ReturnType<typeof builder>>;
|
||||
|
||||
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
const storage = await initStorage(argv.dataPath);
|
||||
|
||||
const usernsid = argv.user ?? await storage.getItem('SelectedUser');
|
||||
const token: string = argv.token ||
|
||||
await storage.getItem('NintendoAccountToken-pctl.' + usernsid);
|
||||
const {moon, data} = await getPctlToken(storage, token);
|
||||
|
||||
await mkdirp(argv.directory);
|
||||
|
||||
const devices = await moon.getDevices();
|
||||
|
||||
for (const device of argv.device ?? devices.items.map(d => d.deviceId)) {
|
||||
console.warn('Downloading summaries for device %s', device);
|
||||
|
||||
await dumpMonthlySummariesForDevice(moon, argv.directory, '' + device);
|
||||
await dumpDailySummariesForDevice(moon, argv.directory, '' + device);
|
||||
}
|
||||
}
|
||||
|
||||
async function dumpMonthlySummariesForDevice(moon: MoonApi, directory: string, device: string) {
|
||||
debug('Fetching monthly summaries for device %s', device);
|
||||
const monthlySummaries = await moon.getMonthlySummaries(device);
|
||||
|
||||
for (const item of monthlySummaries.items) {
|
||||
const filename = 'pctl-monthly-' + item.deviceId + '-' + item.month + '.json';
|
||||
const file = path.join(directory, filename);
|
||||
|
||||
try {
|
||||
await fs.stat(file);
|
||||
debug('Skipping monthly summary %s for device %s, file already exists', item.month, item.deviceId);
|
||||
continue;
|
||||
} catch (err) {}
|
||||
|
||||
debug('Fetching monthly summary %s for device %s', item.month, item.deviceId);
|
||||
const summary = await moon.getMonthlySummary(item.deviceId, item.month);
|
||||
|
||||
debug('Writing %s', filename);
|
||||
await fs.writeFile(file, JSON.stringify(summary, null, 4) + '\n', 'utf-8');
|
||||
}
|
||||
}
|
||||
|
||||
async function dumpDailySummariesForDevice(moon: MoonApi, directory: string, device: string) {
|
||||
debug('Fetching daily summaries for device %s', device);
|
||||
const summaries = await moon.getDailySummaries(device);
|
||||
const timestamp = Date.now();
|
||||
|
||||
for (const summary of summaries.items) {
|
||||
const filename = 'pctl-daily-' + summary.deviceId + '-' + summary.date +
|
||||
(summary.result === DailySummaryResult.ACHIEVED ? '' : '-' + timestamp) + '.json';
|
||||
const file = path.join(directory, filename);
|
||||
|
||||
try {
|
||||
await fs.stat(file);
|
||||
debug('Skipping daily summary %s for device %s, file already exists', summary.date, summary.deviceId);
|
||||
continue;
|
||||
} catch (err) {}
|
||||
|
||||
debug('Writing %s', filename);
|
||||
await fs.writeFile(file, JSON.stringify(summary, null, 4) + '\n', 'utf-8');
|
||||
}
|
||||
}
|
||||
8
src/cli/pctl/index.ts
Normal file
8
src/cli/pctl/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export * as token from './token.js';
|
||||
export * as auth from './auth.js';
|
||||
export * as devices from './devices.js';
|
||||
export * as dailySummaries from './daily-summaries.js';
|
||||
export * as monthlySummaries from './monthly-summaries.js';
|
||||
export * as monthlySummary from './monthly-summary.js';
|
||||
export * as settings from './settings.js';
|
||||
export * as dumpSummaries from './dump-summaries.js';
|
||||
51
src/cli/pctl/monthly-summaries.ts
Normal file
51
src/cli/pctl/monthly-summaries.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import createDebug from 'debug';
|
||||
// @ts-expect-error
|
||||
import Table from 'cli-table/lib/index.js';
|
||||
import type { Arguments as ParentArguments } from '../../cli.js';
|
||||
import { ArgumentsCamelCase, Argv, getPctlToken, initStorage, YargsArguments } from '../../util.js';
|
||||
|
||||
const debug = createDebug('cli:pctl:monthly-summaries');
|
||||
|
||||
export const command = 'monthly-summaries <device>';
|
||||
export const desc = 'List monthly summaries';
|
||||
|
||||
export function builder(yargs: Argv<ParentArguments>) {
|
||||
return yargs.positional('device', {
|
||||
describe: 'Nintendo Switch device ID',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
}).option('user', {
|
||||
describe: 'Nintendo Account ID',
|
||||
type: 'string',
|
||||
}).option('token', {
|
||||
describe: 'Nintendo Account session token',
|
||||
type: 'string',
|
||||
});
|
||||
}
|
||||
|
||||
type Arguments = YargsArguments<ReturnType<typeof builder>>;
|
||||
|
||||
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
const storage = await initStorage(argv.dataPath);
|
||||
|
||||
const usernsid = argv.user ?? await storage.getItem('SelectedUser');
|
||||
const token: string = argv.token ||
|
||||
await storage.getItem('NintendoAccountToken-pctl.' + usernsid);
|
||||
const {moon, data} = await getPctlToken(storage, token);
|
||||
|
||||
const summaries = await moon.getMonthlySummaries(argv.device);
|
||||
|
||||
const table = new Table({
|
||||
head: [
|
||||
'Month',
|
||||
],
|
||||
});
|
||||
|
||||
for (const summary of summaries.items) {
|
||||
table.push([
|
||||
summary.month,
|
||||
]);
|
||||
}
|
||||
|
||||
console.log(table.toString());
|
||||
}
|
||||
76
src/cli/pctl/monthly-summary.ts
Normal file
76
src/cli/pctl/monthly-summary.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import createDebug from 'debug';
|
||||
// @ts-expect-error
|
||||
import Table from 'cli-table/lib/index.js';
|
||||
import type { Arguments as ParentArguments } from '../../cli.js';
|
||||
import { ArgumentsCamelCase, Argv, getPctlToken, initStorage, YargsArguments } from '../../util.js';
|
||||
|
||||
const debug = createDebug('cli:pctl:monthly-summary');
|
||||
|
||||
export const command = 'monthly-summary <device> <month>';
|
||||
export const desc = 'Show monthly summary data';
|
||||
|
||||
export function builder(yargs: Argv<ParentArguments>) {
|
||||
return yargs.positional('device', {
|
||||
describe: 'Nintendo Switch device ID',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
}).positional('month', {
|
||||
describe: 'Report month',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
}).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',
|
||||
});
|
||||
}
|
||||
|
||||
type Arguments = YargsArguments<ReturnType<typeof builder>>;
|
||||
|
||||
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
const storage = await initStorage(argv.dataPath);
|
||||
|
||||
const usernsid = argv.user ?? await storage.getItem('SelectedUser');
|
||||
const token: string = argv.token ||
|
||||
await storage.getItem('NintendoAccountToken-pctl.' + usernsid);
|
||||
const {moon, data} = await getPctlToken(storage, token);
|
||||
|
||||
const summary = await moon.getMonthlySummary(argv.device, argv.month);
|
||||
|
||||
if (argv.jsonPrettyPrint) {
|
||||
console.log(JSON.stringify(summary, null, 4));
|
||||
return;
|
||||
}
|
||||
if (argv.json) {
|
||||
console.log(JSON.stringify(summary));
|
||||
return;
|
||||
}
|
||||
|
||||
const titles = new Table({
|
||||
head: [
|
||||
'Title',
|
||||
'First played',
|
||||
'Days',
|
||||
'Ranking',
|
||||
],
|
||||
});
|
||||
|
||||
for (const title of summary.playedApps) {
|
||||
titles.push([
|
||||
title.title,
|
||||
title.firstPlayDate,
|
||||
title.playingDays,
|
||||
title.position,
|
||||
]);
|
||||
}
|
||||
|
||||
console.log(titles.toString());
|
||||
}
|
||||
37
src/cli/pctl/settings.ts
Normal file
37
src/cli/pctl/settings.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import createDebug from 'debug';
|
||||
import type { Arguments as ParentArguments } from '../../cli.js';
|
||||
import { ArgumentsCamelCase, Argv, getPctlToken, initStorage, YargsArguments } from '../../util.js';
|
||||
|
||||
const debug = createDebug('cli:pctl:settings');
|
||||
|
||||
export const command = 'settings <device>';
|
||||
export const desc = 'Show parental control setting state';
|
||||
|
||||
export function builder(yargs: Argv<ParentArguments>) {
|
||||
return yargs.positional('device', {
|
||||
describe: 'Nintendo Switch device ID',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
}).option('user', {
|
||||
describe: 'Nintendo Account ID',
|
||||
type: 'string',
|
||||
}).option('token', {
|
||||
describe: 'Nintendo Account session token',
|
||||
type: 'string',
|
||||
});
|
||||
}
|
||||
|
||||
type Arguments = YargsArguments<ReturnType<typeof builder>>;
|
||||
|
||||
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
const storage = await initStorage(argv.dataPath);
|
||||
|
||||
const usernsid = argv.user ?? await storage.getItem('SelectedUser');
|
||||
const token: string = argv.token ||
|
||||
await storage.getItem('NintendoAccountToken-pctl.' + usernsid);
|
||||
const {moon, data} = await getPctlToken(storage, token);
|
||||
|
||||
const d = await moon.getParentalControlSettingState(argv.device);
|
||||
|
||||
console.log(d);
|
||||
}
|
||||
56
src/cli/pctl/token.ts
Normal file
56
src/cli/pctl/token.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import * as util from 'util';
|
||||
import createDebug from 'debug';
|
||||
import type { Arguments as ParentArguments } from '../../cli.js';
|
||||
import { ArgumentsCamelCase, Argv, getPctlToken, initStorage, YargsArguments } from '../../util.js';
|
||||
|
||||
const debug = createDebug('cli:pctl:token');
|
||||
|
||||
export const command = 'token [token]';
|
||||
export const desc = 'Authenticate with a Nintendo Account session token';
|
||||
|
||||
export function builder(yargs: Argv<ParentArguments>) {
|
||||
return yargs.positional('token', {
|
||||
describe: 'Nintendo Account session token (it is recommended this is not set and you enter it interactively)',
|
||||
type: 'string',
|
||||
}).option('select', {
|
||||
describe: 'Set as default user (default: true if only user)',
|
||||
type: 'boolean',
|
||||
});
|
||||
}
|
||||
|
||||
type Arguments = YargsArguments<ReturnType<typeof builder>>;
|
||||
|
||||
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
const storage = await initStorage(argv.dataPath);
|
||||
|
||||
if (!argv.token) {
|
||||
const read = await import('read');
|
||||
// @ts-expect-error
|
||||
const prompt = util.promisify(read.default as typeof read);
|
||||
|
||||
argv.token = await prompt({
|
||||
prompt: `Token: `,
|
||||
silent: true,
|
||||
output: process.stderr,
|
||||
});
|
||||
}
|
||||
|
||||
const {moon, data} = await getPctlToken(storage, argv.token);
|
||||
|
||||
console.warn('Authenticated as Nintendo Account %s (%s)',
|
||||
data.user.nickname, data.user.id);
|
||||
|
||||
await storage.setItem('NintendoAccountToken-pctl.' + data.user.id, argv.token);
|
||||
|
||||
const users = new Set(await storage.getItem('NintendoAccountIds') ?? []);
|
||||
users.add(data.user.id);
|
||||
await storage.setItem('NintendoAccountIds', [...users]);
|
||||
|
||||
console.log('Saved token');
|
||||
|
||||
if ('select' in argv ? argv.select : users.size === 1) {
|
||||
await storage.setItem('SelectedUser', data.user.id);
|
||||
|
||||
console.log('Set as default user');
|
||||
}
|
||||
}
|
||||
31
src/cli/pctl/user.ts
Normal file
31
src/cli/pctl/user.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import createDebug from 'debug';
|
||||
import type { Arguments as ParentArguments } from '../../cli.js';
|
||||
import { ArgumentsCamelCase, Argv, getPctlToken, initStorage, YargsArguments } from '../../util.js';
|
||||
|
||||
const debug = createDebug('cli:pctl:user');
|
||||
|
||||
export const command = 'user';
|
||||
export const desc = 'Get the authenticated Nintendo Account';
|
||||
|
||||
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',
|
||||
});
|
||||
}
|
||||
|
||||
type Arguments = YargsArguments<ReturnType<typeof builder>>;
|
||||
|
||||
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
const storage = await initStorage(argv.dataPath);
|
||||
|
||||
const usernsid = argv.user ?? await storage.getItem('SelectedUser');
|
||||
const token: string = argv.token ||
|
||||
await storage.getItem('NintendoAccountToken-pctl.' + usernsid);
|
||||
const {moon, data} = await getPctlToken(storage, token);
|
||||
|
||||
console.log('Nintendo Account', data.user);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import createDebug from 'debug';
|
||||
// @ts-expect-error
|
||||
import Table from 'cli-table/lib/index.js';
|
||||
import type { Arguments as ParentArguments } from '../cli.js';
|
||||
import { Argv, initStorage, SavedToken } from '../util.js';
|
||||
import { Argv, initStorage, SavedMoonToken, SavedToken } from '../util.js';
|
||||
|
||||
const debug = createDebug('cli:users');
|
||||
|
||||
@@ -24,22 +24,27 @@ export function builder(yargs: Argv<ParentArguments>) {
|
||||
'Country',
|
||||
'Nintendo Switch ID',
|
||||
'Nintendo Switch username',
|
||||
'Parental Controls',
|
||||
],
|
||||
});
|
||||
|
||||
for (const userid of users ?? []) {
|
||||
const token: string | undefined = await storage.getItem('NintendoAccountToken.' + userid);
|
||||
if (!token) continue;
|
||||
const cache: SavedToken | undefined = await storage.getItem('NsoToken.' + token);
|
||||
if (!cache) continue;
|
||||
const nsoCache: SavedToken | undefined = token ? await storage.getItem('NsoToken.' + token) : undefined;
|
||||
const moonToken: string | undefined = await storage.getItem('NintendoAccountToken-pctl.' + userid);
|
||||
const moonCache: SavedMoonToken | undefined = moonToken ? await storage.getItem('MoonToken.' + moonToken) : undefined;
|
||||
|
||||
const user = nsoCache?.user ?? moonCache?.user;
|
||||
if (!user) continue;
|
||||
|
||||
table.push([
|
||||
cache.user.id + (selected === cache.user.id ? ' *' : ''),
|
||||
cache.user.screenName,
|
||||
cache.user.nickname,
|
||||
cache.user.country,
|
||||
cache.nsoAccount.user.nsaId,
|
||||
cache.nsoAccount.user.name,
|
||||
user.id + (selected === user.id ? ' *' : ''),
|
||||
user.screenName,
|
||||
user.nickname,
|
||||
user.country,
|
||||
nsoCache?.nsoAccount.user.nsaId ?? 'Not signed in',
|
||||
nsoCache?.nsoAccount.user.name ?? 'Not signed in',
|
||||
moonCache ? 'Signed in' : 'Not signed in',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user