mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-04-21 22:47:46 -05:00
Add friend code lookup and friend requests to the Electron app
This commit is contained in:
parent
ca36935f78
commit
4c5203e50e
334
src/app/browser/add-friend/index.tsx
Normal file
334
src/app/browser/add-friend/index.tsx
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { ActivityIndicator, Image, NativeSyntheticEvent, StyleSheet, Text, TextInput, TextInputChangeEventData, TextInputKeyPressEventData, TouchableOpacity, useColorScheme, View } from 'react-native';
|
||||
import { Friend, FriendCodeUser } from '../../../api/coral-types.js';
|
||||
import Warning from '../components/icons/warning.js';
|
||||
import { Button } from '../components/index.js';
|
||||
import { DEFAULT_ACCENT_COLOUR, HIGHLIGHT_COLOUR_DARK, HIGHLIGHT_COLOUR_LIGHT, TEXT_COLOUR_DARK, TEXT_COLOUR_LIGHT } from '../constants.js';
|
||||
import ipc, { events } from '../ipc.js';
|
||||
import { RequestState, Root, useAsync, useEventListener } from '../util.js';
|
||||
|
||||
export interface AddFriendProps {
|
||||
user: string;
|
||||
friendcode?: string;
|
||||
}
|
||||
|
||||
enum SendFriendRequestState {
|
||||
NOT_LOADING,
|
||||
SENDING,
|
||||
SENT,
|
||||
ERROR,
|
||||
}
|
||||
type SendFriendRequestStateArray =
|
||||
[SendFriendRequestState.NOT_LOADING] |
|
||||
[SendFriendRequestState.SENDING, FriendCodeUser, string] |
|
||||
[SendFriendRequestState.SENT, FriendCodeUser, string, Friend | null] |
|
||||
[SendFriendRequestState.ERROR, FriendCodeUser, string, Error];
|
||||
|
||||
const FRIEND_CODE = /^\d{4}-\d{4}-\d{4}$/;
|
||||
const FRIEND_CODE_URL = /^(?!https\:\/\/lounge\.nintendo\.com\/|com\.nintendo\.znca\:\/\/znca\/)friendcode\/(\d{4}-\d{4}-\d{4})\//;
|
||||
|
||||
export default function AddFriend(props: AddFriendProps) {
|
||||
const colour_scheme = useColorScheme();
|
||||
const theme = colour_scheme === 'light' ? light : dark;
|
||||
|
||||
const [accent_colour, setAccentColour] = React.useState(() => ipc.getAccentColour());
|
||||
useEventListener(events, 'systemPreferences:accent-colour', setAccentColour, []);
|
||||
|
||||
const [token] = useAsync(useCallback(() => ipc.getNintendoAccountCoralToken(props.user), [ipc, props.user]));
|
||||
const [user] = useAsync(useCallback(() => token ?
|
||||
ipc.getSavedCoralToken(token) : Promise.resolve(null), [ipc, token]));
|
||||
|
||||
const [friendcode, setFriendCode] = useState(props.friendcode ?? '');
|
||||
const is_valid_friendcode = FRIEND_CODE.test(friendcode);
|
||||
const show_friendcode_field = !props.friendcode || !FRIEND_CODE.test(props.friendcode);
|
||||
|
||||
const onChangeFriendCode = useCallback((event: NativeSyntheticEvent<TextInputChangeEventData>) => {
|
||||
let match;
|
||||
if (match = event.nativeEvent.text.match(FRIEND_CODE_URL)) {
|
||||
setFriendCode(match[1]);
|
||||
} else {
|
||||
const friendcode = event.nativeEvent.text
|
||||
.replace(/[^0-9]/g, '')
|
||||
.replace(/^([0-9]{4})/g, '$1-')
|
||||
.replace(/^([0-9]{4}-[0-9]{4})/g, '$1-')
|
||||
.substr(0, 14);
|
||||
|
||||
setFriendCode(friendcode);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const [target_user, lookup_error, lookup_state] = useAsync(useCallback(() => token && is_valid_friendcode ?
|
||||
ipc.getNsoUserByFriendCode(token, friendcode) : Promise.resolve(null),
|
||||
[ipc, token, friendcode, is_valid_friendcode]));
|
||||
|
||||
const [friends, , friends_state, forceRefreshFriends] = useAsync(useCallback(() => token ?
|
||||
ipc.getNsoFriends(token) : Promise.resolve(null), [ipc, token]));
|
||||
const friend = friends?.find(f => f.nsaId === target_user?.nsaId);
|
||||
|
||||
const showLookupErrorDetails = useCallback(() => {
|
||||
alert(lookup_error);
|
||||
}, [lookup_error]);
|
||||
|
||||
const [send_state, setSendFriendRequestState] = useState<SendFriendRequestStateArray>([SendFriendRequestState.NOT_LOADING]);
|
||||
|
||||
const sendFriendRequest = useCallback(async () => {
|
||||
if (send_state[0] === SendFriendRequestState.SENDING) return;
|
||||
if (!token || !target_user) return;
|
||||
setSendFriendRequestState([SendFriendRequestState.SENDING, target_user, friendcode]);
|
||||
|
||||
try {
|
||||
const {result, friend} = await ipc.addNsoFriend(token, target_user.nsaId);
|
||||
|
||||
setSendFriendRequestState([SendFriendRequestState.SENT, target_user, friendcode, friend]);
|
||||
} catch (err) {
|
||||
setSendFriendRequestState([SendFriendRequestState.ERROR, target_user, friendcode, err as Error]);
|
||||
}
|
||||
}, [token, target_user, friendcode, send_state]);
|
||||
|
||||
const showSendFriendRequestErrorDetails = useCallback(() => {
|
||||
if (send_state[0] !== SendFriendRequestState.ERROR) return;
|
||||
alert(send_state[3]);
|
||||
}, [send_state]);
|
||||
|
||||
const onFriendCodeKeyPress = useCallback((event: NativeSyntheticEvent<TextInputKeyPressEventData>) =>
|
||||
event.nativeEvent.key === 'Escape' && window.close(), []);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (event: KeyboardEvent) => event.key === 'Escape' && window.close();
|
||||
window.addEventListener('keydown', handler);
|
||||
return () => window.removeEventListener('keydown', handler);
|
||||
}, []);
|
||||
|
||||
if (!user || (!show_friendcode_field && lookup_state === RequestState.LOADING)) {
|
||||
return <Root title="Add friend" titleUser={user ?? undefined} autoresize={!!user}>
|
||||
<View style={styles.loading}>
|
||||
<ActivityIndicator size="large" color={'#' + (accent_colour ?? DEFAULT_ACCENT_COLOUR)} />
|
||||
</View>
|
||||
</Root>;
|
||||
}
|
||||
|
||||
return <Root title="Add friend" titleUser={user} scrollable autoresize>
|
||||
<View style={styles.main}>
|
||||
{show_friendcode_field ? <>
|
||||
<Text style={theme.text}>Add friend</Text>
|
||||
|
||||
<Text style={[styles.help, theme.text]}>Type or paste a friend code or friend code URL to send a friend request.</Text>
|
||||
|
||||
<View style={styles.friendCodeInputContainer}>
|
||||
<TextInput value={friendcode} onChange={onChangeFriendCode}
|
||||
onKeyPress={onFriendCodeKeyPress}
|
||||
placeholder="0000-0000-0000"
|
||||
style={[styles.textInput, styles.friendCodeInput, theme.textInput]} />
|
||||
|
||||
{lookup_state === RequestState.LOADING ?
|
||||
<ActivityIndicator style={styles.activityIndicator} size={20} color={'#' + accent_colour} /> :
|
||||
lookup_error ? <TouchableOpacity onPress={showLookupErrorDetails} style={styles.iconTouchable}>
|
||||
<Text style={[styles.icon, {color: '#' + accent_colour}]}><Warning /></Text>
|
||||
</TouchableOpacity> : null}
|
||||
</View>
|
||||
</> : lookup_error ? <TouchableOpacity onPress={showLookupErrorDetails}>
|
||||
<Text style={[styles.lookupErrorNoFriendCodeField, theme.text]}>
|
||||
<Text style={[styles.lookupErrorIcon, {color: '#' + accent_colour}]}><Warning /></Text>
|
||||
Error looking up friend code: {lookup_error.name} {lookup_error.message}
|
||||
</Text>
|
||||
</TouchableOpacity> : null}
|
||||
|
||||
{target_user ? <View style={[
|
||||
styles.targetUser,
|
||||
!show_friendcode_field ? styles.targetUserNoFriendCodeField : null,
|
||||
theme.targetUser,
|
||||
]}>
|
||||
<Image source={{uri: target_user.imageUri, width: 100, height: 100}} style={styles.targetUserImage} />
|
||||
|
||||
<View style={styles.targetUserDetail}>
|
||||
<Text style={[styles.targetUserName, theme.text]}>{target_user.name}</Text>
|
||||
|
||||
<Text style={[styles.targetUserNsaId, theme.text]}>NSA ID: <Text style={styles.targetUserNsaIdValue}>{target_user.nsaId}</Text></Text>
|
||||
<Text style={[styles.targetUserCoralId, theme.text]}>{target_user.id ? <>
|
||||
Coral user ID: <Text style={styles.targetUserCoralIdValue}>{target_user.id}</Text>
|
||||
</> : 'Never used Nintendo Switch Online app'}</Text>
|
||||
|
||||
{send_state[0] === SendFriendRequestState.SENT && send_state[3] ?
|
||||
<Text style={[styles.friendRequestState, theme.text]}>You are now friends with this user.</Text> :
|
||||
send_state[0] === SendFriendRequestState.SENT ?
|
||||
<Text style={[styles.friendRequestState, theme.text]}>Friend request sent. {target_user.name} can accept your friend request using a Nintendo Switch console, or by sending you a friend request using the Nintendo Switch Online app or nxapi.</Text> :
|
||||
send_state[0] === SendFriendRequestState.SENDING ?
|
||||
<Text style={[styles.friendRequestState, theme.text]}>
|
||||
<ActivityIndicator style={styles.friendRequestActivityIndicator} color={'#' + accent_colour} />
|
||||
Sending friend request...
|
||||
</Text> :
|
||||
send_state[0] === SendFriendRequestState.ERROR ?
|
||||
<TouchableOpacity onPress={showSendFriendRequestErrorDetails}>
|
||||
<Text style={[styles.friendRequestState, theme.text]}>
|
||||
<Text style={[styles.friendRequestStateIcon, {color: '#' + accent_colour}]}><Warning /></Text>
|
||||
Error sending friend request: {send_state[3].name} {send_state[3].message}
|
||||
</Text>
|
||||
</TouchableOpacity> :
|
||||
friend ?
|
||||
<Text style={[styles.friendRequestState, theme.text]}>You are already friends with this user.</Text> : null}
|
||||
</View>
|
||||
</View> : null}
|
||||
|
||||
<View style={styles.buttons}>
|
||||
<View style={styles.button}>
|
||||
<Button title="Close"
|
||||
onPress={() => window.close()}
|
||||
color={'#' + (accent_colour ?? DEFAULT_ACCENT_COLOUR)} />
|
||||
</View>
|
||||
|
||||
{lookup_state === RequestState.LOADED && target_user &&
|
||||
friends_state === RequestState.LOADED && !friend &&
|
||||
target_user?.nsaId !== user.nsoAccount.user.nsaId ? <View style={styles.button}>
|
||||
<Button title="Send friend request"
|
||||
onPress={sendFriendRequest}
|
||||
primary
|
||||
color={'#' + (accent_colour ?? DEFAULT_ACCENT_COLOUR)} />
|
||||
</View> : null}
|
||||
</View>
|
||||
</View>
|
||||
</Root>;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
loading: {
|
||||
flex: 1,
|
||||
paddingVertical: 50,
|
||||
paddingHorizontal: 20,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
|
||||
main: {
|
||||
flex: 1,
|
||||
paddingVertical: 20,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
|
||||
help: {
|
||||
marginTop: 8,
|
||||
fontSize: 13,
|
||||
opacity: 0.7,
|
||||
},
|
||||
|
||||
friendCodeInputContainer: {
|
||||
marginTop: 16,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
friendCodeInput: {
|
||||
marginTop: 0,
|
||||
flex: 1,
|
||||
fontSize: 18,
|
||||
fontFamily: 'monospace',
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 14,
|
||||
},
|
||||
activityIndicator: {
|
||||
marginLeft: 10,
|
||||
},
|
||||
iconTouchable: {
|
||||
marginLeft: 10,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
icon: {
|
||||
fontSize: 20,
|
||||
},
|
||||
|
||||
textInput: {
|
||||
marginTop: 8,
|
||||
paddingVertical: 5,
|
||||
paddingHorizontal: 10,
|
||||
borderRadius: 3,
|
||||
fontSize: 13,
|
||||
},
|
||||
|
||||
lookupErrorNoFriendCodeField: {
|
||||
},
|
||||
lookupErrorIcon: {
|
||||
marginRight: 10,
|
||||
},
|
||||
|
||||
targetUser: {
|
||||
marginTop: 20,
|
||||
padding: 14,
|
||||
borderRadius: 3,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
targetUserNoFriendCodeField: {
|
||||
marginTop: 0,
|
||||
},
|
||||
targetUserImage: {
|
||||
marginRight: 14,
|
||||
width: 100,
|
||||
},
|
||||
|
||||
targetUserDetail: {
|
||||
flex: 1,
|
||||
},
|
||||
targetUserName: {
|
||||
fontSize: 18,
|
||||
fontWeight: '500',
|
||||
marginBottom: 8,
|
||||
},
|
||||
|
||||
targetUserNsaId: {
|
||||
fontSize: 13,
|
||||
opacity: 0.7,
|
||||
},
|
||||
targetUserNsaIdValue: {
|
||||
fontFamily: 'monospace',
|
||||
userSelect: 'all',
|
||||
},
|
||||
targetUserCoralId: {
|
||||
fontSize: 13,
|
||||
opacity: 0.7,
|
||||
},
|
||||
targetUserCoralIdValue: {
|
||||
fontFamily: 'monospace',
|
||||
userSelect: 'all',
|
||||
},
|
||||
|
||||
friendRequestState: {
|
||||
marginTop: 10,
|
||||
},
|
||||
friendRequestActivityIndicator: {
|
||||
marginRight: 10,
|
||||
},
|
||||
friendRequestStateIcon: {
|
||||
marginRight: 10,
|
||||
},
|
||||
|
||||
buttons: {
|
||||
marginTop: 20,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
button: {
|
||||
marginLeft: 10,
|
||||
},
|
||||
});
|
||||
|
||||
const light = StyleSheet.create({
|
||||
text: {
|
||||
color: TEXT_COLOUR_LIGHT,
|
||||
},
|
||||
textInput: {
|
||||
backgroundColor: HIGHLIGHT_COLOUR_LIGHT,
|
||||
color: TEXT_COLOUR_LIGHT,
|
||||
},
|
||||
targetUser: {
|
||||
backgroundColor: HIGHLIGHT_COLOUR_LIGHT,
|
||||
},
|
||||
});
|
||||
|
||||
const dark = StyleSheet.create({
|
||||
text: {
|
||||
color: TEXT_COLOUR_DARK,
|
||||
},
|
||||
textInput: {
|
||||
backgroundColor: HIGHLIGHT_COLOUR_DARK,
|
||||
color: TEXT_COLOUR_DARK,
|
||||
},
|
||||
targetUser: {
|
||||
backgroundColor: HIGHLIGHT_COLOUR_DARK,
|
||||
},
|
||||
});
|
||||
9
src/app/browser/components/icons/add-outline.tsx
Normal file
9
src/app/browser/components/icons/add-outline.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
import { Platform, Text } from 'react-native';
|
||||
import { svg_styles } from './util.js';
|
||||
|
||||
const IconWeb = React.memo(() => <Text>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style={svg_styles}><title>Add</title><path fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="32" d="M256 112v288M400 256H112"/></svg>
|
||||
</Text>);
|
||||
|
||||
export default Platform.OS === 'web' ? IconWeb : React.memo(() => null);
|
||||
|
|
@ -24,9 +24,9 @@ export default function Friend(props: FriendProps) {
|
|||
|
||||
const [discord_presence_source, discord_presence_source_state] = useDiscordPresenceSource();
|
||||
|
||||
const [token] = useAsync(useCallback(() => ipc.getNintendoAccountNsoToken(props.user), [ipc, props.user]));
|
||||
const [token] = useAsync(useCallback(() => ipc.getNintendoAccountCoralToken(props.user), [ipc, props.user]));
|
||||
const [user] = useAsync(useCallback(() => token ?
|
||||
ipc.getSavedNsoToken(token) : Promise.resolve(null), [ipc, token]));
|
||||
ipc.getSavedCoralToken(token) : Promise.resolve(null), [ipc, token]));
|
||||
const [friends, , friends_state, forceRefreshFriends] = useAsync(useCallback(() => token ?
|
||||
ipc.getNsoFriends(token) : Promise.resolve(null), [ipc, token]));
|
||||
const friend = friends?.find(f => f.nsaId === props.friend);
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ import { config } from './ipc.js';
|
|||
import App from './main/index.js';
|
||||
import Friend from './friend/index.js';
|
||||
import DiscordSetup from './discord/index.js';
|
||||
import AddFriend from './add-friend/index.js';
|
||||
|
||||
AppRegistry.registerComponent('App', () => App);
|
||||
AppRegistry.registerComponent('Friend', () => Friend);
|
||||
AppRegistry.registerComponent('DiscordPresence', () => DiscordSetup);
|
||||
AppRegistry.registerComponent('AddFriend', () => AddFriend);
|
||||
|
||||
const style = window.document.createElement('style');
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ function DiscordPresenceSourceCoral(props: {
|
|||
source: DiscordPresenceSourceCoral;
|
||||
}) {
|
||||
const [token] = useAsync(useCallback(() =>
|
||||
ipc.getNintendoAccountNsoToken(props.source.na_id), [ipc, props.source.na_id]));
|
||||
ipc.getNintendoAccountCoralToken(props.source.na_id), [ipc, props.source.na_id]));
|
||||
const [friends, , friends_state, forceRefreshFriends] = useAsync(useCallback(() => token ?
|
||||
ipc.getNsoFriends(token) : Promise.resolve(null), [ipc, token]));
|
||||
const friend = friends?.find(f => f.nsaId === props.source.friend_nsa_id);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { Image, ImageStyle, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import ipc from '../ipc.js';
|
||||
import { useColourScheme, User } from '../util.js';
|
||||
import { useAccentColour, useColourScheme, User } from '../util.js';
|
||||
import { Friend, Presence, PresenceState } from '../../../api/coral-types.js';
|
||||
import { TEXT_COLOUR_ACTIVE, TEXT_COLOUR_DARK, TEXT_COLOUR_LIGHT } from '../constants.js';
|
||||
import Section from './section.js';
|
||||
import Section, { HEADER_SIZE } from './section.js';
|
||||
import AddOutline from '../components/icons/add-outline.js';
|
||||
|
||||
export default function Friends(props: {
|
||||
user: User;
|
||||
|
|
@ -13,6 +14,15 @@ export default function Friends(props: {
|
|||
error?: Error;
|
||||
}) {
|
||||
const theme = useColourScheme() === 'light' ? light : dark;
|
||||
const accent_colour = useAccentColour();
|
||||
|
||||
const showAddFriendModal = useCallback(() => {
|
||||
ipc.showAddFriendModal({user: props.user.user.id});
|
||||
}, [props.user.user.id]);
|
||||
|
||||
const header_buttons = <TouchableOpacity onPress={showAddFriendModal} style={styles.iconTouchable}>
|
||||
<Text style={[styles.icon, {color: '#' + accent_colour}]}><AddOutline /></Text>
|
||||
</TouchableOpacity>;
|
||||
|
||||
const onFriendCodeContextMenu = useCallback(() => {
|
||||
ipc.showFriendCodeMenu(props.user.nso!.nsoAccount.user.links.friendCode);
|
||||
|
|
@ -24,7 +34,7 @@ export default function Friends(props: {
|
|||
onContextMenu={onFriendCodeContextMenu}
|
||||
>SW-{props.user.nso!.nsoAccount.user.links.friendCode.id}</Text>;
|
||||
|
||||
return <Section title="Friends" loading={props.loading} error={props.error}>
|
||||
return <Section title="Friends" loading={props.loading} error={props.error} headerButtons={header_buttons}>
|
||||
{props.friends.length ? <ScrollView horizontal>
|
||||
<View style={styles.content}>
|
||||
{props.friends.map(f => <Friend key={f.nsaId} friend={f} user={props.user} />)}
|
||||
|
|
@ -93,6 +103,13 @@ function FriendPresence(props: {
|
|||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
iconTouchable: {
|
||||
marginLeft: 10,
|
||||
},
|
||||
icon: {
|
||||
fontSize: HEADER_SIZE,
|
||||
},
|
||||
|
||||
footer: {
|
||||
paddingBottom: 16,
|
||||
paddingHorizontal: ipc.platform === 'win32' ? 24 : 20,
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ export default function Main(props: {
|
|||
const accent_colour = useAccentColour();
|
||||
|
||||
const [announcements, announcements_error, announcements_state] = useAsync(useCallback(() => props.user.nsotoken ?
|
||||
ipc.getNsoAnnouncements(props.user.nsotoken) : Promise.resolve(null), [ipc, props.user.nsotoken]));
|
||||
ipc.getCoralAnnouncements(props.user.nsotoken) : Promise.resolve(null), [ipc, props.user.nsotoken]));
|
||||
const [friends, friends_error, friends_state, forceRefreshFriends] = useAsync(useCallback(() => props.user.nsotoken ?
|
||||
ipc.getNsoFriends(props.user.nsotoken) : Promise.resolve(null), [ipc, props.user.nsotoken]));
|
||||
const [webservices, webservices_error, webservices_state, forceRefreshWebServices] = useAsync(useCallback(() => props.user.nsotoken ?
|
||||
ipc.getNsoWebServices(props.user.nsotoken) : Promise.resolve(null), [ipc, props.user.nsotoken]));
|
||||
ipc.getWebServices(props.user.nsotoken) : Promise.resolve(null), [ipc, props.user.nsotoken]));
|
||||
const [active_event, active_event_error, active_event_state, forceRefreshActiveEvent] = useAsync(useCallback(() => props.user.nsotoken ?
|
||||
ipc.getNsoActiveEvent(props.user.nsotoken) : Promise.resolve(null), [ipc, props.user.nsotoken]));
|
||||
ipc.getCoralActiveEvent(props.user.nsotoken) : Promise.resolve(null), [ipc, props.user.nsotoken]));
|
||||
|
||||
const loading = announcements_state === RequestState.LOADING ||
|
||||
friends_state === RequestState.LOADING ||
|
||||
|
|
@ -49,7 +49,7 @@ export default function Main(props: {
|
|||
if (active_event_error) alert(active_event_error.stack ?? active_event_error.message);
|
||||
}, [friends_error, webservices_error, active_event_error]);
|
||||
|
||||
if (!announcements || !friends || !webservices || !active_event) {
|
||||
if (!friends || !webservices || !active_event) {
|
||||
if (loading) {
|
||||
return <View style={styles.loading}>
|
||||
<ActivityIndicator size="large" color={'#' + accent_colour} />
|
||||
|
|
@ -99,7 +99,7 @@ function MoonOnlyUser() {
|
|||
<Text style={[styles.moonOnlyUserText, theme.text]}>Login to the Nintendo Switch Online app to view details here, or use the nxapi command to access Parental Controls data.</Text>
|
||||
|
||||
<View style={styles.moonOnlyUserButton}>
|
||||
<Button title="Login" onPress={() => ipc.addNsoAccount()} color={'#' + accent_colour} primary />
|
||||
<Button title="Login" onPress={() => ipc.addCoralAccount()} color={'#' + accent_colour} primary />
|
||||
</View>
|
||||
</View>
|
||||
</Section>;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export default function Section(props: React.PropsWithChildren<{
|
|||
title: string;
|
||||
loading?: boolean;
|
||||
error?: Error;
|
||||
headerButtons?: React.ReactNode;
|
||||
}>) {
|
||||
const theme = useColourScheme() === 'light' ? light : dark;
|
||||
const accent_colour = useAccentColour();
|
||||
|
|
@ -25,13 +26,14 @@ export default function Section(props: React.PropsWithChildren<{
|
|||
props.error ? <TouchableOpacity onPress={showErrorDetails} style={styles.iconTouchable}>
|
||||
<Text style={[styles.icon, {color: '#' + accent_colour}]}><Warning /></Text>
|
||||
</TouchableOpacity> : null}
|
||||
{props.headerButtons}
|
||||
</View>
|
||||
|
||||
{props.children}
|
||||
</View>;
|
||||
}
|
||||
|
||||
const HEADER_SIZE = ipc.platform === 'win32' ? 24 : 14;
|
||||
export const HEADER_SIZE = ipc.platform === 'win32' ? 24 : 14;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
|
|
|||
|
|
@ -208,10 +208,10 @@ export async function getAccounts() {
|
|||
const accounts: User[] = [];
|
||||
|
||||
for (const id of ids ?? []) {
|
||||
const nsotoken = await ipc.getNintendoAccountNsoToken(id);
|
||||
const nsotoken = await ipc.getNintendoAccountCoralToken(id);
|
||||
const moontoken = await ipc.getNintendoAccountMoonToken(id);
|
||||
|
||||
const nso = nsotoken ? await ipc.getSavedNsoToken(nsotoken) ?? null : null;
|
||||
const nso = nsotoken ? await ipc.getSavedCoralToken(nsotoken) ?? null : null;
|
||||
const moon = moontoken ? await ipc.getSavedMoonToken(moontoken) ?? null : null;
|
||||
|
||||
if (!nso && !moon) continue;
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ export enum WindowType {
|
|||
MAIN_WINDOW = 'App',
|
||||
FRIEND = 'Friend',
|
||||
DISCORD_PRESENCE = 'DiscordPresence',
|
||||
ADD_FRIEND = 'AddFriend',
|
||||
}
|
||||
|
||||
interface WindowProps {
|
||||
[WindowType.MAIN_WINDOW]: import('../browser/main/index.js').AppProps;
|
||||
[WindowType.FRIEND]: import('../browser/friend/index.js').FriendProps;
|
||||
[WindowType.DISCORD_PRESENCE]: import('../browser/discord/index.js').DiscordSetupProps;
|
||||
[WindowType.ADD_FRIEND]: import('../browser/add-friend/index.js').AddFriendProps;
|
||||
}
|
||||
|
||||
export interface WindowConfiguration<T extends WindowType = WindowType> {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import Users, { CoralUser } from '../../common/users.js';
|
|||
import { setupIpc } from './ipc.js';
|
||||
import { dev, dir } from '../../util/product.js';
|
||||
import { addUserAgent } from '../../util/useragent.js';
|
||||
import { askUserForUri } from './util.js';
|
||||
|
||||
const debug = createDebug('app:main');
|
||||
|
||||
|
|
@ -135,9 +136,40 @@ function tryHandleUrl(app: App, url: string) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (url.match(/^com\.nintendo\.znca:\/\/(znca\/)?friendcode\/(\d{4}-\d{4}-\d{4})\/([A-Za-z0-9]{10})($|\?|\#)/i)) {
|
||||
handleOpenFriendCodeUri(app.store, url);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function handleOpenFriendCodeUri(store: Store, uri: string) {
|
||||
const match = uri.match(/^com\.nintendo\.znca:\/\/(znca\/)friendcode\/(\d{4}-\d{4}-\d{4})\/([A-Za-z0-9]{10})($|\?|\#)/i);
|
||||
if (!match) return;
|
||||
|
||||
const friendcode = match[2];
|
||||
const hash = match[3];
|
||||
|
||||
const selected_user = await askUserForUri(store, uri, 'Select a user to add friends');
|
||||
if (!selected_user) return;
|
||||
|
||||
createWindow(WindowType.ADD_FRIEND, {
|
||||
user: selected_user[1].user.id,
|
||||
friendcode,
|
||||
}, {
|
||||
// show: false,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
width: 560,
|
||||
height: 300,
|
||||
minWidth: 450,
|
||||
maxWidth: 700,
|
||||
minHeight: 300,
|
||||
maxHeight: 300,
|
||||
});
|
||||
}
|
||||
|
||||
class Updater {
|
||||
private _cache: UpdateCacheData | null = null;
|
||||
private _check: Promise<UpdateCacheData | null> | null = null;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { defaultTitle } from '../../discord/titles.js';
|
|||
import type { FriendProps } from '../browser/friend/index.js';
|
||||
import type { DiscordSetupProps } from '../browser/discord/index.js';
|
||||
import { EmbeddedPresenceMonitor } from './monitor.js';
|
||||
import { AddFriendProps } from '../browser/add-friend/index.js';
|
||||
|
||||
const debug = createDebug('app:main:ipc');
|
||||
|
||||
|
|
@ -49,12 +50,12 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) {
|
|||
ipcMain.handle('nxapi:accounts:add-coral', () => askAddNsoAccount(store.storage).then(u => u?.data.user.id));
|
||||
ipcMain.handle('nxapi:accounts:add-moon', () => askAddPctlAccount(store.storage).then(u => u?.data.user.id));
|
||||
|
||||
ipcMain.handle('nxapi:nso:gettoken', (e, id: string) => storage.getItem('NintendoAccountToken.' + id));
|
||||
ipcMain.handle('nxapi:nso:getcachedtoken', (e, token: string) => storage.getItem('NsoToken.' + token));
|
||||
ipcMain.handle('nxapi:nso:announcements', (e, token: string) => store.users.get(token).then(u => u.announcements.result));
|
||||
ipcMain.handle('nxapi:nso:friends', (e, token: string) => store.users.get(token).then(u => u.getFriends()));
|
||||
ipcMain.handle('nxapi:nso:webservices', (e, token: string) => store.users.get(token).then(u => u.getWebServices()));
|
||||
ipcMain.handle('nxapi:nso:openwebservice', (e, webservice: WebService, token: string, qs?: string) =>
|
||||
ipcMain.handle('nxapi:coral:gettoken', (e, id: string) => storage.getItem('NintendoAccountToken.' + id));
|
||||
ipcMain.handle('nxapi:coral:getcachedtoken', (e, token: string) => storage.getItem('NsoToken.' + token));
|
||||
ipcMain.handle('nxapi:coral:announcements', (e, token: string) => store.users.get(token).then(u => u.announcements.result));
|
||||
ipcMain.handle('nxapi:coral:friends', (e, token: string) => store.users.get(token).then(u => u.getFriends()));
|
||||
ipcMain.handle('nxapi:coral:webservices', (e, token: string) => store.users.get(token).then(u => u.getWebServices()));
|
||||
ipcMain.handle('nxapi:coral:openwebservice', (e, webservice: WebService, token: string, qs?: string) =>
|
||||
store.users.get(token).then(u => openWebService(store, token, u.nso, u.data, webservice, qs)
|
||||
.catch(err => dialog.showMessageBox(BrowserWindow.fromWebContents(e.sender)!, {
|
||||
type: 'error',
|
||||
|
|
@ -72,7 +73,10 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) {
|
|||
user_coral_id: u.data.nsoAccount.user.id,
|
||||
}, {compact: true}),
|
||||
}))));
|
||||
ipcMain.handle('nxapi:nso:activeevent', (e, token: string) => store.users.get(token).then(u => u.getActiveEvent()));
|
||||
ipcMain.handle('nxapi:coral:activeevent', (e, token: string) => store.users.get(token).then(u => u.getActiveEvent()));
|
||||
ipcMain.handle('nxapi:coral:friendcodeurl', (e, token: string) => store.users.get(token).then(u => u.nso.getFriendCodeUrl()).then(r => r.result));
|
||||
ipcMain.handle('nxapi:coral:friendcode', (e, token: string, friendcode: string, hash?: string) => store.users.get(token).then(u => u.nso.getUserByFriendCode(friendcode, hash)).then(r => r.result));
|
||||
ipcMain.handle('nxapi:coral:addfriend', (e, token: string, nsaid: string) => store.users.get(token).then(u => u.addFriend(nsaid)));
|
||||
|
||||
ipcMain.handle('nxapi:window:showfriend', (e, props: FriendProps) => createWindow(WindowType.FRIEND, props, {
|
||||
parent: BrowserWindow.fromWebContents(e.sender) ?? undefined,
|
||||
|
|
@ -100,6 +104,19 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) {
|
|||
minHeight: 300,
|
||||
maxHeight: 300,
|
||||
}).id);
|
||||
ipcMain.handle('nxapi:window:addfriend', (e, props: AddFriendProps) => createWindow(WindowType.ADD_FRIEND, props, {
|
||||
parent: BrowserWindow.fromWebContents(e.sender) ?? undefined,
|
||||
modal: true,
|
||||
show: false,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
width: 560,
|
||||
height: 300,
|
||||
minWidth: 450,
|
||||
maxWidth: 700,
|
||||
minHeight: 300,
|
||||
maxHeight: 300,
|
||||
}).id);
|
||||
ipcMain.handle('nxapi:window:setheight', (e, height: number) => {
|
||||
const window = BrowserWindow.fromWebContents(e.sender)!;
|
||||
const [curWidth, curHeight] = window.getSize();
|
||||
|
|
@ -228,6 +245,23 @@ function buildUserMenu(app: App, user: NintendoAccountUser, nso?: CurrentUser, m
|
|||
click: () => app.menu?.setFriendNotificationsActive(user.id, !monitor?.friend_notifications)}),
|
||||
new MenuItem({label: 'Update now', enabled: !!monitor,
|
||||
click: () => monitor?.skipIntervalInCurrentLoop(true)}),
|
||||
new MenuItem({type: 'separator'}),
|
||||
new MenuItem({label: 'Add friend',
|
||||
click: () => createWindow(WindowType.ADD_FRIEND, {
|
||||
user: user.id,
|
||||
}, {
|
||||
parent: window,
|
||||
modal: true,
|
||||
show: false,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
width: 560,
|
||||
height: 300,
|
||||
minWidth: 450,
|
||||
maxWidth: 700,
|
||||
minHeight: 300,
|
||||
maxHeight: 300,
|
||||
})}),
|
||||
] : []),
|
||||
new MenuItem({type: 'separator'}),
|
||||
new MenuItem({label: 'Use the nxapi command to remove this user', enabled: false}),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import { SavedToken } from '../../common/auth/coral.js';
|
|||
import { SavedMoonToken } from '../../common/auth/moon.js';
|
||||
import { dev, dir } from '../../util/product.js';
|
||||
import { EmbeddedPresenceMonitor, EmbeddedProxyPresenceMonitor } from './monitor.js';
|
||||
import { createWindow } from './windows.js';
|
||||
import { WindowType } from '../common/types.js';
|
||||
|
||||
const debug = createDebug('app:main:menu');
|
||||
|
||||
|
|
@ -67,6 +69,8 @@ export default class MenuApp {
|
|||
click: () => this.setFriendNotificationsActive(data.user.id, !monitor?.friend_notifications)},
|
||||
{label: 'Update now', enabled: !!monitor, click: () => monitor?.skipIntervalInCurrentLoop(true)},
|
||||
{type: 'separator'},
|
||||
{label: 'Add friend', click: () => this.showAddFriendWindow(data.user.id)},
|
||||
{type: 'separator'},
|
||||
{label: 'Web services', enabled: false},
|
||||
...await this.getWebServiceItems(token) as any,
|
||||
],
|
||||
|
|
@ -275,4 +279,20 @@ export default class MenuApp {
|
|||
this.saveMonitorState();
|
||||
this.updateMenu();
|
||||
}
|
||||
|
||||
showAddFriendWindow(user: string) {
|
||||
createWindow(WindowType.ADD_FRIEND, {
|
||||
user,
|
||||
}, {
|
||||
show: false,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
width: 560,
|
||||
height: 300,
|
||||
minWidth: 450,
|
||||
maxWidth: 700,
|
||||
minHeight: 300,
|
||||
maxHeight: 300,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { nativeImage } from './electron.js';
|
||||
import { BrowserWindow, Menu, MenuItem, nativeImage } from './electron.js';
|
||||
import path from 'node:path';
|
||||
import { Buffer } from 'node:buffer';
|
||||
import fetch from 'node-fetch';
|
||||
import { dir } from '../../util/product.js';
|
||||
import { Store } from './index.js';
|
||||
import { SavedToken } from '../../common/auth/coral.js';
|
||||
|
||||
export const bundlepath = path.resolve(dir, 'dist', 'app', 'bundle');
|
||||
|
||||
|
|
@ -23,3 +25,41 @@ export async function tryGetNativeImageFromUrl(url: URL | string, useragent?: st
|
|||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function askUserForUri(store: Store, uri: string, prompt: string): Promise<[string, SavedToken] | null> {
|
||||
const menu = new Menu();
|
||||
|
||||
const ids = await store.storage.getItem('NintendoAccountIds') as string[] | undefined;
|
||||
menu.append(new MenuItem({label: prompt, enabled: false}));
|
||||
menu.append(new MenuItem({label: uri, enabled: false}));
|
||||
menu.append(new MenuItem({type: 'separator'}));
|
||||
|
||||
let selected_user: [string, SavedToken] | null = null;
|
||||
|
||||
const items = await Promise.all(ids?.map(async id => {
|
||||
const token = await store.storage.getItem('NintendoAccountToken.' + id) as string | undefined;
|
||||
if (!token) return;
|
||||
const data = await store.storage.getItem('NsoToken.' + token) as SavedToken | undefined;
|
||||
if (!data) return;
|
||||
|
||||
return new MenuItem({
|
||||
label: data.nsoAccount.user.name,
|
||||
click: (menuItem, browserWindow, event) => {
|
||||
selected_user = [token, data];
|
||||
menu.closePopup(browserWindow);
|
||||
},
|
||||
});
|
||||
}) ?? []);
|
||||
|
||||
if (!items.length) return null;
|
||||
|
||||
for (const item of items) if (item) menu.append(item);
|
||||
menu.append(new MenuItem({type: 'separator'}));
|
||||
menu.append(new MenuItem({label: 'Cancel', click: (i, w) => menu.closePopup(w)}));
|
||||
|
||||
const window = new BrowserWindow({show: false});
|
||||
await new Promise<void>(rs => menu.popup({callback: rs}));
|
||||
window.destroy();
|
||||
|
||||
return selected_user;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { Store } from './index.js';
|
|||
import type { NativeShareRequest, NativeShareUrlRequest } from '../preload-webservice/znca-js-api.js';
|
||||
import { SavedToken } from '../../common/auth/coral.js';
|
||||
import { createWebServiceWindow } from './windows.js';
|
||||
import { askUserForUri } from './util.js';
|
||||
|
||||
const debug = createDebug('app:main:webservices');
|
||||
|
||||
|
|
@ -155,42 +156,8 @@ export async function handleOpenWebServiceUri(store: Store, uri: string) {
|
|||
return openWebService(store, selected_user[0], nso, data, webservice, new URL(uri).search.substr(1));
|
||||
}
|
||||
|
||||
async function askUserForWebServiceUri(store: Store, uri: string): Promise<[string, SavedToken] | null> {
|
||||
const menu = new Menu();
|
||||
|
||||
const ids = await store.storage.getItem('NintendoAccountIds') as string[] | undefined;
|
||||
menu.append(new MenuItem({label: 'Select a user to open this web service', enabled: false}));
|
||||
menu.append(new MenuItem({label: uri, enabled: false}));
|
||||
menu.append(new MenuItem({type: 'separator'}));
|
||||
|
||||
let selected_user: [string, SavedToken] | null = null;
|
||||
|
||||
const items = await Promise.all(ids?.map(async id => {
|
||||
const token = await store.storage.getItem('NintendoAccountToken.' + id) as string | undefined;
|
||||
if (!token) return;
|
||||
const data = await store.storage.getItem('NsoToken.' + token) as SavedToken | undefined;
|
||||
if (!data) return;
|
||||
|
||||
return new MenuItem({
|
||||
label: data.nsoAccount.user.name,
|
||||
click: (menuItem, browserWindow, event) => {
|
||||
selected_user = [token, data];
|
||||
menu.closePopup(browserWindow);
|
||||
},
|
||||
});
|
||||
}) ?? []);
|
||||
|
||||
if (!items.length) return null;
|
||||
|
||||
for (const item of items) if (item) menu.append(item);
|
||||
menu.append(new MenuItem({type: 'separator'}));
|
||||
menu.append(new MenuItem({label: 'Cancel', click: (i, w) => menu.closePopup(w)}));
|
||||
|
||||
const window = new BrowserWindow({show: false});
|
||||
await new Promise<void>(rs => menu.popup({callback: rs}));
|
||||
window.destroy();
|
||||
|
||||
return selected_user;
|
||||
function askUserForWebServiceUri(store: Store, uri: string) {
|
||||
return askUserForUri(store, uri, 'Select a user to open this web service');
|
||||
}
|
||||
|
||||
export interface WebServiceData {
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ import type { DiscordPresenceConfiguration, DiscordPresenceSource, WindowConfigu
|
|||
import type { SavedToken } from '../../common/auth/coral.js';
|
||||
import type { SavedMoonToken } from '../../common/auth/moon.js';
|
||||
import type { UpdateCacheData } from '../../common/update.js';
|
||||
import type { Announcements, CurrentUser, Friend, GetActiveEventResult, WebService, WebServices } from '../../api/coral-types.js';
|
||||
import type { Announcements, CoralSuccessResponse, CurrentUser, Friend, FriendCodeUrl, FriendCodeUser, GetActiveEventResult, WebService, WebServices } from '../../api/coral-types.js';
|
||||
import type { DiscordPresence } from '../../discord/util.js';
|
||||
import type { NintendoAccountUser } from '../../api/na.js';
|
||||
import type { DiscordSetupProps } from '../browser/discord/index.js';
|
||||
import type { FriendProps } from '../browser/friend/index.js';
|
||||
import { AddFriendProps } from '../browser/add-friend/index.js';
|
||||
|
||||
const debug = createDebug('app:preload');
|
||||
|
||||
|
|
@ -31,16 +32,19 @@ const ipc = {
|
|||
checkUpdates: () => inv<UpdateCacheData | null>('update:check'),
|
||||
|
||||
listNintendoAccounts: () => inv<string[] | undefined>('accounts:list'),
|
||||
addNsoAccount: () => inv<string>('accounts:add-coral'),
|
||||
addCoralAccount: () => inv<string>('accounts:add-coral'),
|
||||
addMoonAccount: () => inv<string>('accounts:add-moon'),
|
||||
|
||||
getNintendoAccountNsoToken: (id: string) => inv<string | undefined>('nso:gettoken', id),
|
||||
getSavedNsoToken: (token: string) => inv<SavedToken | undefined>('nso:getcachedtoken', token),
|
||||
getNsoAnnouncements: (token: string) => inv<Announcements>('nso:announcements', token),
|
||||
getNsoFriends: (token: string) => inv<Friend[]>('nso:friends', token),
|
||||
getNsoWebServices: (token: string) => inv<WebServices | undefined>('nso:webservices', token),
|
||||
openWebService: (webservice: WebService, token: string, qs?: string) => inv<number>('nso:openwebservice', webservice, token, qs),
|
||||
getNsoActiveEvent: (token: string) => inv<GetActiveEventResult>('nso:activeevent', token),
|
||||
getNintendoAccountCoralToken: (id: string) => inv<string | undefined>('coral:gettoken', id),
|
||||
getSavedCoralToken: (token: string) => inv<SavedToken | undefined>('coral:getcachedtoken', token),
|
||||
getCoralAnnouncements: (token: string) => inv<Announcements>('coral:announcements', token),
|
||||
getNsoFriends: (token: string) => inv<Friend[]>('coral:friends', token),
|
||||
getWebServices: (token: string) => inv<WebServices | undefined>('coral:webservices', token),
|
||||
openWebService: (webservice: WebService, token: string, qs?: string) => inv<number>('coral:openwebservice', webservice, token, qs),
|
||||
getCoralActiveEvent: (token: string) => inv<GetActiveEventResult>('coral:activeevent', token),
|
||||
getNsoFriendCodeUrl: (token: string) => inv<FriendCodeUrl>('coral:friendcodeurl', token),
|
||||
getNsoUserByFriendCode: (token: string, friendcode: string, hash?: string) => inv<FriendCodeUser>('coral:friendcode', token, friendcode, hash),
|
||||
addNsoFriend: (token: string, nsa_id: string) => inv<{result: CoralSuccessResponse<{}>; friend: Friend | null}>('coral:addfriend', token, nsa_id),
|
||||
|
||||
getDiscordPresenceConfig: () => inv<DiscordPresenceConfiguration | null>('discord:config'),
|
||||
setDiscordPresenceConfig: (config: DiscordPresenceConfiguration | null) => inv<void>('discord:setconfig', config),
|
||||
|
|
@ -55,6 +59,7 @@ const ipc = {
|
|||
|
||||
showFriendModal: (props: FriendProps) => inv<number>('window:showfriend', props),
|
||||
showDiscordModal: (props: DiscordSetupProps = {}) => inv<number>('window:discord', props),
|
||||
showAddFriendModal: (props: AddFriendProps) => inv<number>('window:addfriend', props),
|
||||
setWindowHeight: (height: number) => inv('window:setheight', height),
|
||||
|
||||
openExternalUrl: (url: string) => inv('misc:open-url', url),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import createDebug from 'debug';
|
|||
import * as persist from 'node-persist';
|
||||
import CoralApi from '../api/coral.js';
|
||||
import ZncProxyApi from '../api/znc-proxy.js';
|
||||
import { Announcements, Friends, GetActiveEventResult, WebServices, CoralSuccessResponse } from '../api/coral-types.js';
|
||||
import { Announcements, Friends, GetActiveEventResult, WebServices, CoralSuccessResponse, Friend } from '../api/coral-types.js';
|
||||
import { getToken, SavedToken } from './auth/coral.js';
|
||||
|
||||
const debug = createDebug('nxapi:users');
|
||||
|
|
@ -136,4 +136,32 @@ export class CoralUser<T extends CoralApi = CoralApi> implements CoralUserData<T
|
|||
|
||||
return this.active_event.result;
|
||||
}
|
||||
|
||||
async addFriend(nsa_id: string) {
|
||||
if (nsa_id === this.data.nsoAccount.user.nsaId) {
|
||||
throw new Error('Cannot add self as a friend');
|
||||
}
|
||||
|
||||
const result = await this.nso.sendFriendRequest(nsa_id);
|
||||
|
||||
// Check if the user is now friends
|
||||
// The Nintendo Switch Online app doesn't do this, but if the other user already sent a friend request to
|
||||
// this user, they will be added as friends immediately. If the user is now friends we can show a message
|
||||
// saying that, instead of saying that a friend request was sent when the user actually just accepted the
|
||||
// other user's friend request.
|
||||
let friend: Friend | null = null;
|
||||
|
||||
try {
|
||||
// Clear the last updated timestamp to force updating the friend list
|
||||
this.updated.friends = 0;
|
||||
|
||||
const friends = await this.getFriends();
|
||||
friend = friends.find(f => f.nsaId === nsa_id) ?? null;
|
||||
} catch (err) {
|
||||
debug('Error updating friend list for %s to check if a friend request was accepted',
|
||||
this.data.nsoAccount.user.name, err);
|
||||
}
|
||||
|
||||
return {result, friend};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user