-
+
+
}
diff --git a/gui/src/pages/Settings.tsx b/gui/src/pages/Settings.tsx
index 2c94cbe..7665d37 100644
--- a/gui/src/pages/Settings.tsx
+++ b/gui/src/pages/Settings.tsx
@@ -1,16 +1,17 @@
-import { ErrorContent } from 'components/ErrorContent';
+import { ErrorContent, FallbackComponent } from 'components/ErrorContent';
import { Loading } from 'components/Loading';
-import { usePromise, usePromiseLazy } from 'hooks/usePromise';
-import React, { useState } from 'react'
+import React, { Suspense, useState } from 'react'
import { useTranslation } from 'react-i18next';
-import { Config, getConfig, getProfile, Profile, setConfig, setProfile } from 'services/config';
-import { composeLoadable } from 'utils/composeLoadable';
-import classNames from 'classnames';
+import { Config, Profile } from 'services/config';
+import clsx from 'clsx';
import { useLogin } from 'services/s3si';
import { STAT_INK } from 'constant';
import { Header } from 'components/Header';
import { useSubField } from 'hooks/useSubField';
import { useNavigate } from 'react-router-dom';
+import useSWRMutation from 'swr/mutation'
+import { useService, useServiceMutation } from 'services/useService';
+import { ErrorBoundary } from 'react-error-boundary';
const STAT_INK_KEY_LENGTH = 43;
@@ -57,6 +58,8 @@ const Form: React.FC<{
const { t, i18n } = useTranslation();
const [value, setValue] = useState(oldValue);
const { subField } = useSubField({ value, onChange: setValue });
+ const { trigger: setProfile } = useServiceMutation('profile', 0)
+ const { trigger: setConfig } = useServiceMutation('config')
const changed = JSON.stringify(value) !== JSON.stringify(oldValue);
@@ -64,12 +67,12 @@ const Form: React.FC<{
const statInkApiKey = subField('profile.state.statInkApiKey')
const splatnet3Lang = subField('profile.state.userLang')
- const [onSave, { loading, error }] = usePromiseLazy(async () => {
- await setProfile(0, value.profile);
+ const { trigger: onSave, isMutating: loading, error } = useSWRMutation('saveSettings', async () => {
+ await setProfile(value.profile);
await setConfig(value.config);
onSaved?.();
})
- const [onLogin, loginState] = usePromiseLazy(async () => {
+ const loginState = useSWRMutation('login', async () => {
const result = await login();
if (!result) {
return;
@@ -85,11 +88,12 @@ const Form: React.FC<{
-
-
+
+
-
+
>
}
-export const Settings: React.FC = () => {
+const SettingsLoader: React.FC = () => {
const navigate = useNavigate();
- let { loading, error, retry, result } = composeLoadable({
- config: usePromise(getConfig),
- profile: usePromise(() => getProfile(0)),
- });
+ const { data: config } = useService('config')
+ const { data: profile } = useService('profile', 0)
- if (loading) {
- return
-
-
+ if (!config || !profile) {
+ return <>
+ Error
+ >
}
- if (error) {
- return
-
-
- }
+ return <>
+
}>
+
+
+
}
diff --git a/gui/src/services/config.ts b/gui/src/services/config.ts
index 0022ab9..039f16d 100644
--- a/gui/src/services/config.ts
+++ b/gui/src/services/config.ts
@@ -1,6 +1,6 @@
import { fs } from "@tauri-apps/api"
import { appConfigDir, join } from '@tauri-apps/api/path'
-import { State } from '../../../src/state';
+import type { State } from '../../../src/state';
const configFile = appConfigDir().then(c => join(c, 'config.json'));
const profileDir = appConfigDir().then(c => join(c, 'profile'));
@@ -9,8 +9,7 @@ export type Profile = {
state: State,
}
-export type Config = {
-}
+export type Config = Record
// TODO: import from state.ts.
const DEFAULT_STATE: State = {
diff --git a/gui/src/services/s3si.tsx b/gui/src/services/s3si.tsx
index 7b8b24a..9ae5175 100644
--- a/gui/src/services/s3si.tsx
+++ b/gui/src/services/s3si.tsx
@@ -9,6 +9,7 @@ const client = new JSONRPCClient({
const LOG_SUB = new Set<(logs: Log[]) => void>();
async function getLogs() {
+ // eslint-disable-next-line no-constant-condition
while (true) {
const r = await client.getLogs()
@@ -57,7 +58,7 @@ export const useLog = () => {
return useContext(LOG_CONTEXT);
}
-function renderMsg(i: any) {
+function renderMsg(i: unknown) {
if (i instanceof Error) {
return i.message
}
@@ -91,14 +92,15 @@ export const LogProvider: React.FC<{ limit?: number, children?: React.ReactNode
LOG_SUB.delete(cb);
}
}, [limit])
+ const value = useMemo(() => {
+ const renderedLogs = logs.map(renderLog)
+ return {
+ logs,
+ renderedLogs,
+ }
+ }, [logs])
-
- const renderedLogs = useMemo(() => logs.map(renderLog), [logs])
-
- return
+ return
{children}
}
diff --git a/gui/src/services/useService.ts b/gui/src/services/useService.ts
new file mode 100644
index 0000000..aaab455
--- /dev/null
+++ b/gui/src/services/useService.ts
@@ -0,0 +1,37 @@
+import useSWR, { Key, SWRResponse } from 'swr'
+import useSWRMutation, { SWRMutationResponse } from 'swr/mutation'
+import { getConfig, getProfile, setConfig, setProfile } from './config'
+
+const SERVICES = {
+ profile: {
+ fetcher: getProfile,
+ updater: setProfile,
+ },
+ config: {
+ fetcher: getConfig,
+ updater: setConfig,
+ },
+} as const
+
+export type Services = keyof typeof SERVICES
+
+export const useService = (service: S, ...args: Parameters<(typeof SERVICES)[S]['fetcher']>): SWRResponse<
+ Awaited>
+> => {
+ // @ts-expect-error TypeScript can not infer type here
+ return useSWR(['service', service, ...args], () => SERVICES[service].fetcher(...args), { suspense: true })
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+type RemoveLastParamters any> = T extends (...args: [...infer P, any]) => any ? P : never;
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+type LastParamter any> = T extends (...args: [...infer _, infer P]) => any ? P : never;
+export const useServiceMutation = (service: S, ...args: RemoveLastParamters<(typeof SERVICES)[S]['updater']>): SWRMutationResponse<
+ Awaited>,
+ Error,
+ Key,
+ LastParamter<(typeof SERVICES)[S]['updater']>
+> => {
+ // @ts-expect-error TypeScript can not infer type here
+ return useSWRMutation(['service', service, ...args], (_, { arg }) => SERVICES[service].updater(...args, arg))
+}
diff --git a/gui/src/utils/composeLoadable.ts b/gui/src/utils/composeLoadable.ts
deleted file mode 100644
index 40e0a04..0000000
--- a/gui/src/utils/composeLoadable.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-export type Loadable = {
- loading: boolean;
- result?: T;
- error?: any;
- retry?: () => void;
-}
-
-export function composeLoadable>>(map: T): Loadable<{
- [P in keyof T]: T[P] extends Loadable ? R : never
-}> {
- const values = Object.values(map)
-
- const loading = values.some(v => v.loading);
- const error = values.find(v => v.error)?.error;
- const result = loading || error ? undefined : Object.fromEntries(Object.entries(map).map(([k, v]) => [k, v.result])) as any;
- const retry = values.some(i => !!i.retry) ? () => Object.values(map).forEach(v => v.retry?.()) : undefined;
-
- return { loading, result, error, retry };
-}
diff --git a/scripts/compile.ts b/scripts/compile.ts
index 3d6794e..1b7594b 100644
--- a/scripts/compile.ts
+++ b/scripts/compile.ts
@@ -1,4 +1,4 @@
-import * as path from "https://deno.land/std@0.178.0/path/mod.ts";
+import * as path from "https://deno.land/std@0.213.0/path/mod.ts";
import { assertEquals } from "../dev_deps.ts";
if (import.meta.main) {
@@ -39,6 +39,8 @@ if (import.meta.main) {
target,
"code:",
status.code,
+ "stderr:",
+ new TextDecoder().decode(status.stderr),
);
Deno.exit(status.code);
}
diff --git a/scripts/deno.json b/scripts/deno.json
deleted file mode 100644
index 0967ef4..0000000
--- a/scripts/deno.json
+++ /dev/null
@@ -1 +0,0 @@
-{}
diff --git a/scripts/deno.lock b/scripts/deno.lock
deleted file mode 100644
index df6de4a..0000000
--- a/scripts/deno.lock
+++ /dev/null
@@ -1,88 +0,0 @@
-{
- "version": "2",
- "remote": {
- "https://deno.land/std@0.141.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74",
- "https://deno.land/std@0.141.0/bytes/bytes_list.ts": "67eb118e0b7891d2f389dad4add35856f4ad5faab46318ff99653456c23b025d",
- "https://deno.land/std@0.141.0/bytes/equals.ts": "fc16dff2090cced02497f16483de123dfa91e591029f985029193dfaa9d894c9",
- "https://deno.land/std@0.141.0/bytes/mod.ts": "763f97d33051cc3f28af1a688dfe2830841192a9fea0cbaa55f927b49d49d0bf",
- "https://deno.land/std@0.141.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37",
- "https://deno.land/std@0.141.0/io/buffer.ts": "bd0c4bf53db4b4be916ca5963e454bddfd3fcd45039041ea161dbf826817822b",
- "https://deno.land/std@0.141.0/io/types.d.ts": "01f60ae7ec02675b5dbed150d258fc184a78dfe5c209ef53ba4422b46b58822c",
- "https://deno.land/std@0.141.0/streams/conversion.ts": "8268f3f1a43324953dd8e9e4e31adb42e3caddb4502433bde03c279e43d70a3b",
- "https://deno.land/std@0.160.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74",
- "https://deno.land/std@0.160.0/_util/os.ts": "8a33345f74990e627b9dfe2de9b040004b08ea5146c7c9e8fe9a29070d193934",
- "https://deno.land/std@0.160.0/bytes/bytes_list.ts": "aba5e2369e77d426b10af1de0dcc4531acecec27f9b9056f4f7bfbf8ac147ab4",
- "https://deno.land/std@0.160.0/bytes/equals.ts": "3c3558c3ae85526f84510aa2b48ab2ad7bdd899e2e0f5b7a8ffc85acb3a6043a",
- "https://deno.land/std@0.160.0/bytes/mod.ts": "b2e342fd3669176a27a4e15061e9d588b89c1aaf5008ab71766e23669565d179",
- "https://deno.land/std@0.160.0/encoding/base64.ts": "c57868ca7fa2fbe919f57f88a623ad34e3d970d675bdc1ff3a9d02bba7409db2",
- "https://deno.land/std@0.160.0/flags/mod.ts": "686b6b36e14b00f11c9e26cecf439021158436a6e34f60eeb0d927f0b169ae20",
- "https://deno.land/std@0.160.0/fmt/colors.ts": "9e36a716611dcd2e4865adea9c4bec916b5c60caad4cdcdc630d4974e6bb8bd4",
- "https://deno.land/std@0.160.0/io/buffer.ts": "fae02290f52301c4e0188670e730cd902f9307fb732d79c4aa14ebdc82497289",
- "https://deno.land/std@0.160.0/io/mod.ts": "6e781ebafd5cdccf9ab4afa1f499b08c513602d023cb08ceebc58758501f78bd",
- "https://deno.land/std@0.160.0/io/readers.ts": "45847ad404afd2f605eae1cff193f223462bc55eeb9ae313c2f3db28aada0fd6",
- "https://deno.land/std@0.160.0/io/types.d.ts": "107e1e64834c5ba917c783f446b407d33432c5d612c4b3430df64fc2b4ecf091",
- "https://deno.land/std@0.160.0/io/util.ts": "23e706b4b6a3ebb34af00ad74d7549d906f3211fc98c1fba1185a36e017fb727",
- "https://deno.land/std@0.160.0/io/writers.ts": "2e1c63ffd0cfba411b1fd8374609abff9ea86187c9d4d885d42e6fc20325ef0e",
- "https://deno.land/std@0.160.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3",
- "https://deno.land/std@0.160.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09",
- "https://deno.land/std@0.160.0/path/_util.ts": "d16be2a16e1204b65f9d0dfc54a9bc472cafe5f4a190b3c8471ec2016ccd1677",
- "https://deno.land/std@0.160.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633",
- "https://deno.land/std@0.160.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee",
- "https://deno.land/std@0.160.0/path/mod.ts": "56fec03ad0ebd61b6ab39ddb9b0ddb4c4a5c9f2f4f632e09dd37ec9ebfd722ac",
- "https://deno.land/std@0.160.0/path/posix.ts": "6b63de7097e68c8663c84ccedc0fd977656eb134432d818ecd3a4e122638ac24",
- "https://deno.land/std@0.160.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9",
- "https://deno.land/std@0.160.0/path/win32.ts": "ee8826dce087d31c5c81cd414714e677eb68febc40308de87a2ce4b40e10fb8d",
- "https://deno.land/std@0.160.0/streams/conversion.ts": "328afbedee0a7e0c330ac4c7b4c1af569ee53974f970230f6a78f545b93abb9b",
- "https://deno.land/std@0.160.0/testing/_diff.ts": "a23e7fc2b4d8daa3e158fa06856bedf5334ce2a2831e8bf9e509717f455adb2c",
- "https://deno.land/std@0.160.0/testing/_format.ts": "cd11136e1797791045e639e9f0f4640d5b4166148796cad37e6ef75f7d7f3832",
- "https://deno.land/std@0.160.0/testing/asserts.ts": "1e340c589853e82e0807629ba31a43c84ebdcdeca910c4a9705715dfdb0f5ce8",
- "https://deno.land/std@0.160.0/uuid/_common.ts": "76e1fdfb03aecf733f7b3a5edc900f5734f2433b359fdb1535f8de72873bdb3f",
- "https://deno.land/std@0.160.0/uuid/mod.ts": "e57ba10200d75f2b17570f13eba19faa6734b1be2da5091e2c01039df41274a5",
- "https://deno.land/std@0.160.0/uuid/v1.ts": "7123410ef9ce980a4f2e54a586ccde5ed7063f6f119a70d86eebd92f8e100295",
- "https://deno.land/std@0.160.0/uuid/v4.ts": "3e983c6ac895ea2a7ba03da927a2438fe1c26ac43fb38dc44f2f8aa50c23cb53",
- "https://deno.land/std@0.160.0/uuid/v5.ts": "43973aeda44ad212f2ec9b8d6c042b74d5cef4ce583d6aa6fc4cdb339344c74c",
- "https://deno.land/std@0.178.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
- "https://deno.land/std@0.178.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3",
- "https://deno.land/std@0.178.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0",
- "https://deno.land/std@0.178.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b",
- "https://deno.land/std@0.178.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0",
- "https://deno.land/std@0.178.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000",
- "https://deno.land/std@0.178.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1",
- "https://deno.land/std@0.178.0/path/mod.ts": "4b83694ac500d7d31b0cdafc927080a53dc0c3027eb2895790fb155082b0d232",
- "https://deno.land/std@0.178.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d",
- "https://deno.land/std@0.178.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1",
- "https://deno.land/std@0.178.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba",
- "https://deno.land/x/another_cookiejar@v4.1.4/cookie.ts": "72d6a6633ea13dd2f13b53d9726735b194996353a958024072c4d6b077c97baf",
- "https://deno.land/x/another_cookiejar@v4.1.4/cookie_jar.ts": "9accd36e76929f2f06fa710d2165fb544703617245fa36ac63560b9fa2a22a25",
- "https://deno.land/x/another_cookiejar@v4.1.4/fetch_wrapper.ts": "d8918c0776413b2d4a675415727973390b4401a026f6dfdcffedce3296b5e0dc",
- "https://deno.land/x/another_cookiejar@v4.1.4/mod.ts": "eff949014965771f2cd447fe78625a1ad28b59333afa40640f02c0922534d89a",
- "https://deno.land/x/msgpack@v1.4/CachedKeyDecoder.ts": "c39b6f1572902ae08c0e4971f639e81031ac59403957fc43c6fb3c7fe69d99a1",
- "https://deno.land/x/msgpack@v1.4/Decoder.ts": "bdb68309cd51da2b9a897f269784c6d636796258838a97f25b0e1b399c6f369b",
- "https://deno.land/x/msgpack@v1.4/Encoder.ts": "4852bbacb30cd66eb2bd61a9e20476802458b991e13aacb5eb984d0348247ffe",
- "https://deno.land/x/msgpack@v1.4/ExtData.ts": "8d97fe43568e119a1eeb93e1ef1c431e0a24e392fb0c6ffed775aac1e579f244",
- "https://deno.land/x/msgpack@v1.4/ExtensionCodec.ts": "e8a24eb1786156239f589cc3058c8ff3d79ed393f420c40fdf7a93df943c91f2",
- "https://deno.land/x/msgpack@v1.4/context.ts": "6228de10854dbadf6aef096960af0115214078ec3784eca4565587769fde3d1c",
- "https://deno.land/x/msgpack@v1.4/decode.ts": "c808aeec46f6d0e5b28d0bbacd40e78d0a3614b229368c70db2e53c03f7555ca",
- "https://deno.land/x/msgpack@v1.4/decodeAsync.ts": "19e4f33ba0cc8d200b857deb9721bace863c0e89f7bff73e2b04379e4ee85bad",
- "https://deno.land/x/msgpack@v1.4/encode.ts": "c5598f8eec9efcbd0ef07f246ade049a8f4906703cdb601baf03b2774b293916",
- "https://deno.land/x/msgpack@v1.4/mod.ts": "c28290db26b1ea027e1798085fd6c8055685ea086f1418d54a33542b285633c9",
- "https://deno.land/x/msgpack@v1.4/timestamp.ts": "5169949efe39bc24f58cd5dcaae682cdf5353c762a54abf9ae6e18c8d9feb648",
- "https://deno.land/x/msgpack@v1.4/utils/int.ts": "b08743982f954d2dd7f4f11d868019576b63cb8147d8acc1bce3843f39398188",
- "https://deno.land/x/msgpack@v1.4/utils/prettyByte.ts": "35c8104d57ba2a727056beaf1063bbe941d512cdd23ce6b04d7c5b44dafcd46e",
- "https://deno.land/x/msgpack@v1.4/utils/stream.ts": "1315e29af5c1a40d97bfa6f1c4f7f73d26067b912236f56851981f2f049500b8",
- "https://deno.land/x/msgpack@v1.4/utils/typedArrays.ts": "bb819c2f28cf7f85ed50b2e57f108462715555cc61ce315e8134cf1eef2ae662",
- "https://deno.land/x/msgpack@v1.4/utils/utf8.ts": "93183055a7a41986080eeb711e83d553e7c8b121642da4379a5adf253b7beefd",
- "https://deno.land/x/murmurhash@v1.0.0/mod.ts": "13fd2c5534dfd22ffbfcd4255ea13e47a2f2b99e9c90a83dc43e814a0e278829",
- "https://deno.land/x/progress@v1.2.8/deps.ts": "e0abdc972a0c152508b28ced5ae9c4be26a5773f0aa4a3caa72371c84d2e28a2",
- "https://deno.land/x/progress@v1.2.8/mod.ts": "5ef7c7ff079d71effed5055666af81cc58a566bc98e2df8473526bd6457976c5",
- "https://deno.land/x/progress@v1.2.8/multi.ts": "392553552243204539d83ee53cadda990db20b1b421520411318ff9bd0320646",
- "https://deno.land/x/semaphore@v1.1.1/mod.ts": "431abb51927a16c537cec1cfb05bf2de6a8f3916331f1ec3f9f13ad7ad6a4ea5",
- "https://deno.land/x/semaphore@v1.1.1/mutex.ts": "2cc6490481f0fdfe97c6b326a2073819d76b76eac3877864a8ada6a2127492f2",
- "https://deno.land/x/semaphore@v1.1.1/semaphore.ts": "0acf1159d635fa3b9198a4ad4acac9e877d79196601aa80544ac0db5a71c646d",
- "https://deno.land/x/ts_essentials@v9.1.2/lib/functions.ts": "20681c98ce82d503dba56f5ef9313c196f18a2317ce7c0c331cc3fdea0d56688",
- "https://deno.land/x/ts_essentials@v9.1.2/lib/literal-types/mod.ts": "c1b9e16a7e49814e9509bed8a5dec25b717761a37d0ef1589d411bd6130dd2e5",
- "https://deno.land/x/ts_essentials@v9.1.2/lib/mod.ts": "d7e44a25aa621425ffd118a0210a492c5c354411018e2db648a68614d5901f5b",
- "https://deno.land/x/ts_essentials@v9.1.2/lib/types.ts": "7ee99797a880948c07020e90d569ca3c5d465c378949262110283aa7856f5603",
- "https://deno.land/x/ts_essentials@v9.1.2/mod.ts": "ffae461c16d4a1bf24c2179582ab8d5c81ad0df61e4ae2fba51ef5e5bdf90345"
- }
-}
\ No newline at end of file
diff --git a/scripts/export-geardata.ts b/scripts/export-geardata.ts
index ff9de8d..1c7f1bc 100644
--- a/scripts/export-geardata.ts
+++ b/scripts/export-geardata.ts
@@ -23,7 +23,7 @@ function encryptKey(uid: string) {
hasher.hash(uid);
const hash = hasher.result();
const key = hash & 0xff;
- const encrypted = base64.encode(
+ const encrypted = base64.encodeBase64(
new TextEncoder().encode(uid).map((i) => i ^ key),
);
return {
diff --git a/scripts/find-id.ts b/scripts/find-id.ts
index d5fdcb6..31bb2f2 100644
--- a/scripts/find-id.ts
+++ b/scripts/find-id.ts
@@ -20,7 +20,7 @@ for (const file of files) {
const content: FileExporterType = JSON.parse(await Deno.readTextFile(file));
if (content.type === "SUMMARY") continue;
const id = content.data.detail.id;
- const rawId = base64.decode(id);
+ const rawId = base64.decodeBase64(id);
const uuid = new TextDecoder().decode(rawId.slice(rawId.length - 36));
if (ids.has(uuid)) {
console.log(
diff --git a/src/RankTracker.test.ts b/src/RankTracker.test.ts
index 0cdd799..94ed977 100644
--- a/src/RankTracker.test.ts
+++ b/src/RankTracker.test.ts
@@ -22,7 +22,7 @@ class TestRankTracker extends RankTracker {
}
function genId(id: number, date = "20220101"): string {
- return base64.encode(
+ return base64.encodeBase64(
`VsHistoryDetail-asdf:asdf:${date}T${
id.toString().padStart(6, "0")
}_------------------------------------`,
diff --git a/src/constant.ts b/src/constant.ts
index ea945ec..45cbb06 100644
--- a/src/constant.ts
+++ b/src/constant.ts
@@ -1,9 +1,9 @@
import type { StatInkPostBody, VsHistoryDetail } from "./types.ts";
export const AGENT_NAME = "s3si.ts";
-export const S3SI_VERSION = "0.4.12";
-export const NSOAPP_VERSION = "2.8.1";
-export const WEB_VIEW_VERSION = "6.0.0-daea5c11";
+export const S3SI_VERSION = "0.4.15";
+export const NSOAPP_VERSION = "2.9.0";
+export const WEB_VIEW_VERSION = "6.0.0-eb33aadc";
export enum Queries {
HomeQuery =
"51fc56bbf006caf37728914aa8bc0e2c86a80cf195b4d4027d6822a3623098a8",
@@ -38,9 +38,9 @@ export const S3SI_LINK = "https://github.com/spacemeowx2/s3si.ts";
export const USERAGENT = `${AGENT_NAME}/${S3SI_VERSION} (${S3SI_LINK})`;
export const DEFAULT_APP_USER_AGENT =
- "Mozilla/5.0 (Linux; Android 11; Pixel 5) " +
+ "Mozilla/5.0 (Linux; Android 14; Pixel 7a) " +
"AppleWebKit/537.36 (KHTML, like Gecko) " +
- "Chrome/94.0.4606.61 Mobile Safari/537.36";
+ "Chrome/120.0.6099.230 Mobile Safari/537.36";
export const SPLATNET3_URL = "https://api.lp1.av5ja.srv.nintendo.net";
export const SPLATNET3_ENDPOINT =
"https://api.lp1.av5ja.srv.nintendo.net/api/graphql";
diff --git a/src/daemon.ts b/src/daemon.ts
index f098cb9..6b120da 100644
--- a/src/daemon.ts
+++ b/src/daemon.ts
@@ -126,8 +126,8 @@ if (import.meta.main) {
const service = new S3SIServiceImplement();
const server = new JSONRPCServer({
transport: new DenoIO({
- reader: Deno.stdin,
- writer: Deno.stdout,
+ reader: Deno.stdin.readable,
+ writer: Deno.stdout.writable,
}),
service,
});
diff --git a/src/exporters/splashcat.ts b/src/exporters/splashcat.ts
index cacc2fe..f4ba63b 100644
--- a/src/exporters/splashcat.ts
+++ b/src/exporters/splashcat.ts
@@ -169,7 +169,7 @@ export class SplashcatExporter implements GameExporter {
}
static getGameId(id: string) {
- const plainText = new TextDecoder().decode(base64.decode(id));
+ const plainText = new TextDecoder().decode(base64.decodeBase64(id));
return plainText.split(":").at(-1);
}
@@ -200,12 +200,14 @@ export class SplashcatExporter implements GameExporter {
const result: Player = {
badges: (player.nameplate as Nameplate).badges.map((i) =>
i
- ? Number(new TextDecoder().decode(base64.decode(i.id)).split("-")[1])
+ ? Number(
+ new TextDecoder().decode(base64.decodeBase64(i.id)).split("-")[1],
+ )
: null
),
splashtagBackgroundId: Number(
new TextDecoder().decode(
- base64.decode((player.nameplate as Nameplate).background.id),
+ base64.decodeBase64((player.nameplate as Nameplate).background.id),
).split("-")[1],
),
clothingGear: this.mapGear(player.clothingGear),
@@ -215,13 +217,17 @@ export class SplashcatExporter implements GameExporter {
isMe: player.isMyself,
name: player.name,
nameId: player.nameId ?? "",
- nplnId: new TextDecoder().decode(base64.decode(player.id)).split(":").at(
+ nplnId: new TextDecoder().decode(base64.decodeBase64(player.id)).split(
+ ":",
+ ).at(
-1,
)!,
paint: player.paint,
species: player.species,
weaponId: Number(
- new TextDecoder().decode(base64.decode(player.weapon.id)).split("-")[1],
+ new TextDecoder().decode(base64.decodeBase64(player.weapon.id)).split(
+ "-",
+ )[1],
),
assists: player.result?.assist,
deaths: player.result?.death,
@@ -270,9 +276,10 @@ export class SplashcatExporter implements GameExporter {
: vsDetail.vsMode.mode,
vsRule: vsDetail.vsRule.rule,
vsStageId: Number(
- new TextDecoder().decode(base64.decode(vsDetail.vsStage.id)).split(
- "-",
- )[1],
+ new TextDecoder().decode(base64.decodeBase64(vsDetail.vsStage.id))
+ .split(
+ "-",
+ )[1],
),
anarchy: vsDetail.vsMode.mode === "BANKARA"
? {
@@ -301,7 +308,7 @@ export class SplashcatExporter implements GameExporter {
challenge: vsDetail.vsMode.mode === "LEAGUE"
? {
id: new TextDecoder().decode(
- base64.decode(vsDetail.leagueMatch?.leagueMatchEvent?.id!),
+ base64.decodeBase64(vsDetail.leagueMatch?.leagueMatchEvent?.id!),
).split("-")[1],
power: vsDetail.leagueMatch?.myLeaguePower ?? undefined,
}
diff --git a/src/iksm.ts b/src/iksm.ts
index f53db9f..9fb537d 100644
--- a/src/iksm.ts
+++ b/src/iksm.ts
@@ -140,7 +140,8 @@ export async function getGToken(
"Content-Type": "application/json",
"Accept": "application/json",
"Connection": "Keep-Alive",
- "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 7.1.2)",
+ "User-Agent":
+ "Dalvik/2.1.0 (Linux; U; Android 14; Pixel 7a Build/UQ1A.240105.004)",
},
body: JSON.stringify({
"client_id": "71b963c1b7b6d119",
@@ -194,7 +195,7 @@ export async function getGToken(
"Content-Type": "application/json; charset=utf-8",
"Connection": "Keep-Alive",
"Accept-Encoding": "gzip",
- "User-Agent": `com.nintendo.znca/${NSOAPP_VERSION}(Android/7.1.2)`,
+ "User-Agent": `com.nintendo.znca/${NSOAPP_VERSION}(Android/14)`,
},
body: JSON.stringify({
parameter: {
@@ -244,7 +245,7 @@ export async function getGToken(
"Authorization": `Bearer ${idToken}`,
"Content-Type": "application/json; charset=utf-8",
"Accept-Encoding": "gzip",
- "User-Agent": `com.nintendo.znca/${NSOAPP_VERSION}(Android/7.1.2)`,
+ "User-Agent": `com.nintendo.znca/${NSOAPP_VERSION}(Android/14)`,
},
body: JSON.stringify({
parameter: {
@@ -425,6 +426,8 @@ async function callImink(
headers: {
"User-Agent": USERAGENT,
"Content-Type": "application/json",
+ "X-znca-Platform": "Android",
+ "X-znca-Version": NSOAPP_VERSION,
},
body: JSON.stringify({
"token": idToken,
diff --git a/src/ipc/mod.ts b/src/ipc/mod.ts
deleted file mode 100644
index f0cfe10..0000000
--- a/src/ipc/mod.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { IPC } from "./stdio.ts";
diff --git a/src/ipc/stdio.ts b/src/ipc/stdio.ts
deleted file mode 100644
index eaabbd2..0000000
--- a/src/ipc/stdio.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-///
-
-import { io, writeAll } from "../../deps.ts";
-import type { ExtractType } from "./types.ts";
-
-export class IPC {
- lines: AsyncIterableIterator;
- writer: Deno.Writer;
- constructor({ reader, writer }: {
- reader: Deno.Reader;
- writer: Deno.Writer;
- }) {
- this.lines = io.readLines(reader);
- this.writer = writer;
- }
- async recvType(
- type: K,
- ): Promise> {
- const data = await this.recv();
- if (data.type !== type) {
- throw new Error(`Unexpected type: ${data.type}`);
- }
- return data as ExtractType;
- }
- async recv(): Promise {
- const result = await this.lines.next();
-
- if (!result.done) {
- return JSON.parse(result.value);
- }
-
- throw new Error("EOF");
- }
- async send(data: T) {
- await writeAll(
- this.writer,
- new TextEncoder().encode(JSON.stringify(data) + "\n"),
- );
- }
-}
diff --git a/src/ipc/types.ts b/src/ipc/types.ts
deleted file mode 100644
index c19944d..0000000
--- a/src/ipc/types.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export type Command = {
- type: "hello";
- data: string;
-};
-
-export type ExtractType =
- Extract<
- T,
- { type: K }
- >;
diff --git a/src/jsonrpc/deno.ts b/src/jsonrpc/deno.ts
index 139200b..80f9e83 100644
--- a/src/jsonrpc/deno.ts
+++ b/src/jsonrpc/deno.ts
@@ -1,15 +1,15 @@
-import { io, writeAll } from "../../deps.ts";
+import { readLines } from "../utils.ts";
import { Transport } from "./types.ts";
export class DenoIO implements Transport {
lines: AsyncIterableIterator;
- writer: Deno.Writer & Deno.Closer;
+ writer: WritableStreamDefaultWriter;
constructor({ reader, writer }: {
- reader: Deno.Reader;
- writer: Deno.Writer & Deno.Closer;
+ reader: ReadableStream;
+ writer: WritableStream;
}) {
- this.lines = io.readLines(reader);
- this.writer = writer;
+ this.lines = readLines(reader);
+ this.writer = writer.getWriter();
}
async recv(): Promise {
const result = await this.lines.next();
@@ -21,10 +21,8 @@ export class DenoIO implements Transport {
return undefined;
}
async send(data: string) {
- await writeAll(
- this.writer,
- new TextEncoder().encode(data + "\n"),
- );
+ await this.writer.ready;
+ await this.writer.write(new TextEncoder().encode(data + "\n"));
}
async close() {
await this.writer.close();
diff --git a/src/utils.test.ts b/src/utils.test.ts
index ff9df7c..622c4c3 100644
--- a/src/utils.test.ts
+++ b/src/utils.test.ts
@@ -9,12 +9,12 @@ const COOP_ID =
Deno.test("gameId", async () => {
assertEquals(
- await gameId(base64.encode(VS_ID)),
+ await gameId(base64.encodeBase64(VS_ID)),
"042bcac9-6b25-5d2e-a5ea-800939a6dea1",
);
assertEquals(
- await gameId(base64.encode(COOP_ID)),
+ await gameId(base64.encodeBase64(COOP_ID)),
"58329d62-737d-5b43-ac22-e35e6e44b077",
);
});
@@ -22,7 +22,7 @@ Deno.test("gameId", async () => {
Deno.test("s3sCoopGameId", async () => {
const S3S_COOP_UUID = "be4435b1-0ac5-577b-81bb-766585bec028";
assertEquals(
- await s3sCoopGameId(base64.encode(COOP_ID)),
+ await s3sCoopGameId(base64.encodeBase64(COOP_ID)),
S3S_COOP_UUID,
);
});
diff --git a/src/utils.ts b/src/utils.ts
index 0bd356a..ea68a35 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -6,29 +6,53 @@ import {
} from "./constant.ts";
import { base64, uuid } from "../deps.ts";
import { Env } from "./env.ts";
-import { io } from "../deps.ts";
-const stdinLines = io.readLines(Deno.stdin);
+export async function* readLines(readable: ReadableStream) {
+ const decoder = new TextDecoder();
+ let buffer = "";
+
+ for await (const chunk of readable) {
+ buffer += decoder.decode(chunk, { stream: true });
+ let lineEndIndex;
+
+ while ((lineEndIndex = buffer.indexOf("\n")) !== -1) {
+ const line = buffer.slice(0, lineEndIndex).trim();
+ buffer = buffer.slice(lineEndIndex + 1);
+
+ yield line;
+ }
+ }
+
+ if (buffer.length > 0) {
+ yield buffer;
+ }
+}
+
+const stdinLines = readLines(Deno.stdin.readable);
export async function readline(
{ skipEmpty = true }: { skipEmpty?: boolean } = {},
) {
- for await (const line of stdinLines) {
+ while (true) {
+ const result = await stdinLines.next();
+ if (result.done) {
+ throw new Error("EOF");
+ }
+ const line = result.value;
if (!skipEmpty || line !== "") {
return line;
}
}
- throw new Error("EOF");
}
export function urlBase64Encode(data: ArrayBuffer) {
- return base64.encode(data)
+ return base64.encodeBase64(data)
.replaceAll("+", "-")
.replaceAll("/", "_")
.replaceAll("=", "");
}
export function urlBase64Decode(data: string) {
- return base64.decode(
+ return base64.encodeBase64(
data
.replaceAll("_", "/")
.replaceAll("-", "+"),
@@ -103,14 +127,14 @@ export function gameId(
);
return uuid.v5.generate(BATTLE_NAMESPACE, content);
} else if (parsed.type === "CoopHistoryDetail") {
- return uuid.v5.generate(COOP_NAMESPACE, base64.decode(id));
+ return uuid.v5.generate(COOP_NAMESPACE, base64.decodeBase64(id));
} else {
throw new Error("Unknown type");
}
}
export function s3siGameId(id: string) {
- const fullId = base64.decode(id);
+ const fullId = base64.decodeBase64(id);
const tsUuid = fullId.slice(fullId.length - 52, fullId.length);
return uuid.v5.generate(S3SI_NAMESPACE, tsUuid);
}
@@ -122,7 +146,7 @@ export function s3siGameId(id: string) {
* @returns uuid used in stat.ink
*/
export function s3sCoopGameId(id: string) {
- const fullId = base64.decode(id);
+ const fullId = base64.decodeBase64(id);
const tsUuid = fullId.slice(fullId.length - 52, fullId.length);
return uuid.v5.generate(COOP_NAMESPACE, tsUuid);
}
@@ -131,7 +155,7 @@ export function s3sCoopGameId(id: string) {
* @param id VsHistoryDetail id or CoopHistoryDetail id
*/
export function parseHistoryDetailId(id: string) {
- const plainText = new TextDecoder().decode(base64.decode(id));
+ const plainText = new TextDecoder().decode(base64.decodeBase64(id));
const vsRE =
/VsHistoryDetail-([a-z0-9-]+):(\w+):(\d{8}T\d{6})_([0-9a-f-]{36})/;
@@ -167,7 +191,7 @@ export const delay = (ms: number) =>
* Decode ID and get number after '-'
*/
export function b64Number(id: string): number {
- const text = new TextDecoder().decode(base64.decode(id));
+ const text = new TextDecoder().decode(base64.decodeBase64(id));
const [_, num] = text.split("-");
return parseInt(num);
}