mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-10-26 03:21:50 -05:00
Merge branch 'i18n' into pull/63
# Conflicts: # src/app/i18n/index.ts
This commit is contained in:
@@ -2,7 +2,11 @@ const electron = require('electron');
|
||||
|
||||
// Do anything that must be run before the app is ready...
|
||||
|
||||
electron.app.whenReady()
|
||||
Promise.all([
|
||||
// @ts-expect-error
|
||||
typeof __NXAPI_BUNDLE_APP_INIT__ !== 'undefined' ? import(__NXAPI_BUNDLE_APP_INIT__) : import('./app-init.js'),
|
||||
electron.app.whenReady(),
|
||||
])
|
||||
// @ts-expect-error
|
||||
.then(() => typeof __NXAPI_BUNDLE_APP_MAIN__ !== 'undefined' ? import(__NXAPI_BUNDLE_APP_MAIN__) : import('./main/index.js'))
|
||||
.then(m => m.init.call(null))
|
||||
|
||||
5
src/app/app-init.ts
Normal file
5
src/app/app-init.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { join } from 'node:path';
|
||||
import { init as initDebug } from '../util/debug.js';
|
||||
import { paths } from '../util/product.js';
|
||||
|
||||
await initDebug(join(paths.log, 'app'));
|
||||
@@ -19,7 +19,7 @@ export default function DiscordPresenceSource(props: {
|
||||
return <TouchableOpacity onPress={() => ipc.showDiscordModal()}>
|
||||
<View style={[styles.discord, !props.source ? styles.discordInactive : null]}>
|
||||
{renderDiscordPresenceSource(props.source)}
|
||||
{props.presence && props.user ? <DiscordPresence presence={props.presence} user={props.user} /> : null}
|
||||
{props.presence || props.user ? <DiscordPresence presence={props.presence} user={props.user} /> : null}
|
||||
</View>
|
||||
</TouchableOpacity>;
|
||||
}
|
||||
@@ -86,28 +86,37 @@ function DiscordPresenceInactive() {
|
||||
}
|
||||
|
||||
function DiscordPresence(props: {
|
||||
presence: DiscordPresence;
|
||||
user: User;
|
||||
presence: DiscordPresence | null;
|
||||
user: User | null;
|
||||
}) {
|
||||
const { t, i18n } = useTranslation('main_window', { keyPrefix: 'sidebar' });
|
||||
|
||||
const large_image_url = props.presence.activity.largeImageKey?.match(/^\d{16}$/) ?
|
||||
const large_image_url = props.presence ? props.presence.activity.largeImageKey?.match(/^\d{16}$/) ?
|
||||
'https://cdn.discordapp.com/app-assets/' + props.presence.id + '/' +
|
||||
props.presence.activity.largeImageKey + '.png' :
|
||||
props.presence.activity.largeImageKey;
|
||||
const user_image_url = 'https://cdn.discordapp.com/avatars/' + props.user.id + '/' + props.user.avatar + '.png';
|
||||
props.presence.activity.largeImageKey : undefined;
|
||||
|
||||
return <>
|
||||
<View style={styles.discordPresence}>
|
||||
const user_image_url = props.user ?
|
||||
props.user.avatar ? 'https://cdn.discordapp.com/avatars/' + props.user.id + '/' + props.user.avatar + '.png' :
|
||||
!props.user.discriminator || props.user.discriminator === '0' ?
|
||||
'https://cdn.discordapp.com/embed/avatars/' + ((parseInt(props.user.id) >> 22) % 5) + '.png' :
|
||||
'https://cdn.discordapp.com/embed/avatars/' + (parseInt(props.user.discriminator) % 5) + '.png' : undefined;
|
||||
|
||||
return <View style={styles.discordPresenceContainer}>
|
||||
{props.presence ? <View style={styles.discordPresence}>
|
||||
<Image source={{uri: large_image_url, width: 18, height: 18}} style={styles.discordPresenceImage} />
|
||||
<Text style={styles.discordPresenceText} numberOfLines={1} ellipsizeMode="tail">{t('discord_playing')}</Text>
|
||||
</View>
|
||||
</View> : null}
|
||||
|
||||
<View style={styles.discordUser}>
|
||||
{props.user ? <View style={styles.discordUser}>
|
||||
<Image source={{uri: user_image_url, width: 18, height: 18}} style={styles.discordUserImage} />
|
||||
<Text style={styles.discordUserText} numberOfLines={1} ellipsizeMode="tail">{props.user.username}#{props.user.discriminator}</Text>
|
||||
</View>
|
||||
</>;
|
||||
<Text style={styles.discordUserText} numberOfLines={1} ellipsizeMode="tail">
|
||||
{props.user.username}<Text style={styles.discordUserDiscriminator}>#{props.user.discriminator}</Text>
|
||||
</Text>
|
||||
</View> : <View style={styles.discordUser}>
|
||||
<Text style={styles.discordUserText} numberOfLines={1} ellipsizeMode="tail">{t('discord_not_connected')}</Text>
|
||||
</View>}
|
||||
</View>;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@@ -131,8 +140,12 @@ const styles = StyleSheet.create({
|
||||
userSelect: 'all',
|
||||
},
|
||||
|
||||
discordPresenceContainer: {
|
||||
marginTop: 2,
|
||||
},
|
||||
|
||||
discordPresence: {
|
||||
marginTop: 12,
|
||||
marginTop: 10,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
@@ -156,4 +169,7 @@ const styles = StyleSheet.create({
|
||||
discordUserText: {
|
||||
color: TEXT_COLOUR_DARK,
|
||||
},
|
||||
discordUserDiscriminator: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,6 +13,13 @@ export const languages = {
|
||||
['Samuel Elliott', 'https://gitlab.fancy.org.uk/samuel', 'https://github.com/samuelthomas2774'],
|
||||
],
|
||||
},
|
||||
'de-DE': {
|
||||
name: 'Deutsch',
|
||||
app: () => import('./locale/de-de.js'),
|
||||
authors: [
|
||||
['Inkception', 'https://github.com/Inkception'],
|
||||
],
|
||||
},
|
||||
'ja-JP': {
|
||||
name: 'Japanese',
|
||||
app: () => import('./locale/ja-jp.js'),
|
||||
|
||||
393
src/app/i18n/locale/de-de.ts
Normal file
393
src/app/i18n/locale/de-de.ts
Normal file
@@ -0,0 +1,393 @@
|
||||
import { CREDITS_NOTICE, LICENCE_NOTICE } from '../../../common/constants.js';
|
||||
|
||||
export const app = {
|
||||
default_title: 'Nintendo Switch Online',
|
||||
|
||||
licence: LICENCE_NOTICE,
|
||||
credits: CREDITS_NOTICE,
|
||||
translation_credits: '{{language}} Übersetzungen von {{authors, list}}.',
|
||||
};
|
||||
|
||||
export const app_menu = {
|
||||
preferences: 'Einstellungen',
|
||||
view: 'Anschauen',
|
||||
learn_more: 'Mehr erfahren',
|
||||
learn_more_github: 'Mehr erfahren (GitHub)',
|
||||
search_issues: 'Probleme untersuchen',
|
||||
|
||||
refresh: 'Aktualisieren',
|
||||
};
|
||||
|
||||
export const menu_app = {
|
||||
coral_heading: 'Nintendo Switch Online',
|
||||
na_id: 'Nintendo Account ID: {{id}}',
|
||||
coral_id: 'Coral ID: {{id}}',
|
||||
nsa_id: 'NSA ID: {{id}}',
|
||||
discord_presence_enable: 'Discord Rich Presence aktivieren',
|
||||
user_notifications_enable: 'Aktiviere Benachrichtigungen für diesen User',
|
||||
friend_notifications_enable: 'Aktiviere Benachrichtigungen für Freunde von diesen User',
|
||||
refresh: 'Jetzt aktualisieren',
|
||||
add_friend: 'Freund hinzufügen',
|
||||
web_services: 'Web-Services',
|
||||
|
||||
moon_heading: 'Nintendo Switch-Altersbeschränkungen',
|
||||
|
||||
add_account: 'Account hinzufügen',
|
||||
|
||||
show_main_window: 'Zeige Hauptfenster',
|
||||
preferences: 'Einstellungen',
|
||||
quit: 'Beenden',
|
||||
};
|
||||
|
||||
export const menus = {
|
||||
add_account: {
|
||||
add_account_coral: 'Nintendo Switch Online Account hinzufügen',
|
||||
add_account_moon: 'Altersbeschränkter Nintendo Switch Account hinzufügen',
|
||||
},
|
||||
|
||||
friend_code: {
|
||||
share: 'Teilen',
|
||||
copy: 'Kopieren',
|
||||
friend_code_regenerable: 'Regeneriere durch Nintendo Switch Konsole',
|
||||
friend_code_regenerable_at: 'Kann {{date, datetime}} neu generiert werden',
|
||||
},
|
||||
|
||||
user: {
|
||||
na_id: 'Nintendo Account ID: {{id}}',
|
||||
coral_id: 'Coral ID: {{id}}',
|
||||
nsa_id: 'NSA ID: {{id}}',
|
||||
discord_disable: 'Deaktiviere Discord Rich Presence',
|
||||
discord_enabled_for: 'Discord Rich Presence für {{name}} aktiviert',
|
||||
discord_enabled_via: 'Discord Rich Presence durch {{name}} aktiviert',
|
||||
discord_enable: 'Aktiviere Discord Rich Presence für diesen User...',
|
||||
friend_notifications_enable: 'Aktiviere Freund-Benachrichtigungen',
|
||||
refresh: 'Jetzt aktualisieren',
|
||||
add_friend: 'Freund hinzufügen',
|
||||
remove_help: 'Benutze den nxapi Befehl, um diesen User zu entfernen',
|
||||
},
|
||||
|
||||
friend: {
|
||||
presence_online: 'Online',
|
||||
game_first_played: 'Zuerst gespielt: {{date, datetime}}',
|
||||
|
||||
game_play_time_h: 'Spielzeit: $t(friend.hours, {"count": {{hours}}})',
|
||||
game_play_time_hm: 'Spielzeit: $t(friend.hours, {"count": {{hours}}}), $t(friend.minutes, {"count": {{minutes}}})',
|
||||
game_play_time_m: 'Spielzeit: $t(friend.minutes, {"count": {{minutes}}})',
|
||||
hours_one: '{{count}} Stunde',
|
||||
hours_other: '{{count}} Stunden',
|
||||
minutes_one: '{{count}} Minute',
|
||||
minutes_other: '{{count}} Minuten',
|
||||
|
||||
presence_inactive: 'Offline (Konsole online)',
|
||||
presence_offline: 'Offline',
|
||||
presence_updated: 'Aktualisiert: {{date, datetime}}',
|
||||
presence_logout_time: 'Ausgeloggt: {{date, datetime}}',
|
||||
discord_presence_enable: 'Aktiviere Discord Rich Presence',
|
||||
},
|
||||
};
|
||||
|
||||
export const notifications = {
|
||||
playing: 'Spielt {{name}}',
|
||||
offline: 'Offline',
|
||||
};
|
||||
|
||||
export const handle_uri = {
|
||||
friend_code_select: 'Wähle einen User aus, um Freunde hinzuzufügen',
|
||||
web_service_select: 'Wähle einen User aus, um diesen Service zu öffnen',
|
||||
web_service_invalid_title: 'Unbekannter Titel',
|
||||
web_service_invalid_detail: 'Die angegebene URL verwies nicht auf einen existierenden Web-Service.',
|
||||
cancel: 'Abbrechen',
|
||||
};
|
||||
|
||||
export const na_auth = {
|
||||
window: {
|
||||
title: 'Nintendo Account',
|
||||
},
|
||||
|
||||
znca_api_use: {
|
||||
title: 'Verwendung einer Drittanbieter-API',
|
||||
|
||||
text: `Um Zugriff auf die API der Nintendo Switch Online App zu erhalten, muss nxapi einige Daten an Drittanbieter-APIs senden. Dieser Schritt wird benötigt, um Daten zu generieren, damit Nintendo denkt, dass du die echte Nintendo Switch Online App verwendest.
|
||||
Standardmäßig wird nxapi-znca-api.fancy.org.uk oder api.imink.app benutzt. Ein anderer Service kann ebenfalls benutzt werden, indem eine Umgebungsvariable gesetzt wird. Die standardmäßige API könnte sich jederzeit ohne Hinweis ändern, wenn du keinen spezifischen Service erzwingst.
|
||||
|
||||
Die gesendeten Daten beinhalten:
|
||||
|
||||
- Deine Nintendo Account ID
|
||||
- Bei Authorisierung mit der Nintendo Switch Online App: Ein Nintendo Account ID Token, welcher dein Land beinhaltet und für 15 Minuten gültig ist
|
||||
- Bei Authorisierung mit Spielspezifischen Services: Deine Coral (Nintendo Switch Online App) User ID und ein Coral ID Token, welcher deine aktuelle Nintendo Switch Online Mitgliedschaft und Altersbeschränkungsstatus beinhaltet und für 2 Stunden gültig ist`,
|
||||
|
||||
ok: 'OK',
|
||||
cancel: 'Abbrechen',
|
||||
more_information: 'Weitere Informationen',
|
||||
},
|
||||
|
||||
notification_coral: {
|
||||
title: 'Nintendo Switch Online',
|
||||
body_existing: 'Bereits als {{name}} (Nintendo Account {{na_name}} / {{na_username}}) eingeloggt',
|
||||
body_authenticated: 'Authentifiziert als {{name}} (Nintendo Account {{na_name}} / {{na_username}})',
|
||||
body_reauthenticated: 'Erneut als {{name}} (Nintendo Account {{na_name}} / {{na_username}}) authentifiziert',
|
||||
},
|
||||
|
||||
notification_moon: {
|
||||
title: 'Nintendo Switch-Altersbeschränkungen',
|
||||
body_existing: 'Bereits als {{na_name}} ({{na_username}}) eingeloggt',
|
||||
body_authenticated: 'Authentifiziert als {{na_name}} ({{na_username}})',
|
||||
body_reauthenticated: 'Erneut als {{na_name}} ({{na_username}}) authentifiziert',
|
||||
},
|
||||
|
||||
error: {
|
||||
title: 'Fehler beim Hinzufügen des Accounts',
|
||||
},
|
||||
};
|
||||
|
||||
export const time_since = {
|
||||
default: {
|
||||
now: 'jetzt',
|
||||
seconds_one: 'Vor {{count}} Sekunde',
|
||||
seconds_other: 'Vor {{count}} Sekunden',
|
||||
minutes_one: 'Vor {{count}} Minute',
|
||||
minutes_other: 'Vor {{count}} Minuten',
|
||||
hours_one: 'Vor {{count}} Stunde',
|
||||
hours_other: 'Vor {{count}} Stunden',
|
||||
days_one: 'Vor {{count}} Tag',
|
||||
days_other: 'Vor {{count}} Tagen',
|
||||
},
|
||||
|
||||
short: {
|
||||
now: 'jetzt',
|
||||
seconds_one: '{{count}} Sek',
|
||||
seconds_other: '{{count}} Sek',
|
||||
minutes_one: '{{count}} Min',
|
||||
minutes_other: '{{count}} Min',
|
||||
hours_one: '{{count}} Std',
|
||||
hours_other: '{{count}} Std',
|
||||
days_one: '{{count}} Tg',
|
||||
days_other: '{{count}} Tg',
|
||||
},
|
||||
};
|
||||
|
||||
export const main_window = {
|
||||
sidebar: {
|
||||
discord_active: 'Discord Rich Presence aktiv',
|
||||
discord_active_friend: 'Discord Rich Presence aktiv: <0></0>',
|
||||
discord_not_active: 'Discord Rich Presence nicht aktiv',
|
||||
discord_playing: 'Spielt',
|
||||
discord_not_connected: 'Nicht mit Discord verbunden',
|
||||
|
||||
add_user: 'User hinzufügen',
|
||||
discord_setup: 'Discord Rich Presence einrichten',
|
||||
|
||||
enable_auto_refresh: 'Automatisch aktualisieren',
|
||||
},
|
||||
|
||||
update: {
|
||||
update_available: 'Update verfügbar: {{name}}',
|
||||
download: 'Download',
|
||||
error: 'Fehler beim Überprüfen von Updates aufgetreten: {{message}}',
|
||||
retry: 'Erneut versuchen',
|
||||
},
|
||||
|
||||
main_section: {
|
||||
error: {
|
||||
title: 'Fehler beim Laden der Daten',
|
||||
message: 'Ein Fehler ist beim Laden der {{errors, list}} Daten aufgetreten.',
|
||||
message_friends: 'freunde',
|
||||
message_webservices: 'spielspezifische services',
|
||||
message_event: 'sprachchat',
|
||||
retry: 'Erneut versuchen',
|
||||
view_details: 'Details anschauen',
|
||||
},
|
||||
|
||||
moon_only_user: {
|
||||
title: 'Nintendo Switch Online',
|
||||
desc_1: 'Dieser User ist mit der Nintendo Switch-Altersbeschränkungen App angemeldet, aber nicht mit der Nintendo Switch Online App.',
|
||||
desc_2: 'Logge dich mit der Nintendo Switch Online App ein, um Details einzusehen oder benutze den nxapi Befehl, um auf Altersbeschränkungen zuzugreifen.',
|
||||
login: 'Login',
|
||||
},
|
||||
|
||||
section_error: 'Fehler beim Aktualisieren der Daten',
|
||||
},
|
||||
|
||||
discord_section: {
|
||||
title: 'Discord Rich Presence',
|
||||
|
||||
setup_with_existing_user: 'Benutze einer dieser Account, um die Discord Rich Presence einzurichten: <0></0>.',
|
||||
add_user: 'Füge einen Nintendo Switch Online Account mit diesem User als Freund hinzu, um die Discord Rich Presence einzurichten.',
|
||||
active_self: 'Die Aktivität wird mit diesem User auf Discord geteilt.',
|
||||
active_friend: '<0></0>\'s Aktivität wird mit diesem Account auf Discord geteilt.',
|
||||
active_unknown: 'Die Aktivität eines unbekannten Users wird mit Discord geteilt.',
|
||||
active_via: 'Die Aktivität wird mit diesem User auf Discord über <0></0> geteilt.',
|
||||
|
||||
setup: 'Einrichten',
|
||||
disable: 'Deaktivieren',
|
||||
},
|
||||
|
||||
friends_section: {
|
||||
title: 'Freunde',
|
||||
|
||||
no_friends: 'Füge Freunde mit deiner Nintendo Switch Konsole hinzu.',
|
||||
friend_code: 'Dein Freundescode: <0></0>',
|
||||
|
||||
presence_playing: 'Spielt',
|
||||
presence_offline: 'Offline',
|
||||
},
|
||||
|
||||
webservices_section: {
|
||||
title: 'Spielspezifische Services',
|
||||
},
|
||||
|
||||
event_section: {
|
||||
title: 'Sprachchat',
|
||||
|
||||
members: '{{event}} im Spiel, {{voip}} im Sprachchat',
|
||||
members_with_total: '{{event}} im Spiel, {{voip}} von {{total}} im Sprachkanal',
|
||||
|
||||
app_start: 'Benutze die Nintendo Switch Online App auf iOS oder Android, um einen Sprachchat zu starten.',
|
||||
app_join: 'Benutze die Nintendo Switch Online App auf iOS oder Android, um einem Sprachchat beizutreten.',
|
||||
|
||||
share: 'Teilen',
|
||||
},
|
||||
};
|
||||
|
||||
export const preferences_window = {
|
||||
title: 'Einstellungen',
|
||||
|
||||
startup: {
|
||||
heading: 'Start',
|
||||
login: 'Öffne beim Start',
|
||||
background: 'Öffne im Hintergrund',
|
||||
},
|
||||
|
||||
sleep: {
|
||||
heading: 'Sleep',
|
||||
},
|
||||
|
||||
discord: {
|
||||
heading: 'Discord Rich Presence',
|
||||
enabled: 'Discord Rich Presence ist aktiviert.',
|
||||
disabled: 'Discord Rich Presence ist deaktiviert.',
|
||||
setup: 'Discord Rich Presence Setup',
|
||||
|
||||
user: 'Discord User',
|
||||
user_any: 'Zuerst gesehen',
|
||||
|
||||
friend_code: 'Freundescode',
|
||||
friend_code_help: 'Wenn du deinen Freundescode hinzufügst, wird ebenfalls dein User Icon in Discord angezeigt.',
|
||||
friend_code_self: 'Teile meinen Freundescode',
|
||||
friend_code_custom: 'Eigenen Freundescode festlegen',
|
||||
|
||||
inactive_presence: 'Zeige inaktive Aktivität',
|
||||
inactive_presence_help: 'Zeigt "Spielt nicht", wenn eine verbundene Konsole online ist, du aber nicht spielst.',
|
||||
|
||||
play_time: 'Spielzeit',
|
||||
play_time_hidden: 'Spielzeit niemals anzeigen',
|
||||
play_time_nintendo: 'Zeige Spielzeit wie sie auf der Nintendo Switch Konsole angezeigt wird',
|
||||
play_time_approximate_play_time: 'Zeige ungefähre Spielzeit (nächste 5 Stunden) an',
|
||||
play_time_approximate_play_time_since: 'Zeige ungefähre Spielzeit (nächste 5 Stunden) mit erstem Startdatum an',
|
||||
play_time_hour_play_time: 'Zeige ungefähre Spielzeit (nächste Stunde) an',
|
||||
play_time_hour_play_time_since: 'Zeige ungefähre Spielzeit (nächste Stunde) mit erstem Startdatum an',
|
||||
play_time_detailed_play_time: 'Zeige exakte Spielzeit an',
|
||||
play_time_detailed_play_time_since: 'Zeige exakte Spielzeit mit erstem Startdatum an',
|
||||
},
|
||||
|
||||
splatnet3: {
|
||||
heading: 'SplatNet 3',
|
||||
discord: 'Aktiviere die erweiterte Discord Rich Presence für Splatoon 3',
|
||||
discord_help_1: 'Benutzt SplatNet 3, um zusätzliche Informationen anzuzeigen, während du Splatoon 3 spielst. Du musst einen zweiten Nintendo Switch Account hinzufügen, welcher mit deinem Hauptaccount befreundet ist und Zugriff auf SplatNet 3 hat.',
|
||||
discord_help_2: 'Wenn du eine Presence URL benutzt, werden die zusätzlichen Informationen angezeigt, ohne diese Einstellung zu berücksichtigen.',
|
||||
},
|
||||
};
|
||||
|
||||
export const friend_window = {
|
||||
no_presence: 'Du hast keinen Zugriff auf die Aktivität von diesem User oder er war nie online.',
|
||||
|
||||
nsa_id: 'NSA ID',
|
||||
coral_id: 'Coral user ID',
|
||||
no_coral_user: 'Hat nie die Nintendo Switch Online App genutzt',
|
||||
|
||||
friends_since: 'Freunde seit: {{date, datetime}}',
|
||||
presence_updated_at: 'Aktivität aktualisiert: {{date, datetime}}',
|
||||
presence_logout_at: 'Zuletzt online: {{date, datetime}}',
|
||||
|
||||
presence_sharing: 'Dieser User kann deine Aktivität sehen.',
|
||||
presence_not_sharing: 'Dieser User kann deine Aktivität nicht sehen.',
|
||||
|
||||
discord_presence: 'Aktivität auf Discord teilen',
|
||||
close: 'Schließen',
|
||||
|
||||
presence_playing: 'Spielt {{game}}',
|
||||
presence_offline: 'Offline',
|
||||
presence_last_seen: 'Zuletzt gesehen: {{since_logout}}',
|
||||
|
||||
game_played_for_h: 'Gespielt für $t(hours, {"count": {{hours}}})',
|
||||
game_played_for_hm: 'Gespielt für $t(hours, {"count": {{hours}}}), $t(minutes, {"count": {{minutes}}})',
|
||||
game_played_for_m: 'Gespielt für $t(minutes, {"count": {{minutes}}})',
|
||||
hours_one: '{{count}} Stunde',
|
||||
hours_other: '{{count}} Stunden',
|
||||
minutes_one: '{{count}} Minute',
|
||||
minutes_other: '{{count}} Minuten',
|
||||
|
||||
game_first_played: 'Zuerst gespielt am {{date, datetime}}',
|
||||
game_first_played_now: 'Zuerst gespielt jetzt',
|
||||
game_title_id: 'Titel ID',
|
||||
game_shop: 'Nintendo eShop',
|
||||
};
|
||||
|
||||
export const addfriend_window = {
|
||||
title: 'Freund hinzufügen',
|
||||
help: 'Gebe oder füge einen Freundescode oder eine Freundescode-URL ein, um eine Freundschaftsanfrage zu senden.',
|
||||
|
||||
lookup_error: 'Fehler beim Aufrufen des Freundescodes aufgetreten: {{message}}',
|
||||
|
||||
nsa_id: 'NSA ID',
|
||||
coral_id: 'Coral user ID',
|
||||
no_coral_user: 'Hat nie die Nintendo Switch Online App genutzt',
|
||||
|
||||
send_added: 'Du bist nun mit diesem User befreundet.',
|
||||
send_sent: 'Freundschaftsanfrage gesendet. {{user}} kann deine Freundschaftsanfrage über die Nintendo Switch Konsole annehmen oder dir eine Freundschaftsanfrage über die Nintendo Switch Online App oder nxapi senden.',
|
||||
send_sending: 'Sende Freundschaftsanfrage...',
|
||||
send_error: 'Fehler beim Senden der Freundschaftsanfrage aufgetreten: {{message}}',
|
||||
|
||||
already_friends: 'Du bist bereits mit diesem User befreundet',
|
||||
|
||||
close: 'Schließen',
|
||||
send: 'Anfrage senden',
|
||||
};
|
||||
|
||||
export const discordsetup_window = {
|
||||
title: 'Discord Rich Presence Setup',
|
||||
|
||||
mode_heading: '1. Wähle den Modus aus',
|
||||
mode_coral_friend: 'Wähle einen User, der mit dir befreundet ist, aus.',
|
||||
mode_url: 'Gebe eine URL ein, die deine Aktivität zurückgibt.',
|
||||
mode_none: 'Deaktivieren',
|
||||
|
||||
coral_user_heading: '2. Wähle den User aus',
|
||||
coral_user_help: 'Der User muss mit dir befreundet sein, um die Aktivität zu teilen.',
|
||||
coral_friend_heading: '3. Wähle einen Freund aus',
|
||||
coral_friend_help: 'Das ist der User, den du teilen möchtest.',
|
||||
|
||||
url_heading: '2. Gebe eine Presence-URL ein',
|
||||
url_help: 'Der Link muss eine HTTPS-URL sein, die ein JSON-Objekt mit einem User, Freund oder Aktivitätsschlüssel zurückgibt. Diese Funktion ist für nxapi\'s znc API Proxy vorgesehen.',
|
||||
|
||||
preferences_heading: 'Konfiguriere zusätzliche Optionen für die Discord Rich Presence',
|
||||
preferences: 'Einstellungen',
|
||||
|
||||
cancel: 'Abbrechen',
|
||||
save: 'Speichern',
|
||||
};
|
||||
|
||||
export const addaccountmanual_window = {
|
||||
title: 'Account hinzufügen',
|
||||
|
||||
authorise_heading: '1. Logge dich in deinen Nintendo Account ein.',
|
||||
authorise_help: 'Wähle noch keinen Account aus.',
|
||||
authorise_open: 'Öffne die Nintendo Account Authorisierung',
|
||||
|
||||
response_heading: '2. Gebe den Weiterleitungslink ein',
|
||||
response_help_1: 'Rechtsklick auf der "Externen Account verlinken" Seite, Rechtsklick auf "Diese Person auswählen" und den Link kopieren. Der Link sollte mit "{{url}}". beginnen',
|
||||
response_help_2: 'Wenn du einen Kinder-Account, welcher mit deinem Account verlinkt ist, hinzufügen möchtest, klicke auf \"Diese Person auswählen\". Wenn dann nur der Kinder-Account angezeigt wird, Rechtsklick auf \"Diese Person auswählen\" und den Link kopieren.',
|
||||
|
||||
cancel: 'Abbrechen',
|
||||
save: 'Account hinzufügen',
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CREDITS_NOTICE, LICENCE_NOTICE } from '../../../common/constants.js';
|
||||
import { CREDITS_NOTICE, LICENCE_NOTICE, ZNCA_API_USE_TEXT } from '../../../common/constants.js';
|
||||
|
||||
export const app = {
|
||||
default_title: 'Nintendo Switch Online',
|
||||
@@ -99,6 +99,41 @@ export const handle_uri = {
|
||||
cancel: 'Cancel',
|
||||
};
|
||||
|
||||
export const na_auth = {
|
||||
window: {
|
||||
title: 'Nintendo Account',
|
||||
},
|
||||
|
||||
znca_api_use: {
|
||||
title: 'Third-party API usage',
|
||||
|
||||
// This should be translated in other languages
|
||||
text: ZNCA_API_USE_TEXT,
|
||||
|
||||
ok: 'OK',
|
||||
cancel: 'Cancel',
|
||||
more_information: 'More information',
|
||||
},
|
||||
|
||||
notification_coral: {
|
||||
title: 'Nintendo Switch Online',
|
||||
body_existing: 'Already signed in as {{name}} (Nintendo Account {{na_name}} / {{na_username}})',
|
||||
body_authenticated: 'Authenticated as {{name}} (Nintendo Account {{na_name}} / {{na_username}})',
|
||||
body_reauthenticated: 'Reauthenticated to {{name}} (Nintendo Account {{na_name}} / {{na_username}})',
|
||||
},
|
||||
|
||||
notification_moon: {
|
||||
title: 'Nintendo Switch Parental Controls',
|
||||
body_existing: 'Already signed in as {{na_name}} ({{na_username}})',
|
||||
body_authenticated: 'Authenticated as {{na_name}} ({{na_username}})',
|
||||
body_reauthenticated: 'Reauthenticated to {{na_name}} ({{na_username}})',
|
||||
},
|
||||
|
||||
error: {
|
||||
title: 'Error adding account',
|
||||
},
|
||||
};
|
||||
|
||||
export const time_since = {
|
||||
default: {
|
||||
now: 'just now',
|
||||
@@ -131,6 +166,7 @@ export const main_window = {
|
||||
discord_active_friend: 'Discord Rich Presence active: <0></0>',
|
||||
discord_not_active: 'Discord Rich Presence not active',
|
||||
discord_playing: 'Playing',
|
||||
discord_not_connected: 'Not connected to Discord',
|
||||
|
||||
add_user: 'Add user',
|
||||
discord_setup: 'Set up Discord Rich Presence',
|
||||
|
||||
@@ -2,25 +2,25 @@ import { app, BrowserWindow, dialog, ipcMain, LoginItemSettingsOptions, Menu } f
|
||||
import process from 'node:process';
|
||||
import * as path from 'node:path';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import createDebug from 'debug';
|
||||
import * as persist from 'node-persist';
|
||||
import { i18n } from 'i18next';
|
||||
import { init as initGlobals } from '../../common/globals.js';
|
||||
import MenuApp from './menu.js';
|
||||
import { handleOpenWebServiceUri } from './webservices.js';
|
||||
import { EmbeddedPresenceMonitor, PresenceMonitorManager } from './monitor.js';
|
||||
import { createWindow } from './windows.js';
|
||||
import { DiscordPresenceConfiguration, LoginItem, LoginItemOptions, WindowType } from '../common/types.js';
|
||||
import { initStorage, paths } from '../../util/storage.js';
|
||||
import { checkUpdates, UpdateCacheData } from '../../common/update.js';
|
||||
import Users, { CoralUser } from '../../common/users.js';
|
||||
import { createModalWindow, createWindow } from './windows.js';
|
||||
import { sendToAllWindows, setupIpc } from './ipc.js';
|
||||
import { dev, dir, git, release, version } from '../../util/product.js';
|
||||
import { addUserAgent } from '../../util/useragent.js';
|
||||
import { askUserForUri } from './util.js';
|
||||
import { setAppInstance, updateMenuLanguage } from './app-menu.js';
|
||||
import { handleAuthUri } from './na-auth.js';
|
||||
import { DiscordPresenceConfiguration, LoginItem, LoginItemOptions, WindowType } from '../common/types.js';
|
||||
import { init as initGlobals } from '../../common/globals.js';
|
||||
import { CREDITS_NOTICE, GITLAB_URL, LICENCE_NOTICE } from '../../common/constants.js';
|
||||
import { checkUpdates, UpdateCacheData } from '../../common/update.js';
|
||||
import Users, { CoralUser } from '../../common/users.js';
|
||||
import createDebug from '../../util/debug.js';
|
||||
import { dev, dir, git, release, version } from '../../util/product.js';
|
||||
import { addUserAgent } from '../../util/useragent.js';
|
||||
import { initStorage, paths } from '../../util/storage.js';
|
||||
import createI18n, { languages } from '../i18n/index.js';
|
||||
|
||||
const debug = createDebug('app:main');
|
||||
@@ -101,17 +101,7 @@ export class App {
|
||||
return this.preferences_window;
|
||||
}
|
||||
|
||||
const window = createWindow(WindowType.PREFERENCES, {}, {
|
||||
show: false,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
width: 580,
|
||||
height: 400,
|
||||
minWidth: 580,
|
||||
maxWidth: 580,
|
||||
minHeight: 400,
|
||||
maxHeight: 400,
|
||||
});
|
||||
const window = createModalWindow(WindowType.PREFERENCES, {});
|
||||
|
||||
window.on('closed', () => this.preferences_window = null);
|
||||
|
||||
@@ -125,7 +115,7 @@ export class App {
|
||||
debug('Initialising i18n with language %s', language);
|
||||
|
||||
await i18n.init({lng: language ?? undefined});
|
||||
await i18n.loadNamespaces(['app', 'app_menu', 'menus', 'handle_uri']);
|
||||
await i18n.loadNamespaces(['app', 'app_menu', 'menus', 'handle_uri', 'na_auth']);
|
||||
|
||||
return i18n;
|
||||
}
|
||||
@@ -290,19 +280,9 @@ export async function handleOpenFriendCodeUri(app: App, uri: string) {
|
||||
const selected_user = await askUserForUri(app, uri, app.i18n.t('handle_uri:friend_code_select'));
|
||||
if (!selected_user) return;
|
||||
|
||||
createWindow(WindowType.ADD_FRIEND, {
|
||||
createModalWindow(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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
import { BrowserWindow, clipboard, dialog, IpcMain, KeyboardEvent, Menu, MenuItem, Settings, ShareMenu, SharingItem, shell, systemPreferences } from './electron.js';
|
||||
import { BrowserWindow, clipboard, dialog, IpcMain, KeyboardEvent, Menu, MenuItem, ShareMenu, SharingItem, shell, systemPreferences } from './electron.js';
|
||||
import * as util from 'node:util';
|
||||
import createDebug from 'debug';
|
||||
import { User } from 'discord-rpc';
|
||||
import openWebService, { QrCodeReaderOptions, WebServiceIpc, WebServiceValidationError } from './webservices.js';
|
||||
import { createWindow, getWindowConfiguration } from './windows.js';
|
||||
import { DiscordPresenceConfiguration, DiscordPresenceSource, LoginItemOptions, WindowType } from '../common/types.js';
|
||||
import { CurrentUser, Friend, Game, PresenceState, WebService } from '../../api/coral-types.js';
|
||||
import { createModalWindow, createWindow, getWindowConfiguration, setWindowHeight } from './windows.js';
|
||||
import { askAddNsoAccount, askAddPctlAccount } from './na-auth.js';
|
||||
import { App } from './index.js';
|
||||
import { EmbeddedPresenceMonitor } from './monitor.js';
|
||||
import { DiscordPresenceConfiguration, DiscordPresenceSource, LoginItemOptions, WindowType } from '../common/types.js';
|
||||
import { CurrentUser, Friend, Game, PresenceState, WebService } from '../../api/coral-types.js';
|
||||
import { NintendoAccountUser } from '../../api/na.js';
|
||||
import { hrduration } from '../../util/misc.js';
|
||||
import createDebug from '../../util/debug.js';
|
||||
import { DiscordPresence } from '../../discord/types.js';
|
||||
import { getDiscordRpcClients } from '../../discord/rpc.js';
|
||||
import { defaultTitle } from '../../discord/titles.js';
|
||||
import type { FriendProps } from '../browser/friend/index.js';
|
||||
import type { DiscordSetupProps } from '../browser/discord/index.js';
|
||||
import type { AddFriendProps } from '../browser/add-friend/index.js';
|
||||
import { EmbeddedPresenceMonitor } from './monitor.js';
|
||||
|
||||
const debug = createDebug('app:main:ipc');
|
||||
|
||||
const shown_modal_windows = new WeakSet<BrowserWindow>();
|
||||
|
||||
export function setupIpc(appinstance: App, ipcMain: IpcMain) {
|
||||
const store = appinstance.store;
|
||||
const storage = appinstance.store.storage;
|
||||
@@ -54,8 +51,8 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) {
|
||||
}, 60 * 60 * 1000);
|
||||
|
||||
ipcMain.handle('nxapi:accounts:list', () => storage.getItem('NintendoAccountIds'));
|
||||
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:accounts:add-coral', () => askAddNsoAccount(appinstance).then(u => u?.data.user.id));
|
||||
ipcMain.handle('nxapi:accounts:add-moon', () => askAddPctlAccount(appinstance).then(u => u?.data.user.id));
|
||||
|
||||
ipcMain.handle('nxapi:coral:gettoken', (e, id: string) => storage.getItem('NintendoAccountToken.' + id));
|
||||
ipcMain.handle('nxapi:coral:getcachedtoken', (e, token: string) => storage.getItem('NsoToken.' + token));
|
||||
@@ -85,61 +82,15 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) {
|
||||
ipcMain.handle('nxapi:coral:addfriend', (e, token: string, nsaid: string) => store.users.get(token).then(u => u.addFriend(nsaid)));
|
||||
|
||||
ipcMain.handle('nxapi:window:showpreferences', () => appinstance.showPreferencesWindow().id);
|
||||
ipcMain.handle('nxapi:window:showfriend', (e, props: FriendProps) => createWindow(WindowType.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:discord', (e, props: DiscordSetupProps) => createWindow(WindowType.DISCORD_PRESENCE, 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: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:showfriend', (e, props: FriendProps) =>
|
||||
createModalWindow(WindowType.FRIEND, props, e.sender).id);
|
||||
ipcMain.handle('nxapi:window:discord', (e, props: DiscordSetupProps) =>
|
||||
createModalWindow(WindowType.DISCORD_PRESENCE, props).id);
|
||||
ipcMain.handle('nxapi:window:addfriend', (e, props: AddFriendProps) =>
|
||||
createModalWindow(WindowType.ADD_FRIEND, props, e.sender).id);
|
||||
ipcMain.handle('nxapi:window:setheight', (e, height: number) => {
|
||||
const window = BrowserWindow.fromWebContents(e.sender)!;
|
||||
const [curWidth, curHeight] = window.getSize();
|
||||
const [curContentWidth, curContentHeight] = window.getContentSize();
|
||||
const [minWidth, minHeight] = window.getMinimumSize();
|
||||
const [maxWidth, maxHeight] = window.getMaximumSize();
|
||||
if (height !== curContentHeight && curHeight === minHeight && curHeight === maxHeight) {
|
||||
window.setMinimumSize(minWidth, height + (curHeight - curContentHeight));
|
||||
window.setMaximumSize(maxWidth, height + (curHeight - curContentHeight));
|
||||
}
|
||||
window.setContentSize(curContentWidth, height);
|
||||
|
||||
if (!shown_modal_windows.has(window)) {
|
||||
window.show();
|
||||
shown_modal_windows.add(window);
|
||||
}
|
||||
setWindowHeight(window, height);
|
||||
});
|
||||
|
||||
ipcMain.handle('nxapi:discord:config', () => appinstance.monitors.getDiscordPresenceConfiguration());
|
||||
@@ -155,8 +106,12 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) {
|
||||
const users: User[] = [];
|
||||
|
||||
for (const client of await getDiscordRpcClients()) {
|
||||
await client.connect(defaultTitle.client);
|
||||
if (client.user && !users.find(u => u.id === client.user!.id)) users.push(client.user);
|
||||
try {
|
||||
await client.connect(defaultTitle.client);
|
||||
if (client.user && !users.find(u => u.id === client.user!.id)) users.push(client.user);
|
||||
} finally {
|
||||
await client.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
return users;
|
||||
@@ -175,10 +130,10 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) {
|
||||
ipcMain.handle('nxapi:menu:add-user', e => (Menu.buildFromTemplate([
|
||||
new MenuItem({label: t('add_account.add_account_coral')!, click:
|
||||
(item: MenuItem, window: BrowserWindow | undefined, event: KeyboardEvent) =>
|
||||
askAddNsoAccount(storage, !event.shiftKey)}),
|
||||
askAddNsoAccount(appinstance, !event.shiftKey)}),
|
||||
new MenuItem({label: t('add_account.add_account_moon')!, click:
|
||||
(item: MenuItem, window: BrowserWindow | undefined, event: KeyboardEvent) =>
|
||||
askAddPctlAccount(storage, !event.shiftKey)}),
|
||||
askAddPctlAccount(appinstance, !event.shiftKey)}),
|
||||
]).popup({window: BrowserWindow.fromWebContents(e.sender)!}), undefined));
|
||||
ipcMain.handle('nxapi:menu:friend-code', (e, fc: CurrentUser['links']['friendCode']) => (Menu.buildFromTemplate([
|
||||
new MenuItem({label: 'SW-' + fc.id, enabled: false}),
|
||||
@@ -252,21 +207,9 @@ function buildUserMenu(app: App, user: NintendoAccountUser, nso?: CurrentUser, m
|
||||
click: () => app.menu?.setActiveDiscordPresenceUser(null)}),
|
||||
] : [
|
||||
new MenuItem({label: t('discord_enable')!,
|
||||
click: () => createWindow(WindowType.DISCORD_PRESENCE, {
|
||||
click: () => createModalWindow(WindowType.DISCORD_PRESENCE, {
|
||||
friend_nsa_id: nso.nsaId,
|
||||
}, {
|
||||
parent: window,
|
||||
modal: true,
|
||||
show: false,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
width: 560,
|
||||
height: 300,
|
||||
minWidth: 450,
|
||||
maxWidth: 700,
|
||||
minHeight: 300,
|
||||
maxHeight: 300,
|
||||
})}),
|
||||
}, window)}),
|
||||
]),
|
||||
new MenuItem({label: t('friend_notifications_enable')!, type: 'checkbox',
|
||||
checked: monitor?.friend_notifications,
|
||||
@@ -275,21 +218,9 @@ function buildUserMenu(app: App, user: NintendoAccountUser, nso?: CurrentUser, m
|
||||
click: () => monitor?.skipIntervalInCurrentLoop(true)}),
|
||||
new MenuItem({type: 'separator'}),
|
||||
new MenuItem({label: t('add_friend')!,
|
||||
click: () => createWindow(WindowType.ADD_FRIEND, {
|
||||
click: () => createModalWindow(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,
|
||||
})}),
|
||||
}, window)}),
|
||||
] : []),
|
||||
new MenuItem({type: 'separator'}),
|
||||
new MenuItem({label: t('remove_help')!, enabled: false}),
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import { app, dialog, Menu, Tray, nativeImage, MenuItem, BrowserWindow, KeyboardEvent } from './electron.js';
|
||||
import path from 'node:path';
|
||||
import * as util from 'node:util';
|
||||
import createDebug from 'debug';
|
||||
import { askAddNsoAccount, askAddPctlAccount } from './na-auth.js';
|
||||
import { App } from './index.js';
|
||||
import { WebService } from '../../api/coral-types.js';
|
||||
import openWebService, { WebServiceValidationError } from './webservices.js';
|
||||
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 { createModalWindow, createWindow } from './windows.js';
|
||||
import { WindowType } from '../common/types.js';
|
||||
import CoralApi from '../../api/coral.js';
|
||||
import { WebService } from '../../api/coral-types.js';
|
||||
import { SavedToken } from '../../common/auth/coral.js';
|
||||
import { SavedMoonToken } from '../../common/auth/moon.js';
|
||||
import { CachedWebServicesList } from '../../common/users.js';
|
||||
import createDebug from '../../util/debug.js';
|
||||
import { dev, dir, git } from '../../util/product.js';
|
||||
import { languages } from '../i18n/index.js';
|
||||
|
||||
const debug = createDebug('app:main:menu');
|
||||
|
||||
const show_force_language_menu = dev || git?.branch?.match(/^(i18n$|trans-)/);
|
||||
|
||||
export default class MenuApp {
|
||||
tray: Tray;
|
||||
|
||||
@@ -121,7 +123,7 @@ export default class MenuApp {
|
||||
menu.append(new MenuItem({type: 'separator'}));
|
||||
menu.append(new MenuItem({label: t('show_main_window')!, click: () => this.app.showMainWindow()}));
|
||||
menu.append(new MenuItem({label: t('preferences')!, click: () => this.app.showPreferencesWindow()}));
|
||||
if (dev) menu.append(new MenuItem({label: 'Language', submenu: Menu.buildFromTemplate([
|
||||
if (show_force_language_menu) menu.append(new MenuItem({label: 'Language', submenu: Menu.buildFromTemplate([
|
||||
...this.app.i18n.options.supportedLngs || ['cimode'],
|
||||
].map(l => new MenuItem({
|
||||
label: languages[l as keyof typeof languages]?.name ?? l,
|
||||
@@ -139,9 +141,9 @@ export default class MenuApp {
|
||||
}
|
||||
|
||||
addNsoAccount = (item: MenuItem, window: BrowserWindow | undefined, event: KeyboardEvent) =>
|
||||
askAddNsoAccount(this.app.store.storage, !event.shiftKey);
|
||||
askAddNsoAccount(this.app, !event.shiftKey);
|
||||
addPctlAccount = (item: MenuItem, window: BrowserWindow | undefined, event: KeyboardEvent) =>
|
||||
askAddPctlAccount(this.app.store.storage, !event.shiftKey);
|
||||
askAddPctlAccount(this.app, !event.shiftKey);
|
||||
|
||||
protected webservices = new Map</** language */ string, WebService[]>();
|
||||
|
||||
@@ -300,18 +302,8 @@ export default class MenuApp {
|
||||
}
|
||||
|
||||
showAddFriendWindow(user: string) {
|
||||
createWindow(WindowType.ADD_FRIEND, {
|
||||
createModalWindow(WindowType.ADD_FRIEND, {
|
||||
user,
|
||||
}, {
|
||||
show: false,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
width: 560,
|
||||
height: 300,
|
||||
minWidth: 450,
|
||||
maxWidth: 700,
|
||||
minHeight: 300,
|
||||
maxHeight: 300,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { dialog, Notification } from './electron.js';
|
||||
import createDebug from 'debug';
|
||||
import { i18n } from 'i18next';
|
||||
import { App } from './index.js';
|
||||
import { tryGetNativeImageFromUrl } from './util.js';
|
||||
import { DiscordPresenceConfiguration, DiscordPresenceExternalMonitorsConfiguration, DiscordPresenceSource } from '../common/types.js';
|
||||
import { CurrentUser, Friend, Game, CoralErrorResponse } from '../../api/coral-types.js';
|
||||
import { ErrorResponse } from '../../api/util.js';
|
||||
import { ZncDiscordPresence, ZncProxyDiscordPresence } from '../../common/presence.js';
|
||||
import { NotificationManager } from '../../common/notify.js';
|
||||
import createDebug from '../../util/debug.js';
|
||||
import { LoopResult } from '../../util/loop.js';
|
||||
import { tryGetNativeImageFromUrl } from './util.js';
|
||||
import { App } from './index.js';
|
||||
import { DiscordPresenceConfiguration, DiscordPresenceExternalMonitorsConfiguration, DiscordPresenceSource } from '../common/types.js';
|
||||
import { DiscordPresence, DiscordPresencePlayTime, ErrorResult } from '../../discord/types.js';
|
||||
import { DiscordRpcClient } from '../../discord/rpc.js';
|
||||
import SplatNet3Monitor, { getConfigFromAppConfig as getSplatNet3MonitorConfigFromAppConfig } from '../../discord/monitor/splatoon3.js';
|
||||
@@ -45,6 +45,7 @@ export class PresenceMonitorManager {
|
||||
i.presence_user = null;
|
||||
i.user_notifications = false;
|
||||
i.friend_notifications = false;
|
||||
i.discord_preconnect = true;
|
||||
|
||||
i.discord.onUpdateActivity = (presence: DiscordPresence | null) => {
|
||||
this.app.store.emit('update-discord-presence', presence ? {...presence, config: undefined} : null);
|
||||
@@ -91,6 +92,7 @@ export class PresenceMonitorManager {
|
||||
const i = new EmbeddedProxyPresenceMonitor(presence_url);
|
||||
|
||||
i.notifications = this.notifications;
|
||||
i.discord_preconnect = true;
|
||||
|
||||
i.discord.onUpdateActivity = (presence: DiscordPresence | null) => {
|
||||
this.app.store.emit('update-discord-presence', presence ? {...presence, config: undefined} : null);
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { app, BrowserWindow, dialog, MessageBoxOptions, Notification, session, shell } from './electron.js';
|
||||
import process from 'node:process';
|
||||
import * as crypto from 'node:crypto';
|
||||
import createDebug from 'debug';
|
||||
import * as persist from 'node-persist';
|
||||
import { app, BrowserWindow, dialog, MessageBoxOptions, Notification, session, shell } from './electron.js';
|
||||
import { getNintendoAccountSessionToken, NintendoAccountSessionToken } from '../../api/na.js';
|
||||
import { App, protocol_registration_options } from './index.js';
|
||||
import { createModalWindow, createWindow } from './windows.js';
|
||||
import { tryGetNativeImageFromUrl } from './util.js';
|
||||
import { WindowType } from '../common/types.js';
|
||||
import { getNintendoAccountSessionToken, NintendoAccountAuthError, NintendoAccountSessionToken } from '../../api/na.js';
|
||||
import { ZNCA_CLIENT_ID } from '../../api/coral.js';
|
||||
import { ZNMA_CLIENT_ID } from '../../api/moon.js';
|
||||
import { getToken, SavedToken } from '../../common/auth/coral.js';
|
||||
import { getPctlToken, SavedMoonToken } from '../../common/auth/moon.js';
|
||||
import { ErrorResponse } from '../../api/util.js';
|
||||
import { getToken } from '../../common/auth/coral.js';
|
||||
import { getPctlToken } from '../../common/auth/moon.js';
|
||||
import createDebug from '../../util/debug.js';
|
||||
import { Jwt } from '../../util/jwt.js';
|
||||
import { tryGetNativeImageFromUrl } from './util.js';
|
||||
import { ZNCA_API_USE_URL } from '../../common/constants.js';
|
||||
import { createWindow } from './windows.js';
|
||||
import { WindowType } from '../common/types.js';
|
||||
import { protocol_registration_options } from './index.js';
|
||||
import { ZNCA_API_USE_TEXT, ZNCA_API_USE_URL } from '../../common/constants.js';
|
||||
|
||||
const debug = createDebug('app:main:na-auth');
|
||||
|
||||
@@ -54,7 +55,7 @@ html {
|
||||
|
||||
let i = 0;
|
||||
|
||||
export function createAuthWindow() {
|
||||
export function createAuthWindow(app: App) {
|
||||
const browser_session = session.defaultSession;
|
||||
|
||||
const window = new BrowserWindow({
|
||||
@@ -63,7 +64,7 @@ export function createAuthWindow() {
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
title: 'Nintendo Account',
|
||||
title: app.i18n.t('na_auth:window.title') ?? 'Nintendo Account',
|
||||
webPreferences: {
|
||||
session: browser_session,
|
||||
scrollBounce: true,
|
||||
@@ -100,16 +101,22 @@ export class AuthoriseCancelError extends AuthoriseError {
|
||||
}
|
||||
}
|
||||
|
||||
export function getSessionTokenCodeByInAppBrowser(client_id: string, scope: string | string[], close_window: false):
|
||||
Promise<NintendoAccountSessionTokenCode & {window: BrowserWindow}>
|
||||
export function getSessionTokenCodeByInAppBrowser(client_id: string, scope: string | string[], close_window: true):
|
||||
Promise<NintendoAccountSessionTokenCode & {window?: never}>
|
||||
export function getSessionTokenCodeByInAppBrowser(client_id: string, scope: string | string[], close_window?: boolean):
|
||||
Promise<NintendoAccountSessionTokenCode & {window?: BrowserWindow}>
|
||||
export function getSessionTokenCodeByInAppBrowser(client_id: string, scope: string | string[], close_window = true) {
|
||||
export function getSessionTokenCodeByInAppBrowser(
|
||||
app: App, client_id: string, scope: string | string[], close_window: false,
|
||||
): Promise<NintendoAccountSessionTokenCode & {window: BrowserWindow}>
|
||||
export function getSessionTokenCodeByInAppBrowser(
|
||||
app: App, client_id: string, scope: string | string[], close_window: true,
|
||||
): Promise<NintendoAccountSessionTokenCode & {window?: never}>
|
||||
export function getSessionTokenCodeByInAppBrowser(
|
||||
app: App, client_id: string, scope: string | string[], close_window?: boolean,
|
||||
): Promise<NintendoAccountSessionTokenCode & {window?: BrowserWindow}>
|
||||
|
||||
export function getSessionTokenCodeByInAppBrowser(
|
||||
app: App, client_id: string, scope: string | string[], close_window = true,
|
||||
) {
|
||||
return new Promise<NintendoAccountSessionTokenCode>((rs, rj) => {
|
||||
const {url: authoriseurl, state, verifier, challenge} = getAuthUrl(client_id, scope);
|
||||
const window = createAuthWindow();
|
||||
const window = createAuthWindow(app);
|
||||
|
||||
const handleAuthUrl = (url: URL) => {
|
||||
const authorisedparams = new URLSearchParams(url.hash.substr(1));
|
||||
@@ -312,19 +319,9 @@ function askUserForRedirectUri(
|
||||
authoriseurl: string, client_id: string,
|
||||
handleAuthUrl: (url: URL) => void, rj: (reason: any) => void
|
||||
) {
|
||||
const window = createWindow(WindowType.ADD_ACCOUNT_MANUAL_PROMPT, {
|
||||
const window = createModalWindow(WindowType.ADD_ACCOUNT_MANUAL_PROMPT, {
|
||||
authoriseurl,
|
||||
client_id,
|
||||
}, {
|
||||
show: false,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
width: 560,
|
||||
height: 300,
|
||||
minWidth: 450,
|
||||
maxWidth: 700,
|
||||
minHeight: 300,
|
||||
maxHeight: 300,
|
||||
});
|
||||
|
||||
window.webContents.on('will-navigate', (event, url_string) => {
|
||||
@@ -354,9 +351,9 @@ const NSO_SCOPE = [
|
||||
'user.screenName',
|
||||
];
|
||||
|
||||
export async function addNsoAccount(storage: persist.LocalStorage, use_in_app_browser = true) {
|
||||
export async function addNsoAccount(app: App, use_in_app_browser = true) {
|
||||
const {code, verifier, window} = use_in_app_browser ?
|
||||
await getSessionTokenCodeByInAppBrowser(ZNCA_CLIENT_ID, NSO_SCOPE, false) :
|
||||
await getSessionTokenCodeByInAppBrowser(app, ZNCA_CLIENT_ID, NSO_SCOPE, false) :
|
||||
await getSessionTokenCodeByDefaultBrowser(ZNCA_CLIENT_ID, NSO_SCOPE, false);
|
||||
|
||||
window?.setFocusable(false);
|
||||
@@ -365,101 +362,131 @@ export async function addNsoAccount(storage: persist.LocalStorage, use_in_app_br
|
||||
try {
|
||||
const [jwt, sig] = Jwt.decode(code);
|
||||
|
||||
const nsotoken = await storage.getItem('NintendoAccountToken.' + jwt.payload.sub) as string | undefined;
|
||||
const nsotoken = await app.store.storage.getItem('NintendoAccountToken.' + jwt.payload.sub) as string | undefined;
|
||||
|
||||
if (nsotoken) {
|
||||
const data = await storage.getItem('NsoToken.' + nsotoken) as SavedToken | undefined;
|
||||
debug('Already authenticated', jwt.payload);
|
||||
|
||||
debug('Already authenticated', data);
|
||||
try {
|
||||
const {nso, data} = await getToken(app.store.storage, nsotoken, process.env.ZNC_PROXY_URL, false);
|
||||
|
||||
new Notification({
|
||||
title: 'Nintendo Switch Online',
|
||||
body: 'Already signed in as ' + data?.nsoAccount.user.name + ' (' + data?.user.nickname + ')',
|
||||
icon: await tryGetNativeImageFromUrl(data!.nsoAccount.user.imageUri),
|
||||
}).show();
|
||||
new Notification({
|
||||
title: app.i18n.t('na_auth:notification_coral.title') ?? 'Nintendo Switch Online',
|
||||
body: app.i18n.t('na_auth:notification_coral.body_existing', {
|
||||
name: data.nsoAccount.user.name,
|
||||
na_name: data.user.nickname,
|
||||
na_username: data.user.screenName,
|
||||
}) ?? 'Already signed in as ' + data.nsoAccount.user.name + ' (Nintendo Account ' +
|
||||
data.user.nickname + ' / ' + data.user.screenName + ')',
|
||||
icon: await tryGetNativeImageFromUrl(data.nsoAccount.user.imageUri),
|
||||
}).show();
|
||||
|
||||
return getToken(storage, nsotoken, process.env.ZNC_PROXY_URL, false);
|
||||
return {nso, data};
|
||||
} catch (err) {
|
||||
if (err instanceof ErrorResponse && err.response.url.startsWith('https://accounts.nintendo.com/')) {
|
||||
const data: NintendoAccountAuthError = err.data;
|
||||
|
||||
if (data.error === 'invalid_grant') {
|
||||
// The session token has expired/was revoked
|
||||
return authenticateCoralSessionToken(app, code, verifier, true);
|
||||
}
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
await checkZncaApiUseAllowed(storage, window);
|
||||
await checkZncaApiUseAllowed(app, window);
|
||||
|
||||
const token = await getNintendoAccountSessionToken(code, verifier, ZNCA_CLIENT_ID);
|
||||
|
||||
debug('session token', token);
|
||||
|
||||
const {nso, data} = await getToken(storage, token.session_token, process.env.ZNC_PROXY_URL, false);
|
||||
|
||||
const users = new Set(await storage.getItem('NintendoAccountIds') ?? []);
|
||||
users.add(data.user.id);
|
||||
await storage.setItem('NintendoAccountIds', [...users]);
|
||||
|
||||
new Notification({
|
||||
title: 'Nintendo Switch Online',
|
||||
body: 'Authenticated as ' + data.nsoAccount.user.name + ' (NSO ' + data.user.nickname + ')',
|
||||
icon: await tryGetNativeImageFromUrl(data.nsoAccount.user.imageUri),
|
||||
}).show();
|
||||
|
||||
return {nso, data};
|
||||
return authenticateCoralSessionToken(app, code, verifier);
|
||||
} finally {
|
||||
window?.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function askAddNsoAccount(storage: persist.LocalStorage, iab = true) {
|
||||
async function authenticateCoralSessionToken(
|
||||
app: App,
|
||||
code: string, verifier: string,
|
||||
reauthenticate = false,
|
||||
) {
|
||||
const token = await getNintendoAccountSessionToken(code, verifier, ZNCA_CLIENT_ID);
|
||||
|
||||
debug('session token', token);
|
||||
|
||||
const {nso, data} = await getToken(app.store.storage, token.session_token, process.env.ZNC_PROXY_URL, false);
|
||||
|
||||
const users = new Set(await app.store.storage.getItem('NintendoAccountIds') ?? []);
|
||||
users.add(data.user.id);
|
||||
await app.store.storage.setItem('NintendoAccountIds', [...users]);
|
||||
|
||||
new Notification({
|
||||
title: app.i18n.t('na_auth:notification_coral.title') ?? 'Nintendo Switch Online',
|
||||
body: app.i18n.t('na_auth:notification_coral.body_' + (reauthenticate ? 're' : '') + 'authenticated', {
|
||||
name: data.nsoAccount.user.name,
|
||||
na_name: data.user.nickname,
|
||||
na_username: data.user.screenName,
|
||||
}) ?? (reauthenticate ?
|
||||
'Reauthenticated to ' + data.nsoAccount.user.name + ' (Nintendo Account ' + data.user.nickname + ' / ' +
|
||||
data.user.screenName + ')' :
|
||||
'Authenticated as ' + data.nsoAccount.user.name + ' (Nintendo Account ' + data.user.nickname + ' / ' +
|
||||
data.user.screenName + ')'),
|
||||
icon: await tryGetNativeImageFromUrl(data.nsoAccount.user.imageUri),
|
||||
}).show();
|
||||
|
||||
return {nso, data};
|
||||
}
|
||||
|
||||
export async function askAddNsoAccount(app: App, iab = true) {
|
||||
try {
|
||||
return await addNsoAccount(storage, iab);
|
||||
return await addNsoAccount(app, iab);
|
||||
} catch (err: any) {
|
||||
if (err instanceof AuthoriseError && err.code === 'access_denied') return;
|
||||
|
||||
dialog.showErrorBox('Error adding account', err.stack || err.message);
|
||||
dialog.showErrorBox(app.i18n.t('na_auth:error.title') ?? 'Error adding account',
|
||||
err.stack || err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkZncaApiUseAllowed(storage: persist.LocalStorage, window?: BrowserWindow, force = false) {
|
||||
async function checkZncaApiUseAllowed(app: App, window?: BrowserWindow, force = false) {
|
||||
if (!force) {
|
||||
if (await storage.getItem('ZncaApiConsent')) {
|
||||
if (await app.store.storage.getItem('ZncaApiConsent')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env.ZNC_PROXY_URL) {
|
||||
debug('Skipping znca API consent; znc proxy URL set');
|
||||
await storage.setItem('ZncaApiConsent', true);
|
||||
await app.store.storage.setItem('ZncaApiConsent', true);
|
||||
return;
|
||||
}
|
||||
|
||||
const ids: string[] | undefined = await storage.getItem('NintendoAccountIds');
|
||||
const ids: string[] | undefined = await app.store.storage.getItem('NintendoAccountIds');
|
||||
|
||||
for (const id of ids ?? []) {
|
||||
const nsotoken: string | undefined = await storage.getItem('NintendoAccountToken.' + id);
|
||||
const nsotoken: string | undefined = await app.store.storage.getItem('NintendoAccountToken.' + id);
|
||||
if (!nsotoken) continue;
|
||||
|
||||
debug('Skipping znca API consent; Nintendo Switch Online account already linked');
|
||||
await storage.setItem('ZncaApiConsent', true);
|
||||
await app.store.storage.setItem('ZncaApiConsent', true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (await askZncaApiUseAllowed(window)) {
|
||||
await storage.setItem('ZncaApiConsent', true);
|
||||
if (await askZncaApiUseAllowed(app, window)) {
|
||||
await app.store.storage.setItem('ZncaApiConsent', true);
|
||||
} else {
|
||||
throw new Error('Cannot continue without third-party APIs allowed');
|
||||
}
|
||||
}
|
||||
|
||||
const ZNCA_API_USE_TEXT = `To access the Nintendo Switch Online app API, nxapi must send some data to third-party APIs. This is required to generate some data to make Nintendo think you\'re using the real Nintendo Switch Online app.
|
||||
|
||||
By default, this uses the imink API, but another service can be used by setting an environment variable. The default API may change without notice if you do not force use of a specific service.
|
||||
|
||||
The data sent includes:
|
||||
|
||||
- When authenticating to the Nintendo Switch Online app: a Nintendo Account ID token, containing your Nintendo Account ID and country, which is valid for 15 minutes
|
||||
- When authenticating to game-specific services: a Coral (Nintendo Switch Online app) ID token, containing your Coral user ID, Nintendo Switch Online membership status, and Nintendo Account child restriction status, which is valid for 2 hours`;
|
||||
|
||||
async function askZncaApiUseAllowed(window?: BrowserWindow): Promise<boolean> {
|
||||
async function askZncaApiUseAllowed(app?: App, window?: BrowserWindow): Promise<boolean> {
|
||||
const options: MessageBoxOptions = {
|
||||
message: 'Third-party API usage',
|
||||
detail: ZNCA_API_USE_TEXT,
|
||||
buttons: ['OK', 'Cancel', 'More information'],
|
||||
message: app?.i18n.t('na_auth:znca_api_use.title') ?? 'Third-party API usage',
|
||||
detail: app?.i18n.t('na_auth:znca_api_use.text') ?? ZNCA_API_USE_TEXT,
|
||||
buttons: [
|
||||
app?.i18n.t('na_auth:znca_api_use.ok') ?? 'OK',
|
||||
app?.i18n.t('na_auth:znca_api_use.cancel') ?? 'Cancel',
|
||||
app?.i18n.t('na_auth:znca_api_use.more_information') ?? 'More information',
|
||||
],
|
||||
cancelId: 1,
|
||||
};
|
||||
|
||||
@@ -471,7 +498,7 @@ async function askZncaApiUseAllowed(window?: BrowserWindow): Promise<boolean> {
|
||||
|
||||
if (result.response === 2) {
|
||||
shell.openExternal(ZNCA_API_USE_URL);
|
||||
return askZncaApiUseAllowed(window);
|
||||
return askZncaApiUseAllowed(app, window);
|
||||
}
|
||||
|
||||
return result.response === 0;
|
||||
@@ -493,9 +520,9 @@ const MOON_SCOPE = [
|
||||
'moonMonthlySummary',
|
||||
];
|
||||
|
||||
export async function addPctlAccount(storage: persist.LocalStorage, use_in_app_browser = true) {
|
||||
export async function addPctlAccount(app: App, use_in_app_browser = true) {
|
||||
const {code, verifier, window} = use_in_app_browser ?
|
||||
await getSessionTokenCodeByInAppBrowser(ZNMA_CLIENT_ID, MOON_SCOPE, false) :
|
||||
await getSessionTokenCodeByInAppBrowser(app, ZNMA_CLIENT_ID, MOON_SCOPE, false) :
|
||||
await getSessionTokenCodeByDefaultBrowser(ZNMA_CLIENT_ID, MOON_SCOPE, false);
|
||||
|
||||
window?.setFocusable(false);
|
||||
@@ -504,48 +531,78 @@ export async function addPctlAccount(storage: persist.LocalStorage, use_in_app_b
|
||||
try {
|
||||
const [jwt, sig] = Jwt.decode(code);
|
||||
|
||||
const moontoken = await storage.getItem('NintendoAccountToken-pctl.' + jwt.payload.sub) as string | undefined;
|
||||
const moontoken = await app.store.storage.getItem('NintendoAccountToken-pctl.' + jwt.payload.sub) as string | undefined;
|
||||
|
||||
if (moontoken) {
|
||||
const data = await storage.getItem('MoonToken.' + moontoken) as SavedMoonToken | undefined;
|
||||
debug('Already authenticated', jwt.payload);
|
||||
|
||||
debug('Already authenticated', data);
|
||||
try {
|
||||
const {moon, data} = await getPctlToken(app.store.storage, moontoken, false);
|
||||
|
||||
new Notification({
|
||||
title: 'Nintendo Switch Parental Controls',
|
||||
body: 'Already signed in as ' + data?.user.nickname,
|
||||
}).show();
|
||||
new Notification({
|
||||
title: app.i18n.t('na_auth:notification_moon.title') ?? 'Nintendo Switch Parental Controls',
|
||||
body: app.i18n.t('na_auth:notification_moon.body_existing', {
|
||||
na_name: data.user.nickname,
|
||||
na_username: data.user.screenName,
|
||||
}) ?? 'Already signed in as ' + data.user.nickname + ' (' + data.user.screenName + ')',
|
||||
}).show();
|
||||
|
||||
return getPctlToken(storage, moontoken, false);
|
||||
return {moon, data};
|
||||
} catch (err) {
|
||||
if (err instanceof ErrorResponse && err.response.url.startsWith('https://accounts.nintendo.com/')) {
|
||||
const data: NintendoAccountAuthError = err.data;
|
||||
|
||||
if (data.error === 'invalid_grant') {
|
||||
// The session token has expired/was revoked
|
||||
return authenticateMoonSessionToken(app, code, verifier, true);
|
||||
}
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const token = await getNintendoAccountSessionToken(code, verifier, ZNMA_CLIENT_ID);
|
||||
|
||||
debug('session token', token);
|
||||
|
||||
const {moon, data} = await getPctlToken(storage, token.session_token, false);
|
||||
|
||||
const users = new Set(await storage.getItem('NintendoAccountIds') ?? []);
|
||||
users.add(data.user.id);
|
||||
await storage.setItem('NintendoAccountIds', [...users]);
|
||||
|
||||
new Notification({
|
||||
title: 'Nintendo Switch Parental Controls',
|
||||
body: 'Authenticated as ' + data.user.nickname,
|
||||
}).show();
|
||||
|
||||
return {moon, data};
|
||||
return authenticateMoonSessionToken(app, code, verifier);
|
||||
} finally {
|
||||
window?.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function askAddPctlAccount(storage: persist.LocalStorage, iab = true) {
|
||||
async function authenticateMoonSessionToken(
|
||||
app: App,
|
||||
code: string, verifier: string,
|
||||
reauthenticate = false,
|
||||
) {
|
||||
const token = await getNintendoAccountSessionToken(code, verifier, ZNMA_CLIENT_ID);
|
||||
|
||||
debug('session token', token);
|
||||
|
||||
const {moon, data} = await getPctlToken(app.store.storage, token.session_token, false);
|
||||
|
||||
const users = new Set(await app.store.storage.getItem('NintendoAccountIds') ?? []);
|
||||
users.add(data.user.id);
|
||||
await app.store.storage.setItem('NintendoAccountIds', [...users]);
|
||||
|
||||
new Notification({
|
||||
title: app.i18n.t('na_auth:notification_moon.title') ?? 'Nintendo Switch Parental Controls',
|
||||
body: app.i18n.t('na_auth:notification_moon.body_' + (reauthenticate ? 're' : '') + 'authenticated', {
|
||||
na_name: data.user.nickname,
|
||||
na_username: data.user.screenName,
|
||||
}) ?? (reauthenticate ?
|
||||
'Reauthenticated to ' + data.user.nickname + ' (' + data.user.screenName + ')' :
|
||||
'Authenticated as ' + data.user.nickname + ' (' + data.user.screenName + ')'),
|
||||
}).show();
|
||||
|
||||
return {moon, data};
|
||||
}
|
||||
|
||||
export async function askAddPctlAccount(app: App, iab = true) {
|
||||
try {
|
||||
return await addPctlAccount(storage, iab);
|
||||
return await addPctlAccount(app, iab);
|
||||
} catch (err: any) {
|
||||
if (err instanceof AuthoriseError && err.code === 'access_denied') return;
|
||||
|
||||
dialog.showErrorBox('Error adding account', err.stack || err.message);
|
||||
dialog.showErrorBox(app.i18n.t('na_auth:error.title') ?? 'Error adding account',
|
||||
err.stack || err.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { app, BrowserWindow, clipboard, dialog, IpcMainInvokeEvent, nativeImage, nativeTheme, Notification, ShareMenu, shell, WebContents } from './electron.js';
|
||||
import * as path from 'node:path';
|
||||
import { constants } from 'node:fs';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import { Buffer } from 'node:buffer';
|
||||
import * as util from 'node:util';
|
||||
import createDebug from 'debug';
|
||||
import { app, BrowserWindow, clipboard, dialog, IpcMainInvokeEvent, nativeImage, nativeTheme, Notification, ShareMenu, shell, WebContents } from './electron.js';
|
||||
import fetch from 'node-fetch';
|
||||
import CoralApi from '../../api/coral.js';
|
||||
import { CurrentUser, WebService, WebServiceToken } from '../../api/coral-types.js';
|
||||
import { App, Store } from './index.js';
|
||||
import type { DownloadImagesRequest, NativeShareRequest, NativeShareUrlRequest, QrCodeReaderCameraOptions, QrCodeReaderCheckinOptions, QrCodeReaderCheckinResult, QrCodeReaderPhotoLibraryOptions, SendMessageOptions } from '../preload-webservice/znca-js-api.js';
|
||||
import { SavedToken } from '../../common/auth/coral.js';
|
||||
import { createWebServiceWindow } from './windows.js';
|
||||
import { askUserForUri } from './util.js';
|
||||
import type { DownloadImagesRequest, NativeShareRequest, NativeShareUrlRequest, QrCodeReaderCameraOptions, QrCodeReaderCheckinOptions, QrCodeReaderCheckinResult, QrCodeReaderPhotoLibraryOptions, SendMessageOptions } from '../preload-webservice/znca-js-api.js';
|
||||
import createDebug from '../../util/debug.js';
|
||||
import CoralApi from '../../api/coral.js';
|
||||
import { CurrentUser, WebService, WebServiceToken } from '../../api/coral-types.js';
|
||||
import { NintendoAccountUser } from '../../api/na.js';
|
||||
import { SavedToken } from '../../common/auth/coral.js';
|
||||
|
||||
const debug = createDebug('app:main:webservices');
|
||||
|
||||
@@ -68,6 +68,8 @@ export default async function openWebService(
|
||||
|
||||
if (!isWebServiceUrlAllowed(webservice, url)) {
|
||||
debug('Web service attempted to navigate to a URL not allowed by it\'s `whiteList`', webservice, url);
|
||||
debug('open', url);
|
||||
shell.openExternal(url);
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ const windows = new WeakMap<WebContents, WindowConfiguration>();
|
||||
|
||||
export function createWindow<T extends WindowType>(
|
||||
type: T, props: WindowConfiguration<T>['props'],
|
||||
options?: BrowserWindowConstructorOptions
|
||||
options?: BrowserWindowConstructorOptions,
|
||||
) {
|
||||
// Create the browser window
|
||||
const window = new BrowserWindow({
|
||||
@@ -51,6 +51,62 @@ export function getWindowConfiguration(webcontents: WebContents): WindowConfigur
|
||||
return data;
|
||||
}
|
||||
|
||||
const modal_window_width = new WeakMap<BrowserWindow, number>();
|
||||
const modal_window_shown = new WeakSet<BrowserWindow>();
|
||||
|
||||
export function createModalWindow<T extends WindowType>(
|
||||
type: T, props: WindowConfiguration<T>['props'],
|
||||
parent?: BrowserWindow | WebContents,
|
||||
options?: BrowserWindowConstructorOptions,
|
||||
) {
|
||||
if (parent && !(parent instanceof BrowserWindow)) {
|
||||
parent = BrowserWindow.fromWebContents(parent) ?? undefined;
|
||||
}
|
||||
|
||||
const window = createWindow(type, props, {
|
||||
parent,
|
||||
modal: !!parent,
|
||||
show: false,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
width: 560,
|
||||
height: 300,
|
||||
minWidth: 450,
|
||||
maxWidth: 700,
|
||||
minHeight: 300,
|
||||
maxHeight: 300,
|
||||
|
||||
...options,
|
||||
});
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Use a fixed window width on Windows due to a bug getting/setting window size
|
||||
window.setResizable(false);
|
||||
modal_window_width.set(window, options?.width ?? 560);
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
export function setWindowHeight(window: BrowserWindow, height: number) {
|
||||
const [curWidth, curHeight] = window.getSize();
|
||||
const [curContentWidth, curContentHeight] = window.getContentSize();
|
||||
const [minWidth, minHeight] = window.getMinimumSize();
|
||||
const [maxWidth, maxHeight] = window.getMaximumSize();
|
||||
|
||||
if (height !== curContentHeight && curHeight === minHeight && curHeight === maxHeight) {
|
||||
window.setMinimumSize(minWidth, height + (curHeight - curContentHeight));
|
||||
window.setMaximumSize(maxWidth, height + (curHeight - curContentHeight));
|
||||
}
|
||||
|
||||
window.setContentSize(modal_window_width.get(window) ?? curContentWidth, height);
|
||||
|
||||
if (!modal_window_shown.has(window)) {
|
||||
window.show();
|
||||
modal_window_shown.add(window);
|
||||
}
|
||||
}
|
||||
|
||||
const BACKGROUND_COLOUR_MAIN_LIGHT = process.platform === 'win32' ? '#ffffff' : '#ececec';
|
||||
const BACKGROUND_COLOUR_MAIN_DARK = process.platform === 'win32' ? '#000000' : '#252424';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user