Initial Splatfest types and commands

This commit is contained in:
Samuel Elliott 2022-09-19 19:12:22 +01:00
parent c60442fa5e
commit 8012dba821
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
7 changed files with 394 additions and 5 deletions

View File

@ -319,7 +319,7 @@ export default class CoralApi {
if ('errorMessage' in data) {
throw new ErrorResponse('[znc] ' + data.errorMessage, response, data);
}
if (data.status !== 0) {
if (data.status !== CoralStatus.OK) {
throw new ErrorResponse('[znc] Unknown error', response, data);
}

View File

@ -213,6 +213,39 @@ interface GearPower {
};
}
export enum FestState {
SCHEDULED = 'SCHEDULED',
FIRST_HALF = 'FIRST_HALF',
SECOND_HALF = 'SECOND_HALF',
CLOSED = 'CLOSED',
}
export enum FestVoteState {
VOTED = 'VOTED',
PRE_VOTED = 'PRE_VOTED',
}
export enum FestTeamRole {
ATTACK = 'ATTACK',
DEFENSE = 'DEFENSE',
}
/** a2c742c840718f37488e0394cd6e1e08 VotesUpdateFestVoteMutation */
export interface VotesUpdateFestVoteResult {
updateFestVote: {
fest: {
id: string;
teams: DetailVotingStatusTeam<{
totalCount: number;
nodes: FestVotePlayer[];
}>[];
isVotable: boolean;
undecidedVotes: {
totalCount: number;
nodes: FestVotePlayer[];
};
}
userErrors: null;
}
}
/** f8ae00773cc412a50dd41a6d9a159ddd ConfigureAnalyticsQuery */
export interface ConfigureAnalyticsResult {
@ -419,6 +452,7 @@ export interface BattleHistoryCurrentPlayerResult {
/** 7a0e05c28c7d3f7e5a06def87ab8cd2d FriendListQuery */
export interface FriendListResult {
/** Only includes friends that have played Splatoon 3 */
friends: {
nodes: Friend[];
};
@ -431,7 +465,9 @@ export type FriendListRefetchResult = FriendListResult;
interface Friend {
id: string;
onlineState: FriendOnlineState;
/** Switch user name */
nickname: string;
/** Splatoon 3 name, if the user has one set and is currently playing Splatoon 3 */
playerName: string | null;
userIcon: {
url: string;
@ -450,6 +486,10 @@ interface Friend {
export enum FriendOnlineState {
OFFLINE = 'OFFLINE',
/**
* The user is online and selected in *any* game, not just Splatoon 3.
* Coral may be used to check which game the user is playing.
*/
ONLINE = 'ONLINE',
VS_MODE_MATCHING = 'VS_MODE_MATCHING',
COOP_MODE_MATCHING = 'COOP_MODE_MATCHING',
@ -535,6 +575,129 @@ interface WeaponHistoryCategoryWeapon extends WeaponHistoryWeapon {
};
}
/** 2d661988c055d843b3be290f04fb0db9 DetailFestRecordDetailQuery */
export interface DetailFestRecordDetailResult {
fest: {
__typename: 'Fest';
id: string;
title: string;
lang: string;
startTime: string;
endTime: string;
state: FestState;
image: {
url: string;
};
teams: DetailFestTeam[];
playerResult: unknown | null;
myTeam: unknown | null;
isVotable: boolean;
undecidedVotes: {
totalCount: number;
};
};
currentPlayer: {
name: string;
userIcon: {
url: string;
};
};
}
interface DetailFestTeam<Votes = {
totalCount: number;
}> {
result: unknown | null;
id: string;
teamName: string;
color: Colour;
image: {
url: string;
};
myVoteState: FestVoteState | null;
preVotes: Votes;
votes: Votes;
role: FestTeamRole | null;
}
/** 0eb7bac3d8aabcad0e9d663ee5b90846 DetailFestRefethQuery */
export type DetailFestRefetchResult = DetailFestRecordDetailResult;
/** 92f51ed1ab462bbf1ab64cad49d36f79 DetailFestVotingStatusRefethQuery */
export type DetailFestVotingStatusRefetchResult = DetailVotingStatusResult;
/** 53ee6b6e2acc3859bf42454266d671fc DetailVotingStatusQuery */
export interface DetailVotingStatusResult {
fest: {
__typename: 'Fest';
id: string;
lang: string;
teams: DetailVotingStatusTeam[];
undecidedVotes: {
nodes: FestVotePlayer[];
};
};
}
interface DetailVotingStatusTeam<Votes = {
nodes: FestVotePlayer[];
}> {
id: string;
teamName: string;
image: {
url: string;
};
color: Colour;
/** undefined = not voted, null = not voted for this team */
myVoteState?: FestVoteState | null;
preVotes: Votes;
votes: Votes;
}
interface FestVotePlayer {
playerName: string;
userIcon: {
url: string;
};
}
/** 44c76790b68ca0f3da87f2a3452de986 FestRecordQuery */
export interface FestRecordResult {
festRecords: {
nodes: FestRecord[];
};
currentPlayer: {
name: string;
userIcon: {
url: string;
};
};
}
interface FestRecord {
id: string;
state: FestState;
startTime: string;
endTime: string;
title: string;
lang: string;
image: {
url: string;
};
playerResult: unknown | null;
teams: FestRecordTeam[];
myTeam: unknown | null;
}
interface FestRecordTeam {
id: string;
teamName: string;
result: unknown | null;
}
/** 73b9837d0e4dd29bfa2f1a7d7ee0814a FestRecordRefetchQuery */
export type FestRecordRefetchResult = FestRecordResult;
/** 61228d553e7463c203e05e7810dd79a7 SettingQuery */
export interface SettingResult {
currentPlayer: {
@ -723,12 +886,12 @@ export interface HomeResult {
};
};
banners: HomeBanner[];
/** Only includes online friends */
/** Only includes online friends that have played Splatoon 3, even if they are currently playing a different game */
friends: {
nodes: HomeFriend[];
totalCount: number;
};
footerMessages: unknown[];
footerMessages: HomeFooterMessage[];
}
interface HomeBanner {
@ -751,6 +914,22 @@ interface HomeFriend {
};
}
type HomeFooterMessage = FooterBigRunMessage | FooterFestMessage | FooterSeasonMessage;
interface FooterBigRunMessage {
__typename: 'FooterBigRunMessage';
bigRunState: 'SCHEDULED' | unknown;
}
interface FooterFestMessage {
__typename: 'FooterFestMessage';
festState: FestState;
festTitle: string;
}
interface FooterSeasonMessage {
__typename: 'FooterSeasonMessage';
seasonName: string;
}
/** 994cf141e55213e6923426caf37a1934 VsHistoryDetailPagerRefetchQuery */
export interface VsHistoryDetailPagerRefetchQueryResult {
vsHistoryDetail: {

View File

@ -5,7 +5,7 @@ import { NintendoAccountUser } from './na.js';
import { defineResponse, ErrorResponse } from './util.js';
import CoralApi from './coral.js';
import { timeoutSignal } from '../util/misc.js';
import { BankaraBattleHistoriesResult, BattleHistoryCurrentPlayerResult, BulletToken, CurrentFestResult, FriendListResult, GraphQLRequest, GraphQLResponse, HistoryRecordResult, HomeResult, LatestBattleHistoriesResult, PrivateBattleHistoriesResult, RegularBattleHistoriesResult, RequestId, SettingResult, StageScheduleResult, VsHistoryDetailResult, CoopHistoryResult, CoopHistoryDetailResult } from './splatnet3-types.js';
import { BankaraBattleHistoriesResult, BattleHistoryCurrentPlayerResult, BulletToken, CurrentFestResult, FriendListResult, GraphQLRequest, GraphQLResponse, HistoryRecordResult, HomeResult, LatestBattleHistoriesResult, PrivateBattleHistoriesResult, RegularBattleHistoriesResult, RequestId, SettingResult, StageScheduleResult, VsHistoryDetailResult, CoopHistoryResult, CoopHistoryDetailResult, FestRecordResult, FestRecordRefetchResult, DetailFestRecordDetailResult, DetailVotingStatusResult, DetailFestVotingStatusRefetchResult, VotesUpdateFestVoteResult } from './splatnet3-types.js';
const debug = createDebug('nxapi:api:splatnet3');
@ -94,6 +94,44 @@ export default class SplatNet3Api {
return this.persistedQuery<SettingResult>(RequestId.SettingQuery, {});
}
async getFestRecords() {
return this.persistedQuery<FestRecordResult>(RequestId.FestRecordQuery, {});
}
async getFestRecordsRefetch() {
return this.persistedQuery<FestRecordRefetchResult>(RequestId.FestRecordRefetchQuery, {});
}
async getFestDetail(id: string) {
return this.persistedQuery<DetailFestRecordDetailResult>(RequestId.DetailFestRecordDetailQuery, {
festId: id,
});
}
async getFestDetailRefetch(id: string) {
return this.persistedQuery<FestRecordRefetchResult>(RequestId.DetailFestRefethQuery, {
festId: id,
});
}
async getFestVotingStatus(id: string) {
return this.persistedQuery<DetailVotingStatusResult>(RequestId.DetailVotingStatusQuery, {
festId: id,
});
}
async getFestVotingStatusRefetch(id: string) {
return this.persistedQuery<DetailFestVotingStatusRefetchResult>(RequestId.DetailFestVotingStatusRefethQuery, {
festId: id,
});
}
async updateFestPoll(id: string) {
return this.persistedQuery<VotesUpdateFestVoteResult>(RequestId.VotesUpdateFestVoteMutation, {
teamId: id,
});
}
async getFriends() {
return this.persistedQuery<FriendListResult>(RequestId.FriendListQuery, {});
}

View File

@ -0,0 +1,92 @@
import createDebug from 'debug';
import Table from '../util/table.js';
import type { Arguments as ParentArguments } from '../splatnet3.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getBulletToken } from '../../common/auth/splatnet3.js';
const debug = createDebug('cli:splatnet3:festival');
export const command = 'festival <id>';
export const desc = 'Show details about a specific Splatfest in your region';
export function builder(yargs: Argv<ParentArguments>) {
return yargs.positional('id', {
describe: 'Splatfest 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.' + usernsid);
const {splatnet, data} = await getBulletToken(storage, token, argv.zncProxyUrl, argv.autoUpdateSession);
const fest_records = await splatnet.getFestRecords();
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 ||
f.id === encoded_req_id || f.id === encoded_part_req_id);
if (!fest) {
throw new Error('Invalid Splatfest ID');
}
const detail = await splatnet.getFestDetail(fest.id);
const votes = await splatnet.getFestVotingStatus(fest.id);
if (argv.jsonPrettyPrint) {
console.log(JSON.stringify({fest: detail.data.fest, votes: votes.data.fest}, null, 4));
return;
}
if (argv.json) {
console.log(JSON.stringify({fest: detail.data.fest, votes: votes.data.fest}));
return;
}
console.log('Details', detail.data.fest);
const table = new Table({
head: [
'Name',
'State',
'Team',
],
});
for (const team of votes.data.fest.teams) {
for (const vote of team.votes.nodes) {
table.push([vote.playerName, 'Voted', team.teamName]);
}
for (const vote of team.preVotes.nodes) {
table.push([vote.playerName, 'Planning to vote', team.teamName]);
}
}
for (const vote of votes.data.fest.undecidedVotes.nodes) {
table.push([vote.playerName, 'Undecided', '-']);
}
console.log('Friends votes');
console.log(table.toString());
}

View File

@ -0,0 +1,77 @@
import createDebug from 'debug';
import Table from '../util/table.js';
import type { Arguments as ParentArguments } from '../splatnet3.js';
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
import { initStorage } from '../../util/storage.js';
import { getBulletToken } from '../../common/auth/splatnet3.js';
const debug = createDebug('cli:splatnet3:festivals');
export const command = 'festivals';
export const desc = 'List all Splatfests in your region';
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',
}).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.' + usernsid);
const {splatnet} = await getBulletToken(storage, token, argv.zncProxyUrl, argv.autoUpdateSession);
const fest_records = await splatnet.getFestRecords();
if (argv.jsonPrettyPrint) {
console.log(JSON.stringify({festRecords: fest_records.data.festRecords.nodes}, null, 4));
return;
}
if (argv.json) {
console.log(JSON.stringify({festRecords: fest_records.data.festRecords.nodes}));
return;
}
const table = new Table({
head: [
'ID',
'State',
'Start',
// 'L',
'Title',
'A',
'B',
'C',
],
});
for (const fest of fest_records.data.festRecords.nodes) {
const id_str = Buffer.from(fest.id, 'base64').toString() || fest.id;
table.push([
id_str,
fest.state,
fest.startTime,
// fest.lang,
fest.title,
...fest.teams.map(t => t.teamName),
]);
}
console.log(table.toString());
}

View File

@ -2,5 +2,7 @@ export * as user from './user.js';
export * as token from './token.js';
export * as friends from './friends.js';
export * as schedule from './schedule.js';
export * as festivals from './festivals.js';
export * as festival from './festival.js';
export * as battles from './battles.js';
export * as dumpResults from './dump-results.js';

View File

@ -34,5 +34,6 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
console.log('Player %s#%s (title %s, first played %s)',
history.data.currentPlayer.name,
history.data.currentPlayer.nameId,
history.data.playHistory.gameStartTime);
history.data.currentPlayer.byname,
new Date(history.data.playHistory.gameStartTime).toLocaleString());
}