Add a command and functions to get SplatNet 2 X Rank seasons

This commit is contained in:
Samuel Elliott
2022-06-26 18:00:47 +01:00
parent ed91f2d085
commit 1ddbe10f25
5 changed files with 243 additions and 23 deletions

169
src/api/splatnet2-xrank.ts Normal file
View File

@@ -0,0 +1,169 @@
//
// X Rank seasons
//
// X Rank seasons are calculated by the web app, not retrieved from the API.
//
export interface Season {
/** Season ID use by the API, e.g. "220501T00_220601T00" */
id: string;
/** Season ID use by the web app, e.g. "2022_05" */
key: string;
start: Date;
end: Date;
complete: boolean;
}
export function* getAllSeasons(sort_ascending = false) {
let season = sort_ascending ? FIRST_SEASON : CURRENT_SEASON;
while (season) {
yield season;
season = sort_ascending ? getNextSeason(season) : getPreviousSeason(season);
}
}
export function getSeason(year: number, month: number): Season | null
export function getSeason(start: Date): Season | null
export function getSeason(id: string): Season | null
export function getSeason(year: number | Date | string, month?: number): Season | null {
if (year instanceof Date) {
month = year.getUTCMonth() + 1;
year = year.getUTCFullYear();
}
if (typeof year === 'string') {
// 180401T00_180601T00
const match = year.match(/^(\d{2,})(0\d,1[012])01T00_(\d{2,})(0\d,1[012])01T00$/);
if (!match) throw new Error('Invalid season ID');
year = 2000 + parseInt(match[1]);
month = parseInt(match[2]);
const nextyear = month === 12 ? year + 1 : year;
const nextmonth = year === 2018 && month === 4 ? 6 :
month === 12 ? 1 : month + 1;
if (nextyear !== parseInt(match[3]) || nextmonth !== parseInt(match[4])) throw new Error('Invalid season ID');
}
const start = new Date(Date.UTC(year, month! - 1));
if (Date.now() < start.getTime()) return null;
if (start.getUTCFullYear() < 2018 || (start.getUTCFullYear() === 2018 && start.getUTCMonth() < 3)) return null;
const end = new Date(Date.UTC(
start.getFullYear() + (start.getMonth() === 11 ? 1 : 0),
start.getMonth() === 11 ? 0 : start.getMonth() + 1,
));
const id = toSeasonId(start.getUTCFullYear(), start.getUTCMonth() + 1);
const key = start.getUTCFullYear() + '_' + ('' + (start.getUTCMonth() + 1)).padStart(2, '0');
if (start.getUTCFullYear() === 2018 && start.getUTCMonth() === 4) {
start.setUTCMonth(3);
}
return {
id,
key,
start,
end,
complete: Date.now() > end.getTime(),
};
}
export function getFirstSeason() {
return getSeason(2018, 5);
}
const FIRST_SEASON = getFirstSeason();
const CURRENT_SEASON = getSeason(new Date());
export function getNextSeason(season: Season): Season | null
export function getNextSeason(year: number, month: number): Season | null
export function getNextSeason(season: Season | number, month?: number): Season | null {
const current_start = typeof season === 'number' ? new Date(Date.UTC(season, month!)) : season.start;
if (current_start.getUTCFullYear() === 2018 && current_start.getUTCMonth() === 3) {
current_start.setUTCMonth(4);
}
const start = new Date(Date.UTC(
current_start.getFullYear() + (current_start.getMonth() === 11 ? 1 : 0),
current_start.getMonth() === 11 ? 0 : current_start.getMonth() + 1,
));
if (Date.now() < start.getTime()) return null;
const end = new Date(Date.UTC(
start.getFullYear() + (start.getMonth() === 11 ? 1 : 0),
start.getMonth() === 11 ? 0 : start.getMonth() + 1,
));
const id = toSeasonId(start.getUTCFullYear(), start.getUTCMonth() + 1);
const key = start.getUTCFullYear() + '_' + ('' + (start.getUTCMonth() + 1)).padStart(2, '0');
return {
id,
key,
start,
end,
complete: Date.now() > end.getTime(),
};
}
export function getPreviousSeason(season: Season): Season | null
export function getPreviousSeason(year: number, month: number): Season | null
export function getPreviousSeason(season: Season | number, month?: number): Season | null {
const current_start = typeof season === 'number' ? new Date(Date.UTC(season, month!)) : season.start;
const start = new Date(Date.UTC(
current_start.getFullYear() - (current_start.getMonth() === 0 ? 1 : 0),
current_start.getMonth() === 0 ? 11 : current_start.getMonth() - 1,
));
if (start.getUTCFullYear() < 2018 || (start.getUTCFullYear() === 2018 && start.getUTCMonth() < 3)) return null;
const end = new Date(Date.UTC(
start.getFullYear() + (start.getMonth() === 11 ? 1 : 0),
start.getMonth() === 11 ? 0 : start.getMonth() + 1,
));
const id = toSeasonId(start.getUTCFullYear(), start.getUTCMonth() + 1);
const key = start.getUTCFullYear() + '_' + ('' + (start.getUTCMonth() + 1)).padStart(2, '0');
if (start.getUTCFullYear() === 2018 && start.getUTCMonth() === 4) {
start.setUTCMonth(3);
}
return {
id,
key,
start,
end,
complete: Date.now() > end.getTime(),
};
}
export function toSeasonId(year: number, month: number) {
const nextyear = month === 12 ? year + 1 : year;
const nextmonth = month === 12 ? 1 : month + 1;
if (year < 2000) throw new Error('Invalid season ID');
if (nextyear >= 2100) throw new Error('Invalid season ID');
if (year === 2018 && month === 5) month = 4;
return ('' + (year - 2000)).padStart(2, '0') +
('' + month).padStart(2, '0') +
'01T00_' +
('' + (nextyear - 2000)).padStart(2, '0') +
('' + nextmonth).padStart(2, '0') +
'01T00';
}
export enum Rule {
SPLAT_ZONES = 'splat_zones',
TOWER_CONTROL = 'tower_control',
RAINMAKER = 'rainmaker',
CLAM_BLITZ = 'clam_blitz',
}

