diff --git a/src/api/splatnet2-types.ts b/src/api/splatnet2-types.ts index 056b54d..1c3f936 100644 --- a/src/api/splatnet2-types.ts +++ b/src/api/splatnet2-types.ts @@ -101,7 +101,7 @@ interface SpecialWeapon { } interface Player { - clothes: Gear & {kind: 'clothes'}; + clothes: Gear & {kind: GearType.CLOTHES}; star_rank: number; head_skills: Skills; nickname: string; @@ -109,13 +109,13 @@ interface Player { weapon: Weapon; player_rank: number; max_league_point_team: number; - shoes: Gear & {kind: 'shoes'}; + shoes: Gear & {kind: GearType.SHOES}; clothes_skills: Skills; udemae_tower: Rank; udemae_clam: Rank; udemae_rainmaker: Rank; player_type: PlayerType; - head: Gear & {kind: 'head'}; + head: Gear & {kind: GearType.HEAD}; max_league_point_pair: number; principal_id: string; udemae_zones: Rank; @@ -127,9 +127,14 @@ interface Gear { id: string; rarity: number; image: string; - kind: 'clothes' | 'shoes' | 'head'; + kind: GearType; thumbnail: string; } +export enum GearType { + CLOTHES = 'clothes', + SHOES = 'shoes', + HEAD = 'head', +} interface Brand { image: string; frequent_skill?: Skill; @@ -232,38 +237,6 @@ export interface Timeline { }; } -interface ShopMerchandise { - skill: Skill; - end_time: number; - gear: Gear; - id: string; - price: number; - kind: string; -} - -interface CoopSchedule { - weapons: CoopWeapon[]; - stage: { - image: string; - name: string; - }; - start_time: number; - end_time: number; -} - -type CoopWeapon = CoopStandardWeapon | CoopSpecialWeapon; -interface CoopStandardWeapon { - weapon: Omit; - id: string; -} -interface CoopSpecialWeapon { - id: string; - coop_special_weapon: { - image: string; - name: string; - }; -} - interface CoopRewardGear { available_time: number; gear: Gear; @@ -300,17 +273,19 @@ interface ScheduleItem { /** GET /records/hero */ export interface HeroRecords { stage_infos: StageInfo[]; - summary: { - weapon_cleared_info: Record; - honor: { - code: string; - name: string; - }; - clear_rate: number; - }; + summary: HeroRecordsSummary; weapon_map: Record; } +interface HeroRecordsSummary { + weapon_cleared_info: Record; + honor: HeroHonor; + clear_rate: number; +} +interface HeroHonor { + code: string; + name: string; +} interface StageInfo { clear_weapons: Record; stage: HeroStage; @@ -338,17 +313,23 @@ export interface XPowerRankingSummary { splat_zones: XPowerRankingRecords; } -interface XPowerRankingRecords { +/** GET /x_power_ranking/{season}/{rule} */ +export interface XPowerRankingRecords { weapon_ranking: null; season_id: string; top_rankings_count: number; top_rankings: XPowerRankingRecordsRanking[]; - status: string; + status: XPowerRankingStatus; rule: Rule; start_time: number; end_time: number; my_ranking: null; } + +export enum XPowerRankingStatus { + CALCULATED = 'calculated', + ONGOING = 'ongoing', +} interface XPowerRankingRecordsRanking { name: string; principal_id: string; @@ -367,34 +348,35 @@ export interface PastFestivals { } interface Festival { - colors: { - middle: InkColour; - bravo: InkColour; - alpha: InkColour; - }; + colors: FestivalInkColours; festival_id: number; - names: { - bravo_short: string; - bravo_long: string; - alpha_short: string; - alpha_long: string; - }; - images: { - bravo: string; - alpha: string; - panel: string; - }; - times: { - start: number; - announce: number; - end: number; - result: number; - }; - special_stage: { - name: string; - id: string; - image: string; - }; + names: FestivalTeamNames; + images: FestivalImages; + times: FestivalTimes; + special_stage: Stage; +} + +interface FestivalTeamNames { + bravo_short: string; + bravo_long: string; + alpha_short: string; + alpha_long: string; +} +interface FestivalImages { + bravo: string; + alpha: string; + panel: string; +} +interface FestivalTimes { + start: number; + announce: number; + end: number; + result: number; +} +interface FestivalInkColours { + middle: InkColour; + bravo: InkColour; + alpha: InkColour; } interface InkColour { @@ -450,18 +432,20 @@ interface FestivalResults1 { /** GET /league_match_ranking/{league}/{region} */ export interface LeagueMatchRankings { start_time: number; - league_type: { - name: string; - key: string; - }; - league_ranking_region: { - code: string; - id: number; - }; + league_type: LeagueType; + league_ranking_region: LeagueRankingRegion; rankings: LeagueMatchRanking[]; league_id: string; } +interface LeagueType { + key: string; // "team", "pair" + name: string; // "Mode: Team", "Mode: Pair" +} +interface LeagueRankingRegion { + id: number; // 0, 1, 2, 4 + code: string; // ALL, JP, US, EU +} interface LeagueMatchRanking { tag_members: LeagueTagMember[]; point: number; @@ -479,16 +463,18 @@ interface LeagueTagMember { export interface Results { results: MatchResults[]; unique_id: string; - summary: { - kill_count_average: number; - victory_count: number; - count: number; - defeat_count: number; - special_count_average: number; - victory_rate: number; - death_count_average: number; - assist_count_average: number; - }; + summary: ResultsSummary; +} + +interface ResultsSummary { + kill_count_average: number; + victory_count: number; + count: number; + defeat_count: number; + special_count_average: number; + victory_rate: number; + death_count_average: number; + assist_count_average: number; } type MatchResults = RegularMatchResults | RankedMatchResults; @@ -669,6 +655,18 @@ interface CoopWaveWaterLevel { key: string; name: string; } +type CoopWeapon = CoopStandardWeapon | CoopSpecialWeapon; +interface CoopStandardWeapon { + weapon: Omit; + id: string; +} +interface CoopSpecialWeapon { + id: string; + coop_special_weapon: { + image: string; + name: string; + }; +} interface CoopSummaryResult { schedule: CoopSchedule; @@ -710,6 +708,15 @@ export interface CoopSchedules { schedules: CoopScheduleTimes[]; } +interface CoopSchedule { + weapons: CoopWeapon[]; + stage: { + image: string; + name: string; + }; + start_time: number; + end_time: number; +} interface CoopScheduleTimes { start_time: number; end_time: number; @@ -721,6 +728,15 @@ export interface ShopMerchandises { ordered_info: null; } +interface ShopMerchandise { + skill: Skill; + end_time: number; + gear: Gear; + id: string; + price: number; + kind: GearType; +} + /** POST /share/profile, POST /share/results/summary, POST /share/results/1 */ export interface ShareResponse { url: string; diff --git a/src/api/splatnet2-xrank.ts b/src/api/splatnet2-xrank.ts index 2df8b1b..4a931a9 100644 --- a/src/api/splatnet2-xrank.ts +++ b/src/api/splatnet2-xrank.ts @@ -51,8 +51,8 @@ export function getSeason(year: number | Date | string, month?: number): Season 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, + start.getUTCFullYear() + (start.getUTCMonth() === 11 ? 1 : 0), + start.getUTCMonth() === 11 ? 0 : start.getUTCMonth() + 1, )); const id = toSeasonId(start.getUTCFullYear(), start.getUTCMonth() + 1); @@ -88,15 +88,15 @@ export function getNextSeason(season: Season | number, month?: number): Season | } const start = new Date(Date.UTC( - current_start.getFullYear() + (current_start.getMonth() === 11 ? 1 : 0), - current_start.getMonth() === 11 ? 0 : current_start.getMonth() + 1, + current_start.getUTCFullYear() + (current_start.getUTCMonth() === 11 ? 1 : 0), + current_start.getUTCMonth() === 11 ? 0 : current_start.getUTCMonth() + 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, + start.getUTCFullYear() + (start.getUTCMonth() === 11 ? 1 : 0), + start.getUTCMonth() === 11 ? 0 : start.getUTCMonth() + 1, )); const id = toSeasonId(start.getUTCFullYear(), start.getUTCMonth() + 1); @@ -117,15 +117,15 @@ export function getPreviousSeason(season: Season | number, month?: number): Seas 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, + current_start.getUTCFullYear() - (current_start.getUTCMonth() === 0 ? 1 : 0), + current_start.getUTCMonth() === 0 ? 11 : current_start.getUTCMonth() - 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, + start.getUTCFullYear() + (start.getUTCMonth() === 11 ? 1 : 0), + start.getUTCMonth() === 11 ? 0 : start.getUTCMonth() + 1, )); const id = toSeasonId(start.getUTCFullYear(), start.getUTCMonth() + 1); @@ -151,6 +151,13 @@ export function toSeasonId(year: number, month: number) { if (year < 2000) throw new Error('Invalid season ID'); if (nextyear >= 2100) throw new Error('Invalid season ID'); + if (year < 2018) throw new Error('Invalid season ID'); + if (year === 2018 && month < 5) throw new Error('Invalid season ID'); + + const now = new Date(); + if (year > now.getUTCFullYear()) throw new Error('Invalid season ID'); + if (year === now.getUTCFullYear() && month > (now.getUTCMonth() + 1)) throw new Error('Invalid season ID'); + if (year === 2018 && month === 5) month = 4; return ('' + (year - 2000)).padStart(2, '0') + diff --git a/src/api/splatnet2.ts b/src/api/splatnet2.ts index 2b03a96..d8da0ed 100644 --- a/src/api/splatnet2.ts +++ b/src/api/splatnet2.ts @@ -5,9 +5,9 @@ import { WebServiceToken } from './coral-types.js'; import { NintendoAccountUser } from './na.js'; import { ErrorResponse } from './util.js'; import CoralApi from './coral.js'; -import { ActiveFestivals, CoopResult, CoopResults, CoopSchedules, HeroRecords, NicknameAndIcons, PastFestivals, Records, Result, Results, Schedules, ShareResponse, ShopMerchandises, Stages, Timeline, WebServiceError, XPowerRankingSummary } from './splatnet2-types.js'; +import { ActiveFestivals, CoopResult, CoopResults, CoopSchedules, HeroRecords, LeagueMatchRankings, NicknameAndIcons, PastFestivals, Records, Result, Results, Schedules, ShareResponse, ShopMerchandises, Stages, Timeline, WebServiceError, XPowerRankingRecords, XPowerRankingSummary } from './splatnet2-types.js'; import { timeoutSignal } from '../util/misc.js'; -import { toSeasonId, Rule as XPowerRankingRule } from './splatnet2-xrank.js'; +import { toSeasonId, Rule as XPowerRankingRule, Season } from './splatnet2-xrank.js'; const debug = createDebug('nxapi:api:splatnet2'); @@ -93,7 +93,11 @@ export default class SplatNet2Api { return this.fetch('/records/hero'); } - async getXPowerRankingSummary(season: string | Date) { + async getXPowerRankingSummary(season: string | Date | Season) { + if (typeof season === 'object' && 'start' in season) { + season = season.id; + } + if (season instanceof Date) { season = toSeasonId(season.getUTCFullYear(), season.getUTCMonth() + 1); } @@ -110,7 +114,11 @@ export default class SplatNet2Api { return this.fetch('/x_power_ranking/' + season + '/summary'); } - async getXPowerRankingLeaderboard(season: string | Date, rule: XPowerRankingRule, page: number = 1) { + async getXPowerRankingLeaderboard(season: string | Date | Season, rule: XPowerRankingRule, page: number = 1) { + if (typeof season === 'object' && 'start' in season) { + season = season.id; + } + if (season instanceof Date) { season = toSeasonId(season.getUTCFullYear(), season.getUTCMonth() + 1); } @@ -124,19 +132,27 @@ export default class SplatNet2Api { throw new Error('Invalid season ID'); } - return this.fetch('/x_power_ranking/' + season + '/' + rule + '?page=' + page); + return this.fetch('/x_power_ranking/' + season + '/' + rule + '?page=' + page); } async getPastFestivals() { return this.fetch('/festivals/pasts'); } - async getLeagueMatchRanking(id: string, region: LeagueRegion) { + async getLeagueMatchRanking(id: string, region: LeagueRegion): Promise + async getLeagueMatchRanking(date: Date, type: LeagueType, region: LeagueRegion): Promise + async getLeagueMatchRanking(id: string | Date, arg1: LeagueRegion | LeagueType, arg2?: LeagueRegion) { + const region = id instanceof Date ? arg2! : arg1 as LeagueRegion; + + if (id instanceof Date) { + id = toLeagueId(id, arg1 as LeagueType); + } + if (!id.match(LEAGUE_ID)) { throw new Error('Invalid league ID'); } - return this.fetch('/league_match_ranking/' + id + '/' + region); + return this.fetch('/league_match_ranking/' + id + '/' + region); } async getResults() {