Use Splatoon 3 presence data from custom presence URLs

This commit is contained in:
Samuel Elliott 2022-09-24 23:02:24 +01:00
parent a4d328c72a
commit 72995b1a05
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
7 changed files with 106 additions and 54 deletions

View File

@ -97,13 +97,13 @@ export enum RequestId {
VsHistoryDetailQuery = 'cd82f2ade8aca7687947c5f3210805a6',
}
interface NodeList<T, C extends boolean = false> {
interface _NodeList<T, C extends boolean = false> {
nodes: T[];
totalCount: C extends true ? number : never;
}
interface NodeListTotal {
totalCount: number;
}
type NodeList<T, C extends boolean = false> =
C extends true ? _NodeList<T> : Pick<_NodeList<T>, 'nodes'>;
type NodeListTotal = Pick<_NodeList<unknown>, 'totalCount'>;
interface Colour {
a: number;

View File

@ -239,5 +239,5 @@ export async function getPresenceFromUrl(presence_url: string, useragent?: strin
throw new Error('Invalid presence data');
}
return [presence, user] as const;
return defineResponse([presence, user, data as unknown] as const, response);
}

View File

@ -266,6 +266,7 @@ export default function Preferences(props: PreferencesProps) {
</TouchableOpacity>
</View>
<Text style={[styles.help, theme.text]}>Uses SplatNet 3 to retrieve additional presence information while playing Splatoon 3. You must be using a secondary Nintendo Account that is friends with your main account to fetch your presence, and the secondary account must be able to access SplatNet 3.</Text>
<Text style={[styles.help, theme.text]}>When using a presence URL that returns Splatoon 3 data additional presence information will be shown regardless of this setting.</Text>
</View>
</View>
</View>

View File

@ -90,7 +90,7 @@ export class PresenceMonitorManager {
i.notifications = this.notifications;
i.discord.onUpdateActivity = (presence: DiscordPresence | null) => {
this.app.store.emit('update-discord-presence', presence);
this.app.store.emit('update-discord-presence', presence ? {...presence, config: undefined} : null);
};
i.discord.onUpdateClient = (client: DiscordRpcClient | null) => {
this.app.store.emit('update-discord-user', client?.user ?? null);

View File

@ -69,7 +69,7 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
friend.playerName === friend.nickname ? friend.playerName :
friend.playerName ? friend.playerName + ' (' + friend.nickname + ')' :
friend.nickname,
getStateDescription(friend.onlineState, friend.vsMode?.name),
getStateDescription(friend.onlineState, getVsModeDescription(friend.vsMode) ?? friend.vsMode?.name),
friend.isFavorite ? 'Yes' : 'No',
typeof friend.isLocked === 'boolean' ? friend.isLocked ? 'Yes' : 'No' : '-',
typeof friend.isVcEnabled === 'boolean' ? friend.isVcEnabled ? 'Yes' : 'No' : '-',
@ -96,3 +96,19 @@ function getStateDescription(state: FriendOnlineState, vs_mode_desc?: string) {
default: return state;
}
}
function getVsModeDescription(vs_mode: {id: string; mode: string;} | null) {
if (!vs_mode) return null;
if (vs_mode.mode === 'REGULAR') return 'Regular Battle';
if (vs_mode.id === 'VnNNb2RlLTI=') return 'Anarchy Battle (Series)'; // VsMode-2
if (vs_mode.id === 'VnNNb2RlLTUx') return 'Anarchy Battle (Open)'; // VsMode-51
if (vs_mode.mode === 'BANKARA') return 'Anarchy Battle';
if (vs_mode.id === 'VnNNb2RlLTY=') return 'Splatfest Battle (Open)'; // VsMode-6
if (vs_mode.id === 'VnNNb2RlLTc=') return 'Splatfest Battle (Pro)'; // VsMode-7
if (vs_mode.mode === 'FEST') return 'Splatfest Battle';
if (vs_mode.mode === 'LEAGUE') return 'League Battle';
if (vs_mode.mode === 'X_MATCH') return 'X Battle';
return null;
}

View File

@ -506,6 +506,8 @@ export class ZncProxyDiscordPresence extends Loop {
readonly discord = new ZncDiscordPresenceClient(this);
last_data: unknown | null = null;
constructor(
public presence_url: string
) {
@ -523,7 +525,8 @@ export class ZncProxyDiscordPresence extends Loop {
}
async update() {
const [presence, user] = await getPresenceFromUrl(this.presence_url);
const [presence, user, data] = await getPresenceFromUrl(this.presence_url);
this.last_data = data;
await this.discord.updatePresenceForDiscord(presence, user);
await this.updatePresenceForSplatNet2Monitor(presence, this.presence_url);

View File

@ -7,7 +7,7 @@ import SplatNet3Api from '../../../api/splatnet3.js';
import { DiscordPresenceExternalMonitorsConfiguration } from '../../../app/common/types.js';
import { Arguments } from '../../../cli/nso/presence.js';
import { getBulletToken, SavedBulletToken } from '../../../common/auth/splatnet3.js';
import { ExternalMonitorPresenceInterface } from '../../../common/presence.js';
import { ExternalMonitorPresenceInterface, ZncProxyDiscordPresence } from '../../../common/presence.js';
import { EmbeddedLoop, LoopResult } from '../../../util/loop.js';
import { ArgumentsCamelCase } from '../../../util/yargs.js';
import { DiscordPresenceContext, ErrorResult } from '../../types.js';
@ -117,8 +117,7 @@ export default class SplatNet3Monitor extends EmbeddedLoop {
// 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 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);
@ -219,95 +218,128 @@ export function getConfigFromAppConfig(
};
}
interface PresenceUrlResponse {
splatoon3?: FriendListResult['friends']['nodes'][0] | null;
splatoon3_fest_team?:
(DetailVotingStatusResult['fest']['teams'][0] &
Exclude<StageScheduleResult['currentFest'], null>['teams'][0]) | null;
splatoon3_vs_setting?:
StageScheduleResult['regularSchedules']['nodes'][0]['regularMatchSetting'] |
Exclude<StageScheduleResult['bankaraSchedules']['nodes'][0]['bankaraMatchSettings'], null>[0] |
StageScheduleResult['festSchedules']['nodes'][0]['festMatchSetting'] |
StageScheduleResult['leagueSchedules']['nodes'][0]['leagueMatchSetting'] |
StageScheduleResult['xSchedules']['nodes'][0]['xMatchSetting'] |
null;
splatoon3_coop_setting?:
StageScheduleResult['coopGroupingSchedule']['regularSchedules']['nodes'][0]['setting'] | null;
splatoon3_fest?: StageScheduleResult['currentFest'] | null;
}
export function callback(activity: DiscordRPC.Presence, game: Game, context?: DiscordPresenceContext) {
const monitor = context?.monitors?.find(m => m instanceof SplatNet3Monitor) as SplatNet3Monitor | undefined;
if (!monitor?.friend) return;
const presence_proxy_data =
context?.znc_discord_presence instanceof ZncProxyDiscordPresence ?
context.znc_discord_presence.last_data as PresenceUrlResponse : null;
if ((monitor.friend.onlineState === FriendOnlineState.VS_MODE_MATCHING ||
monitor.friend.onlineState === FriendOnlineState.VS_MODE_FIGHTING) && monitor.friend.vsMode
const friend = presence_proxy_data?.splatoon3 ?? monitor?.friend;
const fest = presence_proxy_data?.splatoon3_fest ?? monitor?.fest;
const fest_team = presence_proxy_data?.splatoon3_fest_team ?? monitor?.fest_team;
const fest_team_voting_status = presence_proxy_data?.splatoon3_fest_team ?? monitor?.fest_team_voting_status;
if (!friend) return;
if ((friend.onlineState === FriendOnlineState.VS_MODE_MATCHING ||
friend.onlineState === FriendOnlineState.VS_MODE_FIGHTING) && friend.vsMode
) {
const mode = monitor.friend.vsMode;
const mode_name =
mode.mode === 'REGULAR' ? 'Regular Battle' :
mode.id === 'VnNNb2RlLTI=' ? 'Anarchy Battle (Series)' : // VsMode-2
mode.id === 'VnNNb2RlLTUx' ? 'Anarchy Battle (Open)' : // VsMode-51
mode.mode === 'BANKARA' ? 'Anarchy Battle' :
mode.id === 'VnNNb2RlLTY=' ? 'Splatfest Battle (Open)' : // VsMode-6
mode.id === 'VnNNb2RlLTc=' ? 'Splatfest Battle (Pro)' : // VsMode-7
mode.mode === 'FEST' ? 'Splatfest Battle' :
mode.mode === 'LEAGUE' ? 'League Battle' :
mode.mode === 'X_MATCH' ? 'X Battle' :
friend.vsMode.mode === 'REGULAR' ? 'Regular Battle' :
friend.vsMode.id === 'VnNNb2RlLTI=' ? 'Anarchy Battle (Series)' : // VsMode-2
friend.vsMode.id === 'VnNNb2RlLTUx' ? 'Anarchy Battle (Open)' : // VsMode-51
friend.vsMode.mode === 'BANKARA' ? 'Anarchy Battle' :
friend.vsMode.id === 'VnNNb2RlLTY=' ? 'Splatfest Battle (Open)' : // VsMode-6
friend.vsMode.id === 'VnNNb2RlLTc=' ? 'Splatfest Battle (Pro)' : // VsMode-7
friend.vsMode.mode === 'FEST' ? 'Splatfest Battle' :
friend.vsMode.mode === 'LEAGUE' ? 'League Battle' :
friend.vsMode.mode === 'X_MATCH' ? 'X Battle' :
undefined;
const setting =
mode.mode === 'REGULAR' ? monitor.regular_schedule?.regularMatchSetting :
mode.mode === 'BANKARA' ?
mode.id === 'VnNNb2RlLTI=' ?
presence_proxy_data && 'splatoon3_vs_setting' in presence_proxy_data ?
presence_proxy_data.splatoon3_vs_setting :
!monitor ? null :
friend.vsMode.mode === 'REGULAR' ? monitor.regular_schedule?.regularMatchSetting :
friend.vsMode.mode === 'BANKARA' ?
friend.vsMode.id === 'VnNNb2RlLTI=' ?
monitor.anarchy_schedule?.bankaraMatchSettings?.find(s => s.mode === BankaraMatchMode.CHALLENGE) :
mode.id === 'VnNNb2RlLTUx' ?
friend.vsMode.id === 'VnNNb2RlLTUx' ?
monitor.anarchy_schedule?.bankaraMatchSettings?.find(s => s.mode === BankaraMatchMode.OPEN) :
null :
mode.mode === 'FEST' ? monitor.fest_schedule?.festMatchSetting :
mode.mode === 'LEAGUE' ? monitor.league_schedule?.leagueMatchSetting :
mode.mode === 'X_MATCH' ? monitor.x_schedule?.xMatchSetting :
friend.vsMode.mode === 'FEST' ? monitor.fest_schedule?.festMatchSetting :
friend.vsMode.mode === 'LEAGUE' ? monitor.league_schedule?.leagueMatchSetting :
friend.vsMode.mode === 'X_MATCH' ? monitor.x_schedule?.xMatchSetting :
null;
activity.details =
(mode_name ?? mode.name) +
(mode.mode === 'FEST' && monitor.fest_team_voting_status ?
' - Team ' + monitor.fest_team_voting_status.teamName : '') +
(mode.mode !== 'FEST' && setting ? ' - ' + setting.vsRule.name : '') +
(monitor.friend.onlineState === FriendOnlineState.VS_MODE_MATCHING ? ' (matching)' : '');
(mode_name ?? friend.vsMode.name) +
(friend.vsMode.mode === 'FEST' && fest_team_voting_status ?
' - Team ' + fest_team_voting_status.teamName : '') +
(friend.vsMode.mode !== 'FEST' && setting ? ' - ' + setting.vsRule.name : '') +
(friend.onlineState === FriendOnlineState.VS_MODE_MATCHING ? ' (matching)' : '');
if (setting) {
// 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 = monitor.fest && new Date(monitor.fest.midtermTime).getTime() >= Date.now() && (
const possibly_tricolour = fest && new Date(fest.midtermTime).getTime() <= Date.now() && (
false
// (monitor.friend.vsMode?.id === 'VnNNb2RlLTY=' && monitor.fest_team?.role === FestTeamRole.DEFENSE) ||
// (monitor.friend.vsMode?.id === '... tricolour mode ID ...')
// (friend.vsMode?.id === 'VnNNb2RlLTY=' && fest_team?.role === FestTeamRole.DEFENSE) ||
// (friend.vsMode?.id === '... tricolour mode ID ...')
);
activity.largeImageKey = 'https://fancy.org.uk/api/nxapi/s3/image?' + new URLSearchParams({
a: setting.vsStages[0].id,
b: setting.vsStages[1].id,
...(possibly_tricolour ? {t: monitor.fest?.tricolorStage.id} : {}),
...(possibly_tricolour ? {t: fest?.tricolorStage.id} : {}),
v: '2022092400',
}).toString();
activity.largeImageText = setting.vsStages.map(s => s.name).join('/') +
(possibly_tricolour ? '/' + monitor.fest?.tricolorStage.name : '') +
(possibly_tricolour ? '/' + fest?.tricolorStage.name : '') +
' | ' + product;
}
// REGULAR, BANKARA, X_MATCH, LEAGUE, PRIVATE, FEST
const mode_image =
mode.mode === 'REGULAR' ? 'mode-regular-1' :
mode.mode === 'BANKARA' ? 'mode-anarchy-1' :
mode.mode === 'FEST' ? 'mode-fest-1' :
mode.mode === 'LEAGUE' ? 'mode-league-1' :
mode.mode === 'X_MATCH' ? 'mode-x-1' :
friend.vsMode.mode === 'REGULAR' ? 'mode-regular-1' :
friend.vsMode.mode === 'BANKARA' ? 'mode-anarchy-1' :
friend.vsMode.mode === 'FEST' ? 'mode-fest-1' :
friend.vsMode.mode === 'LEAGUE' ? 'mode-league-1' :
friend.vsMode.mode === 'X_MATCH' ? 'mode-x-1' :
undefined;
activity.smallImageKey = mode_image;
activity.smallImageText = mode_name ?? mode.name;
activity.smallImageText = mode_name ?? friend.vsMode.name;
}
if (monitor.friend.onlineState === FriendOnlineState.COOP_MODE_MATCHING ||
monitor.friend.onlineState === FriendOnlineState.COOP_MODE_FIGHTING
if (friend.onlineState === FriendOnlineState.COOP_MODE_MATCHING ||
friend.onlineState === FriendOnlineState.COOP_MODE_FIGHTING
) {
activity.details = 'Salmon Run' +
(monitor.friend.onlineState === FriendOnlineState.COOP_MODE_MATCHING ? ' (matching)' : '');
(friend.onlineState === FriendOnlineState.COOP_MODE_MATCHING ? ' (matching)' : '');
if (monitor.coop_schedule) {
const coop_stage_image = new URL(monitor.coop_schedule.setting.coopStage.image.url);
const coop_setting =
presence_proxy_data && 'splatoon3_coop_setting' in presence_proxy_data ?
presence_proxy_data.splatoon3_coop_setting :
monitor ? monitor.coop_schedule?.setting :
null;
if (coop_setting) {
const coop_stage_image = new URL(coop_setting.coopStage.image.url);
const match = coop_stage_image.pathname.match(/^\/resources\/prod\/(.+)$/);
const proxy_stage_image = match ? 'https://splatoon3.ink/assets/splatnet/' + match[1] : null;
if (proxy_stage_image) {
activity.largeImageKey = proxy_stage_image;
activity.largeImageText = monitor.coop_schedule.setting.coopStage.name +
activity.largeImageText = coop_setting.coopStage.name +
' | ' + product;
}
}