View File

@@ -7,6 +7,7 @@ import { ErrorResponse } from './util.js';
import ZncApi from './znc.js';
import { ActiveFestivals, CoopResult, CoopResults, CoopSchedules, HeroRecords, NicknameAndIcons, PastFestivals, Records, Result, Results, Schedules, ShareResponse, ShopMerchandises, Stages, Timeline, WebServiceError, XPowerRankingSummary } from './splatnet2-types.js';
import { timeoutSignal } from '../util/misc.js';
import { toSeasonId, Rule as XPowerRankingRule } from './splatnet2-xrank.js';
const debug = createDebug('nxapi:api:splatnet2');
@@ -302,28 +303,6 @@ ${colour}
}
}
export function toSeasonId(year: number, month: number) {
const nextyear = month === 12 ? year + 1 : year;
const nextmonth = month === 12 ? 1 : month + 1;
if (year < 2000) throw new Error('Invalid season ID');
if (nextyear >= 2100) throw new Error('Invalid season ID');
return ('' + (year - 2000)).padStart(2, '0') +
('' + month).padStart(2, '0') +
'01T00_' +
('' + (nextyear - 2000)).padStart(2, '0') +
('' + nextmonth).padStart(2, '0') +
'01T00';
}
export enum XPowerRankingRule {
SPLAT_ZONES = 'splat_zones',
TOWER_CONTROL = 'tower_control',
RAINMAKER = 'rainmaker',
CLAM_BLITZ = 'clam_blitz',
}
export function toLeagueId(date: Date, type: LeagueType) {
const year = date.getUTCFullYear();
const month = date.getUTCMonth() + 1;

View File

@@ -9,3 +9,4 @@ export * as schedule from './schedule.js';
export * as dumpResults from './dump-results.js';
export * as dumpRecords from './dump-records.js';
export * as monitor from './monitor.js';
export * as xRankSeasons from './x-rank-seasons.js';

View File

@@ -0,0 +1,62 @@
import createDebug from 'debug';
import Table from '../util/table.js';
import type { Arguments as ParentArguments } from '../splatnet2.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { getAllSeasons } from '../../api/splatnet2-xrank.js';
const debug = createDebug('cli:splatnet2:x-rank-seasons');
export const command = 'x-rank-seasons';
export const desc = 'Show X Rank seasons';
export function builder(yargs: Argv<ParentArguments>) {
return yargs.option('sort', {
describe: 'Sort',
type: 'string',
choices: ['asc', 'desc'],
default: 'asc',
}).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 sort_ascending = argv.sort !== 'desc';
if (argv.json || argv.jsonPrettyPrint) {
const result = {
seasons: [...getAllSeasons()],
};
console.log(JSON.stringify(result, null, argv.jsonPrettyPrint ? 4 : 0));
return;
}
const table = new Table({
head: [
'ID',
'Key',
'Start',
'End',
'Status',
],
});
for (const season of getAllSeasons(sort_ascending)) {
table.push([
season.id,
season.key,
season.start.toLocaleString('en-GB'),
season.end.toLocaleString('en-GB'),
season.complete ? 'Complete' : 'Calculating',
]);
}
console.log(table.toString());
}

View File

@@ -8,11 +8,20 @@ export * as f from './api/f.js';
export {
default as SplatNet2Api,
XPowerRankingRule as SplatNet2XPowerRankingRule,
LeagueType as SplatNet2LeagueType,
LeagueRegion as SplatNet2LeagueRegion,
ShareColour as SplatNet2ProfileColour,
toLeagueId as toSplatNet2LeagueId,
} from './api/splatnet2.js';
export {
Season as SplatNet2XRankSeason,
Rule as SplatNet2XPowerRankingRule,
getAllSeasons as getSplatNet2XRankSeasons,
getSeason as getSplatNet2XRankSeason,
getNextSeason as getSplatNet2NextXRankSeason,
getPreviousSeason as getSplatNet2PreviousXRankSeason,
toSeasonId as toSplatNet2XRankSeasonId,
} from './api/splatnet2-xrank.js';
export * as splatnet2 from './api/splatnet2-types.js';
export {
default as NooklinkApi,