mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-10-25 11:06:45 -05:00
Move monitoring classes, use nxapi debug namespace for non-app/cli modules
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs/promises';
|
||||
import createDebug from 'debug';
|
||||
import mkdirp from 'mkdirp';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import fetch from 'node-fetch';
|
||||
import * as crypto from 'crypto';
|
||||
import type { Arguments as ParentArguments } from '../splatnet2.js';
|
||||
import { ArgumentsCamelCase, Argv, initStorage, YargsArguments } from '../../util.js';
|
||||
import { getIksmToken } from './util.js';
|
||||
import SplatNet2Api, { ShareColour } from '../../api/splatnet2.js';
|
||||
import { Challenge, NicknameAndIcon, Records, Stages } from '../../api/splatnet2-types.js';
|
||||
import { dumpChallenges, dumpHeroRecords, dumpProfileImage, dumpRecords } from '../../common/splatnet2/dump-records.js';
|
||||
|
||||
const debug = createDebug('cli:splatnet2:dump-records');
|
||||
|
||||
@@ -112,152 +109,3 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
await fs.writeFile(file, JSON.stringify(timeline, null, 4) + '\n', 'utf-8');
|
||||
}
|
||||
}
|
||||
|
||||
export async function dumpRecords(
|
||||
directory: string, user_id: string,
|
||||
records: Records, nickname_and_icon: NicknameAndIcon,
|
||||
updated?: Date
|
||||
) {
|
||||
const latest_filename = 'splatnet2-records-' + user_id + '-latest.json';
|
||||
const latest_file = path.join(directory, latest_filename);
|
||||
|
||||
if (updated) {
|
||||
try {
|
||||
const {sn2_updated_timestamp} = JSON.parse(await fs.readFile(latest_file, 'utf-8'));
|
||||
|
||||
if ((sn2_updated_timestamp * 1000) >= updated.getTime()) {
|
||||
debug('Skipping user records, not updated');
|
||||
return;
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
const timestamp = Date.now();
|
||||
const sn2_updated_timestamp = records.records.update_time;
|
||||
|
||||
const filename = 'splatnet2-records-' + user_id + '-' + timestamp + '.json';
|
||||
const file = path.join(directory, filename);
|
||||
const ni_filename = 'splatnet2-ni-' + user_id + '-' + timestamp + '.json';
|
||||
const ni_file = path.join(directory, ni_filename);
|
||||
|
||||
debug('Writing records %s', filename);
|
||||
await fs.writeFile(file, JSON.stringify(records, null, 4) + '\n', 'utf-8');
|
||||
|
||||
debug('Writing records %s', ni_filename);
|
||||
await fs.writeFile(ni_file, JSON.stringify(nickname_and_icon, null, 4) + '\n', 'utf-8');
|
||||
|
||||
await fs.writeFile(latest_file, JSON.stringify({timestamp, sn2_updated_timestamp}, null, 4) + '\n', 'utf-8');
|
||||
}
|
||||
|
||||
export async function dumpProfileImage(
|
||||
splatnet: SplatNet2Api, directory: string, user_id: string,
|
||||
stages: Stages, nickname_and_icon: NicknameAndIcon,
|
||||
favourite_stage?: Arguments['favourite-stage'], favourite_colour?: Arguments['favourite-colour'],
|
||||
updated?: Date
|
||||
) {
|
||||
const stage = favourite_stage ?
|
||||
stages.stages.find(s => s.id === favourite_stage ||
|
||||
s.name.toLowerCase() === favourite_stage?.toLowerCase()) :
|
||||
stages.stages[0];
|
||||
|
||||
if (!stage) {
|
||||
debug('Invalid favourite stage "%s"', favourite_stage);
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
if (!Object.values(ShareColour).includes(favourite_colour)) {
|
||||
favourite_colour = ShareColour.PINK;
|
||||
}
|
||||
|
||||
const latest_filename = 'splatnet2-profile-' + user_id + '-latest.json';
|
||||
const latest_file = path.join(directory, latest_filename);
|
||||
|
||||
const etag_data = {
|
||||
stage: (stage ?? stages.stages[0]).id,
|
||||
stage_image: (stage ?? stages.stages[0]).image,
|
||||
colour: favourite_colour,
|
||||
|
||||
name: nickname_and_icon.nickname,
|
||||
image_url: nickname_and_icon.thumbnail_url,
|
||||
};
|
||||
const etag = crypto.createHash('sha256').update(JSON.stringify(etag_data)).digest('base64');
|
||||
|
||||
if (updated) {
|
||||
try {
|
||||
const {timestamp, etag: prev_etag} = JSON.parse(await fs.readFile(latest_file, 'utf-8'));
|
||||
|
||||
if (timestamp > updated.getTime() && etag === prev_etag) {
|
||||
debug('Skipping profile image, not updated');
|
||||
return;
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
const timestamp = Date.now();
|
||||
const filename = 'splatnet2-profile-' + user_id + '-' + timestamp + '.json';
|
||||
const file = path.join(directory, filename);
|
||||
const image_filename = 'splatnet2-profile-' + user_id + '-' + timestamp + '.png';
|
||||
const image_file = path.join(directory, image_filename);
|
||||
|
||||
debug('Fetching profile image URL');
|
||||
const share = await splatnet.shareProfile(stage?.id ?? stages.stages[0].id, favourite_colour as ShareColour);
|
||||
|
||||
debug('Fetching profile image');
|
||||
const image_response = await fetch(share.url);
|
||||
const image = await image_response.buffer();
|
||||
|
||||
debug('Writing profile image data %s', filename);
|
||||
await fs.writeFile(file, JSON.stringify({
|
||||
share,
|
||||
stage: stage ?? stages.stages[0],
|
||||
colour: favourite_colour,
|
||||
}, null, 4) + '\n', 'utf-8');
|
||||
|
||||
debug('Writing profile image %s', image_filename);
|
||||
await fs.writeFile(image_file, image);
|
||||
|
||||
await fs.writeFile(latest_file, JSON.stringify({timestamp, etag, etag_data}, null, 4) + '\n', 'utf-8');
|
||||
}
|
||||
|
||||
async function dumpChallenges(
|
||||
splatnet: SplatNet2Api, directory: string, user_id: string,
|
||||
challenges: Challenge[], season: 1 | 2
|
||||
) {
|
||||
for (const challenge of challenges) {
|
||||
const filename = 'splatnet2-challenge-' + user_id + '-' + challenge.key + '.json';
|
||||
const file = path.join(directory, filename);
|
||||
const image_filename = 'splatnet2-challenge-' + user_id + '-' + challenge.key + '.png';
|
||||
const image_file = path.join(directory, image_filename);
|
||||
|
||||
try {
|
||||
await fs.stat(file);
|
||||
await fs.stat(image_file);
|
||||
debug('Skipping challenge image %s, file already exists', challenge.key);
|
||||
continue;
|
||||
} catch (err) {}
|
||||
|
||||
debug('Fetching challenge image URL for %s', challenge.key);
|
||||
const share = await splatnet.shareChallenge(challenge.key, season);
|
||||
|
||||
debug('Fetching challenge image for %s', challenge.key);
|
||||
const image_response = await fetch(share.url);
|
||||
const image = await image_response.buffer();
|
||||
|
||||
debug('Writing challenge image data %s', filename);
|
||||
await fs.writeFile(file, JSON.stringify({share}, null, 4) + '\n', 'utf-8');
|
||||
|
||||
debug('Writing challenge image %s', filename);
|
||||
await fs.writeFile(image_file, image);
|
||||
}
|
||||
}
|
||||
|
||||
async function dumpHeroRecords(splatnet: SplatNet2Api, directory: string, user_id: string) {
|
||||
debug('Fetching hero records');
|
||||
const hero = await splatnet.getHeroRecords();
|
||||
|
||||
const filename = 'splatnet2-hero-' + user_id + '-' + Date.now() + '.json';
|
||||
const file = path.join(directory, filename);
|
||||
|
||||
debug('Writing hero records %s', filename);
|
||||
await fs.writeFile(file, JSON.stringify(hero, null, 4) + '\n', 'utf-8');
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import * as path from 'path';
|
||||
import createDebug from 'debug';
|
||||
import mkdirp from 'mkdirp';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import type { Arguments as ParentArguments } from '../splatnet2.js';
|
||||
import { ArgumentsCamelCase, Argv, initStorage, YargsArguments } from '../../util.js';
|
||||
import { getIksmToken } from './util.js';
|
||||
import SplatNet2Api from '../../api/splatnet2.js';
|
||||
import { NicknameAndIcon } from '../../api/splatnet2-types.js';
|
||||
import fetch from 'node-fetch';
|
||||
import { dumpCoopResults, dumpResults } from '../../common/splatnet2/dump-results.js';
|
||||
|
||||
const debug = createDebug('cli:splatnet2:dump-results');
|
||||
|
||||
@@ -73,187 +70,3 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
await dumpCoopResults(splatnet, directory, records.records.unique_id, updated);
|
||||
}
|
||||
}
|
||||
|
||||
export async function dumpResults(
|
||||
splatnet: SplatNet2Api, directory: string, user_id: string,
|
||||
images = false, summary_image = images, updated?: Date
|
||||
) {
|
||||
const latest_filename = 'splatnet2-results-summary-' + user_id + '-latest.json';
|
||||
const latest_file = path.join(directory, latest_filename);
|
||||
|
||||
if (updated) {
|
||||
try {
|
||||
const {timestamp} = JSON.parse(await fs.readFile(latest_file, 'utf-8'));
|
||||
|
||||
if (timestamp > updated.getTime()) {
|
||||
debug('Skipping battle results, user records not updated');
|
||||
return;
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
debug('Fetching battle results');
|
||||
const results = await splatnet.getResults();
|
||||
|
||||
const timestamp = Date.now();
|
||||
const summary_filename = 'splatnet2-results-summary-' + results.unique_id + '-' + timestamp + '.json';
|
||||
const summary_file = path.join(directory, summary_filename);
|
||||
|
||||
debug('Writing summary %s', summary_filename);
|
||||
await fs.writeFile(summary_file, JSON.stringify(results, null, 4) + '\n', 'utf-8');
|
||||
|
||||
if (summary_image) {
|
||||
const filename = 'splatnet2-results-summary-image-' + results.unique_id + '-' + timestamp + '.json';
|
||||
const file = path.join(directory, filename);
|
||||
const image_filename = 'splatnet2-results-summary-image-' + results.unique_id + '-' + timestamp + '.png';
|
||||
const image_file = path.join(directory, image_filename);
|
||||
|
||||
debug('Fetching battle results summary image URL');
|
||||
const share = await splatnet.shareResultsSummary();
|
||||
|
||||
debug('Fetching battle results summary image');
|
||||
const image_response = await fetch(share.url);
|
||||
const image = await image_response.buffer();
|
||||
|
||||
debug('Writing battle results summary image data %s', filename);
|
||||
await fs.writeFile(file, JSON.stringify({
|
||||
share,
|
||||
}, null, 4) + '\n', 'utf-8');
|
||||
|
||||
debug('Writing battle results summary image %s', filename);
|
||||
await fs.writeFile(image_file, image);
|
||||
}
|
||||
|
||||
const skipped = [];
|
||||
const skipped_images = [];
|
||||
|
||||
for (const item of results.results) {
|
||||
const filename = 'splatnet2-result-' + results.unique_id + '-' + item.battle_number + '-' + item.type + '.json';
|
||||
const file = path.join(directory, filename);
|
||||
|
||||
try {
|
||||
await fs.stat(file);
|
||||
skipped.push(item.battle_number);
|
||||
} catch (err) {
|
||||
debug('Fetching battle result %d', item.battle_number);
|
||||
const result = await splatnet.getResult(item.battle_number);
|
||||
|
||||
const nickname_and_icons: NicknameAndIcon[] = [];
|
||||
|
||||
for (const playerresult of [result.player_result, ...result.my_team_members, ...result.other_team_members]) {
|
||||
const nickname_and_icon = await splatnet.getUserNicknameAndIcon([playerresult.player.principal_id]);
|
||||
nickname_and_icons.push(...nickname_and_icon.nickname_and_icons);
|
||||
}
|
||||
|
||||
debug('Writing %s', filename);
|
||||
await fs.writeFile(file, JSON.stringify({
|
||||
result,
|
||||
nickname_and_icons,
|
||||
}, null, 4) + '\n', 'utf-8');
|
||||
}
|
||||
|
||||
if (images) {
|
||||
const filename = 'splatnet2-result-image-' + results.unique_id + '-' +
|
||||
item.battle_number + '-' + item.type + '.json';
|
||||
const file = path.join(directory, filename);
|
||||
const image_filename = 'splatnet2-result-image-' + results.unique_id + '-' +
|
||||
item.battle_number + '-' + item.type + '.png';
|
||||
const image_file = path.join(directory, image_filename);
|
||||
|
||||
try {
|
||||
await fs.stat(file);
|
||||
await fs.stat(image_file);
|
||||
skipped_images.push(item.battle_number);
|
||||
} catch (err) {
|
||||
debug('Fetching battle results image URL');
|
||||
const share = await splatnet.shareResult(item.battle_number);
|
||||
|
||||
debug('Fetching battle results image');
|
||||
const image_response = await fetch(share.url);
|
||||
const image = await image_response.buffer();
|
||||
|
||||
debug('Writing battle results image data %s', filename);
|
||||
await fs.writeFile(file, JSON.stringify({
|
||||
share,
|
||||
}, null, 4) + '\n', 'utf-8');
|
||||
|
||||
debug('Writing battle results image %s', filename);
|
||||
await fs.writeFile(image_file, image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (skipped.length) {
|
||||
if (skipped.length === 1) debug('Skipped battle result %d, file already exists', skipped[0]);
|
||||
else debug('Skipped battle results %s, files already exist', skipped.join(', '));
|
||||
}
|
||||
if (skipped_images.length) {
|
||||
if (skipped_images.length === 1) debug('Skipped battle result image %d, file already exists',
|
||||
skipped_images[0]);
|
||||
else debug('Skipped battle result images %s, files already exist', skipped_images.join(', '));
|
||||
}
|
||||
|
||||
await fs.writeFile(latest_file, JSON.stringify({timestamp}, null, 4) + '\n', 'utf-8');
|
||||
}
|
||||
|
||||
export async function dumpCoopResults(splatnet: SplatNet2Api, directory: string, user_id: string, updated?: Date) {
|
||||
const latest_filename = 'splatnet2-coop-summary-' + user_id + '-latest.json';
|
||||
const latest_file = path.join(directory, latest_filename);
|
||||
|
||||
if (updated) {
|
||||
try {
|
||||
const {timestamp} = JSON.parse(await fs.readFile(latest_file, 'utf-8'));
|
||||
|
||||
if (timestamp > updated.getTime()) {
|
||||
debug('Skipping coop results, user records not updated');
|
||||
return;
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
debug('Fetching coop results');
|
||||
const results = await splatnet.getCoopResults();
|
||||
|
||||
const timestamp = Date.now();
|
||||
const summary_filename = 'splatnet2-coop-summary-' + user_id + '-' + Date.now() + '.json';
|
||||
const summary_file = path.join(directory, summary_filename);
|
||||
|
||||
debug('Writing summary %s', summary_filename);
|
||||
await fs.writeFile(summary_file, JSON.stringify(results, null, 4) + '\n', 'utf-8');
|
||||
|
||||
const skipped = [];
|
||||
|
||||
for (const item of results.results) {
|
||||
const filename = 'splatnet2-coop-result-' + user_id + '-' + item.job_id + '.json';
|
||||
const file = path.join(directory, filename);
|
||||
|
||||
try {
|
||||
await fs.stat(file);
|
||||
skipped.push(item.job_id);
|
||||
continue;
|
||||
} catch (err) {}
|
||||
|
||||
debug('Fetching coop result %d', item.job_id);
|
||||
const result = await splatnet.getCoopResult(item.job_id);
|
||||
|
||||
const nickname_and_icons: NicknameAndIcon[] = [];
|
||||
|
||||
for (const playerresult of [result.my_result, ...result.other_results]) {
|
||||
const nickname_and_icon = await splatnet.getUserNicknameAndIcon([playerresult.pid]);
|
||||
nickname_and_icons.push(...nickname_and_icon.nickname_and_icons);
|
||||
}
|
||||
|
||||
debug('Writing %s', filename);
|
||||
await fs.writeFile(file, JSON.stringify({
|
||||
result,
|
||||
nickname_and_icons,
|
||||
}, null, 4) + '\n', 'utf-8');
|
||||
}
|
||||
|
||||
if (skipped.length) {
|
||||
if (skipped.length === 1) debug('Skipped coop result %d, file already exists', skipped[0]);
|
||||
else debug('Skipped coop results %s, files already exist', skipped.join(', '));
|
||||
}
|
||||
|
||||
await fs.writeFile(latest_file, JSON.stringify({timestamp}, null, 4) + '\n', 'utf-8');
|
||||
}
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import createDebug from 'debug';
|
||||
import persist from 'node-persist';
|
||||
import mkdirp from 'mkdirp';
|
||||
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, Loop, LoopResult, YargsArguments } from '../../util.js';
|
||||
import { Records, Stages, WebServiceError } from '../../api/splatnet2-types.js';
|
||||
import createDebug from 'debug';
|
||||
import { getIksmToken } from './util.js';
|
||||
import { ArgumentsCamelCase, Argv, initStorage, YargsArguments } from '../../util.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';
|
||||
import { SplatNet2RecordsMonitor } from '../../common/splatnet2/monitor.js';
|
||||
|
||||
const debug = createDebug('cli:splatnet2:monitor');
|
||||
|
||||
@@ -107,98 +100,3 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
await i.loop();
|
||||
}
|
||||
}
|
||||
|
||||
export class SplatNet2RecordsMonitor extends Loop {
|
||||
update_interval: number = 3 * 60; // 3 minutes in seconds
|
||||
|
||||
profile_image = true;
|
||||
favourite_stage: string | undefined = undefined;
|
||||
favourite_colour: string | undefined = undefined;
|
||||
|
||||
results = true;
|
||||
results_summary_image = true;
|
||||
result_images = true;
|
||||
coop_results = true;
|
||||
|
||||
/** Prevents redownloading user records on the first loop run */
|
||||
cached_records: Records | null = null;
|
||||
|
||||
auto_update_iksm_session = true;
|
||||
|
||||
constructor(
|
||||
public storage: persist.LocalStorage,
|
||||
public token: string,
|
||||
public splatnet: SplatNet2Api,
|
||||
public stages: Stages,
|
||||
public directory: string,
|
||||
public znc_proxy_url?: string
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async init() {
|
||||
await mkdirp(this.directory);
|
||||
}
|
||||
|
||||
async hasChanged(records: Records) {
|
||||
const latest_filename = 'splatnet2-records-' + records.records.unique_id + '-latest.json';
|
||||
const latest_file = path.join(this.directory, latest_filename);
|
||||
|
||||
const updated = new Date(records.records.update_time * 1000);
|
||||
|
||||
try {
|
||||
const {sn2_updated_timestamp} = JSON.parse(await fs.readFile(latest_file, 'utf-8'));
|
||||
|
||||
return (sn2_updated_timestamp * 1000) < updated.getTime();
|
||||
} catch (err) {}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async update() {
|
||||
const records = this.cached_records ?? await this.splatnet.getRecords();
|
||||
this.cached_records = null;
|
||||
|
||||
if (!(await this.hasChanged(records))) {
|
||||
debug('No data changed');
|
||||
return;
|
||||
}
|
||||
|
||||
const nickname_and_icons = await this.splatnet.getUserNicknameAndIcon([records.records.player.principal_id]);
|
||||
|
||||
// This will also update the splatnet2-records-...-latest.json file
|
||||
await dumpRecords(this.directory, records.records.unique_id, records,
|
||||
nickname_and_icons.nickname_and_icons[0]);
|
||||
|
||||
if (this.profile_image) {
|
||||
await dumpProfileImage(this.splatnet, this.directory, records.records.unique_id, this.stages,
|
||||
nickname_and_icons.nickname_and_icons[0],
|
||||
this.favourite_stage, this.favourite_colour);
|
||||
}
|
||||
|
||||
if (this.results) {
|
||||
await dumpResults(this.splatnet, this.directory, records.records.unique_id,
|
||||
this.result_images, this.results_summary_image);
|
||||
}
|
||||
if (this.coop_results) {
|
||||
await dumpCoopResults(this.splatnet, this.directory, records.records.unique_id);
|
||||
}
|
||||
}
|
||||
|
||||
async handleError(err: Error | ErrorResponse<WebServiceError>): Promise<LoopResult> {
|
||||
if ('response' in err && err.data?.code === 'AUTHENTICATION_ERROR') {
|
||||
// Token expired
|
||||
debug('Renewing iksm_session cookie');
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user