From ef5da22df03fe2e9c4e9a5e4cf771bfcf8a97845 Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Mon, 5 Dec 2022 20:04:51 +0000 Subject: [PATCH] Update SplatNet 3 revision and add support for Big Run for Splatoon 3 presence --- package-lock.json | 14 +++++++------- package.json | 2 +- resources/common/remote-config.json | 4 ++-- src/api/splatnet3.ts | 10 +++++----- src/cli/presence-server.ts | 14 ++++++++------ src/cli/splatnet3/dump-fests.ts | 2 +- src/cli/splatnet3/festival.ts | 21 ++++++++++---------- src/cli/splatnet3/friends.ts | 25 ++++++++++++++++-------- src/cli/splatnet3/monitor.ts | 4 ++-- src/discord/monitor/splatoon3.ts | 30 ++++++++++++++--------------- 10 files changed, 68 insertions(+), 58 deletions(-) diff --git a/package-lock.json b/package-lock.json index d5c29d7..fbb4989 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "node-notifier": "^10.0.1", "node-persist": "^3.1.0", "read": "^1.0.7", - "splatnet3-types": "^0.1.20221026095800", + "splatnet3-types": "^0.1.20221202224136", "supports-color": "^8.1.1", "tslib": "^2.4.1", "uuid": "^8.3.2", @@ -4065,9 +4065,9 @@ "dev": true }, "node_modules/splatnet3-types": { - "version": "0.1.20221026095800", - "resolved": "https://registry.npmjs.org/splatnet3-types/-/splatnet3-types-0.1.20221026095800.tgz", - "integrity": "sha512-2PEFKno7ZYfMqLxZbEU9UCRZV9YV+a3j9P8otn189qPHlURfwTQC5uoQ5s5x2+IEkacYmo2P9ex+RZWzy1q6kA==" + "version": "0.1.20221202224136", + "resolved": "https://registry.npmjs.org/splatnet3-types/-/splatnet3-types-0.1.20221202224136.tgz", + "integrity": "sha512-2C9ZynJQPcESAndxn/lbRxNH2l0vOSfykXtz1oVnR2yCqiiiR8kRXpqbI0mvJSNHzYlLz3KWSXATNYgYBw/0OQ==" }, "node_modules/sprintf-js": { "version": "1.1.2", @@ -7769,9 +7769,9 @@ "dev": true }, "splatnet3-types": { - "version": "0.1.20221026095800", - "resolved": "https://registry.npmjs.org/splatnet3-types/-/splatnet3-types-0.1.20221026095800.tgz", - "integrity": "sha512-2PEFKno7ZYfMqLxZbEU9UCRZV9YV+a3j9P8otn189qPHlURfwTQC5uoQ5s5x2+IEkacYmo2P9ex+RZWzy1q6kA==" + "version": "0.1.20221202224136", + "resolved": "https://registry.npmjs.org/splatnet3-types/-/splatnet3-types-0.1.20221202224136.tgz", + "integrity": "sha512-2C9ZynJQPcESAndxn/lbRxNH2l0vOSfykXtz1oVnR2yCqiiiR8kRXpqbI0mvJSNHzYlLz3KWSXATNYgYBw/0OQ==" }, "sprintf-js": { "version": "1.1.2", diff --git a/package.json b/package.json index a73245b..43cd04f 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "node-notifier": "^10.0.1", "node-persist": "^3.1.0", "read": "^1.0.7", - "splatnet3-types": "^0.1.20221026095800", + "splatnet3-types": "^0.1.20221202224136", "supports-color": "^8.1.1", "tslib": "^2.4.1", "uuid": "^8.3.2", diff --git a/resources/common/remote-config.json b/resources/common/remote-config.json index 0129a35..1cc7267 100644 --- a/resources/common/remote-config.json +++ b/resources/common/remote-config.json @@ -17,8 +17,8 @@ "blanco_version": "2.1.1" }, "coral_gws_splatnet3": { - "app_ver": "2.0.0-8a061f6c", + "app_ver": "2.0.0-18810d39", "version": "2.0.0", - "revision": "8a061f6c34f6149b4775a13262f9e059fda92a31" + "revision": "18810d3918b0a7ac1c9e23c0c15a5ec0f71aa409" } } diff --git a/src/api/splatnet3.ts b/src/api/splatnet3.ts index a265629..8ec52ca 100644 --- a/src/api/splatnet3.ts +++ b/src/api/splatnet3.ts @@ -116,11 +116,11 @@ export default class SplatNet3Api { /** @private */ _Id extends string = string, /** @private */ - _Result extends (T extends unknown ? _Id extends KnownRequestId ? ResultTypes[_Id] : unknown : T) = - (T extends unknown ? _Id extends KnownRequestId ? ResultTypes[_Id] : unknown : T), + _Result extends (T extends object ? T : _Id extends KnownRequestId ? ResultTypes[_Id] : unknown) = + (T extends object ? T : _Id extends KnownRequestId ? ResultTypes[_Id] : unknown), /** @private */ - _Variables extends (V extends unknown ? _Id extends KnownRequestId ? VariablesTypes[_Id] : unknown : V) = - (V extends unknown ? _Id extends KnownRequestId ? VariablesTypes[_Id] : unknown : V), + _Variables extends (V extends object ? V : _Id extends KnownRequestId ? VariablesTypes[_Id] : unknown) = + (V extends object ? V : _Id extends KnownRequestId ? VariablesTypes[_Id] : unknown), >(id: _Id, variables: _Variables) { const req: GraphQLRequest<_Variables> = { variables, @@ -135,7 +135,7 @@ export default class SplatNet3Api { const data = await this.fetch>('/graphql', 'POST', JSON.stringify(req), undefined, 'graphql query ' + id); - if ('errors' in data) { + if (!('data' in data)) { throw new ErrorResponse('[splatnet3] GraphQL error: ' + data.errors.map(e => e.message).join(', '), data[ResponseSymbol], data); } diff --git a/src/cli/presence-server.ts b/src/cli/presence-server.ts index af9b5f3..adc749a 100644 --- a/src/cli/presence-server.ts +++ b/src/cli/presence-server.ts @@ -2,7 +2,7 @@ import * as net from 'node:net'; import createDebug from 'debug'; import express, { Request, Response } from 'express'; import * as persist from 'node-persist'; -import { BankaraMatchMode, BankaraMatchSetting, CoopSetting, DetailVotingStatusResult, FestMatchSetting, FestState, FestTeam_schedule, FestTeam_votingStatus, FestVoteState, Fest_schedule, Friend as SplatNetFriend, FriendListResult, FriendOnlineState, GraphQLSuccessResponse, LeagueMatchSetting, RegularMatchSetting, StageScheduleResult, VsMode, XMatchSetting } from 'splatnet3-types/splatnet3'; +import { BankaraMatchMode, BankaraMatchSetting, CoopSetting, DetailVotingStatusResult, FestMatchSetting, FestState, FestTeam_schedule, FestTeam_votingStatus, FestVoteState, Fest_schedule, FriendListResult, FriendOnlineState, Friend_friendList, GraphQLSuccessResponse, LeagueMatchSetting, RegularMatchSetting, StageScheduleResult, VsMode, XMatchSetting } from 'splatnet3-types/splatnet3'; import type { Arguments as ParentArguments } from '../cli.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../util/yargs.js'; import { initStorage } from '../util/storage.js'; @@ -22,12 +22,12 @@ const debug = createDebug('cli:presence-server'); type CoopSetting_schedule = Pick; interface AllUsersResult extends Friend { - splatoon3?: SplatNetFriend | null; + splatoon3?: Friend_friendList | null; splatoon3_fest_team?: FestTeam_votingStatus | null; } interface PresenceResponse { friend: Friend; - splatoon3?: SplatNetFriend | null; + splatoon3?: Friend_friendList | null; splatoon3_fest_team?: (FestTeam_schedule & FestTeam_votingStatus) | null; splatoon3_vs_setting?: RegularMatchSetting | BankaraMatchSetting | FestMatchSetting | @@ -157,7 +157,7 @@ export class SplatNet3User { } } - async getFriends(): Promise { + async getFriends(): Promise { await this.update('friends', async () => { this.friends = await this.splatnet.getFriendsRefetch(); }, this.update_interval); @@ -465,7 +465,10 @@ class Server extends HttpServer { friend.onlineState === FriendOnlineState.COOP_MODE_FIGHTING ) { const schedules = await user.getSchedules(); - const coop_setting = getSchedule(schedules.coopGroupingSchedule.regularSchedules)?.setting; + const coop_schedules = friend.coopMode === 'BIG_RUN' ? + schedules.coopGroupingSchedule.bigRunSchedules : + schedules.coopGroupingSchedule.regularSchedules; + const coop_setting = getSchedule(coop_schedules)?.setting; response.splatoon3_coop_setting = coop_setting ?? null; } @@ -608,7 +611,6 @@ function createFestScheduleTeam( id: team.id, color: team.color, myVoteState: state, - role: team.role, }; } diff --git a/src/cli/splatnet3/dump-fests.ts b/src/cli/splatnet3/dump-fests.ts index a6f6859..229a924 100644 --- a/src/cli/splatnet3/dump-fests.ts +++ b/src/cli/splatnet3/dump-fests.ts @@ -137,7 +137,7 @@ export async function dumpFestRecords(splatnet: SplatNet3Api, directory: string, // Fetch this now to match the behavour of Nintendo's app if (!record) { const result = await splatnet.getFestDetail(fest_record.id); - record = result.data.fest; + record = result.data.fest!; } const rankings_available = record.state === FestState.CLOSED && diff --git a/src/cli/splatnet3/festival.ts b/src/cli/splatnet3/festival.ts index 0867ac4..0170664 100644 --- a/src/cli/splatnet3/festival.ts +++ b/src/cli/splatnet3/festival.ts @@ -46,28 +46,29 @@ export async function handler(argv: ArgumentsCamelCase) { const req_id = argv.id; const encoded_req_id = Buffer.from(req_id).toString('base64'); const encoded_part_req_id = Buffer.from('Fest-' + req_id).toString('base64'); - const fest = fest_records.data.festRecords.nodes.find(f => f.id === req_id || + const fest_record = fest_records.data.festRecords.nodes.find(f => f.id === req_id || f.id === encoded_req_id || f.id === encoded_part_req_id); - if (!fest) { + if (!fest_record) { throw new Error('Invalid Splatfest ID'); } - const detail = await splatnet.getFestDetail(fest.id); - const votes = detail.data.fest.state !== FestState.CLOSED ? await splatnet.getFestVotingStatus(fest.id) : null; + const fest = (await splatnet.getFestDetail(fest_record.id)).data.fest!; + const fest_votes = fest.state !== FestState.CLOSED ? + (await splatnet.getFestVotingStatus(fest_record.id)).data.fest : null; if (argv.jsonPrettyPrint) { - console.log(JSON.stringify({fest: detail.data.fest, votes: votes?.data.fest ?? undefined}, null, 4)); + console.log(JSON.stringify({fest: fest, votes: fest_votes ?? undefined}, null, 4)); return; } if (argv.json) { - console.log(JSON.stringify({fest: detail.data.fest, votes: votes?.data.fest ?? undefined})); + console.log(JSON.stringify({fest: fest, votes: fest_votes ?? undefined})); return; } - console.log('Details', detail.data.fest); + console.log('Details', fest); - if (votes) { + if (fest_votes) { const table = new Table({ head: [ 'Name', @@ -76,7 +77,7 @@ export async function handler(argv: ArgumentsCamelCase) { ], }); - for (const team of votes.data.fest.teams) { + for (const team of fest_votes.teams) { for (const vote of team.votes?.nodes ?? []) { table.push([vote.playerName, 'Voted', team.teamName]); } @@ -85,7 +86,7 @@ export async function handler(argv: ArgumentsCamelCase) { } } - for (const vote of votes.data.fest.undecidedVotes?.nodes ?? []) { + for (const vote of fest_votes.undecidedVotes?.nodes ?? []) { table.push([vote.playerName, 'Undecided', '-']); } diff --git a/src/cli/splatnet3/friends.ts b/src/cli/splatnet3/friends.ts index f9ce377..e7260aa 100644 --- a/src/cli/splatnet3/friends.ts +++ b/src/cli/splatnet3/friends.ts @@ -1,5 +1,5 @@ import createDebug from 'debug'; -import { FriendOnlineState } from 'splatnet3-types/splatnet3'; +import { FriendOnlineState, Friend_friendList } from 'splatnet3-types/splatnet3'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../splatnet3.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; @@ -53,7 +53,6 @@ export async function handler(argv: ArgumentsCamelCase) { 'NSA ID', 'Name', 'Status', - 'Favourite?', 'Locked?', 'Voice chat?', ], @@ -69,8 +68,9 @@ export async function handler(argv: ArgumentsCamelCase) { friend.playerName === friend.nickname ? friend.playerName : friend.playerName ? friend.playerName + ' (' + friend.nickname + ')' : friend.nickname, - getStateDescription(friend.onlineState, getVsModeDescription(friend.vsMode) ?? friend.vsMode?.name), - friend.isFavorite ? 'Yes' : 'No', + getStateDescription(friend.onlineState, + getVsModeDescription(friend.vsMode) ?? friend.vsMode?.name, + getCoopModeDescription(friend.coopMode) ?? undefined), typeof friend.isLocked === 'boolean' ? friend.isLocked ? 'Yes' : 'No' : '-', typeof friend.isVcEnabled === 'boolean' ? friend.isVcEnabled ? 'Yes' : 'No' : '-', ]); @@ -79,7 +79,7 @@ export async function handler(argv: ArgumentsCamelCase) { console.log(table.toString()); } -function getStateDescription(state: FriendOnlineState, vs_mode_desc?: string) { +function getStateDescription(state: FriendOnlineState, vs_mode_desc?: string, coop_mode_desc?: string) { switch (state) { case FriendOnlineState.OFFLINE: return 'Offline'; @@ -88,16 +88,16 @@ function getStateDescription(state: FriendOnlineState, vs_mode_desc?: string) { case FriendOnlineState.VS_MODE_MATCHING: return 'In lobby (' + (vs_mode_desc ?? 'VS') + ')'; case FriendOnlineState.COOP_MODE_MATCHING: - return 'In lobby (Salmon Run)'; + return 'In lobby (' + (coop_mode_desc ?? 'Salmon Run') + ')'; case FriendOnlineState.VS_MODE_FIGHTING: return 'In game (' + (vs_mode_desc ?? 'VS') + ')'; case FriendOnlineState.COOP_MODE_FIGHTING: - return 'In game (Salmon Run)'; + return 'In game (' + (coop_mode_desc ?? 'Salmon Run') + ')'; default: return state; } } -function getVsModeDescription(vs_mode: {id: string; mode: string;} | null) { +function getVsModeDescription(vs_mode: Friend_friendList['vsMode'] | null) { if (!vs_mode) return null; if (vs_mode.mode === 'REGULAR') return 'Regular Battle'; @@ -113,3 +113,12 @@ function getVsModeDescription(vs_mode: {id: string; mode: string;} | null) { return null; } + +function getCoopModeDescription(coop_mode: string | null) { + if (!coop_mode) return null; + + if (coop_mode === 'REGULAR') return 'Salmon Run'; + if (coop_mode === 'BIG_RUN') return 'Big Run'; + + return null; +} diff --git a/src/cli/splatnet3/monitor.ts b/src/cli/splatnet3/monitor.ts index 618b306..dbd8c7f 100644 --- a/src/cli/splatnet3/monitor.ts +++ b/src/cli/splatnet3/monitor.ts @@ -191,7 +191,7 @@ async function update( const pager = await splatnet.getBattleHistoryDetailPagerRefetch(latest_id); - if (pager.data.vsHistoryDetail.nextHistoryDetail) { + if (pager.data.vsHistoryDetail?.nextHistoryDetail) { // New battle results available debug('New battle result', pager.data.vsHistoryDetail.nextHistoryDetail); vs = await dumpResults(splatnet, directory, vs.battles.data); @@ -204,7 +204,7 @@ async function update( const pager = await splatnet.getCoopHistoryDetailRefetch(latest_id); - if (pager.data.node.nextHistoryDetail) { + if (pager.data.node?.nextHistoryDetail) { // New coop results available debug('New coop result', pager.data.node.nextHistoryDetail); coop = await dumpCoopResults(splatnet, directory, coop.results.data); diff --git a/src/discord/monitor/splatoon3.ts b/src/discord/monitor/splatoon3.ts index a14f99b..c4633c6 100644 --- a/src/discord/monitor/splatoon3.ts +++ b/src/discord/monitor/splatoon3.ts @@ -34,7 +34,8 @@ export default class SplatNet3Monitor extends EmbeddedLoop { fest_schedule: VsSchedule_fest | null = null; league_schedule: VsSchedule_league | null = null; x_schedule: VsSchedule_xMatch | null = null; - coop_schedule: CoopSchedule_schedule | null = null; + coop_regular_schedule: CoopSchedule_schedule | null = null; + coop_big_run_schedule: CoopSchedule_schedule | null = null; fest: Fest_schedule | null = null; fest_team_voting_status: FestTeam_votingStatus | null = null; fest_team: FestTeam_schedule | null = null; @@ -121,16 +122,9 @@ export default class SplatNet3Monitor extends EmbeddedLoop { this.friend = friend; - // Refresh Splatfest data at the start of the second half - // At the point one team becomes the defending team and others attacking teams, this needs to be known - // to check if the player may join a Tricolour battle - const tricolour_open = this.fest && new Date(this.fest.midtermTime).getTime() < Date.now(); - const should_refresh_fest = this.fest && tricolour_open && - ![FestState.SECOND_HALF, FestState.CLOSED].includes(this.fest.state as FestState); - this.regular_schedule = this.getSchedule(this.cached_schedules?.data.regularSchedules.nodes ?? []); - if (!this.regular_schedule || should_refresh_fest) { + if (!this.regular_schedule) { this.cached_schedules = await this.splatnet?.getSchedules() ?? null; this.regular_schedule = this.getSchedule(this.cached_schedules?.data.regularSchedules.nodes ?? []); } @@ -139,7 +133,8 @@ export default class SplatNet3Monitor extends EmbeddedLoop { this.fest_schedule = this.getSchedule(this.cached_schedules?.data.festSchedules.nodes ?? []); this.league_schedule = this.getSchedule(this.cached_schedules?.data.leagueSchedules.nodes ?? []); this.x_schedule = this.getSchedule(this.cached_schedules?.data.xSchedules.nodes ?? []); - this.coop_schedule = this.getSchedule(this.cached_schedules?.data.coopGroupingSchedule.regularSchedules.nodes ?? []); + this.coop_regular_schedule = this.getSchedule(this.cached_schedules?.data.coopGroupingSchedule.regularSchedules.nodes ?? []); + this.coop_big_run_schedule = this.getSchedule(this.cached_schedules?.data.coopGroupingSchedule.bigRunSchedules.nodes ?? []); this.fest = this.cached_schedules?.data.currentFest ?? null; // Identify the user by their icon as the vote list doesn't have friend IDs @@ -154,7 +149,6 @@ export default class SplatNet3Monitor extends EmbeddedLoop { } this.fest_team_voting_status = fest_team ?? null; - this.fest_team = this.fest?.teams.find(t => t.id === fest_team?.id) ?? null; this.discord_presence.refreshPresence(); } @@ -289,10 +283,11 @@ export function callback(activity: DiscordRPC.Presence, game: Game, context?: Di // In the second half the player may be in a Tricolour battle if either: // the player is on the defending team and joins Splatfest Battle (Open) or // the player is on the attacking team and joins Tricolour Battle - const possibly_tricolour = fest?.state === FestState.SECOND_HALF && ( - (friend.vsMode?.id === 'VnNNb2RlLTY=' && fest_team?.role === FestTeamRole.DEFENSE) || - (friend.vsMode?.id === 'VnNNb2RlLTg=') - ); + // const possibly_tricolour = fest?.state === FestState.SECOND_HALF && ( + // (friend.vsMode?.id === 'VnNNb2RlLTY=' && fest_team?.role === FestTeamRole.DEFENSE) || + // (friend.vsMode?.id === 'VnNNb2RlLTg=') + // ); + const possibly_tricolour = friend.vsMode?.id === 'VnNNb2RlLTg='; activity.largeImageKey = 'https://fancy.org.uk/api/nxapi/s3/image?' + new URLSearchParams({ a: setting.vsStages[0].id, @@ -327,7 +322,10 @@ export function callback(activity: DiscordRPC.Presence, game: Game, context?: Di const coop_setting = presence_proxy_data && 'splatoon3_coop_setting' in presence_proxy_data ? presence_proxy_data.splatoon3_coop_setting : - monitor ? monitor.coop_schedule?.setting : + monitor ? + friend.coopMode === 'BIG_RUN' ? + monitor.coop_big_run_schedule?.setting : + monitor.coop_regular_schedule?.setting : null; if (coop_setting) {