Compare commits
56 Commits
main
...
nxapi-pres
| Author | SHA1 | Date |
|---|---|---|
|
|
32200ce9bb | |
|
|
02a01188c6 | |
|
|
54a7ff55fa | |
|
|
ed5d286ac7 | |
|
|
f33c03c691 | |
|
|
c004e6596c | |
|
|
c336f0e7b7 | |
|
|
055b1405df | |
|
|
b8e53fc719 | |
|
|
cbe7a5424a | |
|
|
21b02fb44d | |
|
|
6f396d012c | |
|
|
4d65d0acef | |
|
|
72c3b7c9d3 | |
|
|
81a228cfb8 | |
|
|
cc348653d6 | |
|
|
4a0cda32ab | |
|
|
454f294045 | |
|
|
209e5e75ed | |
|
|
02c1c92191 | |
|
|
6a4fd3cceb | |
|
|
a7b0783f89 | |
|
|
bfb7d79609 | |
|
|
e4be0f2fe3 | |
|
|
af0ea16ecc | |
|
|
addb535d96 | |
|
|
94c33bae8f | |
|
|
f236a523f7 | |
|
|
b2555783bb | |
|
|
0cfe618f2f | |
|
|
5e36f6c33d | |
|
|
41d71073dc | |
|
|
5b7d320267 | |
|
|
a770901759 | |
|
|
7a2dedfbe5 | |
|
|
cad2edeaf5 | |
|
|
6582ab408b | |
|
|
348cf6045a | |
|
|
0baad9c04b | |
|
|
56c75385fa | |
|
|
417a52138d | |
|
|
5867740de3 | |
|
|
8707feac01 | |
|
|
740259e156 | |
|
|
2702e6cdf3 | |
|
|
1bc0d3eefc | |
|
|
a67bb4814d | |
|
|
6e5c2e05f3 | |
|
|
40cfd13e6c | |
|
|
6d044a15ae | |
|
|
63ea9347da | |
|
|
a5f35c78c9 | |
|
|
91f528a3be | |
|
|
8a96cb321c | |
|
|
0517bda98d | |
|
|
cabfa8f8c0 |
|
|
@ -7,7 +7,7 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
deno: [1.x, "1.31.x", canary]
|
deno: [1.x, "1.37.x", canary]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: denoland/setup-deno@v1
|
- uses: denoland/setup-deno@v1
|
||||||
|
|
@ -15,8 +15,10 @@ jobs:
|
||||||
deno-version: ${{ matrix.deno }}
|
deno-version: ${{ matrix.deno }}
|
||||||
- name: Check fmt
|
- name: Check fmt
|
||||||
run: deno fmt --check
|
run: deno fmt --check
|
||||||
|
if: ${{ matrix.deno != '1.31.x' }}
|
||||||
- name: Run lint
|
- name: Run lint
|
||||||
run: deno lint
|
run: deno lint
|
||||||
|
if: ${{ matrix.deno != '1.31.x' }}
|
||||||
- name: All entries
|
- name: All entries
|
||||||
uses: tj-actions/glob@v16
|
uses: tj-actions/glob@v16
|
||||||
id: entries
|
id: entries
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
name: Constant Check
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *"
|
|
||||||
jobs:
|
|
||||||
check:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: denoland/setup-deno@v1
|
|
||||||
with:
|
|
||||||
deno-version: 1.x
|
|
||||||
- name: Check constant updates
|
|
||||||
run: deno run -A ./scripts/update-constant.ts
|
|
||||||
- name: Check if workspace is clean
|
|
||||||
run: git diff --exit-code
|
|
||||||
|
|
@ -29,7 +29,7 @@ jobs:
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
version: 7.29.1
|
version: 8.11.0
|
||||||
|
|
||||||
- name: Sync node version and setup cache
|
- name: Sync node version and setup cache
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
|
|
|
||||||
50
CHANGELOG.md
50
CHANGELOG.md
|
|
@ -1,3 +1,53 @@
|
||||||
|
## 0.4.12
|
||||||
|
|
||||||
|
feat: add 6.0.0 special
|
||||||
|
|
||||||
|
## 0.4.11
|
||||||
|
|
||||||
|
chore: update `WEB_VIEW_VERSION` and queries
|
||||||
|
|
||||||
|
## 0.4.10
|
||||||
|
|
||||||
|
feat: support random primary ability
|
||||||
|
|
||||||
|
## 0.4.9
|
||||||
|
|
||||||
|
feat: add species and crown_type
|
||||||
|
|
||||||
|
## 0.4.8
|
||||||
|
|
||||||
|
chore: update `WEB_VIEW_VERSION` and queries
|
||||||
|
|
||||||
|
feat: update VersionData
|
||||||
|
|
||||||
|
## 0.4.7
|
||||||
|
|
||||||
|
chore: update `WEB_VIEW_VERSION`
|
||||||
|
|
||||||
|
## 0.4.6
|
||||||
|
|
||||||
|
chore: update constants
|
||||||
|
|
||||||
|
fix: skip updateState if history if empty
|
||||||
|
([#81](https://github.com/spacemeowx2/s3si.ts/issues/81))
|
||||||
|
|
||||||
|
## 0.4.5
|
||||||
|
|
||||||
|
fix: list method is not auto
|
||||||
|
|
||||||
|
## 0.4.4
|
||||||
|
|
||||||
|
feat: send Anarchy (Open) Power
|
||||||
|
|
||||||
|
## 0.4.3
|
||||||
|
|
||||||
|
feat: add `list-method` option
|
||||||
|
([#73](https://github.com/spacemeowx2/s3si.ts/issues/73))
|
||||||
|
|
||||||
|
## 0.4.2
|
||||||
|
|
||||||
|
fix: `coral_user_id` is string
|
||||||
|
|
||||||
## 0.4.1
|
## 0.4.1
|
||||||
|
|
||||||
feat: add support for Challenges
|
feat: add support for Challenges
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,17 @@ Options:
|
||||||
--exporter <exporter>, -e Exporter list to use (default: stat.ink)
|
--exporter <exporter>, -e Exporter list to use (default: stat.ink)
|
||||||
Multiple exporters can be separated by commas
|
Multiple exporters can be separated by commas
|
||||||
(e.g. "stat.ink,file")
|
(e.g. "stat.ink,file")
|
||||||
|
--list-method When set to "latest", the latest 50 matches will be obtained.
|
||||||
|
When set to "all", matches of all modes will be obtained with a maximum of 250 matches (5 modes x 50 matches).
|
||||||
|
When set to "auto", the latest 50 matches will be obtained. If 50 matches have not been uploaded yet, matches will be obtained from the list of all modes.
|
||||||
|
"auto" is the default setting.
|
||||||
--no-progress, -n Disable progress bar
|
--no-progress, -n Disable progress bar
|
||||||
--monitor, -m Monitor mode
|
--monitor, -m Monitor mode
|
||||||
--skip-mode <mode>, -s Skip mode (default: null)
|
--skip-mode <mode>, -s Skip mode (default: null)
|
||||||
("vs", "coop")
|
("vs", "coop")
|
||||||
--with-summary Include summary in the output
|
--with-summary Include summary in the output
|
||||||
--help Show this help message and exit`,
|
--help Show this help message and exit
|
||||||
|
--nxapi-presence Extends monitoring mode to use Nintendo Switch presence from nxapi
|
||||||
```
|
```
|
||||||
|
|
||||||
3. If it's your first time running this, follow the instructions to login to
|
3. If it's your first time running this, follow the instructions to login to
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
"https://deno.land/std@0.160.0/bytes/mod.ts": "b2e342fd3669176a27a4e15061e9d588b89c1aaf5008ab71766e23669565d179",
|
"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/encoding/base64.ts": "c57868ca7fa2fbe919f57f88a623ad34e3d970d675bdc1ff3a9d02bba7409db2",
|
||||||
"https://deno.land/std@0.160.0/flags/mod.ts": "686b6b36e14b00f11c9e26cecf439021158436a6e34f60eeb0d927f0b169ae20",
|
"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/buffer.ts": "fae02290f52301c4e0188670e730cd902f9307fb732d79c4aa14ebdc82497289",
|
||||||
"https://deno.land/std@0.160.0/io/mod.ts": "6e781ebafd5cdccf9ab4afa1f499b08c513602d023cb08ceebc58758501f78bd",
|
"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/readers.ts": "45847ad404afd2f605eae1cff193f223462bc55eeb9ae313c2f3db28aada0fd6",
|
||||||
|
|
@ -32,6 +33,9 @@
|
||||||
"https://deno.land/std@0.160.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9",
|
"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/path/win32.ts": "ee8826dce087d31c5c81cd414714e677eb68febc40308de87a2ce4b40e10fb8d",
|
||||||
"https://deno.land/std@0.160.0/streams/conversion.ts": "328afbedee0a7e0c330ac4c7b4c1af569ee53974f970230f6a78f545b93abb9b",
|
"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/_common.ts": "76e1fdfb03aecf733f7b3a5edc900f5734f2433b359fdb1535f8de72873bdb3f",
|
||||||
"https://deno.land/std@0.160.0/uuid/mod.ts": "e57ba10200d75f2b17570f13eba19faa6734b1be2da5091e2c01039df41274a5",
|
"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/v1.ts": "7123410ef9ce980a4f2e54a586ccde5ed7063f6f119a70d86eebd92f8e100295",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Tauri + React + Typescript
|
# s3si.ts GUI
|
||||||
|
|
||||||
This template should help get you started developing with Tauri, React and Typescript in Vite.
|
## Development
|
||||||
|
|
||||||
## Recommended IDE Setup
|
```
|
||||||
|
pnpm tauri dev
|
||||||
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
```
|
||||||
|
|
|
||||||
|
|
@ -11,31 +11,32 @@
|
||||||
"lint": "eslint --max-warnings=0 src"
|
"lint": "eslint --max-warnings=0 src"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^1.3.0",
|
"@tauri-apps/api": "^1.4.0",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"daisyui": "^2.52.0",
|
"daisyui": "^3.1.7",
|
||||||
"i18next": "^22.5.0",
|
"i18next": "^23.2.6",
|
||||||
"i18next-browser-languagedetector": "^7.0.2",
|
"i18next-browser-languagedetector": "^7.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^12.3.1",
|
"react-i18next": "^13.0.1",
|
||||||
"react-icons": "^4.9.0",
|
"react-icons": "^4.10.1",
|
||||||
"react-router-dom": "^6.11.2",
|
"react-router-dom": "^6.14.1",
|
||||||
"react-use": "^17.4.0"
|
"react-use": "^17.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^1.3.1",
|
"@tauri-apps/cli": "^1.4.0",
|
||||||
"@types/node": "^20.2.5",
|
"@types/node": "^20.3.3",
|
||||||
"@types/react": "^18.0.15",
|
"@types/react": "^18.0.15",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@vitejs/plugin-react": "^4.0.0",
|
"@typescript-eslint/parser": "^5.61.0",
|
||||||
|
"@vitejs/plugin-react": "^4.0.1",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"eslint": "^8.41.0",
|
"eslint": "^8.44.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"i18next-http-backend": "^2.2.1",
|
"i18next-http-backend": "^2.2.1",
|
||||||
"postcss": "^8.4.24",
|
"postcss": "^8.4.24",
|
||||||
"tailwindcss": "^3.3.2",
|
"tailwindcss": "^3.3.2",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.1.6",
|
||||||
"vite": "^4.3.9",
|
"vite": "^4.3.9",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-tsconfig-paths": "^4.2.0"
|
"vite-tsconfig-paths": "^4.2.0"
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -10,14 +10,21 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "1.2", features = [] }
|
tauri-build = { version = "1.4.0", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "1.2", features = ["fs-all", "path-all", "process-relaunch", "shell-execute", "shell-open", "shell-sidecar", "window-all"] }
|
tauri = { version = "1.4.1", features = [
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
"fs-all",
|
||||||
serde_json = "1.0"
|
"path-all",
|
||||||
tokio = { version = "1.0", features = ["time"] }
|
"process-relaunch",
|
||||||
urlencoding = "2.1.2"
|
"shell-execute",
|
||||||
|
"shell-open",
|
||||||
|
"shell-sidecar",
|
||||||
|
"window-all",
|
||||||
|
] }
|
||||||
|
serde = { version = "1.0.164", features = ["derive"] }
|
||||||
|
serde_json = "1.0.97"
|
||||||
|
tokio = { version = "1.28.2", features = ["time"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,12 @@ function onSelectUserClick(e) {
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// very hacky way...
|
// a little official way...
|
||||||
window.ipc.postMessage(JSON.stringify({
|
window.__TAURI_INVOKE__({
|
||||||
"cmd":"tauri",
|
|
||||||
"callback":0,
|
|
||||||
"error":0,
|
|
||||||
"__tauriModule":"Event",
|
"__tauriModule":"Event",
|
||||||
|
"cmd": "tauri",
|
||||||
"message":{"cmd":"emit","event":"login","payload":{"url":element.href}}
|
"message":{"cmd":"emit","event":"login","payload":{"url":element.href}}
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
function detectAndInject() {
|
function detectAndInject() {
|
||||||
const element = document.getElementById('authorize-switch-approval-link');
|
const element = document.getElementById('authorize-switch-approval-link');
|
||||||
|
|
@ -74,18 +72,13 @@ document.addEventListener("DOMContentLoaded", () => {{
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn open_login_window(app: tauri::AppHandle, url: String) -> Option<String> {
|
async fn open_login_window(app: tauri::AppHandle, url: String) -> Option<String> {
|
||||||
let encoded = urlencoding::encode(&url);
|
let window = WindowBuilder::new(&app, "login", tauri::WindowUrl::App(url.into()))
|
||||||
let window = WindowBuilder::new(
|
.title("Login")
|
||||||
&app,
|
.center()
|
||||||
"login",
|
.inner_size(1040.0, 960.0)
|
||||||
tauri::WindowUrl::App(format!("/redirect?url={encoded}").into()),
|
.initialization_script(INIT_SCRIPT)
|
||||||
)
|
.build()
|
||||||
.title("Login")
|
.ok()?;
|
||||||
.center()
|
|
||||||
.inner_size(1040.0, 960.0)
|
|
||||||
.initialization_script(INIT_SCRIPT)
|
|
||||||
.build()
|
|
||||||
.ok()?;
|
|
||||||
let result: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
|
let result: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
|
||||||
let r2 = result.clone();
|
let r2 = result.clone();
|
||||||
let r3 = result.clone();
|
let r3 = result.clone();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"$schema": "https://github.com/tauri-apps/tauri/raw/tauri-v1.4.1/core/tauri-config-schema/schema.json",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
"beforeBuildCommand": "pnpm build",
|
"beforeBuildCommand": "pnpm build",
|
||||||
|
|
@ -8,7 +9,7 @@
|
||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "s3si-ts",
|
"productName": "s3si-ts",
|
||||||
"version": "0.4.1"
|
"version": "0.4.12"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
|
@ -69,7 +70,16 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": null,
|
||||||
|
"dangerousRemoteDomainIpcAccess": [
|
||||||
|
{
|
||||||
|
"windows": [
|
||||||
|
"login"
|
||||||
|
],
|
||||||
|
"domain": "accounts.nintendo.com",
|
||||||
|
"enableTauriAPI": true
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"updater": {
|
"updater": {
|
||||||
"active": false,
|
"active": false,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { Layout } from "components/Layout";
|
||||||
import { Home } from "pages/Home";
|
import { Home } from "pages/Home";
|
||||||
import { Settings } from "pages/Settings";
|
import { Settings } from "pages/Settings";
|
||||||
import { Guide } from 'pages/Guide';
|
import { Guide } from 'pages/Guide';
|
||||||
import { RedirectLogin } from 'pages/RedirectLogin';
|
|
||||||
import { useShowWindow } from 'hooks/useShowWindow';
|
import { useShowWindow } from 'hooks/useShowWindow';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
@ -15,7 +14,6 @@ function App() {
|
||||||
<Route index element={<Home />} />
|
<Route index element={<Home />} />
|
||||||
<Route path='/settings' element={<Settings />} />
|
<Route path='/settings' element={<Settings />} />
|
||||||
<Route path='/guide' element={<Guide />} />
|
<Route path='/guide' element={<Guide />} />
|
||||||
<Route path='/redirect' element={<RedirectLogin />} />
|
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,10 @@ export const OpenSplatnet: React.FC<OpenSplatnetProps> = ({ children }) => {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const btnLoading = loading || doing;
|
||||||
return <>
|
return <>
|
||||||
<button className={classNames('btn', {
|
<button className={classNames('btn w-full', {
|
||||||
'btn-disabled': !result?.profile.state.loginState?.sessionToken,
|
'btn-disabled': !result?.profile.state.loginState?.sessionToken,
|
||||||
'loading': loading || doing,
|
})} onClick={onClick} disabled={btnLoading}>{btnLoading ? <span className='loading' /> : children}</button>
|
||||||
})} onClick={onClick}>{children}</button>
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,11 +61,11 @@ export const RunPanel: React.FC<RunPanelProps> = () => {
|
||||||
<Checkbox disabled={disabled || loading} value={exportCoop} onChange={setExportCoop}>{t('导出打工数据')}</Checkbox>
|
<Checkbox disabled={disabled || loading} value={exportCoop} onChange={setExportCoop}>{t('导出打工数据')}</Checkbox>
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={classNames('btn w-full', {
|
className={classNames('btn btn-primary w-full', {
|
||||||
'btn-disabled': disabled || (!exportBattle && !exportCoop),
|
'btn-disabled': disabled || (!exportBattle && !exportCoop),
|
||||||
'loading': loading,
|
|
||||||
})}
|
})}
|
||||||
>{t('导出')}</button>
|
disabled={loading}
|
||||||
|
>{loading ? <span className='loading' /> : t('导出')}</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,40 +2,17 @@
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
|
||||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 24px;
|
|
||||||
font-weight: 400;
|
|
||||||
|
|
||||||
color: #0f0f0f;
|
|
||||||
background-color: #f6f6f6;
|
|
||||||
|
|
||||||
font-synthesis: none;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
color: #f6f6f6;
|
|
||||||
background-color: #2f2f2f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* custom classes */
|
/* custom classes */
|
||||||
|
|
||||||
.flex-auto-all > * {
|
.flex-auto-all > * {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export const Home: React.FC = () => {
|
||||||
<Link to='/settings' className='btn'>{t('设置')}</Link>
|
<Link to='/settings' className='btn'>{t('设置')}</Link>
|
||||||
<div className='flex gap-2 flex-auto-all'>
|
<div className='flex gap-2 flex-auto-all'>
|
||||||
<OpenSplatnet>{t('打开鱿鱼圈3')}</OpenSplatnet>
|
<OpenSplatnet>{t('打开鱿鱼圈3')}</OpenSplatnet>
|
||||||
<a className='btn' href={STAT_INK} target='_blank' rel='noreferrer'>{t('前往 stat.ink')}</a>
|
<a className='btn w-full' href={STAT_INK} target='_blank' rel='noreferrer'>{t('前往 stat.ink')}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import { Loading } from 'components/Loading';
|
|
||||||
import React, { useEffect } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useLocation } from 'react-use';
|
|
||||||
|
|
||||||
export const RedirectLogin: React.FC = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const state = useLocation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const search = state.search ?? '';
|
|
||||||
|
|
||||||
const index = search.indexOf('url=');
|
|
||||||
if (index === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const url = decodeURIComponent(search.substring(index + 4));
|
|
||||||
|
|
||||||
window.location.href = url;
|
|
||||||
}, [state])
|
|
||||||
|
|
||||||
return <div className='h-full flex justify-center items-center'>
|
|
||||||
<span className='flex justify-center items-center gap-1'><Loading className='align-middle' />{t('正在跳转到登录页面...')}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
17
s3si.ts
17
s3si.ts
|
|
@ -4,7 +4,13 @@ import { flags } from "./deps.ts";
|
||||||
|
|
||||||
const parseArgs = (args: string[]) => {
|
const parseArgs = (args: string[]) => {
|
||||||
const parsed = flags.parse(args, {
|
const parsed = flags.parse(args, {
|
||||||
string: ["profilePath", "exporter", "skipMode"],
|
string: [
|
||||||
|
"profilePath",
|
||||||
|
"exporter",
|
||||||
|
"skipMode",
|
||||||
|
"listMethod",
|
||||||
|
"nxapiPresenceUrl",
|
||||||
|
],
|
||||||
boolean: ["help", "noProgress", "monitor", "withSummary"],
|
boolean: ["help", "noProgress", "monitor", "withSummary"],
|
||||||
alias: {
|
alias: {
|
||||||
"help": "h",
|
"help": "h",
|
||||||
|
|
@ -14,6 +20,8 @@ const parseArgs = (args: string[]) => {
|
||||||
"monitor": ["m"],
|
"monitor": ["m"],
|
||||||
"skipMode": ["s", "skip-mode"],
|
"skipMode": ["s", "skip-mode"],
|
||||||
"withSummary": "with-summary",
|
"withSummary": "with-summary",
|
||||||
|
"listMethod": "list-method",
|
||||||
|
"nxapiPresenceUrl": ["nxapi-presence"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return parsed;
|
return parsed;
|
||||||
|
|
@ -29,12 +37,17 @@ Options:
|
||||||
--exporter <exporter>, -e Exporter list to use (default: stat.ink)
|
--exporter <exporter>, -e Exporter list to use (default: stat.ink)
|
||||||
Multiple exporters can be separated by commas
|
Multiple exporters can be separated by commas
|
||||||
(e.g. "stat.ink,file")
|
(e.g. "stat.ink,file")
|
||||||
|
--list-method When set to "latest", the latest 50 matches will be obtained.
|
||||||
|
When set to "all", matches of all modes will be obtained with a maximum of 250 matches (5 modes x 50 matches).
|
||||||
|
When set to "auto", the latest 50 matches will be obtained. If 50 matches have not been uploaded yet, matches will be obtained from the list of all modes.
|
||||||
|
"auto" is the default setting.
|
||||||
--no-progress, -n Disable progress bar
|
--no-progress, -n Disable progress bar
|
||||||
--monitor, -m Monitor mode
|
--monitor, -m Monitor mode
|
||||||
--skip-mode <mode>, -s Skip mode (default: null)
|
--skip-mode <mode>, -s Skip mode (default: null)
|
||||||
("vs", "coop")
|
("vs", "coop")
|
||||||
--with-summary Include summary in the output
|
--with-summary Include summary in the output
|
||||||
--help Show this help message and exit`,
|
--help Show this help message and exit
|
||||||
|
--nxapi-presence Extends monitoring mode to use Nintendo Switch presence from nxapi`,
|
||||||
);
|
);
|
||||||
Deno.exit(0);
|
Deno.exit(0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import {
|
||||||
HistoryGroups,
|
HistoryGroups,
|
||||||
RankParam,
|
RankParam,
|
||||||
} from "./types.ts";
|
} from "./types.ts";
|
||||||
import { gameId, parseHistoryDetailId } from "./utils.ts";
|
import { battleTime, gameId } from "./utils.ts";
|
||||||
import { getSeason } from "./VersionData.ts";
|
import { getSeason } from "./VersionData.ts";
|
||||||
|
|
||||||
const splusParams = () => {
|
const splusParams = () => {
|
||||||
|
|
@ -193,17 +193,6 @@ function addRank(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const battleTime = (id: string) => {
|
|
||||||
const { timestamp } = parseHistoryDetailId(id);
|
|
||||||
|
|
||||||
const dateStr = timestamp.replace(
|
|
||||||
/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/,
|
|
||||||
"$1-$2-$3T$4:$5:$6Z",
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Date(dateStr);
|
|
||||||
};
|
|
||||||
|
|
||||||
type FlattenItem = {
|
type FlattenItem = {
|
||||||
id: string;
|
id: string;
|
||||||
gameId: string;
|
gameId: string;
|
||||||
|
|
@ -358,6 +347,10 @@ export class RankTracker {
|
||||||
async updateState(
|
async updateState(
|
||||||
history: HistoryGroups<BattleListNode>["nodes"],
|
history: HistoryGroups<BattleListNode>["nodes"],
|
||||||
) {
|
) {
|
||||||
|
if (history.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// history order by time. 0 is the oldest.
|
// history order by time. 0 is the oldest.
|
||||||
const flatten: FlattenItem[] = await Promise.all(
|
const flatten: FlattenItem[] = await Promise.all(
|
||||||
history
|
history
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,18 @@ export const SEASONS: Season[] = [
|
||||||
start: new Date("2023-06-01T00:00:00+00:00"),
|
start: new Date("2023-06-01T00:00:00+00:00"),
|
||||||
end: new Date("2023-09-01T00:00:00+00:00"),
|
end: new Date("2023-09-01T00:00:00+00:00"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "season202309",
|
||||||
|
name: "Drizzle Season 2023",
|
||||||
|
start: new Date("2023-09-01T00:00:00+00:00"),
|
||||||
|
end: new Date("2023-12-01T00:00:00+00:00"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "season202312",
|
||||||
|
name: "Chill Season 2023",
|
||||||
|
start: new Date("2023-12-01T00:00:00+00:00"),
|
||||||
|
end: new Date("2024-03-01T00:00:00+00:00"),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getSeason = (date: Date): Season | undefined => {
|
export const getSeason = (date: Date): Season | undefined => {
|
||||||
|
|
|
||||||
176
src/app.ts
176
src/app.ts
|
|
@ -1,14 +1,16 @@
|
||||||
import { loginManually } from "./iksm.ts";
|
import { loginManually } from "./iksm.ts";
|
||||||
import { MultiProgressBar } from "../deps.ts";
|
import { MultiProgressBar, Mutex } from "../deps.ts";
|
||||||
import { FileStateBackend, Profile, StateBackend } from "./state.ts";
|
import { FileStateBackend, Profile, StateBackend } from "./state.ts";
|
||||||
import { Splatnet3 } from "./splatnet3.ts";
|
import { Splatnet3 } from "./splatnet3.ts";
|
||||||
import { BattleListType, Game, GameExporter } from "./types.ts";
|
import { BattleListType, Game, GameExporter, ListMethod } from "./types.ts";
|
||||||
import { Cache, FileCache } from "./cache.ts";
|
import { Cache, FileCache } from "./cache.ts";
|
||||||
import { StatInkExporter } from "./exporters/stat.ink.ts";
|
import { StatInkExporter } from "./exporters/stat.ink.ts";
|
||||||
import { FileExporter } from "./exporters/file.ts";
|
import { FileExporter } from "./exporters/file.ts";
|
||||||
import { delay, showError } from "./utils.ts";
|
import { delay, showError } from "./utils.ts";
|
||||||
import { GameFetcher } from "./GameFetcher.ts";
|
import { GameFetcher } from "./GameFetcher.ts";
|
||||||
import { DEFAULT_ENV, Env } from "./env.ts";
|
import { DEFAULT_ENV, Env } from "./env.ts";
|
||||||
|
import { SPLATOON3_TITLE_ID } from "./constant.ts";
|
||||||
|
import { USERAGENT } from "./constant.ts";
|
||||||
|
|
||||||
export type Opts = {
|
export type Opts = {
|
||||||
profilePath: string;
|
profilePath: string;
|
||||||
|
|
@ -17,9 +19,11 @@ export type Opts = {
|
||||||
monitor: boolean;
|
monitor: boolean;
|
||||||
withSummary: boolean;
|
withSummary: boolean;
|
||||||
skipMode?: string;
|
skipMode?: string;
|
||||||
|
listMethod?: string;
|
||||||
cache?: Cache;
|
cache?: Cache;
|
||||||
stateBackend?: StateBackend;
|
stateBackend?: StateBackend;
|
||||||
env: Env;
|
env: Env;
|
||||||
|
nxapiPresenceUrl?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_OPTS: Opts = {
|
export const DEFAULT_OPTS: Opts = {
|
||||||
|
|
@ -28,6 +32,7 @@ export const DEFAULT_OPTS: Opts = {
|
||||||
noProgress: false,
|
noProgress: false,
|
||||||
monitor: false,
|
monitor: false,
|
||||||
withSummary: false,
|
withSummary: false,
|
||||||
|
listMethod: "auto",
|
||||||
env: DEFAULT_ENV,
|
env: DEFAULT_ENV,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -52,6 +57,103 @@ class StepProgress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GameListFetcher {
|
||||||
|
/**
|
||||||
|
* Return not exported game list.
|
||||||
|
* [0] is the latest game.
|
||||||
|
* @param exporter GameExporter
|
||||||
|
*/
|
||||||
|
fetch(exporter: GameExporter): Promise<string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BattleListFetcher implements GameListFetcher {
|
||||||
|
protected listMethod: ListMethod;
|
||||||
|
protected allBattleList?: string[];
|
||||||
|
protected latestBattleList?: string[];
|
||||||
|
protected allLock = new Mutex();
|
||||||
|
protected latestLock = new Mutex();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
listMethod: string,
|
||||||
|
protected splatnet: Splatnet3,
|
||||||
|
) {
|
||||||
|
if (listMethod === "all") {
|
||||||
|
this.listMethod = "all";
|
||||||
|
} else if (listMethod === "latest") {
|
||||||
|
this.listMethod = "latest";
|
||||||
|
} else {
|
||||||
|
this.listMethod = "auto";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getAllBattleList() {
|
||||||
|
return this.allLock.use(async () => {
|
||||||
|
if (!this.allBattleList) {
|
||||||
|
this.allBattleList = await this.splatnet.getAllBattleList();
|
||||||
|
}
|
||||||
|
return this.allBattleList;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getLatestBattleList() {
|
||||||
|
return this.latestLock.use(async () => {
|
||||||
|
if (!this.latestBattleList) {
|
||||||
|
this.latestBattleList = await this.splatnet.getBattleList();
|
||||||
|
}
|
||||||
|
return this.latestBattleList;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async innerFetch(exporter: GameExporter) {
|
||||||
|
if (this.listMethod === "latest") {
|
||||||
|
return await exporter.notExported({
|
||||||
|
type: "VsInfo",
|
||||||
|
list: await this.getLatestBattleList(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.listMethod === "all") {
|
||||||
|
return await exporter.notExported({
|
||||||
|
type: "VsInfo",
|
||||||
|
list: await this.getAllBattleList(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.listMethod === "auto") {
|
||||||
|
const latestList = await exporter.notExported({
|
||||||
|
type: "VsInfo",
|
||||||
|
list: await this.getLatestBattleList(),
|
||||||
|
});
|
||||||
|
if (latestList.length === 50) {
|
||||||
|
return await exporter.notExported({
|
||||||
|
type: "VsInfo",
|
||||||
|
list: await this.getAllBattleList(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return latestList;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TypeError(`Unknown listMethod: ${this.listMethod}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetch(exporter: GameExporter) {
|
||||||
|
return [...await this.innerFetch(exporter)].reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CoopListFetcher implements GameListFetcher {
|
||||||
|
constructor(
|
||||||
|
protected splatnet: Splatnet3,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async fetch(exporter: GameExporter) {
|
||||||
|
return [
|
||||||
|
...await exporter.notExported({
|
||||||
|
type: "CoopInfo",
|
||||||
|
list: await this.splatnet.getBattleList(BattleListType.Coop),
|
||||||
|
}),
|
||||||
|
].reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function progress({ total, currentUrl, done }: StepProgress): Progress {
|
function progress({ total, currentUrl, done }: StepProgress): Progress {
|
||||||
return {
|
return {
|
||||||
total,
|
total,
|
||||||
|
|
@ -63,6 +165,7 @@ function progress({ total, currentUrl, done }: StepProgress): Progress {
|
||||||
export class App {
|
export class App {
|
||||||
profile: Profile;
|
profile: Profile;
|
||||||
env: Env;
|
env: Env;
|
||||||
|
splatoon3PreviouslyActive = false;
|
||||||
|
|
||||||
constructor(public opts: Opts) {
|
constructor(public opts: Opts) {
|
||||||
const stateBackend = opts.stateBackend ??
|
const stateBackend = opts.stateBackend ??
|
||||||
|
|
@ -72,6 +175,12 @@ export class App {
|
||||||
env: opts.env,
|
env: opts.env,
|
||||||
});
|
});
|
||||||
this.env = opts.env;
|
this.env = opts.env;
|
||||||
|
|
||||||
|
if (
|
||||||
|
opts.listMethod && !["all", "auto", "latest"].includes(opts.listMethod)
|
||||||
|
) {
|
||||||
|
throw new TypeError(`Unknown listMethod: ${opts.listMethod}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getSkipMode(): ("vs" | "coop")[] {
|
getSkipMode(): ("vs" | "coop")[] {
|
||||||
|
|
@ -162,8 +271,10 @@ export class App {
|
||||||
if (skipMode.includes("vs") || exporters.length === 0) {
|
if (skipMode.includes("vs") || exporters.length === 0) {
|
||||||
this.env.logger.log("Skip exporting VS games.");
|
this.env.logger.log("Skip exporting VS games.");
|
||||||
} else {
|
} else {
|
||||||
this.env.logger.log("Fetching battle list...");
|
const gameListFetcher = new BattleListFetcher(
|
||||||
const gameList = await splatnet.getBattleList();
|
this.opts.listMethod ?? "auto",
|
||||||
|
splatnet,
|
||||||
|
);
|
||||||
|
|
||||||
const { redraw, endBar } = this.exporterProgress("Export vs games");
|
const { redraw, endBar } = this.exporterProgress("Export vs games");
|
||||||
const fetcher = new GameFetcher({
|
const fetcher = new GameFetcher({
|
||||||
|
|
@ -182,7 +293,7 @@ export class App {
|
||||||
type: "VsInfo",
|
type: "VsInfo",
|
||||||
fetcher,
|
fetcher,
|
||||||
exporter: e,
|
exporter: e,
|
||||||
gameList,
|
gameListFetcher,
|
||||||
stepProgress: stats[e.name],
|
stepProgress: stats[e.name],
|
||||||
onStep: () => {
|
onStep: () => {
|
||||||
redraw(e.name, progress(stats[e.name]));
|
redraw(e.name, progress(stats[e.name]));
|
||||||
|
|
@ -216,10 +327,7 @@ export class App {
|
||||||
if (skipMode.includes("coop") || exporters.length === 0) {
|
if (skipMode.includes("coop") || exporters.length === 0) {
|
||||||
this.env.logger.log("Skip exporting coop games.");
|
this.env.logger.log("Skip exporting coop games.");
|
||||||
} else {
|
} else {
|
||||||
this.env.logger.log("Fetching coop battle list...");
|
const gameListFetcher = new CoopListFetcher(splatnet);
|
||||||
const coopBattleList = await splatnet.getBattleList(
|
|
||||||
BattleListType.Coop,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { redraw, endBar } = this.exporterProgress("Export coop games");
|
const { redraw, endBar } = this.exporterProgress("Export coop games");
|
||||||
const fetcher = new GameFetcher({
|
const fetcher = new GameFetcher({
|
||||||
|
|
@ -236,7 +344,7 @@ export class App {
|
||||||
type: "CoopInfo",
|
type: "CoopInfo",
|
||||||
fetcher,
|
fetcher,
|
||||||
exporter: e,
|
exporter: e,
|
||||||
gameList: coopBattleList,
|
gameListFetcher,
|
||||||
stepProgress: stats[e.name],
|
stepProgress: stats[e.name],
|
||||||
onStep: () => {
|
onStep: () => {
|
||||||
redraw(e.name, progress(stats[e.name]));
|
redraw(e.name, progress(stats[e.name]));
|
||||||
|
|
@ -291,6 +399,36 @@ export class App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async monitorWithNxapi() {
|
||||||
|
this.env.logger.debug("Monitoring with nxapi presence");
|
||||||
|
const fetcher = this.env.newFetcher();
|
||||||
|
await this.exportOnce();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
await this.countDown(this.profile.state.monitorInterval);
|
||||||
|
const nxapiResponse = await fetcher.get({
|
||||||
|
url: this.opts.nxapiPresenceUrl!,
|
||||||
|
headers: {
|
||||||
|
"User-Agent": USERAGENT,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const nxapiData = await nxapiResponse.json();
|
||||||
|
const isSplatoon3Active = nxapiData.title?.id === SPLATOON3_TITLE_ID;
|
||||||
|
if (isSplatoon3Active || this.splatoon3PreviouslyActive) {
|
||||||
|
this.env.logger.log("Splatoon 3 is active, exporting data");
|
||||||
|
await this.exportOnce();
|
||||||
|
}
|
||||||
|
if (isSplatoon3Active !== this.splatoon3PreviouslyActive) {
|
||||||
|
this.env.logger.debug(
|
||||||
|
"Splatoon 3 status has changed from",
|
||||||
|
this.splatoon3PreviouslyActive,
|
||||||
|
"to",
|
||||||
|
isSplatoon3Active,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.splatoon3PreviouslyActive = isSplatoon3Active;
|
||||||
|
}
|
||||||
|
}
|
||||||
async monitor() {
|
async monitor() {
|
||||||
while (true) {
|
while (true) {
|
||||||
await this.exportOnce();
|
await this.exportOnce();
|
||||||
|
|
@ -328,7 +466,9 @@ export class App {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.opts.monitor) {
|
if (this.opts.nxapiPresenceUrl) {
|
||||||
|
await this.monitorWithNxapi();
|
||||||
|
} else if (this.opts.monitor) {
|
||||||
await this.monitor();
|
await this.monitor();
|
||||||
} else {
|
} else {
|
||||||
await this.exportOnce();
|
await this.exportOnce();
|
||||||
|
|
@ -342,30 +482,24 @@ export class App {
|
||||||
* @param gameList ID list of games, sorted by date, newest first
|
* @param gameList ID list of games, sorted by date, newest first
|
||||||
* @param onStep Callback function called when a game is exported
|
* @param onStep Callback function called when a game is exported
|
||||||
*/
|
*/
|
||||||
async exportGameList({
|
private async exportGameList({
|
||||||
type,
|
type,
|
||||||
fetcher,
|
fetcher,
|
||||||
exporter,
|
exporter,
|
||||||
gameList,
|
gameListFetcher,
|
||||||
stepProgress,
|
stepProgress,
|
||||||
onStep,
|
onStep,
|
||||||
}: {
|
}: {
|
||||||
type: Game["type"];
|
type: Game["type"];
|
||||||
exporter: GameExporter;
|
exporter: GameExporter;
|
||||||
fetcher: GameFetcher;
|
fetcher: GameFetcher;
|
||||||
gameList: string[];
|
gameListFetcher: GameListFetcher;
|
||||||
stepProgress: StepProgress;
|
stepProgress: StepProgress;
|
||||||
onStep: () => void;
|
onStep: () => void;
|
||||||
}): Promise<StepProgress> {
|
}): Promise<StepProgress> {
|
||||||
onStep?.();
|
onStep?.();
|
||||||
|
|
||||||
const workQueue = [
|
const workQueue = await gameListFetcher.fetch(exporter);
|
||||||
...await exporter.notExported({
|
|
||||||
type,
|
|
||||||
list: gameList,
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
.reverse();
|
|
||||||
|
|
||||||
const step = async (id: string) => {
|
const step = async (id: string) => {
|
||||||
const detail = await fetcher.fetch(type, id);
|
const detail = await fetcher.fetch(type, id);
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,38 @@
|
||||||
import type { StatInkPostBody, VsHistoryDetail } from "./types.ts";
|
import type { StatInkPostBody, VsHistoryDetail } from "./types.ts";
|
||||||
|
|
||||||
export const AGENT_NAME = "s3si.ts";
|
export const AGENT_NAME = "s3si.ts";
|
||||||
export const S3SI_VERSION = "0.4.1";
|
export const S3SI_VERSION = "0.4.12";
|
||||||
export const NSOAPP_VERSION = "2.5.1";
|
export const NSOAPP_VERSION = "2.8.1";
|
||||||
export const WEB_VIEW_VERSION = "4.0.0-d5178440";
|
export const WEB_VIEW_VERSION = "6.0.0-daea5c11";
|
||||||
export enum Queries {
|
export enum Queries {
|
||||||
HomeQuery = "7dcc64ea27a08e70919893a0d3f70871",
|
HomeQuery =
|
||||||
LatestBattleHistoriesQuery = "0d90c7576f1916469b2ae69f64292c02",
|
"51fc56bbf006caf37728914aa8bc0e2c86a80cf195b4d4027d6822a3623098a8",
|
||||||
RegularBattleHistoriesQuery = "3baef04b095ad8975ea679d722bc17de",
|
LatestBattleHistoriesQuery =
|
||||||
BankaraBattleHistoriesQuery = "0438ea6978ae8bd77c5d1250f4f84803",
|
"b24d22fd6cb251c515c2b90044039698aa27bc1fab15801d83014d919cd45780",
|
||||||
XBattleHistoriesQuery = "6796e3cd5dc3ebd51864dc709d899fc5",
|
RegularBattleHistoriesQuery =
|
||||||
PrivateBattleHistoriesQuery = "8e5ae78b194264a6c230e262d069bd28",
|
"2fe6ea7a2de1d6a888b7bd3dbeb6acc8e3246f055ca39b80c4531bbcd0727bba",
|
||||||
VsHistoryDetailQuery = "9ee0099fbe3d8db2a838a75cf42856dd",
|
BankaraBattleHistoriesQuery =
|
||||||
CoopHistoryQuery = "91b917becd2fa415890f5b47e15ffb15",
|
"9863ea4744730743268e2940396e21b891104ed40e2286789f05100b45a0b0fd",
|
||||||
CoopHistoryDetailQuery = "379f0d9b78b531be53044bcac031b34b",
|
XBattleHistoriesQuery =
|
||||||
|
"eb5996a12705c2e94813a62e05c0dc419aad2811b8d49d53e5732290105559cb",
|
||||||
|
EventBattleHistoriesQuery =
|
||||||
|
"e47f9aac5599f75c842335ef0ab8f4c640e8bf2afe588a3b1d4b480ee79198ac",
|
||||||
|
PrivateBattleHistoriesQuery =
|
||||||
|
"fef94f39b9eeac6b2fac4de43bc0442c16a9f2df95f4d367dd8a79d7c5ed5ce7",
|
||||||
|
VsHistoryDetailQuery =
|
||||||
|
"f893e1ddcfb8a4fd645fd75ced173f18b2750e5cfba41d2669b9814f6ceaec46",
|
||||||
|
CoopHistoryQuery =
|
||||||
|
"0f8c33970a425683bb1bdecca50a0ca4fb3c3641c0b2a1237aedfde9c0cb2b8f",
|
||||||
|
CoopHistoryDetailQuery =
|
||||||
|
"42262d241291d7324649e21413b29da88c0314387d8fdf5f6637a2d9d29954ae",
|
||||||
myOutfitCommonDataFilteringConditionQuery =
|
myOutfitCommonDataFilteringConditionQuery =
|
||||||
"d02ab22c9dccc440076055c8baa0fa7a",
|
"ac20c44a952131cb0c9d00eda7bc1a84c1a99546f0f1fc170212d5a6bb51a426",
|
||||||
myOutfitCommonDataEquipmentsQuery = "d29cd0c2b5e6bac90dd5b817914832f8",
|
myOutfitCommonDataEquipmentsQuery =
|
||||||
HistoryRecordQuery = "d9246baf077b2a29b5f7aac321810a77",
|
"45a4c343d973864f7bb9e9efac404182be1d48cf2181619505e9b7cd3b56a6e8",
|
||||||
ConfigureAnalyticsQuery = "f8ae00773cc412a50dd41a6d9a159ddd",
|
HistoryRecordQuery =
|
||||||
|
"0a62c0152f27c4218cf6c87523377521c2cff76a4ef0373f2da3300079bf0388",
|
||||||
|
ConfigureAnalyticsQuery =
|
||||||
|
"2a9302bdd09a13f8b344642d4ed483b9464f20889ac17401e993dfa5c2bb3607",
|
||||||
}
|
}
|
||||||
export const S3SI_LINK = "https://github.com/spacemeowx2/s3si.ts";
|
export const S3SI_LINK = "https://github.com/spacemeowx2/s3si.ts";
|
||||||
|
|
||||||
|
|
@ -91,6 +105,10 @@ export const SPLATNET3_STATINK_MAP: {
|
||||||
"sameride",
|
"sameride",
|
||||||
"380e541b5bc5e49d77ff1a616f1343aeba01d500fee36aaddf8f09d74bd3d3bc":
|
"380e541b5bc5e49d77ff1a616f1343aeba01d500fee36aaddf8f09d74bd3d3bc":
|
||||||
"tripletornado",
|
"tripletornado",
|
||||||
|
"8a7ee88a06407f4be1595ef8af4d2d2ac22bbf213a622cd19bbfaf4d0f36bcd7":
|
||||||
|
"teioika",
|
||||||
|
"a75eac34675bc0d4bd9ca9977cf22472848f89e28e08ee986b4461a3f2af28fc":
|
||||||
|
"ultra_chakuchi",
|
||||||
},
|
},
|
||||||
WATER_LEVEL_MAP: {
|
WATER_LEVEL_MAP: {
|
||||||
0: "low",
|
0: "low",
|
||||||
|
|
@ -98,3 +116,5 @@ export const SPLATNET3_STATINK_MAP: {
|
||||||
2: "high",
|
2: "high",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SPLATOON3_TITLE_ID = "0100c2500fc20000";
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ import {
|
||||||
urlSimplify,
|
urlSimplify,
|
||||||
} from "../utils.ts";
|
} from "../utils.ts";
|
||||||
import { Env } from "../env.ts";
|
import { Env } from "../env.ts";
|
||||||
import GEAR_MAP from "../assets/gear-map.json" assert { type: "json" };
|
import GEAR_MAP from "../assets/gear-map.json" with { type: "json" };
|
||||||
|
|
||||||
const COOP_POINT_MAP: Record<number, number | undefined> = {
|
const COOP_POINT_MAP: Record<number, number | undefined> = {
|
||||||
0: -20,
|
0: -20,
|
||||||
|
|
@ -365,7 +365,7 @@ export class StatInkExporter implements GameExporter {
|
||||||
{ primaryGearPower, additionalGearPowers }: PlayerGear,
|
{ primaryGearPower, additionalGearPowers }: PlayerGear,
|
||||||
): StatInkGear => {
|
): StatInkGear => {
|
||||||
const primary = mapAbility(primaryGearPower);
|
const primary = mapAbility(primaryGearPower);
|
||||||
if (!primary) {
|
if (!primary && !this.isRandom(primaryGearPower.image)) {
|
||||||
throw new Error("Unknown ability: " + primaryGearPower.name);
|
throw new Error("Unknown ability: " + primaryGearPower.name);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
@ -393,6 +393,8 @@ export class StatInkExporter implements GameExporter {
|
||||||
inked: player.paint,
|
inked: player.paint,
|
||||||
gears: await this.mapGears(player),
|
gears: await this.mapGears(player),
|
||||||
crown: player.crown ? "yes" : "no",
|
crown: player.crown ? "yes" : "no",
|
||||||
|
crown_type: undefined,
|
||||||
|
species: player.species === "INKLING" ? "inkling" : "octoling",
|
||||||
disconnected: player.result ? "no" : "yes",
|
disconnected: player.result ? "no" : "yes",
|
||||||
};
|
};
|
||||||
if (player.result) {
|
if (player.result) {
|
||||||
|
|
@ -403,6 +405,13 @@ export class StatInkExporter implements GameExporter {
|
||||||
result.signal = player.result.noroshiTry ?? undefined;
|
result.signal = player.result.noroshiTry ?? undefined;
|
||||||
result.special = player.result.special;
|
result.special = player.result.special;
|
||||||
}
|
}
|
||||||
|
if (player.crown) {
|
||||||
|
result.crown_type = "x";
|
||||||
|
} else if (player.festDragonCert === "DRAGON") {
|
||||||
|
result.crown_type = "100x";
|
||||||
|
} else if (player.festDragonCert === "DOUBLE_DRAGON") {
|
||||||
|
result.crown_type = "333x";
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
async mapBattle(
|
async mapBattle(
|
||||||
|
|
@ -587,6 +596,8 @@ export class StatInkExporter implements GameExporter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.bankara_power_after = vsDetail.bankaraMatch?.bankaraPower?.power;
|
||||||
|
|
||||||
if (rankBeforeState && rankState) {
|
if (rankBeforeState && rankState) {
|
||||||
result.rank_before_exp = rankBeforeState.rankPoint;
|
result.rank_before_exp = rankBeforeState.rankPoint;
|
||||||
result.rank_after_exp = rankState.rankPoint;
|
result.rank_after_exp = rankState.rankPoint;
|
||||||
|
|
@ -618,16 +629,18 @@ export class StatInkExporter implements GameExporter {
|
||||||
}
|
}
|
||||||
isRandom(image: Image | null): boolean {
|
isRandom(image: Image | null): boolean {
|
||||||
// question mark
|
// question mark
|
||||||
const RANDOM_FILENAME =
|
const RANDOM_FILENAME = [
|
||||||
"473fffb2442075078d8bb7125744905abdeae651b6a5b7453ae295582e45f7d1";
|
"473fffb2442075078d8bb7125744905abdeae651b6a5b7453ae295582e45f7d1",
|
||||||
|
"dc937b59892604f5a86ac96936cd7ff09e25f18ae6b758e8014a24c7fa039e91",
|
||||||
|
];
|
||||||
// file exporter will replace url to { pathname: string } | string
|
// file exporter will replace url to { pathname: string } | string
|
||||||
const url = image?.url as ReturnType<typeof urlSimplify> | undefined | null;
|
const url = image?.url as ReturnType<typeof urlSimplify> | undefined | null;
|
||||||
if (typeof url === "string") {
|
if (typeof url === "string") {
|
||||||
return url.includes(RANDOM_FILENAME);
|
return RANDOM_FILENAME.some((i) => url.includes(i));
|
||||||
} else if (url === undefined || url === null) {
|
} else if (url === undefined || url === null) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return url.pathname.includes(RANDOM_FILENAME);
|
return RANDOM_FILENAME.some((i) => url.pathname.includes(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async mapCoopWeapon(
|
async mapCoopWeapon(
|
||||||
|
|
@ -697,6 +710,7 @@ export class StatInkExporter implements GameExporter {
|
||||||
rescued: rescuedCount,
|
rescued: rescuedCount,
|
||||||
defeat_boss: defeatEnemyCount,
|
defeat_boss: defeatEnemyCount,
|
||||||
disconnected: disconnected ? "yes" : "no",
|
disconnected: disconnected ? "yes" : "no",
|
||||||
|
species: player.species === "INKLING" ? "inkling" : "octoling",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
mapKing(id?: string) {
|
mapKing(id?: string) {
|
||||||
|
|
|
||||||
|
|
@ -213,20 +213,20 @@ export async function getGToken(
|
||||||
|
|
||||||
const idToken2: string = respJson?.result?.webApiServerCredential
|
const idToken2: string = respJson?.result?.webApiServerCredential
|
||||||
?.accessToken;
|
?.accessToken;
|
||||||
const coralUserId: number = respJson?.result?.user?.id;
|
const coralUserId: string = respJson?.result?.user?.id?.toString();
|
||||||
|
|
||||||
if (!idToken2 || !coralUserId) {
|
if (!idToken2 || !coralUserId) {
|
||||||
throw new APIError({
|
throw new APIError({
|
||||||
response: resp,
|
response: resp,
|
||||||
json: respJson,
|
json: respJson,
|
||||||
message:
|
message:
|
||||||
`No idToken2 or coralUserId found. Please try again later. ('${idToken2}', '${coralUserId}')`,
|
`No idToken2 or coralUserId found. Please try again later. (${idToken2.length}, ${coralUserId.length})`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return [idToken2, coralUserId] as const;
|
return [idToken2, coralUserId] as const;
|
||||||
};
|
};
|
||||||
const getGToken = async (idToken: string, coralUserId: number) => {
|
const getGToken = async (idToken: string, coralUserId: string) => {
|
||||||
const { f, request_id: requestId, timestamp } = await callImink({
|
const { f, request_id: requestId, timestamp } = await callImink({
|
||||||
step: 2,
|
step: 2,
|
||||||
idToken,
|
idToken,
|
||||||
|
|
@ -414,7 +414,7 @@ async function callImink(
|
||||||
step: number;
|
step: number;
|
||||||
idToken: string;
|
idToken: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
coralUserId?: number;
|
coralUserId?: string;
|
||||||
env: Env;
|
env: Env;
|
||||||
},
|
},
|
||||||
): Promise<IminkResponse> {
|
): Promise<IminkResponse> {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
} from "./types.ts";
|
} from "./types.ts";
|
||||||
import { DEFAULT_ENV, Env } from "./env.ts";
|
import { DEFAULT_ENV, Env } from "./env.ts";
|
||||||
import { getBulletToken, getGToken } from "./iksm.ts";
|
import { getBulletToken, getGToken } from "./iksm.ts";
|
||||||
import { parseHistoryDetailId } from "./utils.ts";
|
import { battleTime, parseHistoryDetailId } from "./utils.ts";
|
||||||
|
|
||||||
export class Splatnet3 {
|
export class Splatnet3 {
|
||||||
protected profile: Profile;
|
protected profile: Profile;
|
||||||
|
|
@ -137,6 +137,12 @@ export class Splatnet3 {
|
||||||
[BattleListType.Bankara]: () =>
|
[BattleListType.Bankara]: () =>
|
||||||
this.request(Queries.BankaraBattleHistoriesQuery)
|
this.request(Queries.BankaraBattleHistoriesQuery)
|
||||||
.then((r) => getIdsFromGroups(r.bankaraBattleHistories)),
|
.then((r) => getIdsFromGroups(r.bankaraBattleHistories)),
|
||||||
|
[BattleListType.XBattle]: () =>
|
||||||
|
this.request(Queries.XBattleHistoriesQuery)
|
||||||
|
.then((r) => getIdsFromGroups(r.xBattleHistories)),
|
||||||
|
[BattleListType.Event]: () =>
|
||||||
|
this.request(Queries.EventBattleHistoriesQuery)
|
||||||
|
.then((r) => getIdsFromGroups(r.eventBattleHistories)),
|
||||||
[BattleListType.Private]: () =>
|
[BattleListType.Private]: () =>
|
||||||
this.request(Queries.PrivateBattleHistoriesQuery)
|
this.request(Queries.PrivateBattleHistoriesQuery)
|
||||||
.then((r) => getIdsFromGroups(r.privateBattleHistories)),
|
.then((r) => getIdsFromGroups(r.privateBattleHistories)),
|
||||||
|
|
@ -168,6 +174,29 @@ export class Splatnet3 {
|
||||||
return await this.BATTLE_LIST_TYPE_MAP[battleListType]();
|
return await this.BATTLE_LIST_TYPE_MAP[battleListType]();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get all id from all battle list, sort by time, [0] is the latest
|
||||||
|
async getAllBattleList() {
|
||||||
|
const ALL_TYPE: BattleListType[] = [
|
||||||
|
BattleListType.Regular,
|
||||||
|
BattleListType.Bankara,
|
||||||
|
BattleListType.XBattle,
|
||||||
|
BattleListType.Event,
|
||||||
|
BattleListType.Private,
|
||||||
|
];
|
||||||
|
const ids: string[] = [];
|
||||||
|
for (const type of ALL_TYPE) {
|
||||||
|
ids.push(...await this.getBattleList(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeMap = new Map<string, Date>(
|
||||||
|
ids.map((id) => [id, battleTime(id)] as const),
|
||||||
|
);
|
||||||
|
|
||||||
|
return ids.sort((a, b) =>
|
||||||
|
timeMap.get(b)!.getTime() - timeMap.get(a)!.getTime()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getBattleDetail(
|
getBattleDetail(
|
||||||
id: string,
|
id: string,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
23
src/types.ts
23
src/types.ts
|
|
@ -8,6 +8,7 @@ export type VarsMap = {
|
||||||
[Queries.RegularBattleHistoriesQuery]: [];
|
[Queries.RegularBattleHistoriesQuery]: [];
|
||||||
[Queries.BankaraBattleHistoriesQuery]: [];
|
[Queries.BankaraBattleHistoriesQuery]: [];
|
||||||
[Queries.XBattleHistoriesQuery]: [];
|
[Queries.XBattleHistoriesQuery]: [];
|
||||||
|
[Queries.EventBattleHistoriesQuery]: [];
|
||||||
[Queries.PrivateBattleHistoriesQuery]: [];
|
[Queries.PrivateBattleHistoriesQuery]: [];
|
||||||
[Queries.VsHistoryDetailQuery]: [{
|
[Queries.VsHistoryDetailQuery]: [{
|
||||||
vsResultId: string;
|
vsResultId: string;
|
||||||
|
|
@ -144,6 +145,7 @@ export type VsPlayer = {
|
||||||
} | null;
|
} | null;
|
||||||
paint: number;
|
paint: number;
|
||||||
crown: boolean;
|
crown: boolean;
|
||||||
|
festDragonCert: "NONE" | "DRAGON" | "DOUBLE_DRAGON";
|
||||||
|
|
||||||
headGear: PlayerGear;
|
headGear: PlayerGear;
|
||||||
clothingGear: PlayerGear;
|
clothingGear: PlayerGear;
|
||||||
|
|
@ -235,6 +237,9 @@ export type VsHistoryDetail = {
|
||||||
bankaraMatch: {
|
bankaraMatch: {
|
||||||
earnedUdemaePoint: null | number;
|
earnedUdemaePoint: null | number;
|
||||||
mode: "OPEN" | "CHALLENGE";
|
mode: "OPEN" | "CHALLENGE";
|
||||||
|
bankaraPower?: null | {
|
||||||
|
power?: null | number;
|
||||||
|
};
|
||||||
} | null;
|
} | null;
|
||||||
festMatch: {
|
festMatch: {
|
||||||
dragonMatchType: "NORMAL" | "DECUPLE" | "DRAGON" | "DOUBLE_DRAGON";
|
dragonMatchType: "NORMAL" | "DECUPLE" | "DRAGON" | "DOUBLE_DRAGON";
|
||||||
|
|
@ -266,6 +271,8 @@ export type CoopHistoryPlayerResult = {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
isMyself: boolean;
|
||||||
|
species: "INKLING" | "OCTOLING";
|
||||||
};
|
};
|
||||||
weapons: { name: string; image: Image | null }[];
|
weapons: { name: string; image: Image | null }[];
|
||||||
specialWeapon: null | {
|
specialWeapon: null | {
|
||||||
|
|
@ -415,6 +422,11 @@ export type RespMap = {
|
||||||
};
|
};
|
||||||
[Queries.BankaraBattleHistoriesQuery]: BankaraBattleHistories;
|
[Queries.BankaraBattleHistoriesQuery]: BankaraBattleHistories;
|
||||||
[Queries.XBattleHistoriesQuery]: XBattleHistories;
|
[Queries.XBattleHistoriesQuery]: XBattleHistories;
|
||||||
|
[Queries.EventBattleHistoriesQuery]: {
|
||||||
|
eventBattleHistories: {
|
||||||
|
historyGroups: HistoryGroups<BattleListNode>;
|
||||||
|
};
|
||||||
|
};
|
||||||
[Queries.PrivateBattleHistoriesQuery]: {
|
[Queries.PrivateBattleHistoriesQuery]: {
|
||||||
privateBattleHistories: {
|
privateBattleHistories: {
|
||||||
historyGroups: HistoryGroups<BattleListNode>;
|
historyGroups: HistoryGroups<BattleListNode>;
|
||||||
|
|
@ -601,10 +613,14 @@ export enum BattleListType {
|
||||||
Latest,
|
Latest,
|
||||||
Regular,
|
Regular,
|
||||||
Bankara,
|
Bankara,
|
||||||
|
Event,
|
||||||
|
XBattle,
|
||||||
Private,
|
Private,
|
||||||
Coop,
|
Coop,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ListMethod = "latest" | "all" | "auto";
|
||||||
|
|
||||||
export type StatInkUuidList = {
|
export type StatInkUuidList = {
|
||||||
status: number;
|
status: number;
|
||||||
code: number;
|
code: number;
|
||||||
|
|
@ -624,7 +640,7 @@ export type StatInkWeapon = {
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
export type StatInkGear = {
|
export type StatInkGear = {
|
||||||
primary_ability: string;
|
primary_ability: string | null;
|
||||||
secondary_abilities: (string | null)[];
|
secondary_abilities: (string | null)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -650,7 +666,9 @@ export type StatInkPlayer = {
|
||||||
special?: number;
|
special?: number;
|
||||||
gears?: StatInkGears;
|
gears?: StatInkGears;
|
||||||
crown?: "yes" | "no";
|
crown?: "yes" | "no";
|
||||||
|
crown_type?: "x" | "100x" | "333x";
|
||||||
disconnected: "yes" | "no";
|
disconnected: "yes" | "no";
|
||||||
|
species: "inkling" | "octoling";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StatInkStage = {
|
export type StatInkStage = {
|
||||||
|
|
@ -700,6 +718,7 @@ export type StatInkCoopPlayer = {
|
||||||
rescued: number;
|
rescued: number;
|
||||||
defeat_boss: number;
|
defeat_boss: number;
|
||||||
disconnected: "yes" | "no";
|
disconnected: "yes" | "no";
|
||||||
|
species: "inkling" | "octoling";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StatInkCoopBoss = {
|
export type StatInkCoopBoss = {
|
||||||
|
|
@ -809,6 +828,8 @@ export type StatInkPostBody = {
|
||||||
challenge_lose?: number;
|
challenge_lose?: number;
|
||||||
x_power_before?: number | null;
|
x_power_before?: number | null;
|
||||||
x_power_after?: number | null;
|
x_power_after?: number | null;
|
||||||
|
bankara_power_before?: number | null;
|
||||||
|
bankara_power_after?: number | null;
|
||||||
fest_power?: number; // Splatfest Power (Pro)
|
fest_power?: number; // Splatfest Power (Pro)
|
||||||
fest_dragon?:
|
fest_dragon?:
|
||||||
| "10x"
|
| "10x"
|
||||||
|
|
|
||||||
11
src/utils.ts
11
src/utils.ts
|
|
@ -188,3 +188,14 @@ export function urlSimplify(url: string): { pathname: string } | string {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const battleTime = (id: string) => {
|
||||||
|
const { timestamp } = parseHistoryDetailId(id);
|
||||||
|
|
||||||
|
const dateStr = timestamp.replace(
|
||||||
|
/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/,
|
||||||
|
"$1-$2-$3T$4:$5:$6Z",
|
||||||
|
);
|
||||||
|
|
||||||
|
return new Date(dateStr);
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue