Compare commits

...

13 Commits

Author SHA1 Message Date
5a0f888000 本地化翻译 2025-01-05 22:33:17 +08:00
1b6fb5fa81 fix: 脚本参数异常 2025-01-05 19:20:49 +08:00
d58a0d26cf 修改asset_index位置 2025-01-05 17:14:58 +08:00
7e5888e7e5 Add asset local server service 2025-01-05 10:53:57 +08:00
fae080ea87 Add version link to webui 2024-12-08 17:37:51 -06:00
de31838195 Add build release workflow 2024-12-08 17:25:49 -06:00
1e32cb8688 Disable arm64 build 2024-11-28 11:58:21 -06:00
d420bec782 Fix event scores json endpoint 2024-11-23 08:26:39 -06:00
2a4d927a41 cargo clippy 2024-11-21 21:09:36 -06:00
b21deaee5f Move back to mutex in sql.rs 2024-11-21 11:13:54 -06:00
7493724cd7 i hate programming :(((((((((( >_< 2024-11-20 22:13:11 -06:00
4d9991ce75 Update dependencies 2024-11-19 23:02:35 -06:00
5fc1cea49d Re-add arm build 2024-11-18 23:13:37 -06:00
29 changed files with 401 additions and 3418 deletions

View File

@ -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

View 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
View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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,8 @@ 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"

View File

@ -1,4 +1,10 @@
FROM docker.io/library/debian:latest as builder
# 打包命令
# docker buildx build -t zhushenwudi/lovelive-sif2:1.0.1 --platform=linux/amd64 .
# 使用适用于 Linux x86 的基础映像
# 保存镜像
# docker save -o lovelive-sif2.tar zhushenwudi/lovelive-sif2:1.0.1
FROM docker.io/library/debian:latest AS builder
# First - build
@ -11,13 +17,21 @@ 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
RUN . "$HOME/.cargo/env" && cargo build --release --jobs=16
## Second - sort stuff idk
@ -29,4 +43,7 @@ COPY ./docker/start.sh /root/ew/start.sh
RUN chmod +x /root/ew/start.sh
EXPOSE 8080
EXPOSE 8000
ENTRYPOINT ["/root/ew/start.sh"]

View File

@ -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)
})

View File

@ -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"
}
}
}
}

View File

@ -1,5 +0,0 @@
{
"dependencies": {
"express": "^4.18.2"
}
}

View File

@ -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

View File

@ -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

View File

@ -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`

View File

@ -17,6 +17,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 +39,18 @@ pub struct Args {
#[arg(short, long, default_value_t = 8080, help = "Port to listen on")]
port: u16,
#[arg(short, long, default_value_t = 8000, help = "Assets port to listen on")]
asset_port: u16,
#[arg(long, default_value = "./", help = "Path to store database files")]
path: String,
#[arg(long, default_value = "./asset/", 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,6 +106,8 @@ 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...");
@ -103,9 +115,9 @@ async fn run_server(in_thread: bool) -> std::io::Result<()> {
println!("Purged {} accounts", ct);
}
let rv = HttpServer::new(|| App::new()
let webui_server = HttpServer::new(|| App::new()
.wrap_fn(|req, srv| {
println!("Request: {}", req.path());
println!("WebUI Request: {}", req.path());
srv.call(req)
})
.app_data(web::PayloadConfig::default().limit(1024 * 1024 * 25))
@ -114,26 +126,56 @@ async fn run_server(in_thread: bool) -> std::io::Result<()> {
.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!("WebUI Server started: http://0.0.0.0:{}", port);
println!("Database path is set to {}", args.path);
println!("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| {
println!("Assets Request: {}", 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());
println!("Assets Server started: http://0.0.0.0:{}", asset_port);
println!("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.");
}
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;
}
handle.stop(false).await;
if handle2.is_some() {
handle2.unwrap().stop(false).await;
}
println!("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]
@ -153,7 +195,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 +232,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 +251,5 @@ async fn set_running(running: bool) {
async fn get_running() -> bool {
let result = lock_onto_mutex!(RUNNING);
return *result;
*result
}

View File

@ -28,12 +28,7 @@ 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;
@ -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);
}

View File

@ -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();
}
}
}

View File

@ -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> {

View File

@ -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;
}

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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")

View File

@ -325,7 +325,7 @@ 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);
@ -334,7 +334,7 @@ async fn npps4_req(sha_id: String) -> Option<JsonValue> {
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 {

View File

@ -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;
@ -163,6 +160,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.");

View File

@ -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
View 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
View File

@ -22,3 +22,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
*.lock

View File

@ -1,10 +1,12 @@
import { useState, useParams, useEffect } from 'react'
import {useEffect, useState} from 'react'
import './Help.css'
import Request from '../Request.jsx'
let init = false;
let init = false
let defaultDownloadUrl = "您的服务器管理员没有可供下载的预打补丁的应用程序包。请参阅下面的问题。"
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(<div>{defaultDownloadUrl}</div>);
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");
@ -21,15 +23,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>您的服务器管理员有下载链接下载 <a href={resp.data.links.japan}>日服</a> 或者 <a
href={resp.data.links.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>您的服务器管理员有下载链接下载 <a href={resp.data.links.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>您的服务器管理员有下载链接下载 <a href={resp.data.links.japan}>日服</a></div>
);
}
if (resp.data.links.assets) {
@ -47,42 +50,62 @@ function Help() {
})();
});
const renderNoAndroidDownloadUrl = () => {
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>
</>
}
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>
<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>I'm just trying to play on this server, how do I install the app? (Android)</h2>
<p>{downloadUrl}</p>
<h2>我只是想在这个服务器上玩我该如何安装这个应用程序安卓</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>
<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>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>
<h2>求求我尝试打开SIF2但它显示为不可用iOS</h2>
<p>不要删除当前的SIF2只需重新侧载该应用程序这是苹果的安全功能</p>
<h2>How well does this work?</h2>
<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>程序的执行效率如何</h2>
<p>运行得相当不错服务器本身占用的存储空间甚至不到20兆字节并且是用Rust编写的我个人认为它写得相当好</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>
<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>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>服务器现在是宕机了吗我无法连接</h2>
<p>假设你刚刚在你使用的服务器上加载了这个页面那么答案是否定的否则请联系你的服务器管理员</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>
<h2>打歌活动能正常游玩吗</h2>
<p>大多数活动会让游戏崩溃不过这确实是不应该的明星活动部分已实现你可以提升自己的排名并在排名表中与其他玩家竞争但目前尚未实现奖励功能</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>
<h2>我该如何获取活动UR呢</h2>
<p>可以通过游戏内兑换码的方式领取活动URmeowkodeganuy</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>
<h2>当我操作窗口关闭时为什么游戏会崩溃</h2>
<p>这很可能意味着服务器上有某些地方出故障了如果你是自行托管服务器请通过Matrix联系我否则联系你的服务器管理员并让他们报告这个问题</p>
</div>
);
}

View File

@ -50,28 +50,29 @@ function Bonus() {
updatedItems.splice(index, 1);
setSubmittedItems(updatedItems);
};
// <button onClick={() => handleRemoveItem(index)}>X</button>
return (
<div>
<h2>Current login bonus list</h2>
<h2>当前登录奖励列表</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="输入登录奖励ID"
/>
<button type="submit">Submit</button>
<button type="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>你可以找到可用登录奖励ID的列表 <a href="https://git.ethanthesleepy.one/ethanaobrien/ew/src/branch/main/src/router/databases/json/login_bonus.json">这里</a>. 你应该输入指定 <code>id</code></p>
</div>
);
}
@ -158,10 +159,10 @@ function Home() {
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>引继码: { getMigrationID(user.user.id) } </p>
<p>等级: { user.user.rank } ({ user.user.exp } exp)</p>
<p>上次登录时间: { (new Date(user.user.last_login_time * 1000)).toString() } </p>
<Bonus />
</div>
);
@ -170,23 +171,23 @@ 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}>导出用户数据</button><br/><br/><br/>
<button id="logout" onClick={logout}>登出</button>
<h1>用户主页</h1>
{ user ? <div>
{ user }
<h2>Server time</h2>
<h2>服务器时间</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>
<p>当前时间设置的值为 { serverTime }. 将其设置为0将会设置为当前时间-1则会重置它时间的推进仍将基于您设置此时间戳的时间</p>
<form onSubmit={handleSubmit}>
<input
type="text"
value={inputValue}
onChange={(event) => setInputValue(event.target.value)}
placeholder="Enter Server Time"
placeholder="输入服务器时间"
/>
<button type="submit">Submit</button>
<button type="submit">提交</button>
</form></div>
: <p>Loading...</p> }
</div>

View File

@ -54,27 +54,27 @@ function Login() {
return (
<div id="login-form">
<h1>Transfer</h1>
<h1>导入数据</h1>
<form>
{ <p>{ status[0] } </p> }
<label htmlFor="id">User data file (required):</label>
<label htmlFor="id">userdata.json(必要):</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(必要):</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(可选):</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(可选):</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">数据迁移密钥 (游戏将无法识别特殊字符只能使用字母和数字否则您将被锁定):</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="提交" 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>错误: { error[0] }请重新加载页面并重试</p> : <p></p> }
</div>
</form>
</div>

View File

@ -48,18 +48,19 @@ function Login() {
return (
<div id="login-form">
<h1>Login</h1>
<h1>登录</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>
<label htmlFor="password">数据迁移密钥:</label>
<input type="password" id="password" name="password" onChange={(event) => {password[1](event.target.value)}} />
<input type="submit" value="Submit" onClick={handleSubmit}/>
<input type="submit" value="提交" onClick={handleSubmit}/>
<div id="sub_div">
<button onClick={import_user}>Import User</button><br/><br/>
<button onClick={help}>Need help?</button><br/><br/>
<button onClick={import_user}>导入用户数据</button><br/><br/>
<button onClick={help}>需要帮助</button><br/><br/>
{ error[0] ? <p>Error: { error[0] } </p> : <p></p> }
</div>
<p>EW 版本号 1.0.1 - <a href="https://git.zhushenwudi.top/zhushenwudi/ew">查看源代码</a> - <a href="https://git.zhushenwudi.top/zhushenwudi/ew/src/branch/main/LICENSE">查看许可证</a></p>
</form>
</div>
);