Compare commits
19 Commits
509d8945aa
...
main
Author | SHA1 | Date | |
---|---|---|---|
e38f1669dc | |||
479ad3acd4 | |||
11eaaa43d3 | |||
a5fb3cc100 | |||
621b554711 | |||
ee2e8dd1c6 | |||
5a0f888000 | |||
1b6fb5fa81 | |||
d58a0d26cf | |||
7e5888e7e5 | |||
fae080ea87 | |||
de31838195 | |||
1e32cb8688 | |||
d420bec782 | |||
2a4d927a41 | |||
b21deaee5f | |||
7493724cd7 | |||
4d9991ce75 | |||
5fc1cea49d |
@ -55,7 +55,7 @@ jobs:
|
||||
org.opencontainers.image.version=${{ env.EW_VERSION }}
|
||||
tags: "${{ steps.set-tag.outputs.tags }}"
|
||||
file: "docker/Dockerfile"
|
||||
platforms: linux/amd64
|
||||
platforms: linux/amd64 #,linux/arm64
|
||||
|
||||
# arm64 builds OOM without the git fetch setting. c.f.
|
||||
# https://github.com/rust-lang/cargo/issues/10583
|
||||
|
56
.forgejo/workflows/release.yml
Normal file
56
.forgejo/workflows/release.yml
Normal file
@ -0,0 +1,56 @@
|
||||
name: Build release binaries
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checking out branch
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
shell: bash
|
||||
run: curl -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --default-toolchain stable --profile minimal
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Create out directory
|
||||
run: |
|
||||
mkdir out
|
||||
|
||||
- name: Build webui
|
||||
run: |
|
||||
cd webui
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
- name: Build x86_64-unknown-linux-gnu
|
||||
run: |
|
||||
. "$HOME/.cargo/env"
|
||||
cargo build --target x86_64-unknown-linux-gnu --release || echo "Failed to build"
|
||||
mv target/x86_64-unknown-linux-gnu/release/ew out/ew-x86_64-unknown-linux-gnu
|
||||
|
||||
- name: Build x86_64-pc-windows-gnu
|
||||
run: |
|
||||
. "$HOME/.cargo/env"
|
||||
sudo apt update && sudo apt install gcc-mingw-w64 -y
|
||||
rustup target add x86_64-pc-windows-gnu
|
||||
cargo build --target x86_64-pc-windows-gnu --release || echo "Failed to build"
|
||||
mv target/x86_64-pc-windows-gnu/release/ew.exe out/ew-x86_64-pc-windows-gnu.exe
|
||||
|
||||
- name: Publish release
|
||||
uses: actions/forgejo-release@v2
|
||||
with:
|
||||
direction: upload
|
||||
release-dir: ./out/
|
||||
release-notes-assistant: true
|
||||
tag: "${{ github.ref_name }}"
|
||||
sha: "${{ github.sha }}"
|
||||
title: "Release ${{ github.ref_name }}"
|
||||
token: ${{ github.token }}
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,5 +1,5 @@
|
||||
asset_server/node_modules/
|
||||
asset_server/resources/
|
||||
data/
|
||||
asset/
|
||||
target/
|
||||
python/
|
||||
*.db
|
||||
@ -7,3 +7,5 @@ webui/node_modules/
|
||||
webui/dist/
|
||||
config.json
|
||||
docker/data/
|
||||
.idea
|
||||
*.lock
|
2482
Cargo.lock
generated
2482
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
19
Cargo.toml
19
Cargo.toml
@ -4,12 +4,12 @@ version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
actix-web = { version = "4.9.0", features = [ "openssl" ] }
|
||||
rusqlite = { version = "0.30.0", features = ["bundled"] }
|
||||
actix-web = { version = "4.9.0" }
|
||||
rusqlite = { version = "0.32.1", features = ["bundled"] }
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
reqwest = { version = "0.11", features = ["blocking"] }
|
||||
clap = { version = "4.5.20", features = ["derive"]}
|
||||
base64 = "0.21.7"
|
||||
reqwest = { version = "0.12", features = ["blocking"] }
|
||||
clap = { version = "4.5.21", features = ["derive"]}
|
||||
base64 = "0.22.1"
|
||||
json = "0.12.4"
|
||||
rand = "0.8.5"
|
||||
lazy_static = "1.5.0"
|
||||
@ -26,5 +26,10 @@ mime = "0.3.17"
|
||||
sha2 = "0.10.8"
|
||||
include-flate-codegen = "0.3.0"
|
||||
libflate = "2.1.0"
|
||||
serde_json = "1.0.129"
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
serde_json = "1.0.133"
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
futures = "0.3.31"
|
||||
mime_guess = "2.0.5"
|
||||
actix-files = "0.6.6"
|
||||
simplelog = "0.12.2"
|
||||
log = "0.4.22"
|
55
Dockerfile
Normal file
55
Dockerfile
Normal file
@ -0,0 +1,55 @@
|
||||
# 打包命令
|
||||
# docker buildx build -t zhushenwudi/lovelive-sif2:1.0.2 -t zhushenwudi/lovelive-sif2:latest --platform=linux/amd64 .
|
||||
# 使用适用于 Linux x86 的基础映像
|
||||
# 保存镜像
|
||||
# docker save -o lovelive-sif2.tar zhushenwudi/lovelive-sif2:1.0.2
|
||||
|
||||
FROM docker.io/library/debian:latest AS builder
|
||||
|
||||
# First - build
|
||||
|
||||
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources
|
||||
RUN apt update && apt install -y curl libssl-dev perl git gcc make
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_23.x | bash
|
||||
RUN apt update && apt install -y nodejs
|
||||
RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --default-toolchain stable --profile minimal
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
RUN ln -fs /usr/share/zoneinfo/$TZ /etc/localtime && dpkg-reconfigure -f noninteractive tzdata
|
||||
RUN rm -rf /etc/localtime
|
||||
RUN ln -sv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
|
||||
WORKDIR /ew/
|
||||
|
||||
COPY ./ ./
|
||||
|
||||
RUN rm -f ./*.db
|
||||
RUN rm -f ./*.lock
|
||||
RUN rm -rf ./.idea
|
||||
RUN rm -rf ./target
|
||||
RUN rm -rf ./webui/node_modules
|
||||
RUN rm -rf ./webui/dist
|
||||
RUN rm -rf ./webui/*.lock
|
||||
|
||||
WORKDIR /ew/webui/
|
||||
|
||||
RUN npm i && npm run build
|
||||
|
||||
WORKDIR /ew/
|
||||
|
||||
RUN . "$HOME/.cargo/env" && cargo build --release
|
||||
|
||||
## Second - sort stuff idk
|
||||
|
||||
FROM docker.io/library/debian:bookworm-slim
|
||||
|
||||
RUN mkdir -p /root/ew/
|
||||
COPY --from=builder /ew/target/release/ew /root/ew/ew
|
||||
COPY ./docker/start.sh /root/ew/start.sh
|
||||
|
||||
RUN chmod +x /root/ew/start.sh
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["/root/ew/start.sh"]
|
@ -1,56 +0,0 @@
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
const fs = require('fs');
|
||||
var http = require('http');
|
||||
|
||||
const ip = '0.0.0.0';
|
||||
const port = 8887;
|
||||
//https://lovelive-schoolidolfestival2-album.akamaized.net
|
||||
app.use(function (req, res, next) {
|
||||
console.log(req.method, ":", req.url);
|
||||
next();
|
||||
})
|
||||
function createDirFromFile(path) {
|
||||
fs.mkdirSync(require('path').dirname(path), { recursive: true });
|
||||
}
|
||||
|
||||
app.get('/*', function (req, res) {
|
||||
const expectedPath = __dirname + "/resources"+req.url.split("?")[0];
|
||||
createDirFromFile(expectedPath);
|
||||
|
||||
let downloading = [];
|
||||
if (fs.existsSync(expectedPath)) {
|
||||
res.sendFile(expectedPath)
|
||||
} else {
|
||||
let url;
|
||||
if (req.url.split("/")[2].length !== 2) {
|
||||
url = (req.url.toLowerCase().startsWith("/android") || req.url.toLowerCase().startsWith("/ios") ? "https://lovelive-schoolidolfestival2-assets.akamaized.net" : "https://lovelive-schoolidolfestival2-album.akamaized.net") + req.url;
|
||||
} else {
|
||||
url = (req.url.toLowerCase().startsWith("/android") || req.url.toLowerCase().startsWith("/ios") ? "https://img-sif2.lovelive-sif2.com" : "https://album-sif2.lovelive-sif2.com") + req.url;
|
||||
}
|
||||
const request = require('https').get(url, function(response) {
|
||||
response.pipe(res);
|
||||
});
|
||||
if (downloading.includes(req.url)) return;
|
||||
require('https').get(url, function(response) {
|
||||
console.log("Downloading " + req.url);
|
||||
downloading.push(req.url);
|
||||
const file = fs.createWriteStream(expectedPath);
|
||||
response.pipe(file);
|
||||
|
||||
// after download completed close filestream
|
||||
file.on("finish", () => {
|
||||
file.close();
|
||||
console.log("Download Completed " + req.url);
|
||||
});
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
var httpsServer = http.createServer(app);
|
||||
|
||||
|
||||
httpsServer.listen(port, ip, () => {
|
||||
let url = 'http://' + ip + ':' + port
|
||||
console.log('Server is listening at', url)
|
||||
})
|
690
asset_server/package-lock.json
generated
690
asset_server/package-lock.json
generated
@ -1,690 +0,0 @@
|
||||
{
|
||||
"name": "a",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"express": "^4.18.2"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.11.0",
|
||||
"raw-body": "2.5.1",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.18.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
|
||||
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.1",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.5.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.2.0",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.11.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.18.0",
|
||||
"serve-static": "1.15.0",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "2.0.1",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
|
||||
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||
"dependencies": {
|
||||
"depd": "2.0.0",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||
"dependencies": {
|
||||
"forwarded": "0.2.0",
|
||||
"ipaddr.js": "1.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
|
||||
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "2.4.1",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
||||
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
||||
"dependencies": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
|
||||
"integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.3",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
|
||||
"integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.6",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"object-inspect": "^1.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"express": "^4.18.2"
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
FROM docker.io/library/debian:latest as builder
|
||||
|
||||
# First - build
|
||||
|
||||
RUN apt update && apt install -y curl libssl-dev perl git gcc make
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash
|
||||
RUN apt update && apt install -y nodejs
|
||||
RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --default-toolchain stable --profile minimal
|
||||
|
||||
WORKDIR /ew/
|
||||
|
||||
COPY ./ ./
|
||||
|
||||
WORKDIR /ew/webui/
|
||||
|
||||
RUN npm i && npm run build
|
||||
|
||||
WORKDIR /ew/
|
||||
|
||||
RUN . "$HOME/.cargo/env" && cargo build --release
|
||||
|
||||
## Second - sort stuff idk
|
||||
|
||||
FROM docker.io/library/debian:bookworm-slim
|
||||
|
||||
RUN mkdir -p /root/ew/
|
||||
COPY --from=builder /ew/target/release/ew /root/ew/ew
|
||||
COPY ./docker/start.sh /root/ew/start.sh
|
||||
|
||||
RUN chmod +x /root/ew/start.sh
|
||||
|
||||
ENTRYPOINT ["/root/ew/start.sh"]
|
@ -1,28 +1,42 @@
|
||||
version: '3'
|
||||
services:
|
||||
sif2-ew:
|
||||
image: sif2-ew:latest
|
||||
container_name: sif2-ew
|
||||
build:
|
||||
dockerfile: "./Dockerfile"
|
||||
lovelive_sif2:
|
||||
image: zhushenwudi/lovelive-sif2:1.0.0
|
||||
container_name: sif2
|
||||
environment:
|
||||
PORT: 8080
|
||||
DIRECTORY: /data/
|
||||
# Server URL是否存在SSL/TSL加密协议
|
||||
HTTPS: false
|
||||
NPPS4_ADDRESS: "http://127.0.0.1:51376"
|
||||
MAXTIME: 1717045200 # A day before global EOS
|
||||
HIDDEN: false # Will disable the webui
|
||||
DISABLE_IMPORTS: false # Will disable account imports
|
||||
DISABLE_EXPORTS: false # Will disable account exports
|
||||
# 是否允许内部启动资源服务
|
||||
ENABLE_ASSET_SERVER: false
|
||||
# <数据迁移> SIF1服务器地址
|
||||
#NPPS4_ADDRESS: "http://127.0.0.1:51376"
|
||||
# 服务器默认时间,建议设置到开服时间,最大设置为关服时间
|
||||
MAXTIME: 1736076211
|
||||
# 是否隐藏WebUI
|
||||
HIDDEN: false
|
||||
# 是否禁用账号导入
|
||||
DISABLE_IMPORTS: false
|
||||
# 是否禁用账号导出
|
||||
DISABLE_EXPORTS: false
|
||||
|
||||
# Everything below is for the "Help" page
|
||||
# 以下内容出现在Help界面
|
||||
# 未签名的apk下载地址
|
||||
ANDROID_JAPAN: "http://fir.zhushenwudi.top/n2tvzrhl"
|
||||
#ANDROID_GLOBAL: "link.to/patched/android/global.apk"
|
||||
#ANDROID_JAPAN: "link.to/patched/android/japan.apk"
|
||||
# 脱壳的ipa下载地址
|
||||
#IOS_JAPAN: "link.to/ios/jp.ipa"
|
||||
#IOS_GLOBAL: "link.to/ios/global.ipa"
|
||||
#IOS_JAPAN: "link.to/ios/japan.ipa"
|
||||
#ASSET_URL: "link.to/client/assets/"
|
||||
ports:
|
||||
- 8080:8080
|
||||
# 只修改左侧本地端口
|
||||
# Server URL 端口
|
||||
- "8080:8080"
|
||||
# Assets URL 端口
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
# 只修改左侧本地路径
|
||||
# 数据库存放路径(建议添加)
|
||||
- ./data:/data
|
||||
# 资源文件存放路径(搭建assets server需要指定本地资源路径)
|
||||
- ./assets:/assets
|
||||
restart: unless-stopped
|
||||
|
@ -1,8 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
port="${PORT:-8080}"
|
||||
|
||||
directory="${DIRECTORY:-/data/}"
|
||||
|
||||
enable_asset_server=$([ "$ENABLE_ASSET_SERVER" = "true" ] && echo "--enable-asset-server" || echo "")
|
||||
|
||||
asset_port="${ASSET_PORT:-8000}"
|
||||
|
||||
assets_directory="${ASSET_DIRECTORY:-/assets/}"
|
||||
|
||||
npps4="${NPPS4_ADDRESS:-http://127.0.0.1:51376}"
|
||||
|
||||
https=$([ "$HTTPS" = "true" ] && echo "--https" || echo "")
|
||||
@ -25,4 +32,25 @@ asset_android_en=$([ "$EN_ANDROID_ASSET_HASH" != "" ] && echo "--en-android-asse
|
||||
|
||||
asset_ios_en=$([ "$EN_IOS_ASSET_HASH" != "" ] && echo "--en-ios-asset-hash $EN_IOS_ASSET_HASH" || echo "")
|
||||
|
||||
/root/ew/ew --path $directory --port $port --npps4 $npps4 $asset_android_jp $asset_ios_jp $asset_android_en $asset_ios_en $exports $imports $purge $hidden $https --global-android "$ANDROID_GLOBAL" --japan-android "$ANDROID_JAPAN" --global-ios "$IOS_GLOBAL" --japan-ios "$IOS_JAPAN" --assets-url "$ASSET_URL" --max-time $maxTime
|
||||
/root/ew/ew \
|
||||
--path $directory \
|
||||
--port $port \
|
||||
$enable_asset_server \
|
||||
--asset-path $assets_directory \
|
||||
--asset-port $asset_port \
|
||||
--npps4 $npps4 \
|
||||
$asset_android_jp \
|
||||
$asset_ios_jp \
|
||||
$asset_android_en \
|
||||
$asset_ios_en \
|
||||
$exports \
|
||||
$imports \
|
||||
$purge \
|
||||
$hidden \
|
||||
$https \
|
||||
--global-android "$ANDROID_GLOBAL" \
|
||||
--japan-android "$ANDROID_JAPAN" \
|
||||
--global-ios "$IOS_GLOBAL" \
|
||||
--japan-ios "$IOS_JAPAN" \
|
||||
--assets-url "$ASSET_URL" \
|
||||
--max-time $maxTime
|
||||
|
21
readme.md
21
readme.md
@ -1,31 +1,30 @@
|
||||
# EW
|
||||
(stands for ew why)
|
||||
|
||||
A (mostly functioning) server for Love Live! School idol festival 2 MIRACLE LIVE!
|
||||
一款基本能正常运行的《Love Live! 学园偶像祭2 MIRACLE LIVE!》服务器程序
|
||||
|
||||
## Building
|
||||
## 构建
|
||||
|
||||
### Linux
|
||||
|
||||
#### Requirements
|
||||
- [perl](https://www.perl.org/get.html) (This is normally pre-installed)
|
||||
#### 环境要求
|
||||
- [perl](https://www.perl.org/get.html) (这通常是预装的)
|
||||
- [rust](https://www.rust-lang.org/tools/install)
|
||||
- [npm](https://www.npmjs.com/)
|
||||
- The [libssl-dev](https://packages.debian.org/buster/libssl-dev) package. This will vary across distros.
|
||||
- [libssl-dev](https://packages.debian.org/buster/libssl-dev) (这在不同的发行版中会有所不同)
|
||||
|
||||
`apt install -y npm libssl-dev perl`
|
||||
|
||||
### Windows
|
||||
|
||||
#### Requirements
|
||||
#### 环境要求
|
||||
- [Strawberry Perl](https://strawberryperl.com/)
|
||||
- [rust](https://www.rust-lang.org/tools/install)
|
||||
|
||||
### Packaging/Running
|
||||
### 打包/运行
|
||||
|
||||
**Build npm:**
|
||||
**编译WebUI**
|
||||
`cd webui && npm install && npm run build`
|
||||
|
||||
**Build Rust:**
|
||||
**编译Rust**
|
||||
Debug: `cargo run`
|
||||
Release: `cargo build --release`
|
||||
Release: `cargo build --release --jobs=16`
|
||||
|
67
src/logger.rs
Normal file
67
src/logger.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use log::{debug, error, info, warn, LevelFilter};
|
||||
use simplelog::*;
|
||||
use std::fs::{create_dir_all, File, OpenOptions};
|
||||
use std::path::Path;
|
||||
|
||||
const MAX_LOG_SIZE: u64 = 50 * 1024 * 1024;
|
||||
const LOG_FILE_PATH: &str = "data/rust.log";
|
||||
|
||||
pub fn init_logger() {
|
||||
let mut builder = ConfigBuilder::new();
|
||||
builder.set_time_offset_to_local().unwrap();
|
||||
let config = builder.build();
|
||||
|
||||
// 获取文件的目录路径
|
||||
let dir = Path::new(LOG_FILE_PATH).parent().unwrap();
|
||||
|
||||
// 检查并创建目录
|
||||
if !dir.exists() {
|
||||
create_dir_all(dir).unwrap();
|
||||
}
|
||||
|
||||
let file = OpenOptions::new()
|
||||
.write(true)
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open(LOG_FILE_PATH).unwrap();
|
||||
|
||||
CombinedLogger::init(
|
||||
vec![
|
||||
TermLogger::new(LevelFilter::Info, config.clone(), TerminalMode::Mixed, ColorChoice::Auto),
|
||||
WriteLogger::new(LevelFilter::Info, config.clone(), file),
|
||||
]
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
// 检查文件大小并清空文件(如果超出限制)
|
||||
fn check_and_clear_log_file() {
|
||||
let metadata = std::fs::metadata(LOG_FILE_PATH).unwrap();
|
||||
if metadata.len() > MAX_LOG_SIZE {
|
||||
// 清空文件
|
||||
File::create(LOG_FILE_PATH).expect(&format!("Can't re-create file {}", LOG_FILE_PATH));
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn debug(message: &str) {
|
||||
check_and_clear_log_file();
|
||||
debug!("{}", message);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn info(message: &str) {
|
||||
check_and_clear_log_file();
|
||||
info!("{}", message);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn warn(message: &str) {
|
||||
check_and_clear_log_file();
|
||||
warn!("{}", message);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn error(message: &str) {
|
||||
check_and_clear_log_file();
|
||||
error!("{}", message);
|
||||
}
|
101
src/main.rs
101
src/main.rs
@ -1,6 +1,7 @@
|
||||
mod encryption;
|
||||
mod router;
|
||||
mod sql;
|
||||
mod logger;
|
||||
|
||||
use actix_web::{
|
||||
rt,
|
||||
@ -17,6 +18,7 @@ use clap::Parser;
|
||||
use std::fs;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use actix_web::dev::{Server, ServerHandle};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
#[get("/index.css")]
|
||||
@ -38,9 +40,18 @@ pub struct Args {
|
||||
#[arg(short, long, default_value_t = 8080, help = "Port to listen on")]
|
||||
port: u16,
|
||||
|
||||
#[arg(long, default_value = "./", help = "Path to store database files")]
|
||||
#[arg(short, long, default_value_t = 8000, help = "Assets port to listen on")]
|
||||
asset_port: u16,
|
||||
|
||||
#[arg(long, default_value = "./data/", help = "Path to store database files")]
|
||||
path: String,
|
||||
|
||||
#[arg(long, default_value = "./assets/", help = "Path to store database files")]
|
||||
asset_path: String,
|
||||
|
||||
#[arg(long, default_value_t = false, help = "Enable assets server")]
|
||||
enable_asset_server: bool,
|
||||
|
||||
#[arg(long, default_value_t = false, help = "Serve gree headers with https. WILL NOT ACCEPT HTTPS REQUESTS")]
|
||||
https: bool,
|
||||
|
||||
@ -96,53 +107,90 @@ pub struct Args {
|
||||
async fn run_server(in_thread: bool) -> std::io::Result<()> {
|
||||
let args = get_args();
|
||||
let port = args.port;
|
||||
let enable_asset_server = args.enable_asset_server;
|
||||
let asset_port = args.asset_port;
|
||||
|
||||
if args.purge {
|
||||
println!("Purging accounts...");
|
||||
let ct = crate::router::userdata::purge_accounts();
|
||||
println!("Purged {} accounts", ct);
|
||||
logger::debug("Purging accounts...");
|
||||
let ct = router::userdata::purge_accounts();
|
||||
logger::warn(&format!("Purged {} accounts", ct));
|
||||
}
|
||||
|
||||
let rv = HttpServer::new(|| App::new()
|
||||
.wrap_fn(|req, srv| {
|
||||
println!("Request: {}", req.path());
|
||||
srv.call(req)
|
||||
})
|
||||
.app_data(web::PayloadConfig::default().limit(1024 * 1024 * 25))
|
||||
.service(css)
|
||||
.service(js)
|
||||
.default_service(web::route().to(router::request))
|
||||
).bind(("0.0.0.0", port))?.run();
|
||||
let webui_server = HttpServer::new(|| {
|
||||
App::new()
|
||||
.wrap_fn(|req, srv| {
|
||||
let ip = req.peer_addr().map(|addr| addr.ip().to_string()).unwrap_or_else(|| "Unknown IP".to_string());
|
||||
logger::info(&format!("from {}, WebUI Request: {}", ip, req.path()));
|
||||
srv.call(req)
|
||||
})
|
||||
.app_data(web::PayloadConfig::default().limit(1024 * 1024 * 25))
|
||||
.service(css)
|
||||
.service(js)
|
||||
.default_service(web::route().to(router::request))
|
||||
}).bind(("0.0.0.0", port))?.run();
|
||||
|
||||
println!("Server started: http://0.0.0.0:{}", port);
|
||||
println!("Data path is set to {}", args.path);
|
||||
println!("Sif1 transfer requests will attempt to contact NPPS4 at {}", args.npps4);
|
||||
logger::info(&format!("WebUI Server started: http://0.0.0.0:{}", port));
|
||||
logger::info(&format!("Database path is set to {}", args.path));
|
||||
logger::debug(&format!("Sif1 transfer requests will attempt to contact NPPS4 at {}", args.npps4));
|
||||
|
||||
let mut asset_server: Option<Server> = None;
|
||||
if enable_asset_server {
|
||||
asset_server = Some(HttpServer::new(|| {
|
||||
App::new()
|
||||
.wrap_fn(|req, srv| {
|
||||
let ip = req.peer_addr().map(|addr| addr.ip().to_string()).unwrap_or_else(|| "Unknown IP".to_string());
|
||||
logger::info(&format!("from {}, Assets Request: {}", ip, req.path()));
|
||||
srv.call(req)
|
||||
})
|
||||
.app_data(web::PayloadConfig::default().limit(1024 * 1024 * 1024))
|
||||
.default_service(web::route().to(router::asset_request))
|
||||
}).bind(("0.0.0.0", asset_port))?.run());
|
||||
|
||||
logger::info(&format!("Assets Server started: http://0.0.0.0:{}", asset_port));
|
||||
logger::info(&format!("Assets path is set to {}", args.asset_path));
|
||||
}
|
||||
|
||||
if args.https {
|
||||
println!("Note: gree is set to https mode. http requests will fail on jp clients.");
|
||||
logger::warn("Note: gree is set to https mode. http requests will fail on jp clients.");
|
||||
}
|
||||
|
||||
if in_thread {
|
||||
set_running(true).await;
|
||||
let handle = rv.handle();
|
||||
rt::spawn(rv);
|
||||
let handle = webui_server.handle();
|
||||
rt::spawn(webui_server);
|
||||
let mut handle2: Option<ServerHandle> = None;
|
||||
if asset_server.is_some() {
|
||||
let server = asset_server.unwrap();
|
||||
handle2 = Some(server.handle());
|
||||
rt::spawn(server);
|
||||
}
|
||||
|
||||
while get_running().await {
|
||||
actix_web::rt::time::sleep(Duration::from_millis(100)).await;
|
||||
rt::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
handle.stop(false).await;
|
||||
println!("Stopped");
|
||||
if handle2.is_some() {
|
||||
handle2.unwrap().stop(false).await;
|
||||
}
|
||||
logger::info("Stopped");
|
||||
return Ok(());
|
||||
}
|
||||
rv.await
|
||||
if asset_server.is_some() {
|
||||
let _ = futures::join!(webui_server, asset_server.unwrap());
|
||||
} else {
|
||||
webui_server.await?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn stop_server() {
|
||||
set_running(false).await;
|
||||
println!("Stopping");
|
||||
logger::error("Stopping");
|
||||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
logger::init_logger();
|
||||
run_server(false)
|
||||
}
|
||||
|
||||
@ -153,7 +201,7 @@ pub fn get_args() -> Args {
|
||||
pub fn get_data_path(file_name: &str) -> String {
|
||||
let args = get_args();
|
||||
let mut path = args.path;
|
||||
while path.ends_with("/") {
|
||||
while path.ends_with('/') {
|
||||
path.pop();
|
||||
}
|
||||
fs::create_dir_all(&path).unwrap();
|
||||
@ -190,6 +238,7 @@ macro_rules! lock_onto_mutex {
|
||||
break value;
|
||||
}
|
||||
Err(_) => {
|
||||
$mutex.clear_poison();
|
||||
actix_web::rt::time::sleep(std::time::Duration::from_millis(15)).await;
|
||||
}
|
||||
}
|
||||
@ -208,5 +257,5 @@ async fn set_running(running: bool) {
|
||||
|
||||
async fn get_running() -> bool {
|
||||
let result = lock_onto_mutex!(RUNNING);
|
||||
return *result;
|
||||
*result
|
||||
}
|
||||
|
@ -28,20 +28,15 @@ pub mod databases;
|
||||
pub mod location;
|
||||
pub mod event_ranking;
|
||||
|
||||
use actix_web::{
|
||||
HttpResponse,
|
||||
HttpRequest,
|
||||
http::header::HeaderValue,
|
||||
http::header::HeaderMap
|
||||
};
|
||||
use actix_web::{HttpResponse, HttpRequest, http::header::HeaderValue, http::header::HeaderMap, Responder};
|
||||
use json::{JsonValue, object};
|
||||
use crate::encryption;
|
||||
use crate::{encryption, logger};
|
||||
|
||||
fn unhandled(req: HttpRequest, body: String) -> Option<JsonValue> {
|
||||
if body != String::new() {
|
||||
println!("{}", encryption::decrypt_packet(&body).unwrap_or(body));
|
||||
logger::error(&format!("{}", encryption::decrypt_packet(&body).unwrap_or(body)));
|
||||
}
|
||||
println!("Unhandled request: {}", req.path());
|
||||
logger::error(&format!("Unhandled request: {}", req.path()));
|
||||
None
|
||||
}
|
||||
|
||||
@ -51,7 +46,7 @@ fn not_found(headers: &HeaderMap) -> HttpResponse {
|
||||
"server_time": global::timestamp(),
|
||||
"message": ""
|
||||
};
|
||||
return global::send(rv, 0, &headers)
|
||||
global::send(rv, 0, headers)
|
||||
}
|
||||
|
||||
async fn api_req(req: HttpRequest, body: String) -> HttpResponse {
|
||||
@ -167,15 +162,19 @@ async fn api_req(req: HttpRequest, body: String) -> HttpResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn asset_request(req: HttpRequest) -> impl Responder {
|
||||
webui::asset(req).await
|
||||
}
|
||||
|
||||
pub async fn request(req: HttpRequest, body: String) -> HttpResponse {
|
||||
let args = crate::get_args();
|
||||
let headers = req.headers();
|
||||
if args.hidden && req.path().starts_with("/api/webui/") {
|
||||
return not_found(&headers);
|
||||
return not_found(headers);
|
||||
}
|
||||
if headers.get("aoharu-asset-version").is_none() && req.path().starts_with("/api") && !req.path().starts_with("/api/webui") {
|
||||
if args.hidden {
|
||||
return not_found(&headers);
|
||||
return not_found(headers);
|
||||
} else {
|
||||
return webui::main(req);
|
||||
}
|
||||
@ -196,7 +195,7 @@ pub async fn request(req: HttpRequest, body: String) -> HttpResponse {
|
||||
"/v1.0/migration/password/register" => gree::migration_password_register(req, body),
|
||||
"/v1.0/migration" => gree::migration(req, body),
|
||||
"/api/webui/login" => webui::login(req, body),
|
||||
"/api/webui/startLoginbonus" => webui::start_loginbonus(req, body),
|
||||
"/api/webui/submitLoginBonus" => webui::submit_login_bonus(req, body),
|
||||
"/api/webui/import" => webui::import(req, body),
|
||||
"/api/webui/set_time" => webui::set_time(req, body),
|
||||
_ => api_req(req, body).await
|
||||
|
@ -35,16 +35,14 @@ fn do_reinforce(user: &mut JsonValue, body: &JsonValue, exp_id: &str, money_mult
|
||||
data["amount"] = (data["amount"].as_i64().unwrap() - money).into();
|
||||
}
|
||||
}
|
||||
if evolve {
|
||||
if !databases::CHARACTER_CHATS[card["master_card_id"].to_string()]["50"].is_empty() {
|
||||
let chat = &databases::CHARACTER_CHATS[card["master_card_id"].to_string()]["50"];
|
||||
let mission_id = databases::MISSION_REWARD[chat[0].to_string()]["value"].as_i64().unwrap();
|
||||
if evolve && !databases::CHARACTER_CHATS[card["master_card_id"].to_string()]["50"].is_empty() {
|
||||
let chat = &databases::CHARACTER_CHATS[card["master_card_id"].to_string()]["50"];
|
||||
let mission_id = databases::MISSION_REWARD[chat[0].to_string()]["value"].as_i64().unwrap();
|
||||
|
||||
if crate::router::chat::add_chat_from_chapter_id(mission_id, chats) {
|
||||
items::update_mission_status(chat[1].as_i64().unwrap(), 0, true, true, 1, missions);
|
||||
if !clear_mission_ids.contains(chat[1].as_i64().unwrap()) {
|
||||
clear_mission_ids.push(chat[1].clone()).unwrap();
|
||||
}
|
||||
if crate::router::chat::add_chat_from_chapter_id(mission_id, chats) {
|
||||
items::update_mission_status(chat[1].as_i64().unwrap(), 0, true, true, 1, missions);
|
||||
if !clear_mission_ids.contains(chat[1].as_i64().unwrap()) {
|
||||
clear_mission_ids.push(chat[1].clone()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use json::{object, array, JsonValue};
|
||||
use actix_web::{HttpRequest};
|
||||
|
||||
use crate::router::{global, items, userdata, databases};
|
||||
use crate::encryption;
|
||||
use crate::{encryption, logger};
|
||||
|
||||
pub fn add_chat(id: i64, num: i64, chats: &mut JsonValue) -> bool {
|
||||
for data in chats.members() {
|
||||
@ -23,7 +23,7 @@ pub fn add_chat(id: i64, num: i64, chats: &mut JsonValue) -> bool {
|
||||
pub fn add_chat_from_chapter_id(chapter_id: i64, chats: &mut JsonValue) -> bool {
|
||||
let chapter = &databases::CHAPTERS_MASTER[chapter_id.to_string()];
|
||||
if chapter.is_empty() {
|
||||
println!("Attempted to give unknown chapter id {}", chapter_id);
|
||||
logger::info(&format!("Attempted to give unknown chapter id {}", chapter_id));
|
||||
return false;
|
||||
}
|
||||
add_chat(chapter["masterChatId"].as_i64().unwrap(), chapter["roomId"].as_i64().unwrap(), chats)
|
||||
|
@ -167,18 +167,20 @@ fn get_json() -> JsonValue {
|
||||
}
|
||||
|
||||
async fn get_clearrate_json() -> JsonValue {
|
||||
let mut result = crate::lock_onto_mutex!(CACHED_DATA);
|
||||
if result.is_none() {
|
||||
result.replace(get_json());
|
||||
}
|
||||
let cache = result.as_ref().unwrap();
|
||||
let cache = {
|
||||
let mut result = crate::lock_onto_mutex!(CACHED_DATA);
|
||||
if result.is_none() {
|
||||
result.replace(get_json());
|
||||
}
|
||||
result.as_ref().unwrap().clone()
|
||||
};
|
||||
let rv = cache["cache"].clone();
|
||||
if cache["last_updated"].as_u64().unwrap() + (60 * 60) < global::timestamp() {
|
||||
let mut result = crate::lock_onto_mutex!(CACHED_DATA);
|
||||
let new = get_json();
|
||||
result.replace(new.clone());
|
||||
}
|
||||
return rv;
|
||||
rv
|
||||
}
|
||||
|
||||
pub async fn clearrate(_req: HttpRequest) -> Option<JsonValue> {
|
||||
|
@ -1,12 +1,12 @@
|
||||
use json::{JsonValue, object};
|
||||
use actix_web::{HttpRequest};
|
||||
|
||||
use crate::encryption;
|
||||
use crate::{encryption, logger};
|
||||
|
||||
pub fn error(_req: HttpRequest, body: String) -> Option<JsonValue> {
|
||||
let body = json::parse(&encryption::decrypt_packet(&body).unwrap()).unwrap();
|
||||
|
||||
println!("client error: {}", body["code"]);
|
||||
|
||||
logger::error(&format!("client error: {}", body["code"]));
|
||||
|
||||
Some(object!{})
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ fn get_random_song() -> JsonValue {
|
||||
}
|
||||
|
||||
fn switch_music(event: &mut JsonValue, index: i32) {
|
||||
if index > 5 || index < 1 {
|
||||
if !(1..=5).contains(&index) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -98,16 +98,18 @@ fn get_json() -> JsonValue {
|
||||
}
|
||||
|
||||
pub async fn get_scores_json() -> JsonValue {
|
||||
let mut result = crate::lock_onto_mutex!(CACHED_DATA);
|
||||
if result.is_none() {
|
||||
result.replace(get_json());
|
||||
}
|
||||
let cache = result.as_ref().unwrap();
|
||||
let cache = {
|
||||
let mut result = crate::lock_onto_mutex!(CACHED_DATA);
|
||||
if result.is_none() {
|
||||
result.replace(get_json());
|
||||
}
|
||||
result.as_ref().unwrap().clone()
|
||||
};
|
||||
let rv = cache["cache"].clone();
|
||||
if cache["last_updated"].as_u64().unwrap() + (60 * 60) < global::timestamp() {
|
||||
let mut result = crate::lock_onto_mutex!(CACHED_DATA);
|
||||
let new = get_json();
|
||||
result.replace(new.clone());
|
||||
}
|
||||
return rv;
|
||||
rv
|
||||
}
|
||||
|
@ -27,12 +27,10 @@ pub fn get_asset_hash(asset_version: String, android: bool) -> String {
|
||||
} else {
|
||||
ASSET_HASH_ANDROID_JP.to_string()
|
||||
}
|
||||
} else if args.jp_ios_asset_hash != String::new() {
|
||||
args.jp_ios_asset_hash
|
||||
} else {
|
||||
if args.jp_ios_asset_hash != String::new() {
|
||||
args.jp_ios_asset_hash
|
||||
} else {
|
||||
ASSET_HASH_IOS_JP.to_string()
|
||||
}
|
||||
ASSET_HASH_IOS_JP.to_string()
|
||||
}
|
||||
} else if android {
|
||||
if args.en_android_asset_hash != String::new() {
|
||||
@ -40,12 +38,10 @@ pub fn get_asset_hash(asset_version: String, android: bool) -> String {
|
||||
} else {
|
||||
ASSET_HASH_ANDROID.to_string()
|
||||
}
|
||||
} else if args.en_ios_asset_hash != String::new() {
|
||||
args.en_ios_asset_hash
|
||||
} else {
|
||||
if args.en_ios_asset_hash != String::new() {
|
||||
args.en_ios_asset_hash
|
||||
} else {
|
||||
ASSET_HASH_IOS.to_string()
|
||||
}
|
||||
ASSET_HASH_IOS.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,20 +164,33 @@ pub fn send(mut data: JsonValue, uid: i64, headers: &HeaderMap) -> HttpResponse
|
||||
HttpResponse::Ok().body(resp)
|
||||
}
|
||||
|
||||
pub fn start_login_bonus(id: i64, bonus: &mut JsonValue) -> bool {
|
||||
pub fn submit_login_bonus(id: i64, bonus: &mut JsonValue, is_add: bool) -> bool {
|
||||
if crate::router::login::get_login_bonus_info(id).is_empty() {
|
||||
return false;
|
||||
}
|
||||
for dataa in bonus["bonus_list"].members() {
|
||||
if dataa["master_login_bonus_id"].as_i64().unwrap() == id {
|
||||
return false;
|
||||
|
||||
if is_add {
|
||||
for dataa in bonus["bonus_list"].members() {
|
||||
if dataa["master_login_bonus_id"].as_i64().unwrap() == id {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
let item = object!{master_login_bonus_id: id,day_counts: [],event_bonus_list: []};
|
||||
bonus["bonus_list"].push(item).unwrap();
|
||||
} else {
|
||||
let mut index_to_remove = None;
|
||||
|
||||
for (index, dataa) in bonus["bonus_list"].members().enumerate() {
|
||||
if dataa["master_login_bonus_id"].as_i64().unwrap() == id {
|
||||
index_to_remove = Some(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(index) = index_to_remove {
|
||||
bonus["bonus_list"].array_remove(index);
|
||||
}
|
||||
}
|
||||
bonus["bonus_list"].push(object!{
|
||||
master_login_bonus_id: id,
|
||||
day_counts: [],
|
||||
event_bonus_list: []
|
||||
}).unwrap();
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -356,7 +356,7 @@ pub fn migration_password_register(req: HttpRequest, body: String) -> HttpRespon
|
||||
|
||||
pub fn get_protocol() -> String {
|
||||
let args = crate::get_args();
|
||||
if args.https == true {
|
||||
if args.https {
|
||||
return String::from("https");
|
||||
}
|
||||
String::from("http")
|
||||
|
@ -1,7 +1,7 @@
|
||||
use json::{array, object, JsonValue};
|
||||
use rand::Rng;
|
||||
use actix_web::{HttpRequest, http::header::{HeaderMap, HeaderValue}};
|
||||
use crate::encryption;
|
||||
use crate::{encryption, logger};
|
||||
|
||||
use crate::router::{userdata, global, databases};
|
||||
|
||||
@ -113,7 +113,7 @@ pub fn use_item(item: &JsonValue, multiplier: i64, user: &mut JsonValue) {
|
||||
} else if item["consumeType"] == 4 {
|
||||
use_itemm(item["value"].as_i64().unwrap(), item["amount"].as_i64().unwrap() * multiplier, user);
|
||||
} else {
|
||||
println!("Unknown consume type {}", item["consumeType"]);
|
||||
logger::error(&format!("Unknown consume type {}", item["consumeType"]));
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,7 +142,7 @@ pub fn give_gift(data: &JsonValue, user: &mut JsonValue, missions: &mut JsonValu
|
||||
}
|
||||
return false;
|
||||
}
|
||||
println!("Redeeming reward not implemented for reward type {}", data["reward_type"]);
|
||||
logger::error(&format!("Redeeming reward not implemented for reward type {}", data["reward_type"]));
|
||||
false
|
||||
}
|
||||
pub fn give_gift_basic(ty_pe: i32, id: i64, amount: i64, user: &mut JsonValue, missions: &mut JsonValue, clear_missions: &mut JsonValue, chats: &mut JsonValue) -> bool {
|
||||
@ -279,7 +279,7 @@ pub fn get_rarity(id: i64) -> i32 {
|
||||
pub fn give_character(id: i64, user: &mut JsonValue, missions: &mut JsonValue, clear_missions: &mut JsonValue, chats: &mut JsonValue) -> bool {
|
||||
let character_rarity = get_rarity(id);
|
||||
if character_rarity == 0 {
|
||||
println!("Attempted to give user undefined card!! Card id: {}", id);
|
||||
logger::error(&format!("Attempted to give user undefined card!! Card id: {}", id));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -498,7 +498,7 @@ pub fn use_item_req(req: HttpRequest, body: String) -> Option<JsonValue> {
|
||||
if item["effectType"].as_i32().unwrap() == 1 {
|
||||
lp_modification(&mut user, item["effectValue"].as_u64().unwrap() * (amount as u64), false);
|
||||
} else {
|
||||
println!("Use item not implemented for effect type {}", item["effectType"]);
|
||||
logger::error(&format!("Use item not implemented for effect type {}", item["effectType"]));
|
||||
}
|
||||
use_item(&object!{
|
||||
value: body["id"].as_i64().unwrap(),
|
||||
|
@ -54,7 +54,7 @@ pub fn bonus(req: HttpRequest, body: String) -> Option<JsonValue> {
|
||||
|
||||
let mut bonuses = userdata::get_acc_loginbonus(&key);
|
||||
if bonuses["bonus_list"].is_empty() {
|
||||
global::start_login_bonus(1, &mut bonuses);
|
||||
global::submit_login_bonus(1, &mut bonuses, true);
|
||||
}
|
||||
let to_send = do_bonus(&mut user_home, &mut bonuses);
|
||||
|
||||
@ -80,7 +80,7 @@ pub fn bonus_event(req: HttpRequest, body: String) -> Option<JsonValue> {
|
||||
|
||||
let mut bonuses = userdata::get_acc_eventlogin(&key);
|
||||
if bonuses["bonus_list"].is_empty() {
|
||||
global::start_login_bonus(20039, &mut bonuses);
|
||||
global::submit_login_bonus(20039, &mut bonuses, true);
|
||||
}
|
||||
let to_send = do_bonus(&mut user_home, &mut bonuses);
|
||||
|
||||
|
@ -1,182 +1,77 @@
|
||||
use actix_web::HttpRequest;
|
||||
use json::{array, object, JsonValue};
|
||||
use actix_web::{HttpRequest};
|
||||
|
||||
use crate::router::{global, userdata, items};
|
||||
use crate::router::{global, items, userdata};
|
||||
use crate::encryption;
|
||||
|
||||
const UR_ARRAY: [i32; 39] = [40010015, 30010015, 20010018, 10040018, 20050016, 30070015, 40030013,
|
||||
10070016, 10060018, 20050019, 10020018, 10010014, 10010015, 10010020, 10040016, 10050018,
|
||||
10080016, 10090015, 20010019, 20030015, 20050014, 20070013, 20080016, 20090013, 20080016,
|
||||
20090013, 30010017, 30020009, 30040012, 30090009, 30040012, 30090009, 40010011, 40030009,
|
||||
40040013, 40060010, 40080011, 40090011, 30030010];
|
||||
|
||||
const PT_ARRAY: [i32; 112] = [15540001, 15500001, 15500002, 15520001, 15520002, 15520003, 15520004,
|
||||
15520005, 15520006, 15520007, 15520008, 15520009, 15520010, 15520011, 15520012, 15520013,
|
||||
15520014, 15520015, 15520016, 15520017, 15520018, 15520019, 15520020, 15510004, 15510005,
|
||||
15510006, 15510007, 15510008, 15510009, 15510010, 15510011, 15510012, 15510013, 15510014,
|
||||
15510015, 15510016, 15510017, 15510018, 15510019, 15510020, 15510021, 15510022, 15510023,
|
||||
15510024, 15530001, 15530002, 15530003, 15530004, 15530005, 15530006, 15530007, 15530008,
|
||||
15530009, 15530010, 15530011, 15530012, 15530013, 15530014, 15530015, 15530016, 15530017,
|
||||
15530018, 15530019, 15530020, 15530021, 15530022, 15530023, 15530024, 15530025, 15530026,
|
||||
15530027, 15530028, 15530029, 15530030, 15530031, 15530032, 15530033, 15530034, 15530035,
|
||||
15530036, 15530037, 15540002, 15540005, 15540006, 15540007, 15540008, 15540009, 15540010,
|
||||
15540011, 15540012, 15540013, 15540014, 15540015, 15540016, 15540017, 15540023, 15540024,
|
||||
15540025, 15540027, 15540028, 15540029, 15540030, 15540031, 15540032, 15540033, 15540034,
|
||||
15540035, 30010002, 30010003, 30010004, 30010005, 30010001
|
||||
];
|
||||
|
||||
pub fn events(_req: HttpRequest) -> Option<JsonValue> {
|
||||
Some(object!{
|
||||
"serial_code_list": []
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_number_from_code(code: String, goods: &str) -> Option<i64> {
|
||||
if code.starts_with(goods) {
|
||||
let number_str = &code[goods.len()..];
|
||||
match number_str.parse::<i64>() {
|
||||
Ok(number) => {
|
||||
if number >= 999999999 {
|
||||
Some(1)
|
||||
} else {
|
||||
Some(number)
|
||||
}
|
||||
},
|
||||
Err(_) => Some(1)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serial_code(req: HttpRequest, body: String) -> Option<JsonValue> {
|
||||
let key = global::get_login(req.headers(), &body);
|
||||
let body = json::parse(&encryption::decrypt_packet(&body).unwrap()).unwrap();
|
||||
let mut user = userdata::get_acc_home(&key);
|
||||
|
||||
let mut itemz = array![];
|
||||
if body["input_code"] == "SIF2REVIVALREAL!" {
|
||||
itemz.push(items::gift_item_basic(1, 10000000, 4, "Another game died... This makes me sad :(", &mut user)).ok()?;
|
||||
} else if body["input_code"] == "pweasegivegems11" {
|
||||
itemz.push(items::gift_item_basic(1, 6000, 1, "Only because you asked...", &mut user)).ok()?;
|
||||
} else if body["input_code"] == "sleepysleepyslep" {
|
||||
itemz.push(items::gift_item_basic(15540001, 50, 3, "I am tired", &mut user)).ok()?;
|
||||
} else if body["input_code"] == "ilikeganyu!!!!!!" {
|
||||
itemz.push(items::gift_item_basic(16005003, 100, 3, "I need more primogems", &mut user)).ok()?;
|
||||
} else if body["input_code"] == "serial_code" {
|
||||
itemz.push(items::gift_item_basic(17001003, 100, 3, "nyaa~", &mut user)).ok()?;
|
||||
} else if body["input_code"] == "ganuy" {
|
||||
itemz.push(items::gift_item_basic(40010015, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(30010015, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(20010018, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(10040018, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(20050016, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(30070015, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(40030013, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(10070016, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
} else if body["input_code"] == "kode" {
|
||||
itemz.push(items::gift_item_basic(10060018, 1, 2, "meow", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(20050019, 1, 2, "meow", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(10020018, 1, 2, "meow", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(10010014, 1, 2, "meow", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(10010015, 1, 2, "meow", &mut user)).ok()?;
|
||||
} else if body["input_code"] == "meow" {
|
||||
itemz.push(items::gift_item_basic(10010020, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(10040016, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(10050018, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(10080016, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(10090015, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(20010019, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(20030015, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(20050014, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(20070013, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(20080016, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(20090013, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(30010017, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(30020009, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(30040012, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(30090009, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(40010011, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(40030009, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(40040013, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(40060010, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(40080011, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(40090011, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(30030010, 1, 2, "I need more primogem!!!!!!", &mut user)).ok()?;
|
||||
} else if body["input_code"] == "HuTao" {
|
||||
itemz.push(items::gift_item_basic(15500001, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15500002, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520001, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520002, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520003, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520004, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520005, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520006, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520007, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520008, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520009, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520010, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520011, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520012, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520013, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520014, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520015, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520016, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520017, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520018, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520019, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15520020, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510004, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510005, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510006, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510007, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510008, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510009, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510010, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510011, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510012, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510013, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510014, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510015, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510016, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510017, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510018, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510019, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510020, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510021, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510022, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510023, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15510024, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530001, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530002, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530003, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530004, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530005, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530006, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530007, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530008, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530009, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530010, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530011, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530012, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530013, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530014, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530015, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530016, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530017, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530018, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530019, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530020, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530021, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530022, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530023, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530024, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530025, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530026, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530027, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530028, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530029, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530030, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530031, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530032, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530033, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530034, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530035, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530036, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15530037, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540002, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540005, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540006, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540007, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540008, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540009, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540010, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540011, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540012, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540013, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540014, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540015, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540016, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540017, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540023, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540024, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540025, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540027, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540028, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540029, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540030, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540031, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540032, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540033, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540034, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540035, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(30010002, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(30010003, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(30010004, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(30010005, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(30010001, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
itemz.push(items::gift_item_basic(15540001, 500, 3, "Okay...............", &mut user)).ok()?;
|
||||
|
||||
let mut item_list = array![];
|
||||
let code = body["input_code"].to_string();
|
||||
|
||||
if let Some(number) = extract_number_from_code(code.clone(), "coins") {
|
||||
item_list.push(items::gift_item_basic(1, number, 4, "请亏我全", &mut user)).ok()?;
|
||||
} else if let Some(number) = extract_number_from_code(code.clone(), "gems") {
|
||||
item_list.push(items::gift_item_basic(1, number, 1, "你的UR和我的R", &mut user)).ok()?;
|
||||
} else if let Some(number) = extract_number_from_code(code.clone(), "training") {
|
||||
item_list.push(items::gift_item_basic(16005003, number, 3, "A~A~O!", &mut user)).ok()?;
|
||||
item_list.push(items::gift_item_basic(17001003, number, 3, "A~A~O!", &mut user)).ok()?;
|
||||
} else if let Some(number) = extract_number_from_code(code.clone(), "UR") {
|
||||
for item in UR_ARRAY {
|
||||
item_list.push(items::gift_item_basic(item, number, 2, "活动UR+1", &mut user)).ok()?;
|
||||
}
|
||||
} else if let Some(number) = extract_number_from_code(code.clone(), "PT") {
|
||||
for item in PT_ARRAY {
|
||||
item_list.push(items::gift_item_basic(item, number, 3, "意味わかんない", &mut user)).ok()?;
|
||||
}
|
||||
} else {
|
||||
return Some(object!{
|
||||
"result_code": 3
|
||||
@ -189,7 +84,7 @@ pub fn serial_code(req: HttpRequest, body: String) -> Option<JsonValue> {
|
||||
|
||||
Some(object!{
|
||||
"serial_code_event": {"id":1,"name":"Serial Code Reward","unique_limit_count":0,"min_user_rank":0,"max_user_rank":0,"end_date":null},
|
||||
"reward_list": itemz,
|
||||
"reward_list": item_list,
|
||||
"result_code": 0,
|
||||
"gift_list": user["gift_list"].clone(),
|
||||
"excluded_gift_list": []
|
||||
|
@ -1,12 +1,12 @@
|
||||
use json::{JsonValue, object};
|
||||
use actix_web::{HttpRequest, http::header::HeaderValue};
|
||||
|
||||
use crate::encryption;
|
||||
use crate::{encryption, logger};
|
||||
use crate::router::{userdata, global};
|
||||
|
||||
fn get_asset_hash(req: &HttpRequest, body: &JsonValue) -> String {
|
||||
if body["asset_version"] != global::ASSET_VERSION && body["asset_version"] != global::ASSET_VERSION_JP {
|
||||
println!("Warning! Asset version is not what was expected. (Did the app update?)");
|
||||
logger::warn("Warning! Asset version is not what was expected. (Did the app update?)");
|
||||
}
|
||||
|
||||
let blank_header = HeaderValue::from_static("");
|
||||
@ -29,7 +29,7 @@ pub fn start(req: HttpRequest, body: String) -> Option<JsonValue> {
|
||||
let body = json::parse(&encryption::decrypt_packet(&body).unwrap()).unwrap();
|
||||
let mut user = userdata::get_acc(&key);
|
||||
|
||||
println!("Signin from uid: {}", user["user"]["id"].clone());
|
||||
logger::info(&format!("Signin from uid: {}", user["user"]["id"].clone()));
|
||||
|
||||
user["user"]["last_login_time"] = global::timestamp().into();
|
||||
|
||||
|
@ -2,7 +2,7 @@ use json::{array, object, JsonValue};
|
||||
use actix_web::{HttpRequest};
|
||||
use sha1::{Sha1, Digest};
|
||||
|
||||
use crate::encryption;
|
||||
use crate::{encryption, logger};
|
||||
use crate::router::{userdata, global, items};
|
||||
use crate::include_file;
|
||||
|
||||
@ -325,16 +325,16 @@ async fn npps4_req(sha_id: String) -> Option<JsonValue> {
|
||||
let args = crate::get_args();
|
||||
|
||||
let mut host = args.npps4;
|
||||
while host.ends_with("/") {
|
||||
while host.ends_with('/') {
|
||||
host.pop();
|
||||
}
|
||||
let url = format!("{}/ewexport?sha1={}", host, sha_id);
|
||||
println!("Polling NPPS4 at {}", host);
|
||||
logger::debug(&format!("Polling NPPS4 at {}", host));
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let response = client.get(url);
|
||||
let response_body = response.send().await.ok()?.text().await.ok()?;
|
||||
Some(json::parse(&response_body).ok()?)
|
||||
json::parse(&response_body).ok()
|
||||
}
|
||||
|
||||
fn clean_sif_data(current: &JsonValue) -> JsonValue {
|
||||
|
@ -8,7 +8,7 @@ use base64::{Engine as _, engine::general_purpose};
|
||||
use crate::router::global;
|
||||
use crate::router::items;
|
||||
use crate::sql::SQLite;
|
||||
use crate::include_file;
|
||||
use crate::{get_args, include_file, logger};
|
||||
|
||||
lazy_static! {
|
||||
static ref DATABASE: SQLite = SQLite::new("userdata.db", setup_tables);
|
||||
@ -543,11 +543,12 @@ pub fn webui_get_user(token: &str) -> Option<JsonValue> {
|
||||
Some(object!{
|
||||
userdata: get_acc(&login_token),
|
||||
loginbonus: get_acc_loginbonus(&login_token),
|
||||
server_time_set: get_server_data(&login_token)["server_time_set"].clone(),
|
||||
time: get_server_data(&login_token)["server_time"].clone()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn webui_start_loginbonus(bonus_id: i64, token: &str) -> JsonValue {
|
||||
pub fn webui_submit_login_bonus(bonus_id: i64, is_add: bool, token: &str) -> JsonValue {
|
||||
let login_token = webui_login_token(token);
|
||||
if login_token.is_none() {
|
||||
return object!{
|
||||
@ -557,7 +558,7 @@ pub fn webui_start_loginbonus(bonus_id: i64, token: &str) -> JsonValue {
|
||||
}
|
||||
let login_token = login_token.unwrap();
|
||||
let mut bonuses = get_acc_loginbonus(&login_token);
|
||||
if !global::start_login_bonus(bonus_id, &mut bonuses) {
|
||||
if !global::submit_login_bonus(bonus_id, &mut bonuses, is_add) {
|
||||
return object!{
|
||||
result: "ERR",
|
||||
message: "Login bonus ID is either already going or does not exist"
|
||||
@ -571,7 +572,7 @@ pub fn webui_start_loginbonus(bonus_id: i64, token: &str) -> JsonValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_server_time(time: i64, token: &str) -> JsonValue {
|
||||
pub fn set_server_time(mut time: i64, token: &str) -> JsonValue {
|
||||
if time as u64 > global::timestamp() {
|
||||
return object!{
|
||||
result: "ERR",
|
||||
@ -588,6 +589,10 @@ pub fn set_server_time(time: i64, token: &str) -> JsonValue {
|
||||
let login_token = login_token.unwrap();
|
||||
let mut server_data = get_server_data(&login_token);
|
||||
server_data["server_time_set"] = global::timestamp().into();
|
||||
if time == -1 {
|
||||
let args = get_args();
|
||||
time = args.max_time as i64;
|
||||
}
|
||||
server_data["server_time"] = time.into();
|
||||
save_server_data(&login_token, server_data);
|
||||
|
||||
@ -625,7 +630,7 @@ pub fn purge_accounts() -> usize {
|
||||
)).unwrap();
|
||||
for uid in dead_uids.members() {
|
||||
let user_id = uid.as_i64().unwrap();
|
||||
println!("Removing dead UID: {}", user_id);
|
||||
logger::error(&format!("Removing dead UID: {}", user_id));
|
||||
crate::router::gree::delete_uuid(user_id);
|
||||
DATABASE.lock_and_exec("DELETE FROM userdata WHERE user_id=?1", params!(user_id));
|
||||
DATABASE.lock_and_exec("DELETE FROM userhome WHERE user_id=?1", params!(user_id));
|
||||
|
@ -1,9 +1,6 @@
|
||||
use actix_web::{
|
||||
HttpResponse,
|
||||
HttpRequest,
|
||||
http::header::HeaderValue,
|
||||
http::header::ContentType
|
||||
};
|
||||
use actix_web::{HttpResponse, HttpRequest, http::header::HeaderValue, http::header::ContentType, Responder};
|
||||
use actix_files::NamedFile;
|
||||
use std::path::PathBuf;
|
||||
use json::{JsonValue, object};
|
||||
|
||||
use crate::include_file;
|
||||
@ -100,13 +97,15 @@ pub fn user(req: HttpRequest) -> HttpResponse {
|
||||
.body(json::stringify(resp))
|
||||
}
|
||||
|
||||
pub fn start_loginbonus(req: HttpRequest, body: String) -> HttpResponse {
|
||||
pub fn submit_login_bonus(req: HttpRequest, body: String) -> HttpResponse {
|
||||
let token = get_login_token(&req);
|
||||
if token.is_none() {
|
||||
return error("Not logged in");
|
||||
}
|
||||
let body = json::parse(&body).unwrap();
|
||||
let resp = userdata::webui_start_loginbonus(body["bonus_id"].as_i64().unwrap(), &token.unwrap());
|
||||
let bonus_id = body["bonus_id"].as_i64().unwrap();
|
||||
let is_add = body["is_add"].as_bool().unwrap();
|
||||
let resp = userdata::webui_submit_login_bonus(bonus_id, is_add, &token.unwrap());
|
||||
|
||||
HttpResponse::Ok()
|
||||
.insert_header(ContentType::json())
|
||||
@ -163,6 +162,28 @@ pub fn main(req: HttpRequest) -> HttpResponse {
|
||||
.body(include_file!("webui/dist/index.html"))
|
||||
}
|
||||
|
||||
pub async fn asset(req: HttpRequest) -> impl Responder {
|
||||
let args = crate::get_args();
|
||||
|
||||
if req.path() == "/" {
|
||||
return HttpResponse::Ok()
|
||||
.insert_header(ContentType::html())
|
||||
.body(include_file!("src/web/asset_index.html"));
|
||||
}
|
||||
|
||||
let path = req.path();
|
||||
let mut modified_path = format!("{}{}", args.asset_path, path);
|
||||
|
||||
modified_path = modified_path.trim_end_matches('/').to_string();
|
||||
|
||||
let filename: PathBuf = modified_path.trim_start_matches('/').into();
|
||||
|
||||
match NamedFile::open(&filename) {
|
||||
Ok(file) => file.into_response(&req),
|
||||
Err(_) => HttpResponse::NotFound().body("File not found"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn export(req: HttpRequest) -> HttpResponse {
|
||||
if !get_config()["export"].as_bool().unwrap() {
|
||||
return error("Exporting accounts is disabled on this server.");
|
||||
|
40
src/sql.rs
40
src/sql.rs
@ -4,11 +4,6 @@ use json::{JsonValue, array};
|
||||
|
||||
use crate::router::clear_rate::Live;
|
||||
|
||||
pub struct SQLite {
|
||||
engine: Mutex<Connection>
|
||||
}
|
||||
|
||||
// This is duplicated, for ease of people wanting to use this file in their project
|
||||
macro_rules! lock_onto_mutex {
|
||||
($mutex:expr) => {{
|
||||
loop {
|
||||
@ -17,6 +12,7 @@ macro_rules! lock_onto_mutex {
|
||||
break value;
|
||||
}
|
||||
Err(_) => {
|
||||
$mutex.clear_poison();
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
}
|
||||
@ -24,6 +20,10 @@ macro_rules! lock_onto_mutex {
|
||||
}};
|
||||
}
|
||||
|
||||
pub struct SQLite {
|
||||
engine: Mutex<Connection>
|
||||
}
|
||||
|
||||
impl SQLite {
|
||||
pub fn new(path: &str, setup: fn(&SQLite)) -> SQLite {
|
||||
let conn = Connection::open(crate::get_data_path(path)).unwrap();
|
||||
@ -41,12 +41,12 @@ impl SQLite {
|
||||
pub fn lock_and_select(&self, command: &str, args: &[&dyn ToSql]) -> Result<String, rusqlite::Error> {
|
||||
let conn = lock_onto_mutex!(self.engine);
|
||||
let mut stmt = conn.prepare(command)?;
|
||||
return stmt.query_row(args, |row| {
|
||||
stmt.query_row(args, |row| {
|
||||
match row.get::<usize, i64>(0) {
|
||||
Ok(val) => Ok(val.to_string()),
|
||||
Err(_) => row.get(0)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
pub fn lock_and_select_all(&self, command: &str, args: &[&dyn ToSql]) -> Result<JsonValue, rusqlite::Error> {
|
||||
let conn = lock_onto_mutex!(self.engine);
|
||||
@ -62,27 +62,27 @@ impl SQLite {
|
||||
let res = val?;
|
||||
match res.clone().parse::<i64>() {
|
||||
Ok(v) => rv.push(v).unwrap(),
|
||||
Err(_) => rv.push(res).unwrap()
|
||||
Err(_) => rv.push(res).unwrap()
|
||||
};
|
||||
}
|
||||
return Ok(rv);
|
||||
Ok(rv)
|
||||
}
|
||||
pub fn get_live_data(&self, id: i64) -> Result<Live, rusqlite::Error> {
|
||||
let conn = lock_onto_mutex!(self.engine);
|
||||
let mut stmt = conn.prepare("SELECT * FROM lives WHERE live_id=?1")?;
|
||||
return stmt.query_row(params!(id), |row| {
|
||||
stmt.query_row(params!(id), |row| {
|
||||
Ok(Live {
|
||||
live_id: row.get(0)?,
|
||||
normal_failed: row.get(1)?,
|
||||
normal_pass: row.get(2)?,
|
||||
hard_failed: row.get(3)?,
|
||||
hard_pass: row.get(4)?,
|
||||
expert_failed: row.get(5)?,
|
||||
expert_pass: row.get(6)?,
|
||||
master_failed: row.get(7)?,
|
||||
master_pass: row.get(8)?,
|
||||
live_id: row.get(0)?,
|
||||
normal_failed: row.get(1)?,
|
||||
normal_pass: row.get(2)?,
|
||||
hard_failed: row.get(3)?,
|
||||
hard_pass: row.get(4)?,
|
||||
expert_failed: row.get(5)?,
|
||||
expert_pass: row.get(6)?,
|
||||
master_failed: row.get(7)?,
|
||||
master_pass: row.get(8)?,
|
||||
})
|
||||
});
|
||||
})
|
||||
}
|
||||
pub fn create_store_v2(&self, table: &str) {
|
||||
self.lock_and_exec(table, params!());
|
||||
|
12
src/web/asset_index.html
Normal file
12
src/web/asset_index.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>EW private server</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">Asset Http Server</div>
|
||||
|
||||
</body>
|
||||
</html>
|
1
webui/.gitignore
vendored
1
webui/.gitignore
vendored
@ -22,3 +22,4 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
*.lock
|
||||
|
4554
webui/package-lock.json
generated
4554
webui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -10,9 +10,11 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"i18next": "^24.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.10.2",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^15.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.66",
|
||||
|
@ -1,15 +1,22 @@
|
||||
import { useState, useParams, useEffect } from 'react'
|
||||
import {useEffect, useState} from 'react'
|
||||
import './Help.css'
|
||||
import Request from '../Request.jsx'
|
||||
let init = false;
|
||||
import i18n from 'i18next';
|
||||
import i18next from "i18next";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
let init = false
|
||||
|
||||
function Help() {
|
||||
const [downloadUrl, setDownloadUrl] = useState(<div>Your server admin has no pre-patched apks to download. See the question below..</div>);
|
||||
const [downloadUrl, setDownloadUrl] = useState();
|
||||
const [defaultDownloadUrl, setDefaultDownloadUrl] = useState();
|
||||
const [downloadUrliOSGL, setDownloadUrliOSGL] = useState("https://ethanthesleepy.one/public/lovelive/sif2/sif2-gl.ipa");
|
||||
const [downloadUrliOSJP, setDownloadUrliOSJP] = useState("https://ethanthesleepy.one/public/lovelive/sif2/sif2-jp.ipa");
|
||||
const [language, setLanguage] = useState()
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [assetUrl, setAssetUrl] = useState("https://sif2.sif.moe");
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (init) return;
|
||||
@ -21,15 +28,16 @@ function Help() {
|
||||
if (!resp.data.links) return;
|
||||
if (resp.data.links.global && resp.data.links.japan) {
|
||||
setDownloadUrl(
|
||||
<div>Your server admin has a link to download! Download <a href={resp.data.links.japan}>Japan</a> or <a href={resp.data.links.global}>Global</a></div>
|
||||
<div>{t('has_download_url')}<a href={resp.data.links.japan}>{t('japan')}</a> {t('or')} <a
|
||||
href={resp.data.links.global}>{t('global')}</a></div>
|
||||
);
|
||||
} else if (resp.data.links.global) {
|
||||
setDownloadUrl(
|
||||
<div>Your server admin has a link to download! Download <a href={resp.data.links.global}>Global</a></div>
|
||||
<div>{t('has_download_url')}<a href={resp.data.links.global}>{t('global')}</a></div>
|
||||
);
|
||||
} else if (resp.data.links.japan) {
|
||||
setDownloadUrl(
|
||||
<div>Your server admin has a link to download! Download <a href={resp.data.links.japan}>Japan</a></div>
|
||||
<div>{t('has_download_url')}<a href={resp.data.links.japan}>{t('japan')}</a></div>
|
||||
);
|
||||
}
|
||||
if (resp.data.links.assets) {
|
||||
@ -46,9 +54,95 @@ function Help() {
|
||||
|
||||
})();
|
||||
});
|
||||
|
||||
return (
|
||||
<div id="home">
|
||||
|
||||
useEffect(() => {
|
||||
setLanguage(i18next.language)
|
||||
setDefaultDownloadUrl(t('default_download_url'))
|
||||
setDownloadUrl(t('default_download_url'))
|
||||
const handleLanguageChanged = (lng) => {
|
||||
setLanguage(lng)
|
||||
setDownloadUrl(t('default_download_url'))
|
||||
};
|
||||
|
||||
i18n.on('languageChanged', handleLanguageChanged);
|
||||
|
||||
return () => {
|
||||
i18n.off('languageChanged', handleLanguageChanged);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const renderNoAndroidDownloadUrl = () => {
|
||||
if (language === "zh") {
|
||||
return <>
|
||||
<h2>我的服务器管理员没有下载链接,我该如何修补apk(安卓)?</h2>
|
||||
<p>你可以用 <a href="https://sif2patch.zhushenwudi.top">@arasfon's sif2 apk 修补工具</a>。
|
||||
在“服务器URL”中填写"{window.location.origin}",在“资源URL”中填写 "{assetUrl}"(默认已经帮你填入了以上信息)。
|
||||
选择你的游戏版本, 更改头部格式为“Lowercase”, 最后点击开始修补。完成修补后使用 <a
|
||||
href="https://github.com/patrickfav/uber-apk-signer">uber-apk-signer</a> 来签署apk.
|
||||
最后便可以安装到你的手机上了!</p>
|
||||
</>
|
||||
} else {
|
||||
return <>
|
||||
<h2>My server admin has no download URLs, how do I patch the apk? (Android)</h2>
|
||||
<p>You can use <a href="https://sif2patch.zhushenwudi.top">@arasfon's sif2 apk 修补工具</a>.
|
||||
Plug and the asset url, which is "{assetUrl}", into the "Assets URL" textbox,
|
||||
and then the server url (Which is likely "{window.location.origin}", though this may not be the case).
|
||||
Select your game version, set "header format" to "Lowercase", and press patch. Once that done, use
|
||||
<a href="https://github.com/patrickfav/uber-apk-signer">uber-apk-signer</a>
|
||||
to sign the apk. Then, get it onto your phone and install!</p>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
const renderZh = () => {
|
||||
return <div id="home">
|
||||
<h1>帮助/关于</h1>
|
||||
<h2>“ew”是什么?这个服务器是用来做什么的?</h2>
|
||||
<p>“ew”是一个用Rust编写的私人服务器,原作者:Ethan O'Brien,原项目:<a
|
||||
href="https://git.ethanthesleepy.one/ethanaobrien/ew">ew仓库地址</a>。
|
||||
本项目为二次开发版本,用于短命的游戏《Love Live! School idol festival 2 MIRACLE LIVE!》,
|
||||
这是一款以Love Live!为主题的移动节奏游戏。</p>
|
||||
|
||||
<h2>我只是想在这个服务器上玩,我该如何安装这个应用程序?(安卓)</h2>
|
||||
<p>{downloadUrl}</p>
|
||||
|
||||
{downloadUrl === defaultDownloadUrl ? renderNoAndroidDownloadUrl() : null}
|
||||
|
||||
<h2>我该如何安装这款应用?(iOS)</h2>
|
||||
<p>在iOS上运行要比Android简单得多,这要归功于Discord上的Triangle。你首先要下载一个用于 <a
|
||||
href={downloadUrliOSGL}>国际服</a> 或者 <a href={downloadUrliOSJP}>日服</a> 的ipa文件,
|
||||
然后使用 <a href="https://sideloadly.io/">Sideloadly</a> (或任何能够实现侧载的应用程序)来安装这个APP。打开手机系统设置,
|
||||
导航到SIF2应用的设置页面, 填入“Server URL”:"{window.location.origin}" 与 “Assets URL”:"{assetUrl}"。
|
||||
如果您在打开应用程序时遇到任何错误,请确保设置中的所有网址都没有以斜杠结尾。</p>
|
||||
|
||||
<h2>求求!我尝试打开SIF2,但它显示为“不可用”(iOS)。</h2>
|
||||
<p>不要删除当前的SIF2,只需重新侧载该应用程序。这是苹果的安全功能。</p>
|
||||
|
||||
<h2>程序的执行效率如何?</h2>
|
||||
<p>运行得相当不错。服务器本身占用的存储空间甚至不到20兆字节,并且是用Rust编写的。我个人认为它写得相当好。</p>
|
||||
|
||||
<h2>我的电脑/笔记本电脑能运行服务器吗?</h2>
|
||||
<p>非常有可能。如果平台是支持 <a
|
||||
href="https://doc.rust-lang.org/nightly/rustc/platform-support.html">Rust语言</a> 的,
|
||||
那么答案是肯定的!在我有时间设置自动操作之前,建议手动编译项目。 <a
|
||||
href="https://git.zhushenwudi.top/zhushenwudi/ew">ew仓库地址</a></p>
|
||||
|
||||
<h2>服务器现在是宕机了吗?我无法连接。</h2>
|
||||
<p>假设你刚刚在你使用的服务器上加载了这个页面,那么答案是否定的,否则请联系你的服务器管理员。</p>
|
||||
|
||||
<h2>打歌活动能正常游玩吗?</h2>
|
||||
<p>大多数活动会让游戏崩溃,不过这确实是不应该的。明星活动部分已实现。你可以提升自己的排名,并在排名表中与其他玩家竞争,但目前尚未实现奖励功能。</p>
|
||||
|
||||
<h2>我该如何获取活动UR呢?</h2>
|
||||
<p>可以通过游戏内兑换码的方式领取活动UR:UR</p>
|
||||
|
||||
<h2>当我操作窗口关闭时,为什么游戏会崩溃?</h2>
|
||||
<p>这很可能意味着服务器上有某些地方出故障了。如果你是自行托管(服务器),请通过Matrix联系我。否则,联系你的服务器管理员,并让他们报告这个问题。</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
const renderEn = () => {
|
||||
return <div id="home">
|
||||
<h1>Help/About</h1>
|
||||
<h2>What is "ew"? What is this server for?</h2>
|
||||
<p>"ew" is a private server, written in Rust, for the short lived game "Love Live! School idol festival 2 MIRACLE LIVE!", a Love Live! themed mobile rhythm game.</p>
|
||||
@ -56,11 +150,15 @@ function Help() {
|
||||
<h2>I'm just trying to play on this server, how do I install the app? (Android)</h2>
|
||||
<p>{downloadUrl}</p>
|
||||
|
||||
<h2>My server admin has no download URLs, how do I patch the apk? (Android)</h2>
|
||||
<p>You can use <a href="https://arasfon.ru/sif2/patcher/">@arasfon's sif2 apk patcher</a>. Plug and the asset url, which is "{assetUrl}", into the "Assets URL" textbox, and then the server url (Which is likely "{window.location.origin}", though this may not be the case). Select your game version, set "header format" to "Lowercase", and press patch. Once that done, use <a href="https://github.com/patrickfav/uber-apk-signer">uber-apk-signer</a> to sign the apk. Then, get it onto your phone and install!</p>
|
||||
{downloadUrl === defaultDownloadUrl ? renderNoAndroidDownloadUrl() : null}
|
||||
|
||||
<h2>How do I install the app? (iOS)</h2>
|
||||
<p>Running on iOS is much simpler than Android, thanks to triangle on the discord. You first download an ipa file for <a href={downloadUrliOSGL}>global</a> or <a href={downloadUrliOSJP}>Japan</a>, and use <a href="https://sideloadly.io/">Sideloadly</a> (or your preferred application installer) to install the app. Then open settings, navigate to the app you just installed, and input the server url (Which is likely "{window.location.origin}", though this may not be the case), and the asset url, which is "{assetUrl}". If you have any errors opening the app, make sure none of the urls in settings end with a slash (/).</p>
|
||||
<p>Running on iOS is much simpler than Android, thanks to triangle on the discord. You first download an ipa file for
|
||||
<a href={downloadUrliOSGL}>global</a> or <a href={downloadUrliOSJP}>Japan</a>,
|
||||
and use <a href="https://sideloadly.io/">Sideloadly</a> (or your preferred application installer) to install the app.
|
||||
Then open settings, navigate to the app you just installed, and input the server url (Which is likely "{window.location.origin}",
|
||||
though this may not be the case), and the asset url, which is "{assetUrl}".
|
||||
If you have any errors opening the app, make sure none of the urls in settings end with a slash (/).</p>
|
||||
|
||||
<h2>Help! I'm trying to open the app and it shows as "unavailable" (iOS)</h2>
|
||||
<p>Do not delete it, Just re-sideload the app. This is an Apple security feature.</p>
|
||||
@ -69,20 +167,27 @@ function Help() {
|
||||
<p>Works well enough. The server itself takes up not even 20mb of storage, and it's written in rust. I personally think it's pretty well written.</p>
|
||||
|
||||
<h2>Could my computer/laptop run a server?</h2>
|
||||
<p>Very very likely. If the platform is <a href="https://doc.rust-lang.org/nightly/rustc/platform-support.html">supported by rust</a>, then the answer is yes! It is recommended to manually compile the project until I get the time to setup actions. <a href="https://git.ethanthesleepy.one/ethanaobrien/ew">ew repo</a></p>
|
||||
<p>Very very likely. If the platform is <a href="https://doc.rust-lang.org/nightly/rustc/platform-support.html">supported by rust</a>,
|
||||
then the answer is yes! It is recommended to manually compile the project until I get the time to setup actions.
|
||||
<a href="https://git.ethanthesleepy.one/ethanaobrien/ew">ew repo</a></p>
|
||||
|
||||
<h2>Is the server down right now? I can't connect</h2>
|
||||
<p>Assuming you have just loaded this page on the server you use, then the answer is no, otherwise please contact your server admin.</p>
|
||||
|
||||
<h2>Do events work?</h2>
|
||||
<p>Most events do not, though most should not crash the game. Star events are partially implemented. You can get your rank up, and compete with other players in a ranking table, but no rewards are currently implemented.</p>
|
||||
<p>Most events do not, though most should not crash the game. Star events are partially implemented.
|
||||
You can get your rank up, and compete with other players in a ranking table, but no rewards are currently implemented.</p>
|
||||
|
||||
<h2>But then, how do I get event URs?</h2>
|
||||
<p>There are serial codes for several things, one of which includes all the event URs. I don't remember what does what but it is recommended to look at the serial code file to get the latest codes.</p>
|
||||
<p>There are serial codes for several things, one of which includes all the event URs. UR</p>
|
||||
|
||||
<h2>Why does the game crash when I do x?</h2>
|
||||
<p>This likely means something on the server is broken. If you're self hosting, please contact me via matrix. Otherwise, contact your server admin and ask them to report the issue.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
return (
|
||||
language === 'zh' ? renderZh() : renderEn()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { useState, useParams, useEffect } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import './Home.css'
|
||||
import Request from '../Request.jsx'
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
let bonusItems = [];
|
||||
|
||||
function getMigrationID(uid) {
|
||||
@ -21,6 +23,7 @@ function getMigrationID(uid) {
|
||||
function Bonus() {
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const error = useState("");
|
||||
const { t } = useTranslation();
|
||||
|
||||
let itemz = [];
|
||||
bonusItems.forEach((e) => {
|
||||
@ -33,8 +36,9 @@ function Bonus() {
|
||||
event.preventDefault();
|
||||
let input = parseInt(inputValue.trim());
|
||||
if (isNaN(input) || submittedItems.includes(input)) return;
|
||||
let resp = await Request("/api/webui/startLoginbonus", {
|
||||
bonus_id: input
|
||||
let resp = await Request("/api/webui/submitLoginBonus", {
|
||||
bonus_id: input,
|
||||
is_add: true
|
||||
});
|
||||
if (resp.result !== "OK") {
|
||||
error[1](resp.message);
|
||||
@ -45,33 +49,43 @@ function Bonus() {
|
||||
setInputValue('');
|
||||
};
|
||||
|
||||
const handleRemoveItem = (index) => {
|
||||
const handleRemoveItem = async (index) => {
|
||||
const updatedItems = [...submittedItems];
|
||||
let resp = await Request("/api/webui/submitLoginBonus", {
|
||||
bonus_id: updatedItems[index],
|
||||
is_add: false
|
||||
});
|
||||
if (resp.result !== "OK") {
|
||||
error[1](resp.message);
|
||||
return;
|
||||
}
|
||||
error[1]("");
|
||||
updatedItems.splice(index, 1);
|
||||
setSubmittedItems(updatedItems);
|
||||
};
|
||||
// <button onClick={() => handleRemoveItem(index)}>X</button>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Current login bonus list</h2>
|
||||
<h2>{t('current_bonus')}</h2>
|
||||
<div id="error"> { error[0] ? <p>Error: { error[0] } </p> : <p></p> } </div>
|
||||
<ul>
|
||||
{submittedItems.map((item, index) => (
|
||||
<li key={index}>
|
||||
{item}
|
||||
</li>
|
||||
<li key={index}>
|
||||
{item}
|
||||
<button onClick={() => handleRemoveItem(index)}>X</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(event) => setInputValue(event.target.value)}
|
||||
placeholder="Enter login bonus ID"
|
||||
placeholder={t('enter_bonus_id')}
|
||||
/>
|
||||
<button type="submit">Submit</button>
|
||||
<button type="submit">{t('submit')}</button>
|
||||
</form>
|
||||
<p>You can find a list of available login bonus IDs <a href="https://git.ethanthesleepy.one/ethanaobrien/ew/src/branch/main/src/router/databases/json/login_bonus.json">here</a>. You should input the <code>id</code> field</p>
|
||||
<p>{t('find_bonus_in_url')} <a href="https://git.ethanthesleepy.one/ethanaobrien/ew/src/branch/main/src/router/databases/json/login_bonus.json">{t('here')}</a>. {t('should_input')} <code>id</code></p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -81,6 +95,7 @@ function Home() {
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [serverTime, setServerTime] = useState('');
|
||||
const error = useState("");
|
||||
const { t } = useTranslation();
|
||||
|
||||
const logout = () => {
|
||||
window.location.href = "/webui/logout";
|
||||
@ -107,12 +122,10 @@ function Home() {
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
let time = Math.round(new Date(inputValue.trim()).getTime() / 1000);
|
||||
if (inputValue.trim() === "-1") {
|
||||
time = 1711741114;
|
||||
} else if (inputValue.trim() === "0") {
|
||||
if (inputValue.trim() === "0") {
|
||||
time = 0;
|
||||
}
|
||||
if (time < 0 || isNaN(time)) return;
|
||||
if (time < -1 || isNaN(time)) return;
|
||||
let resp = await Request("/api/webui/set_time", {
|
||||
timestamp: time
|
||||
});
|
||||
@ -122,7 +135,7 @@ function Home() {
|
||||
}
|
||||
error[1]("");
|
||||
if (time === 0) {
|
||||
setServerTime("now");
|
||||
setServerTime(new Date().toString());
|
||||
} else {
|
||||
setServerTime((new Date(time * 1000)).toString());
|
||||
}
|
||||
@ -148,20 +161,23 @@ function Home() {
|
||||
exp: 10,
|
||||
last_login_time: 5
|
||||
},
|
||||
server_time_set: new Date()
|
||||
time: new Date()
|
||||
}*/
|
||||
if (resp.data.time === 0) {
|
||||
setServerTime("now");
|
||||
setServerTime(new Date().toString());
|
||||
} else {
|
||||
setServerTime((new Date(resp.data.time * 1000)).toString());
|
||||
let deltaTime = new Date().getTime() - resp.data.server_time_set * 1000
|
||||
let gameTime = resp.data.time * 1000 + deltaTime
|
||||
setServerTime((new Date(gameTime)).toString());
|
||||
}
|
||||
|
||||
userdata(
|
||||
<div>
|
||||
<p>User id: { user.user.id } </p>
|
||||
<p>Migration id: { getMigrationID(user.user.id) } </p>
|
||||
<p>Rank: { user.user.rank } ({ user.user.exp } exp)</p>
|
||||
<p>Last Login: { (new Date(user.user.last_login_time * 1000)).toString() } </p>
|
||||
<p>SIF2 ID: { user.user.id } </p>
|
||||
<p>{t('migration_id')}: { getMigrationID(user.user.id) } </p>
|
||||
<p>{t('level')}: { user.user.rank } ({ user.user.exp } exp)</p>
|
||||
<p>{t('last_login_time')}: { (new Date(user.user.last_login_time * 1000)).toString() } </p>
|
||||
<Bonus />
|
||||
</div>
|
||||
);
|
||||
@ -170,25 +186,25 @@ function Home() {
|
||||
|
||||
return (
|
||||
<div id="home">
|
||||
<button id="logout" onClick={expor}>Export account</button><br/><br/><br/>
|
||||
<button id="logout" onClick={logout}>Logout</button>
|
||||
<h1>Home</h1>
|
||||
<button id="logout" onClick={expor}>{t('export_account')}</button><br/><br/><br/>
|
||||
<button id="logout" onClick={logout}>{t('logout')}</button>
|
||||
<h1>{t('home')}</h1>
|
||||
|
||||
{ user ? <div>
|
||||
{ user }
|
||||
<h2>Server time</h2>
|
||||
<div id="error"> { error[0] ? <p>Error: { error[0] } </p> : <p></p> } </div>
|
||||
<p>Currently set to { serverTime }. Setting to 0 will set it to now, and -1 will reset it. Time will still progress, based off of when you set this timestamp.</p>
|
||||
<h2>{t('server_time')}</h2>
|
||||
<div id="error"> { error[0] ? <p>{t('error')}: { error[0] } </p> : <p></p> } </div>
|
||||
<p>{t('current_game_time')} { serverTime }. {t("set_time_hint")}</p>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(event) => setInputValue(event.target.value)}
|
||||
placeholder="Enter Server Time"
|
||||
placeholder={t('input_server_time')}
|
||||
/>
|
||||
<button type="submit">Submit</button>
|
||||
<button type="submit">{t('submit')}</button>
|
||||
</form></div>
|
||||
: <p>Loading...</p> }
|
||||
: <p>{t('loading')}</p> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { useState } from 'react'
|
||||
import './Import.css'
|
||||
import Request from '../Request.jsx'
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
function Login() {
|
||||
const error = useState(new URL(window.location).searchParams.get("message") || "");
|
||||
const status = useState("");
|
||||
const uid = useState((window.localStorage && window.localStorage.getItem("ew_uid")) || "");
|
||||
const { t } = useTranslation();
|
||||
let file=[], file1=[], file2=[], file3=[], password;
|
||||
let has_imported = false;
|
||||
|
||||
@ -23,26 +25,26 @@ function Login() {
|
||||
jp: true
|
||||
};
|
||||
if (!data.userdata || !data.userdata.user || !data.userdata.user.id) {
|
||||
error[1]("Incorrect user data file format");
|
||||
error[1](t('user_data_incorrect'));
|
||||
return;
|
||||
}
|
||||
if (!data.home || !data.home.home || !data.home.home.information_list) {
|
||||
error[1]("Incorrect home data file format");
|
||||
error[1](t('home_data_incorrect'));
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(data.missions) && data.missions) {
|
||||
error[1]("Incorrect mission data file format");
|
||||
error[1](t('mission_data_incorrect'));
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(data.sif_cards) && data.sif_cards) {
|
||||
error[1]("Incorrect sif card data file format");
|
||||
error[1](t('card_data_incorrect'));
|
||||
return;
|
||||
}
|
||||
let resp = await Request(
|
||||
"/api/webui/import",
|
||||
data
|
||||
);
|
||||
if (resp.result == "OK") {
|
||||
if (resp.result === "OK") {
|
||||
status[1](<div><p>Account imported!</p><p>User id: {resp.uid}</p><p>Migration token: {resp.migration_token}</p></div>);
|
||||
} else {
|
||||
error[1](resp.message);
|
||||
@ -54,27 +56,27 @@ function Login() {
|
||||
|
||||
return (
|
||||
<div id="login-form">
|
||||
<h1>Transfer</h1>
|
||||
<h1>{t('transfer')}</h1>
|
||||
<form>
|
||||
{ <p>{ status[0] } </p> }
|
||||
<label htmlFor="id">User data file (required):</label>
|
||||
<label htmlFor="id">userdata.json({t('required')}):</label>
|
||||
<input type="file" id="id" name="id" onChange={(event) => {file = event.target.files}} accept="application/json"/>
|
||||
|
||||
<label htmlFor="file1">User Home data file (required):</label>
|
||||
<label htmlFor="file1">userhome.json({t('required')}):</label>
|
||||
<input type="file" id="file1" name="file1" onChange={(event) => {file1 = event.target.files}} accept="application/json"/>
|
||||
|
||||
<label htmlFor="file2">User Missions data file (optional):</label>
|
||||
<label htmlFor="file2">missions.json({t('optional')}):</label>
|
||||
<input type="file" id="file2" name="file2" onChange={(event) => {file2 = event.target.files}} accept="application/json"/>
|
||||
|
||||
<label htmlFor="file3">Sif cards data file (optional):</label>
|
||||
<label htmlFor="file3">sifcards.json({t('optional')}):</label>
|
||||
<input type="file" id="file3" name="file3" onChange={(event) => {file3 = event.target.files}} accept="application/json"/>
|
||||
|
||||
<label htmlFor="password">Transfer passcode (game will not recognize special characters, only use letters and numbers or you will be locked out):</label>
|
||||
<label htmlFor="password">{t('transfer_passcode')}{t('transfer_passcode_hint')}</label>
|
||||
<input type="password" id="password" name="password" onChange={(event) => {password = event.target.value}} />
|
||||
|
||||
<input type="submit" value="Submit" onClick={handleSubmit}/>
|
||||
<input type="submit" value={t('submit')} onClick={handleSubmit}/>
|
||||
<div id="sub_div">
|
||||
{ error[0] ? <p>Error: { error[0] }. Please reload the page and try again.</p> : <p></p> }
|
||||
{ error[0] ? <p>{t('error')}: { error[0] }. {t('refresh_and_retry')}</p> : <p></p> }
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
43
webui/src/locales/en.json
Normal file
43
webui/src/locales/en.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"login": "Login",
|
||||
"need_full_input": "Please fill in the information completely",
|
||||
"uid_not_correct": "SIF2 ID should be a number, get it in game personal profile",
|
||||
"transfer_passcode": "Transfer passcode:",
|
||||
"submit": "Submit",
|
||||
"import_user": "Import User",
|
||||
"need_help": "Need help?",
|
||||
"ew_version": "EW version",
|
||||
"view_source": "View source",
|
||||
"view_license": "View license",
|
||||
"current_bonus": "Current login bonus list",
|
||||
"enter_bonus_id": "Enter login bonus ID",
|
||||
"find_bonus_in_url": "You can find a list of available login bonus IDs",
|
||||
"here": "here",
|
||||
"should_input": "You should input the",
|
||||
"migration_id": "Migration id",
|
||||
"level": "Level",
|
||||
"last_login_time": "Last Login",
|
||||
"export_account": "Export account",
|
||||
"logout": "Logout",
|
||||
"home": "Home",
|
||||
"server_time": "Server time",
|
||||
"error": "Error",
|
||||
"current_game_time": "Currently set to",
|
||||
"set_time_hint": "Setting to 0 will set it to now, and -1 will reset to open server time. Time will still progress, based off of when you set this timestamp.",
|
||||
"input_server_time": "Enter Server Time",
|
||||
"loading": "Loading...",
|
||||
"transfer": "Transfer",
|
||||
"required": "required",
|
||||
"optional": "optional",
|
||||
"transfer_passcode_hint": "(game will not recognize special characters, only use letters and numbers or you will be locked out)",
|
||||
"refresh_and_retry": "Please reload the page and try again.",
|
||||
"user_data_incorrect": "Incorrect user data file format",
|
||||
"home_data_incorrect": "Incorrect home data file format",
|
||||
"mission_data_incorrect": "Incorrect mission data file format",
|
||||
"card_data_incorrect": "Incorrect sif card data file format",
|
||||
"default_download_url": "Your server admin has no pre-patched apks to download. See the question below.",
|
||||
"has_download_url": "Your server admin has a link to download! Download ",
|
||||
"japan": "Japan",
|
||||
"global": "Global",
|
||||
"or": "Or"
|
||||
}
|
43
webui/src/locales/zh.json
Normal file
43
webui/src/locales/zh.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"login": "登录",
|
||||
"need_full_input": "请完整填入信息",
|
||||
"uid_not_correct": "SIF2 ID必须是数字,在游戏中个人详情页中获取",
|
||||
"transfer_passcode": "数据迁移密钥:",
|
||||
"submit": "提交",
|
||||
"import_user": "导入用户数据",
|
||||
"need_help": "需要帮助?",
|
||||
"ew_version": "EW 版本号",
|
||||
"view_source": "查看源代码",
|
||||
"view_license": "查看许可证",
|
||||
"current_bonus": "当前登录奖励列表",
|
||||
"enter_bonus_id": "输入登录奖励ID",
|
||||
"find_bonus_in_url": "你可以找到可用登录奖励ID的列表",
|
||||
"here": "这里",
|
||||
"should_input": "你应该输入指定",
|
||||
"migration_id": "引继码",
|
||||
"level": "等级",
|
||||
"last_login_time": "上次登录",
|
||||
"export_account": "导出用户数据",
|
||||
"logout": "登出",
|
||||
"home": "用户主页",
|
||||
"server_time": "服务器时间",
|
||||
"error": "错误",
|
||||
"current_game_time": "当前游戏时间为",
|
||||
"set_time_hint": "将其设置为0将会设置为当前时间,而-1则会重置到开服时间。时间的推进仍将基于您设置此时间戳的时间。",
|
||||
"input_server_time": "输入服务器时间",
|
||||
"loading": "请稍候...",
|
||||
"transfer": "导入数据",
|
||||
"required": "必要",
|
||||
"optional": "可选",
|
||||
"transfer_passcode_hint": "(游戏将无法识别特殊字符,只能使用字母和数字,否则您将被锁定)",
|
||||
"refresh_and_retry": "请重新加载页面并重试。",
|
||||
"user_data_incorrect": "用户数据 文件格式不正确",
|
||||
"home_data_incorrect": "主页数据 文件格式不正确",
|
||||
"mission_data_incorrect": "任务数据 文件格式不正确",
|
||||
"card_data_incorrect": "卡牌数据 文件格式不正确",
|
||||
"default_download_url": "您的服务器管理员没有可供下载的预打补丁的应用程序包。请参阅下面的问题。",
|
||||
"has_download_url": "您的服务器管理员有下载链接!下载 ",
|
||||
"japan": "日服",
|
||||
"global": "国际服",
|
||||
"or": "或者"
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
import { useState } from 'react'
|
||||
import {useState} from 'react'
|
||||
import './Login.css'
|
||||
import Request from '../Request.jsx'
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
function Login() {
|
||||
const error = useState(new URL(window.location).searchParams.get("message") || "");
|
||||
const uid = useState((window.localStorage && window.localStorage.getItem("ew_uid")) || "");
|
||||
const password = useState("");
|
||||
const { t } = useTranslation();
|
||||
|
||||
function showError(message) {
|
||||
error[1](message);
|
||||
@ -14,11 +16,11 @@ function Login() {
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
if (!uid[0] || !password[0]) {
|
||||
showError("Missing userid/password");
|
||||
showError(t('need_full_input'));
|
||||
return;
|
||||
}
|
||||
if (isNaN(uid[0])) {
|
||||
showError("UserID should be a number. (The \"Friend ID\" in your profile)");
|
||||
showError(t('uid_not_correct'));
|
||||
return;
|
||||
}
|
||||
if (window.localStorage) window.localStorage.setItem("ew_uid", uid[0]);
|
||||
@ -29,7 +31,7 @@ function Login() {
|
||||
password: password[0]
|
||||
}
|
||||
);
|
||||
if (resp.result == "OK") {
|
||||
if (resp.result === "OK") {
|
||||
window.location.href = "/home/";
|
||||
} else {
|
||||
showError(resp.message);
|
||||
@ -48,18 +50,26 @@ function Login() {
|
||||
|
||||
return (
|
||||
<div id="login-form">
|
||||
<h1>Login</h1>
|
||||
<h1>{t('login')}</h1>
|
||||
<form>
|
||||
<label htmlFor="id">SIF2 ID:</label>
|
||||
<input type="text" id="id" name="id" onChange={(event) => {uid[1](event.target.value)}} value={uid[0]} />
|
||||
<label htmlFor="password">Transfer passcode:</label>
|
||||
<input type="password" id="password" name="password" onChange={(event) => {password[1](event.target.value)}} />
|
||||
<input type="submit" value="Submit" onClick={handleSubmit}/>
|
||||
<input type="text" id="id" name="id" onChange={(event) => {
|
||||
uid[1](event.target.value)
|
||||
}} value={uid[0]}/>
|
||||
<label htmlFor="password">{t('transfer_passcode')}</label>
|
||||
<input type="password" id="password" name="password" onChange={(event) => {
|
||||
password[1](event.target.value)
|
||||
}}/>
|
||||
<input type="submit" value={t('submit')} onClick={handleSubmit}/>
|
||||
<div id="sub_div">
|
||||
<button onClick={import_user}>Import User</button><br/><br/>
|
||||
<button onClick={help}>Need help?</button><br/><br/>
|
||||
{ error[0] ? <p>Error: { error[0] } </p> : <p></p> }
|
||||
<button onClick={import_user}>{t('import_user')}</button>
|
||||
<br/><br/>
|
||||
<button onClick={help}>{t('need_help')}</button>
|
||||
<br/><br/>
|
||||
{error[0] ? <p>Error: {error[0]} </p> : <p></p>}
|
||||
</div>
|
||||
<p>{t('ew_version')} 1.0.1 - <a href="https://git.zhushenwudi.top/zhushenwudi/ew">{t('view_source')}</a> - <a
|
||||
href="https://git.zhushenwudi.top/zhushenwudi/ew/src/branch/main/LICENSE">{t('view_license')}</a></p>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
@ -4,6 +4,11 @@ import Login from './login/Login.jsx'
|
||||
import Home from './home/Home.jsx'
|
||||
import Import from './import/Import.jsx'
|
||||
import Help from './help/Help.jsx'
|
||||
import LanguageSwitcher from "./utils/LanguageSwitcher.jsx";
|
||||
import {I18nextProvider} from 'react-i18next';
|
||||
import i18next from 'i18next';
|
||||
import enTranslation from './locales/en.json';
|
||||
import zhTranslation from './locales/zh.json';
|
||||
|
||||
let Elem;
|
||||
switch (window.location.pathname) {
|
||||
@ -17,16 +22,31 @@ switch (window.location.pathname) {
|
||||
Elem = Import;
|
||||
break;
|
||||
case "/help/":
|
||||
Elem = Help;
|
||||
break;
|
||||
Elem = Help;
|
||||
break;
|
||||
default:
|
||||
window.location.pathname = "/";
|
||||
}
|
||||
|
||||
if (Elem) {
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<Elem />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
}
|
||||
const savedLanguage = localStorage.getItem('language') || 'zh';
|
||||
i18next.init({
|
||||
interpolation: {escapeValue: false},
|
||||
lng: savedLanguage,
|
||||
resources: {
|
||||
en: {translation: enTranslation},
|
||||
zh: {translation: zhTranslation},
|
||||
},
|
||||
}).then(() => {
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<I18nextProvider i18n={i18next}>
|
||||
<div style={{position: 'relative'}}>
|
||||
<LanguageSwitcher/>
|
||||
<Elem/>
|
||||
</div>
|
||||
</I18nextProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
});
|
||||
}
|
56
webui/src/utils/LanguageSwitcher.jsx
Normal file
56
webui/src/utils/LanguageSwitcher.jsx
Normal file
@ -0,0 +1,56 @@
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
|
||||
function LanguageSwitcher() {
|
||||
const {i18n} = useTranslation();
|
||||
const [margin, setMargin] = useState(0);
|
||||
const [language, setLanguage] = useState(() => {
|
||||
return localStorage.getItem('language') || 'zh'; // 从 localStorage 初始化
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
i18n.changeLanguage(language);
|
||||
localStorage.setItem('language', language); // 保存到 localStorage
|
||||
}, [language, i18n]);
|
||||
|
||||
useEffect(() => {
|
||||
changeMargin()
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
changeMargin()
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const changeMargin = () => {
|
||||
switch (window.location.pathname) {
|
||||
case "/home/":
|
||||
case "/help/":
|
||||
setMargin((window.innerWidth - 228) * 0.05);
|
||||
break;
|
||||
default:
|
||||
setMargin((window.innerWidth - 418) / 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const changeLanguage = () => {
|
||||
const newLanguage = language === 'zh' ? 'en' : 'zh';
|
||||
setLanguage(newLanguage);
|
||||
};
|
||||
|
||||
return (
|
||||
<button style={{position: "absolute", top: 0, right: margin, zIndex: 100}} onClick={changeLanguage}>
|
||||
{language === "zh" ? "English" : "中文"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default LanguageSwitcher;
|
Reference in New Issue
Block a user