add api stuff

This commit is contained in:
GEEKiDoS
2024-04-11 15:20:36 +08:00
parent d02979f410
commit f427252a5e
18 changed files with 1194 additions and 1159 deletions
+84
View File
@@ -11,6 +11,8 @@
"dependencies": {
"@cordisjs/logger": "^0.1.3",
"@kamyu/kbinxml": "^2.0.2",
"@koa/cors": "^5.0.0",
"@koa/router": "^12.0.1",
"arc4": "^2.1.4",
"dedent-js": "^1.0.1",
"dotenv": "^16.4.5",
@@ -27,6 +29,8 @@
"devDependencies": {
"@types/jest": "~29.5",
"@types/koa": "^2.13.12",
"@types/koa__cors": "^5.0.0",
"@types/koa__router": "^12.0.4",
"@types/koa-bodyparser": "^4.3.12",
"@types/lodash": "^4.14.202",
"@types/node": "~20",
@@ -1744,6 +1748,55 @@
"resolved": "https://registry.npmmirror.com/@kamyu/kbinxml/-/kbinxml-2.0.2.tgz",
"integrity": "sha512-41ZiqAunpiwF0yu4LtWcYyF4BIhRGrOPMRVe4eAXWQ1KRL1cZI0+XGqnQIIxlhTcfBMDMLUnrmqpXRAh1sxhPQ=="
},
"node_modules/@koa/cors": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/@koa/cors/-/cors-5.0.0.tgz",
"integrity": "sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==",
"dependencies": {
"vary": "^1.1.2"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@koa/router": {
"version": "12.0.1",
"resolved": "https://registry.npmmirror.com/@koa/router/-/router-12.0.1.tgz",
"integrity": "sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==",
"dependencies": {
"debug": "^4.3.4",
"http-errors": "^2.0.0",
"koa-compose": "^4.1.0",
"methods": "^1.1.2",
"path-to-regexp": "^6.2.1"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/@koa/router/node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/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/@koa/router/node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/@mongodb-js/saslprep": {
"version": "1.1.5",
"resolved": "https://registry.npmmirror.com/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz",
@@ -2047,6 +2100,24 @@
"@types/node": "*"
}
},
"node_modules/@types/koa__cors": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/@types/koa__cors/-/koa__cors-5.0.0.tgz",
"integrity": "sha512-LCk/n25Obq5qlernGOK/2LUwa/2YJb2lxHUkkvYFDOpLXlVI6tKcdfCHRBQnOY4LwH6el5WOLs6PD/a8Uzau6g==",
"dev": true,
"dependencies": {
"@types/koa": "*"
}
},
"node_modules/@types/koa__router": {
"version": "12.0.4",
"resolved": "https://registry.npmmirror.com/@types/koa__router/-/koa__router-12.0.4.tgz",
"integrity": "sha512-Y7YBbSmfXZpa/m5UGGzb7XadJIRBRnwNY9cdAojZGp65Cpe5MAP3mOZE7e3bImt8dfKS4UFcR16SLH8L/z7PBw==",
"dev": true,
"dependencies": {
"@types/koa": "*"
}
},
"node_modules/@types/koa-bodyparser": {
"version": "4.3.12",
"resolved": "https://registry.npmmirror.com/@types/koa-bodyparser/-/koa-bodyparser-4.3.12.tgz",
@@ -5332,6 +5403,14 @@
"node": ">= 8"
}
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
@@ -5704,6 +5783,11 @@
"node": "14 || >=16.14"
}
},
"node_modules/path-to-regexp": {
"version": "6.2.2",
"resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz",
"integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw=="
},
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+4
View File
@@ -9,6 +9,8 @@
"devDependencies": {
"@types/jest": "~29.5",
"@types/koa": "^2.13.12",
"@types/koa__cors": "^5.0.0",
"@types/koa__router": "^12.0.4",
"@types/koa-bodyparser": "^4.3.12",
"@types/lodash": "^4.14.202",
"@types/node": "~20",
@@ -45,6 +47,8 @@
"dependencies": {
"@cordisjs/logger": "^0.1.3",
"@kamyu/kbinxml": "^2.0.2",
"@koa/cors": "^5.0.0",
"@koa/router": "^12.0.1",
"arc4": "^2.1.4",
"dedent-js": "^1.0.1",
"dotenv": "^16.4.5",
+13
View File
@@ -0,0 +1,13 @@
import Router from "@koa/router";
import { p2dRoutes } from "./p2d/index.js";
import { DefaultContext, DefaultState, ParameterizedContext } from "koa";
import { ILaochanContext } from "../types.js";
const router = new Router({
prefix: '/api',
})
.use(p2dRoutes);
export const apiRoutes = router.routes();
export type Context = ParameterizedContext<DefaultState, ILaochanContext & Router.RouterParamContext<DefaultState, DefaultContext>, any>;
+68
View File
@@ -0,0 +1,68 @@
import { Logger } from "@cordisjs/logger";
import Router from "@koa/router";
import { Context } from "../index.js";
import { UserService } from "../../services/p2d/user.js";
import { PlayerCustomizeSetting } from "../../database/index.js";
const logger = new Logger('p2d-api');
const router = new Router({
prefix: '/p2d',
})
.get('/pdata/:token', async (ctx: Context) => {
const { token } = ctx.params;
if (!token) {
ctx.status = 400;
return;
}
logger.info('request pdata %s', token);
const pdata = await ctx.resolve(UserService).getPDataDecoded(token);
ctx.status = pdata ? 200 : 404;
ctx.body = pdata ? pdata : {};
})
.get('/customize/:token', async (ctx: Context) => {
const { token } = ctx.params;
if (!token) {
ctx.status = 400;
return;
}
logger.info('request customize %s', token);
const customize = await ctx.resolve(UserService).getCustomizeSetting(token);
ctx.status = 200;
ctx.body = {
customize,
};
})
.put('/customize/:token', async (ctx: Context) => {
const { token } = ctx.params;
if (!token) {
ctx.status = 400;
return;
}
logger.info('set customize %s', token);
const customize = ctx.request.body as PlayerCustomizeSetting;
if (!customize) {
ctx.status = 400;
return;
}
customize._id = token;
await ctx.resolve(UserService).upsertCustomizeSetting(customize);
ctx.status = 200;
ctx.body = { success: true };
});
export const p2dRoutes = router.routes();
@@ -1,555 +1,316 @@
{
"customize": {
"2": [
"I1100000",
"I1100001",
"I1100002",
"I1100003",
"I1100004",
"I1100005",
"I1100006",
"I1100007",
"I1100008",
"I1100009",
"I1100010",
"I1100011",
"I1100012",
"I1100013",
"I1100014",
"I1100015",
"I1100016",
"I1100017",
"I1100018",
"I1100019",
"I1100020"
],
"3": [
"I1200000",
"I1200001",
"I1200002",
"I1200003",
"I1200004",
"I1200005",
"I1200006",
"I1200007",
"I1200008",
"I1200009",
"I1200010",
"I1200011",
"I1200012",
"I1200013",
"I1200014",
"I1200015",
"I1200016",
"I1200017",
"I1200018",
"I1200019",
"I1200020",
"I1200021"
],
"4": [
"I1300000",
"I1300001",
"I1300002",
"I1300003",
"I1300004",
"I1300005",
"I1300006",
"I1300007",
"I1300008",
"I1300009",
"I1300010",
"I1300011",
"I1300012",
"I1300013",
"I1300014",
"I1300015",
"I1300016",
"I1300017",
"I1300018",
"I1300019",
"I1300020",
"I1300021",
"I1300022",
"I1300023",
"I1300024",
"I1300025",
"I1300026",
"I1300027",
"I1300028",
"I1300029",
"I1300030",
"I1300031",
"I1300032",
"I1300033",
"I1300034",
"I1300035",
"I1300036",
"I1300037",
"I1300038",
"I1300039",
"I1300040",
"I1300041",
"I1300042",
"I1300043",
"I1300044",
"I1300045",
"I1300046",
"I1300047",
"I1300048",
"I1300049",
"I1300050",
"I1300051",
"I1300052",
"I1300053",
"I1300054",
"I1300055",
"I1300056",
"I1300057",
"I1300058",
"I1300059",
"I1300060",
"I1300061",
"I1300062",
"I1300063",
"I1300064",
"I1300065",
"I1300066",
"I1300067",
"I1300068",
"I1300069",
"I1300070",
"I1300071",
"I1300072",
"I1300073",
"I1300074",
"I1300075",
"I1300076",
"I1300077",
"I1300078",
"I1300079",
"I1300080",
"I1300081",
"I1300082",
"I1300083",
"I1300084",
"I1300085",
"I1300086",
"I1300087",
"I1300088",
"I1300089",
"I1300090",
"I1300091",
"I1300092",
"I1300093",
"I1300094",
"I1300095",
"I1300096",
"I1300097",
"I1300098",
"I1300099",
"I1300100",
"I1300101",
"I1300102",
"I1300103",
"I1300104",
"I1300105",
"I1300106",
"I1300107",
"I1300108",
"I1300109",
"I1300110",
"I1300111",
"I1300112",
"I1300113",
"I1300114",
"I1300115",
"I1300116",
"I1300117",
"I1300118",
"I1300119",
"I1300120",
"I1300121",
"I1300122",
"I1300123",
"I1300124",
"I1300125",
"I1300126",
"I1300127",
"I1300128",
"I1300129",
"I1300130",
"I1300131",
"I1300132",
"I1300133",
"I1300134",
"I1300135",
"I1300136",
"I1300137",
"I1300138",
"I1300139",
"I1300140",
"I1300141",
"I1300142",
"I1300143",
"I1300144",
"I1300145",
"I1300146",
"I1300147",
"I1300148",
"I1300149",
"I1300150",
"I1300151",
"I1300152",
"I1300153",
"I1300154",
"I1300155",
"I1300156",
"I1300157",
"I1300158",
"I1300159",
"I1300160",
"I1300161",
"I1300162",
"I1300163",
"I1300164",
"I1300165",
"I1300166",
"I1300167",
"I1300168",
"I1300169",
"I1300170",
"I1300171",
"I1300172",
"I1300173",
"I1300174",
"I1300175",
"I1300176",
"I1300177",
"I1300178",
"I1300179",
"I1300180",
"I1300181",
"I1300182",
"I1300183",
"I1300184",
"I1300185",
"I1300186"
],
"5": [
"I1400000",
"I1400001",
"I1400002",
"I1400003",
"I1400004",
"I1400005",
"I1400006",
"I1400007",
"I1400008",
"I1400009",
"I1400010",
"I1400011",
"I1400012",
"I1400013",
"I1400014"
],
"6": [
"I1500000",
"I1500001",
"I1500002",
"I1500003",
"I1500004",
"I1500005",
"I1500006",
"I1500007",
"I1500008",
"I1500009",
"I1500010",
"I1500011"
],
"7": [
"I1600000",
"I1600001",
"I1600002",
"I1600003",
"I1600004",
"I1600005",
"I1600006",
"I1600007",
"I1600008",
"I1600009",
"I1600010",
"I1600011",
"I1600012",
"I1600013",
"I1600014",
"I1600015",
"I1600016",
"I1600017",
"I1600018"
],
"8": [
"I1700000",
"I1700001",
"I1700002",
"I1700003",
"I1700004",
"I1700005",
"I1700006",
"I1700007",
"I1700008",
"I1700009",
"I1700010",
"I1700011"
],
"11": [
"I1300000",
"I1300001",
"I1300002",
"I1300003",
"I1300004",
"I1300005",
"I1300006",
"I1300007",
"I1300008",
"I1300009",
"I1300010",
"I1300011",
"I1300012",
"I1300013",
"I1300014",
"I1300015",
"I1300016",
"I1300017",
"I1300018",
"I1300019",
"I1300020",
"I1300021",
"I1300022",
"I1300023",
"I1300024",
"I1300025",
"I1300026",
"I1300027",
"I1300028",
"I1300029",
"I1300030",
"I1300031",
"I1300032",
"I1300033",
"I1300034",
"I1300035",
"I1300036",
"I1300037",
"I1300038",
"I1300039",
"I1300040",
"I1300041",
"I1300042",
"I1300043",
"I1300044",
"I1300045",
"I1300046",
"I1300047",
"I1300048",
"I1300049",
"I1300050",
"I1300051",
"I1300052",
"I1300053",
"I1300054",
"I1300055",
"I1300056",
"I1300057",
"I1300058",
"I1300059",
"I1300060",
"I1300061",
"I1300062",
"I1300063",
"I1300064",
"I1300065",
"I1300066",
"I1300067",
"I1300068",
"I1300069",
"I1300070",
"I1300071",
"I1300072",
"I1300073",
"I1300074",
"I1300075",
"I1300076",
"I1300077",
"I1300078",
"I1300079",
"I1300080",
"I1300081",
"I1300082",
"I1300083",
"I1300084",
"I1300085",
"I1300086",
"I1300087",
"I1300088",
"I1300089",
"I1300090",
"I1300091",
"I1300092",
"I1300093",
"I1300094",
"I1300095",
"I1300096",
"I1300097",
"I1300098",
"I1300099",
"I1300100",
"I1300101",
"I1300102",
"I1300103",
"I1300104",
"I1300105",
"I1300106",
"I1300107",
"I1300108",
"I1300109",
"I1300110",
"I1300111",
"I1300112",
"I1300113",
"I1300114",
"I1300115",
"I1300116",
"I1300117",
"I1300118",
"I1300119",
"I1300120",
"I1300121",
"I1300122",
"I1300123",
"I1300124",
"I1300125",
"I1300126",
"I1300127",
"I1300128",
"I1300129",
"I1300130",
"I1300131",
"I1300132",
"I1300133",
"I1300134",
"I1300135",
"I1300136",
"I1300137",
"I1300138",
"I1300139",
"I1300140",
"I1300141",
"I1300142",
"I1300143",
"I1300144",
"I1300145",
"I1300146",
"I1300147",
"I1300148",
"I1300149",
"I1300150",
"I1300151",
"I1300152",
"I1300153",
"I1300154",
"I1300155",
"I1300156",
"I1300157",
"I1300158",
"I1300159",
"I1300160",
"I1300161",
"I1300162",
"I1300163",
"I1300164",
"I1300165",
"I1300166",
"I1300167",
"I1300168",
"I1300169",
"I1300170",
"I1300171",
"I1300172",
"I1300173",
"I1300174",
"I1300175",
"I1300176",
"I1300177",
"I1300178",
"I1300179",
"I1300180",
"I1300181",
"I1300182",
"I1300183",
"I1300184",
"I1300185",
"I1300186"
],
"10": [
"I1900000",
"I1900001",
"I1900002",
"I1900003",
"I1900004",
"I1900005",
"I1900006",
"I1900007",
"I1900008",
"I1900009",
"I1900010",
"I1900011",
"I1900012",
"I1900013",
"I1900014",
"I1900015",
"I1900016",
"I1900017",
"I1900018",
"I1900019",
"I1900020",
"I1900021",
"I1900022"
]
},
"other_customize": {
"1": [
"C1000000",
"C1000001",
"C1000002"
],
"2": [
"C1100000",
"C1100001",
"C1100002",
"C1100003",
"C1100004",
"C1100005",
"C1100006"
],
"3": [
"C1200000",
"C1200001"
],
"4": [
"C1300000",
"C1300001",
"C1300002",
"C1300003"
],
"5": [
"C1400000",
"C1400001",
"C1400002",
"C1400003"
]
},
"items": [
"I1700000",
"I1700001",
"I1700002",
"I1700003",
"I1700004",
"I1700005",
"I1700006",
"I1700007",
"I1700008",
"I1700009",
"I1700010",
"I1700011",
"I1500000",
"I1500001",
"I1500002",
"I1500003",
"I1500004",
"I1500005",
"I1500006",
"I1500007",
"I1500008",
"I1500009",
"I1500010",
"I1500011",
"I1400000",
"I1400001",
"I1400002",
"I1400003",
"I1400004",
"I1400005",
"I1400006",
"I1400007",
"I1400008",
"I1400009",
"I1400010",
"I1400011",
"I1400012",
"I1400013",
"I1400014",
"I1300000",
"I1300001",
"I1300002",
"I1300003",
"I1300004",
"I1300005",
"I1300006",
"I1300007",
"I1300008",
"I1300009",
"I1300010",
"I1300011",
"I1300012",
"I1300013",
"I1300014",
"I1300015",
"I1300016",
"I1300017",
"I1300018",
"I1300019",
"I1300020",
"I1300021",
"I1300022",
"I1300023",
"I1300024",
"I1300025",
"I1300026",
"I1300027",
"I1300028",
"I1300029",
"I1300030",
"I1300031",
"I1300032",
"I1300033",
"I1300034",
"I1300035",
"I1300036",
"I1300037",
"I1300038",
"I1300039",
"I1300040",
"I1300041",
"I1300042",
"I1300043",
"I1300044",
"I1300045",
"I1300046",
"I1300047",
"I1300048",
"I1300049",
"I1300050",
"I1300051",
"I1300052",
"I1300053",
"I1300054",
"I1300055",
"I1300056",
"I1300057",
"I1300058",
"I1300059",
"I1300060",
"I1300061",
"I1300062",
"I1300063",
"I1300064",
"I1300065",
"I1300066",
"I1300067",
"I1300068",
"I1300069",
"I1300070",
"I1300071",
"I1300072",
"I1300073",
"I1300074",
"I1300075",
"I1300076",
"I1300077",
"I1300078",
"I1300079",
"I1300080",
"I1300081",
"I1300082",
"I1300083",
"I1300084",
"I1300085",
"I1300086",
"I1300087",
"I1300088",
"I1300089",
"I1300090",
"I1300091",
"I1300092",
"I1300093",
"I1300094",
"I1300095",
"I1300096",
"I1300097",
"I1300098",
"I1300099",
"I1300100",
"I1300101",
"I1300102",
"I1300103",
"I1300104",
"I1300105",
"I1300106",
"I1300107",
"I1300108",
"I1300109",
"I1300110",
"I1300111",
"I1300112",
"I1300113",
"I1300114",
"I1300115",
"I1300116",
"I1300117",
"I1300118",
"I1300119",
"I1300120",
"I1300121",
"I1300122",
"I1300123",
"I1300124",
"I1300125",
"I1300126",
"I1300127",
"I1300128",
"I1300129",
"I1300130",
"I1300131",
"I1300132",
"I1300133",
"I1300134",
"I1300135",
"I1300136",
"I1300137",
"I1300138",
"I1300139",
"I1300140",
"I1300141",
"I1300142",
"I1300143",
"I1300144",
"I1300145",
"I1300146",
"I1300147",
"I1300148",
"I1300149",
"I1300150",
"I1300151",
"I1300152",
"I1300153",
"I1300154",
"I1300155",
"I1300156",
"I1300157",
"I1300158",
"I1300159",
"I1300160",
"I1300161",
"I1300162",
"I1300163",
"I1300164",
"I1300165",
"I1300166",
"I1300167",
"I1300168",
"I1300169",
"I1300170",
"I1300171",
"I1300172",
"I1300173",
"I1300174",
"I1300175",
"I1300176",
"I1300177",
"I1300178",
"I1300179",
"I1300180",
"I1300181",
"I1300182",
"I1300183",
"I1300184",
"I1300185",
"I1300186",
"I1200000",
"I1200001",
"I1200002",
"I1200003",
"I1200004",
"I1200005",
"I1200006",
"I1200007",
"I1200008",
"I1200009",
"I1200010",
"I1200011",
"I1200012",
"I1200013",
"I1200014",
"I1200015",
"I1200016",
"I1200017",
"I1200018",
"I1200019",
"I1200020",
"I1200021",
"I1100000",
"I1100001",
"I1100002",
"I1100003",
"I1100004",
"I1100005",
"I1100006",
"I1100007",
"I1100008",
"I1100009",
"I1100010",
"I1100011",
"I1100012",
"I1100013",
"I1100014",
"I1100015",
"I1100016",
"I1100017",
"I1100018",
"I1100019",
"I1100020",
"I1900000",
"I1900001",
"I1900002",
"I1900003",
"I1900004",
"I1900005",
"I1900006",
"I1900007",
"I1900008",
"I1900009",
"I1900010",
"I1900011",
"I1900012",
"I1900013",
"I1900014",
"I1900015",
"I1900016",
"I1900017",
"I1900018",
"I1900019",
"I1900020",
"I1900021",
"I1900022",
"I1600000",
"I1600001",
"I1600002",
"I1600003",
"I1600004",
"I1600005",
"I1600006",
"I1600007",
"I1600008",
"I1600009",
"I1600010",
"I1600011",
"I1600012",
"I1600013",
"I1600014",
"I1600015",
"I1600016",
"I1600017",
"I1600018",
"I1000000",
"I1000001",
"I1800001",
@@ -4,7 +4,7 @@ import { Service } from "./service.js";
import { User } from "./user.js";
import data from './data.json' with { type: "json" };
import { inject, singleton } from "tsyringe";
import { Db } from "mongodb";
import { UserService } from "../../services/p2d/user.js";
function Combine(...modules: ({ new(): any })[]) {
class Modules {
@@ -31,11 +31,11 @@ function Combine(...modules: ({ new(): any })[]) {
@singleton()
export default class extends Combine(Service, User, Game) {
constructor(
@inject(Db) private readonly db: Db,
@inject(UserService) private readonly userService: UserService,
) {
super();
this.db;
this.userService;
}
}
+315
View File
@@ -0,0 +1,315 @@
import { eacnet } from "../../decorators/eacnet.js";
import { Context } from "../../types.js";
import { v } from "../../utils/kxml-value.js";
import { ITEM_LIST, MUSIC_LIST } from "./index.js";
import { PlayerPlayLog } from "../../database/index.js";
import { fromToken } from "../../utils/laochan-id.js";
import { UserService } from "../../services/p2d/user.js";
export class User {
userService: UserService;
@eacnet('p2d')
async sendGradeCertificationLog(ctx: Context) {
await this.userService.addCourseLog({
player: ctx.token,
...ctx.body,
});
return {
status: v.s32(0),
error: v.s32(0),
}
}
@eacnet('p2d')
async consumeItem() {
return {
status: v.s32(0),
error: v.s32(0),
result: {
free_count: v.s32(1000),
not_free_count: v.s32(1000),
}
};
}
@eacnet('p2d')
async reserveConsumeItem() {
return {
status: v.s32(0),
error: v.s32(0),
};
}
@eacnet('p2d')
async getCustomizeSetting(ctx: Context) {
const { customize, other_customize } = await this.userService.getCustomizeSetting(ctx.token);
return {
status: v.s32(0),
error: v.s32(0),
result: {
customize: customize.map(c => ({
item_category: v.s32(c.item_category),
item_id: v.str(c.item_id),
})),
other_customize: other_customize.map(c => ({
item_category: v.s32(c.item_category),
item_id: v.str(c.item_id),
})),
}
}
}
@eacnet('p2d')
async gameEnd() {
return {
status: v.s32(0),
error: v.s32(0),
}
}
@eacnet('p2d')
async getPointList() {
return {
status: v.s32(0),
error: v.s32(0),
result: {
point_count: v.s32(1),
point: [{
point_id: v.str('P0100000'),
point_num: v.u32(114514),
}]
}
}
}
@eacnet('p2d')
async getPrivilegeClient() {
return {
status: v.s32(0),
error: v.s32(0),
result: {
list_num: v.s32(0),
}
}
}
@eacnet('p2d')
async getPrivilegeServer() {
return {
status: v.s32(0),
error: v.s32(0),
result: {
list_num: v.s32(0),
}
}
}
@eacnet('p2d')
async reportMusicResult(ctx: Context) {
const result = ctx.body as PlayerPlayLog;
result.player = ctx.token;
let updateTask = Promise.resolve();
const play_style = Math.floor(result.note_id / 5);
const diff = result.note_id % 5;
updateTask = (async () => {
const musicData = await this.userService.getMusicData(result.player, result.music_id, play_style);
if (result.clear_flag > musicData.clear_flag[diff])
musicData.clear_flag[diff] = result.clear_flag;
if (result.miss_count < musicData.miss_count[diff])
musicData.miss_count[diff] = result.miss_count;
if (result.score > musicData.score[diff])
musicData.score[diff] = result.score;
if (result.clear_flag >= 2)
musicData.clear_num[diff]++;
musicData.play_num[diff]++;
this.userService.upsertMusicData(musicData);
})();
await Promise.all([
this.userService.addPlayLog(result),
updateTask,
])
return {
status: v.s32(0),
error: v.s32(0),
};
}
@eacnet('p2d')
async savePlayData(ctx: Context) {
const { pdata, check_sum } = ctx.body as { pdata: Buffer, check_sum: string };
await this.userService.upsertPDataBinary(ctx.token, pdata, check_sum);
return {
status: v.s32(0),
error: v.s32(0),
}
}
@eacnet('p2d')
async getPlayData(ctx: Context) {
const result = await this.userService.getPDataBinary(ctx.token);
if (!result) {
return {
status: v.s32(1),
error: v.s32(404),
}
}
return {
status: v.s32(0),
error: v.s32(0),
result: {
size: v.s32(result.pdata.length),
rest_size: v.s32(0),
check_sum: v.str(result.check_sum),
pdata: v.bin(result.pdata),
}
}
}
@eacnet('p2d')
async registPlayer(ctx: Context) {
const { pdata, check_sum } = ctx.body as { pdata: Buffer, check_sum: string };
await this.userService.upsertPDataBinary(ctx.token, pdata, check_sum);
return {
status: v.s32(0),
error: v.s32(0),
}
}
@eacnet('p2d')
async checkPlayData(ctx: Context) {
const { check_sum } = ctx.body as { check_sum: string };
const checksum = await this.userService.getPDataChecksum(ctx.token);
const id = fromToken(ctx.token);
if (!checksum) {
return {
status: v.s32(0),
error: v.s32(0),
result: {
infinitas_id: v.str(id),
valid: v.bool(false),
is_exist: v.bool(false),
}
};
}
if (checksum !== check_sum) {
return {
status: v.s32(0),
error: v.s32(0),
result: {
infinitas_id: v.str(id),
valid: v.bool(false),
is_exist: v.bool(true),
}
};
}
return {
status: v.s32(0),
error: v.s32(0),
result: {
infinitas_id: v.str(id),
valid: v.bool(true),
is_exist: v.bool(true),
}
};
}
@eacnet('p2d')
async getRivalInfo() {
return {
status: v.s32(0),
error: v.s32(0),
result: {},
}
}
@eacnet('p2d')
async getMusicList() {
return {
status: v.s32(0),
error: v.s32(0),
result: {
status: v.s32(0),
check_sum: v.str('2e49eba0b6ca8b6898f5d684ea0c3bc1ed6f1d572a4d308cf32727d64069a588'),
music_list: {
music_num: v.s32(MUSIC_LIST.length),
music: MUSIC_LIST,
}
},
}
}
@eacnet('p2d')
async getCompeScoreData() {
return {
status: v.s32(0),
error: v.s32(0),
result: {},
}
}
@eacnet('p2d')
async getMusicData(ctx: Context) {
const { play_style } = ctx.body as { play_style: number };
const musicDatas = await this.userService.getMusicDatas(ctx.token, play_style);
return {
status: v.s32(0),
error: v.s32(0),
play_style: v.s32(play_style),
result: {
music: musicDatas.map(data => ({
music_id: v.s32(data.music_id),
score: v.s32(data.score),
clear_flag: v.s32(data.clear_flag),
miss_count: v.s32(data.miss_count),
play_num: v.s32(data.play_num),
clear_num: v.s32(data.clear_num),
}))
},
}
}
@eacnet('p2d')
async getItemList() {
return {
status: v.s32(0),
error: v.s32(0),
result: {
item_list: {
item_num: v.s32(ITEM_LIST.length),
item: ITEM_LIST.map(id => ({
item_id: v.str(id),
not_free_count: v.s32(1000),
free_count: v.s32(1000),
}))
}
}
};
}
}
+12
View File
@@ -76,3 +76,15 @@ export interface CourseResult {
clear_rate: number;
groove_gage: number;
}
export interface PlayerCustomizeSetting {
_id: string;
customize: {
item_id: string,
item_category: number,
}[];
other_customize: {
item_id: string,
item_category: number,
}[];
}
+2 -149
View File
@@ -1,130 +1,12 @@
import _ from 'lodash';
import { to_bin } from '@kamyu/kbinxml';
import { LZ77 } from '../utils/lz77.js';
function serializeValue(value: any, type: string): string {
if (
[
's8',
'u8',
's16',
'u16',
's32',
'u32',
's64',
'u64',
'float',
'double',
].includes(type)
) {
return value.toString();
}
if (['b', 'bool'].includes(type)) {
return value ? '1' : '0';
}
if (['bin', 'binary'].includes(type)) {
if (!(value instanceof Buffer)) {
throw new Error('binary with non-buffer value');
}
return value.toString('hex');
}
if (['ip4', 'str', 'string'].includes(type)) {
return value;
}
if (type == 'time') {
if (value instanceof Date) {
return Math.floor(value.valueOf() / 1000).toString();
}
if (typeof value === 'number') {
return value.toString();
}
throw new Error('time with not date or number');
}
throw new Error(`unsupported type ${type}`);
}
function serializeObject(obj: Object, name: string, linePrefix: string = '') {
let output = `${linePrefix}<${name}`;
const entries = Object.entries(obj);
const attrs = entries
.filter(v => v[0].startsWith('$'))
.map(v => [v[0].substring(1), v[1]]);
// handle kxml value shit
const value = entries.find(v => v[0] == '__value');
if (value && value[1] instanceof Array) {
attrs.push(['__count', value[1].length]);
}
if (value && value[1] instanceof Buffer) {
attrs.push(['__size', value[1].length]);
}
// the kbinxml library had issue when serializing non-ordered stuff
attrs.sort((a, b) => a[0] < b[0] ? 1 : (a[0] > b[0] ? -1 : 0));
for (const attr of attrs) {
output += ` ${attr[0]}="${_.escape(attr[1])}"`;
}
output += '>';
if (value) {
const type = attrs.find(v => v[0] == '__type');
if (!type) {
// or normal xml innerXml?
throw new Error(`value with no type in ${name}`);
}
output += (value[1] instanceof Array ? value[1] : [value[1]])
.map(v => serializeValue(v, type[1]))
.join(' ');
} else {
const elements = entries
.filter(v => !v[0].startsWith('$'))
.sort((a, b) => a[0] < b[0] ? 1 : (a[0] > b[0] ? -1 : 0));
if (elements.length) {
output += '\n';
for (const element of elements) {
if (element[1] instanceof Array) {
for (const item of element[1])
output += serializeObject(item, element[0], linePrefix + ' ');
continue;
}
output += serializeObject(element[1], element[0], linePrefix + ' ');
}
output += linePrefix;
}
}
return output + `</${name}>\n`;
}
import { toKBinXml } from '../utils/kbinxml.js';
export function eacnet(topName: string, encoding: 'UTF-8' | 'SHIFT_JIS' = 'UTF-8'): MethodDecorator {
return function (_target, _propertyKey, descriptor) {
const orig = descriptor.value as Function;
descriptor.value = async function (...args: any[]) {
const obj = await orig.apply(this, args);
const xml =
`<?xml version="1.0" encoding="${encoding}"?>` + serializeObject(obj, topName);
const kxml = to_bin(xml);
const kxml = toKBinXml(topName, obj, encoding);
const compressed = LZ77.compress(kxml.data);
const result = Buffer.allocUnsafe(compressed.length + 0x2E);
@@ -135,32 +17,3 @@ export function eacnet(topName: string, encoding: 'UTF-8' | 'SHIFT_JIS' = 'UTF-8
} as unknown as any;
}
}
// console.log(serializeObject({
// $attr: 'attr1',
// sb: {
// $attr: 'attr2',
// $__type: 'str',
// __value: 'sba',
// },
// sb1: {
// $attr: 'attr3',
// nested: {
// $attr: 'attr4',
// $__type: 's32',
// __value: [1, 1, 4, 5, 1, 4],
// }
// },
// array: [{
// $attr: 'test'
// }, {
// $attr: 'test2'
// }, {
// $attr: 'test3'
// }, {
// $attr: 'test4'
// }, {
// $attr: 'test5'
// }]
// }, 'test'))
+24 -17
View File
@@ -1,15 +1,17 @@
import 'reflect-metadata';
import cors from '@koa/cors';
import Koa, { DefaultState } from 'koa';
import bodyParser from 'koa-bodyparser';
import { InjectionToken, Provider, container } from 'tsyringe';
import { ILaochanContext } from './types.js';
import { DefaultService } from './services/default.js';
import { DefaultService } from './controllers/default.js';
import { Logger } from '@cordisjs/logger';
import { eacnet } from './middlewares/eacnet.js';
import config from './utils/config.js';
import { MongoClient, Db } from 'mongodb';
import { apiRoutes } from './apis/index.js';
function tryResolve<T>(token: InjectionToken<T>): T | undefined {
if (!container.isRegistered(token)) return undefined;
@@ -57,21 +59,22 @@ async function main(): Promise<void> {
ctx.body = `${e.stack ?? 'Internal Error'}`;
}
})
.use(cors())
.use(bodyParser({
formLimit: '1mb',
}))
.use(eacnet)
.use(async (ctx, next) => {
if (!ctx.service) {
return await next();
}
ctx.logger = ctx.service
? register(`logger:${ctx.service.name}:${ctx.service.method}`, {
useValue: new Logger(`${ctx.service.name}:${ctx.service.method}`),
})
: logger;
if (!ctx.service) {
return await next();
}
let service: object | undefined = tryResolve(ctx.service.name);
if (service === undefined) {
@@ -80,17 +83,17 @@ async function main(): Promise<void> {
try {
module = await import(
`./services/${ctx.service.name}/index.js`
`./controllers/${ctx.service.name}/index.js`
);
} catch (e) {
if (e.code != 'ERR_MODULE_NOT_FOUND') {
logger.error(
ctx.logger.error(
`load service error %s`,
JSON.stringify(e, Object.getOwnPropertyNames(e), 2),
);
} else {
module = await import(
`./services/${ctx.service.name}.js`
`./controllers/${ctx.service.name}.js`
);
}
}
@@ -100,19 +103,17 @@ async function main(): Promise<void> {
});
} catch (e) {
if (e.code != 'ERR_MODULE_NOT_FOUND') {
logger.error(
`load service error %s`,
ctx.logger.error(
`load controller error %s`,
JSON.stringify(e, Object.getOwnPropertyNames(e), 2),
);
}
}
}
logger.info(
'%s %s.%s [%s]: request = %s',
ctx.logger.info(
'%s [%s]: request = %s',
ctx.url,
ctx.service.name,
ctx.service.method,
ctx.token,
JSON.stringify(ctx.body, (key, value) => {
if (key === 'pdata') {
@@ -126,7 +127,7 @@ async function main(): Promise<void> {
let method = ctx.service.method;
if (!service || !(service[method])?.bind) {
logger.warn(`unimplemented method ${method} in ${ctx.service.name}`);
ctx.logger.warn(`unimplemented method.`);
service = container.resolve(DefaultService);
method = 'default';
@@ -135,13 +136,19 @@ async function main(): Promise<void> {
ctx.body = await (service[method] as Function).bind(service)(ctx);
ctx.status = 200;
logger.info('%s.%s [server]: status = %d', ctx.service.name, ctx.service.method, ctx.status);
ctx.logger.info('[server]: status = %d', ctx.status);
})
.use(apiRoutes)
.use(async (ctx, next) => {
ctx.body = 'Laochan-Eacnet is running.';
if (!ctx.body) {
ctx.body = 'Laochan-Eacnet is running.';
}
return next();
});
app.context.resolve = container.resolve.bind(container);
logger.info('listening on %d', config.port);
app.listen(config.port);
}
+13 -105
View File
@@ -1,103 +1,7 @@
import { Next } from 'koa';
import { Context } from '../types.js';
import { LZ77 } from '../utils/lz77.js';
import { to_xml } from '@kamyu/kbinxml';
import { XMLParser } from 'fast-xml-parser';
const parser = new XMLParser({
ignoreAttributes: false,
parseAttributeValue: false,
attributeNamePrefix: '$',
removeNSPrefix: true,
numberParseOptions: {
hex: false,
leadingZeros: false,
eNotation: false,
skipLike: /.*/,
},
});
function parseValue(node: { $__type: string; $__count?: unknown }): any {
const isArray = '$__count' in node;
if (
[
's8',
'u8',
's16',
'u16',
's32',
'u32',
's64',
'u64',
].includes(node.$__type)
) {
if (isArray) {
return node['#text'].split(' ').map((v) => parseInt(v));
}
return parseInt(node['#text']);
}
if (['float', 'double'].includes(node.$__type)) {
if (isArray) {
return node['#text'].split(' ').map((v) => parseFloat(v));
}
return parseFloat(node['#text']);
}
if (['b', 'bool'].includes(node.$__type)) {
return !!parseInt(node['#text']);
}
if (['bin', 'binary'].includes(node.$__type)) {
return Buffer.from(node['#text'], 'hex');
}
if (['ip4', 'str', 'string'].includes(node.$__type)) {
return node['#text'];
}
if (node.$__type == 'time') {
return new Date(parseInt(node['#text']) * 1000);
}
return {
type: node.$__type,
value: node['#text'],
};
}
function kxmlToObject(node: Record<string, unknown> | unknown[] | string): any {
if (node instanceof Array) {
return node.map((v) => kxmlToObject(v as Record<string, unknown>));
}
if (typeof node === 'string') {
return {};
}
if ('$__type' in node) {
return parseValue(node as { $__type: string; $__count?: unknown });
}
const obj: object = {};
for (const key in node) {
if (key == '?xml') continue;
const value = node[key];
if (key.startsWith('$')) {
obj[key.substring(1)] = value;
continue;
}
obj[key] = kxmlToObject(node[key] as Record<string, unknown>);
}
return obj;
}
import { fromKBinXml } from '../utils/kbinxml.js';
export async function eacnet(ctx: Context, next: Next): Promise<any> {
const body = ctx.request.body as {
@@ -110,32 +14,36 @@ export async function eacnet(ctx: Context, next: Next): Promise<any> {
}
const { request, p2d_token: token } = body;
if (!request || !token) {
return next();
}
const buffer = Buffer.from(request
.replaceAll(' ', '+')
.replaceAll('-', '+')
.replaceAll('_', '/') + '===',
'base64'
);
const decoded = LZ77.decompress(buffer);
const xml = to_xml(decoded).data;
const parsedXml = parser.parse(xml);
const xmlResult = kxmlToObject(parsedXml);
const result = fromKBinXml(decoded);
const game = Object.keys(xmlResult)[0];
const game = Object.keys(result)[0];
if (!(game in xmlResult)) {
if (!(game in result)) {
return next();
}
if (xmlResult[game].params) {
ctx.body = xmlResult.p2d.params;
if (result[game].params) {
ctx.body = result.p2d.params;
} else {
ctx.body = {};
}
ctx.service = {
name: game,
method: xmlResult[game].method,
method: result[game].method,
}
ctx.token = token;
+113 -335
View File
@@ -1,13 +1,18 @@
import { Binary, Db } from "mongodb";
import { eacnet } from "../../decorators/eacnet.js";
import { Context } from "../../types.js";
import { v } from "../../utils/kxml-value.js";
import { ITEM_LIST, MUSIC_LIST } from "./index.js";
import { PlayerCourseLog, PlayerMusicData, PlayerPlayData, PlayerPlayLog } from "../../database/index.js";
import { fromToken } from "../../utils/laochan-id.js";
import { inject, injectable } from "tsyringe";
import { PlayerPlayData, PlayerMusicData, PlayerPlayLog, PlayerCourseLog, PlayerCustomizeSetting } from "../../database/index.js";
import { Pdata } from "../../database/pdata.js";
import { fromKBinXml } from "../../utils/kbinxml.js";
export class User {
db: Db;
@injectable()
export class UserService {
/**
*
*/
constructor(
@inject(Db)
private readonly db: Db,
) { }
get playDataCol() {
return this.db.collection<PlayerPlayData>('player_play_data');
@@ -25,360 +30,133 @@ export class User {
return this.db.collection<PlayerCourseLog>('player_course_log');
}
@eacnet('p2d')
async sendGradeCertificationLog(ctx: Context) {
await this.courseLogCol.insertOne({
player: ctx.token,
...ctx.body,
});
get customizeSettingCol() {
return this.db.collection<PlayerCustomizeSetting>('player_customize_setting');
}
addCourseLog(coruseLog: PlayerCourseLog) {
return this.courseLogCol.insertOne(coruseLog);
}
addPlayLog(playLog: PlayerPlayLog) {
return this.playLogCol.insertOne(playLog);
}
upsertCustomizeSetting(customizeSetting: PlayerCustomizeSetting) {
return this.customizeSettingCol.updateOne({ _id: customizeSetting._id }, {
$set: customizeSetting
}, { upsert: true });
}
async getCustomizeSetting(player: string): Promise<PlayerCustomizeSetting> {
const result = await this.customizeSettingCol.findOne({ _id: player });
if (result)
return result;
return {
status: v.s32(0),
error: v.s32(0),
_id: player,
customize: [
{ item_category: 2, item_id: 'I1100000' },
{ item_category: 3, item_id: 'I1200000' },
{ item_category: 4, item_id: 'I1300000' },
{ item_category: 5, item_id: 'I1400000' },
{ item_category: 6, item_id: 'I1500000' },
{ item_category: 7, item_id: 'I1600000' },
{ item_category: 8, item_id: 'I1700000' },
{ item_category: 11, item_id: 'I1300000' },
{ item_category: 10, item_id: 'I1900000' },
],
other_customize: [
{ item_category: 1, item_id: 'C1000000' },
{ item_category: 2, item_id: 'C1100000' },
{ item_category: 3, item_id: 'C1200000' },
{ item_category: 4, item_id: 'C1300000' },
// change default to very short
{ item_category: 5, item_id: 'C1400002' },
]
}
}
@eacnet('p2d')
async consumeItem() {
return {
status: v.s32(0),
error: v.s32(0),
result: {
free_count: v.s32(1000),
not_free_count: v.s32(1000),
}
};
}
@eacnet('p2d')
async reserveConsumeItem() {
return {
status: v.s32(0),
error: v.s32(0),
};
}
@eacnet('p2d')
async getCustomizeSetting() {
return {
status: v.s32(0),
error: v.s32(0),
result: {
customize: [
{ item_category: v.s32(2), item_id: v.str('I1100000') },
{ item_category: v.s32(3), item_id: v.str('I1200000') },
{ item_category: v.s32(4), item_id: v.str('I1300000') },
{ item_category: v.s32(5), item_id: v.str('I1400000') },
{ item_category: v.s32(6), item_id: v.str('I1500000') },
{ item_category: v.s32(7), item_id: v.str('I1600000') },
{ item_category: v.s32(8), item_id: v.str('I1700000') },
{ item_category: v.s32(11), item_id: v.str('I1300000') },
{ item_category: v.s32(10), item_id: v.str('I1900000') }
],
other_customize: [
{ item_category: v.s32(1), item_id: v.str('C1000000') },
{ item_category: v.s32(2), item_id: v.str('C1100000') },
{ item_category: v.s32(3), item_id: v.str('C1200000') },
{ item_category: v.s32(4), item_id: v.str('C1300000') },
{ item_category: v.s32(5), item_id: v.str('C1400000') }
],
}
}
}
@eacnet('p2d')
async gameEnd() {
return {
status: v.s32(0),
error: v.s32(0),
}
}
@eacnet('p2d')
async getPointList() {
return {
status: v.s32(0),
error: v.s32(0),
result: {
point_count: v.s32(1),
point: [{
point_id: v.str('P0100000'),
point_num: v.u32(114514),
}]
}
}
}
@eacnet('p2d')
async getPrivilegeClient() {
return {
status: v.s32(0),
error: v.s32(0),
result: {
list_num: v.s32(0),
}
}
}
@eacnet('p2d')
async getPrivilegeServer() {
return {
status: v.s32(0),
error: v.s32(0),
result: {
list_num: v.s32(0),
}
}
}
@eacnet('p2d')
async reportMusicResult(ctx: Context) {
const result = ctx.body as PlayerPlayLog;
result.player = ctx.token;
let updateTask = Promise.resolve();
const play_style = Math.floor(result.note_id / 5);
const diff = result.note_id % 5;
updateTask = (async () => {
const musicData = (await this.musicDataCol.findOne({
player: result.player,
music_id: result.music_id,
play_style: play_style
})) ?? {
player: result.player,
music_id: result.music_id,
play_style: play_style,
score: [0, 0, 0, 0, 0],
clear_flag: [0, 0, 0, 0, 0],
miss_count: [0, 0, 0, 0, 0],
play_num: [0, 0, 0, 0, 0],
clear_num: [0, 0, 0, 0, 0],
};
if (result.clear_flag > musicData.clear_flag[diff])
musicData.clear_flag[diff] = result.clear_flag;
if (result.miss_count < musicData.miss_count[diff])
musicData.miss_count[diff] = result.miss_count;
if (result.score > musicData.score[diff])
musicData.score[diff] = result.score;
if (result.clear_flag >= 2)
musicData.clear_num[diff]++;
musicData.play_num[diff]++;
await this.musicDataCol.updateOne({
player: result.player,
music_id: result.music_id,
play_style: play_style
}, {
$set: musicData
}, {
upsert: true
});
})();
await Promise.all([
this.playLogCol.insertOne(result),
updateTask,
])
return {
status: v.s32(0),
error: v.s32(0),
};
}
@eacnet('p2d')
async savePlayData(ctx: Context) {
const uid = ctx.token;
const { pdata, check_sum } = ctx.body as { pdata: Buffer, check_sum: string };
await this.playDataCol
.updateOne({ _id: uid }, {
$set: {
pdata: new Binary(pdata),
check_sum,
}
});
return {
status: v.s32(0),
error: v.s32(0),
}
}
@eacnet('p2d')
async getPlayData(ctx: Context) {
const uid = ctx.token;
const result = await this.playDataCol
.findOne({ _id: uid });
if (!result) {
return {
status: v.s32(1),
error: v.s32(404),
}
}
return {
status: v.s32(0),
error: v.s32(0),
result: {
size: v.s32(result.pdata.length()),
rest_size: v.s32(0),
check_sum: v.str(result.check_sum),
pdata: v.bin(result.pdata.buffer),
}
}
}
@eacnet('p2d')
async registPlayer(ctx: Context) {
const uid = ctx.token;
const { pdata, check_sum } = ctx.body as { pdata: Buffer, check_sum: string };
await this.playDataCol
.insertOne({ _id: uid, pdata: new Binary(pdata), check_sum });
return {
status: v.s32(0),
error: v.s32(0),
}
}
@eacnet('p2d')
async checkPlayData(ctx: Context) {
const { check_sum } = ctx.body as { check_sum: string };
const data = await this.playDataCol.findOne({ _id: ctx.token }, {
async getPDataChecksum(player: string): Promise<string | undefined> {
const result = await this.playDataCol.findOne({ _id: player }, {
projection: {
check_sum: 1,
}
});
const id = fromToken(ctx.token);
if (result)
return result.check_sum;
if (!data) {
return {
status: v.s32(0),
error: v.s32(0),
result: {
infinitas_id: v.str(id),
valid: v.bool(false),
is_exist: v.bool(false),
}
};
}
return undefined;
}
if (data.check_sum !== check_sum) {
return {
status: v.s32(0),
error: v.s32(0),
result: {
infinitas_id: v.str(id),
valid: v.bool(false),
is_exist: v.bool(true),
}
};
}
async getPDataBinary(player: string): Promise<{ pdata: Buffer, check_sum: string } | undefined> {
const result = await this.playDataCol.findOne({ _id: player });
if (!result)
return undefined;
return {
status: v.s32(0),
error: v.s32(0),
result: {
infinitas_id: v.str(id),
valid: v.bool(true),
is_exist: v.bool(true),
}
pdata: Buffer.from(result.pdata.buffer),
check_sum: result.check_sum,
};
}
@eacnet('p2d')
async getRivalInfo() {
return {
status: v.s32(0),
error: v.s32(0),
result: {
rival_data: [{
async getPDataDecoded(player: string): Promise<Pdata | undefined> {
const binary = await this.getPDataBinary(player);
if (!binary) return undefined;
}]
},
}
return fromKBinXml(binary.pdata);
}
@eacnet('p2d')
async getMusicList() {
return {
status: v.s32(0),
error: v.s32(0),
result: {
status: v.s32(0),
check_sum: v.str('2e49eba0b6ca8b6898f5d684ea0c3bc1ed6f1d572a4d308cf32727d64069a588'),
music_list: {
music_num: v.s32(MUSIC_LIST.length),
music: MUSIC_LIST,
upsertPDataBinary(player: string, pdata: Buffer, check_sum: string) {
return this.playDataCol
.updateOne({ _id: player }, {
$set: {
pdata: new Binary(pdata),
check_sum,
}
},
}
}, { upsert: true });
}
@eacnet('p2d')
async getCompeScoreData() {
return {
status: v.s32(0),
error: v.s32(0),
result: {},
}
}
@eacnet('p2d')
async getMusicData(ctx: Context) {
const { play_style } = ctx.body as { play_style: number };
const musicDatas = await this.musicDataCol.find({
player: ctx.token,
play_style,
getMusicDatas(player: string, play_style: number): Promise<PlayerMusicData[]> {
return this.musicDataCol.find({
player, play_style,
}).toArray();
return {
status: v.s32(0),
error: v.s32(0),
play_style: v.s32(play_style),
result: {
music: musicDatas.map(data => ({
music_id: v.s32(data.music_id),
score: v.s32(data.score),
clear_flag: v.s32(data.clear_flag),
miss_count: v.s32(data.miss_count),
play_num: v.s32(data.play_num),
clear_num: v.s32(data.clear_num),
}))
},
}
}
@eacnet('p2d')
async getItemList() {
async getMusicData(player: string, music_id: number, play_style: number): Promise<PlayerMusicData> {
const result = await this.musicDataCol.findOne({
player: player,
music_id: music_id,
play_style: play_style
});
if (result)
return result;
return {
status: v.s32(0),
error: v.s32(0),
result: {
item_list: {
item_num: v.s32(ITEM_LIST.length),
item: ITEM_LIST.map(id => ({
item_id: v.str(id),
not_free_count: v.s32(1000),
free_count: v.s32(1000),
}))
}
}
player: result.player,
music_id: result.music_id,
play_style: play_style,
score: [0, 0, 0, 0, 0],
clear_flag: [0, 0, 0, 0, 0],
miss_count: [-1, -1, -1, -1, -1],
play_num: [0, 0, 0, 0, 0],
clear_num: [0, 0, 0, 0, 0],
};
}
upsertMusicData(musicData: PlayerMusicData) {
return this.musicDataCol.updateOne({
player: musicData.player,
music_id: musicData.music_id,
play_style: musicData.play_style
}, {
$set: musicData
}, {
upsert: true
});
}
}
+2
View File
@@ -1,5 +1,6 @@
import { Logger } from '@cordisjs/logger';
import { DefaultState, ParameterizedContext } from 'koa';
import { InjectionToken } from 'tsyringe';
export interface ILaochanContext {
token?: string;
@@ -11,6 +12,7 @@ export interface ILaochanContext {
logger: Logger;
body?: unknown;
resolve: <T>(resolveToken: InjectionToken<T>) => T;
}
export type Context = ParameterizedContext<DefaultState, ILaochanContext, any>;
+223
View File
@@ -0,0 +1,223 @@
import { to_bin, to_xml } from "@kamyu/kbinxml";
import { XMLParser } from "fast-xml-parser";
import _ from "lodash";
const parser = new XMLParser({
ignoreAttributes: false,
parseAttributeValue: false,
attributeNamePrefix: '$',
removeNSPrefix: true,
numberParseOptions: {
hex: false,
leadingZeros: false,
eNotation: false,
skipLike: /.*/,
},
});
function parseValue(node: { $__type: string; $__count?: unknown }): any {
const isArray = '$__count' in node;
if (
[
's8',
'u8',
's16',
'u16',
's32',
'u32',
's64',
'u64',
].includes(node.$__type)
) {
if (isArray) {
return node['#text'].split(' ').map((v) => parseInt(v));
}
return parseInt(node['#text']);
}
if (['float', 'double'].includes(node.$__type)) {
if (isArray) {
return node['#text'].split(' ').map((v) => parseFloat(v));
}
return parseFloat(node['#text']);
}
if (['b', 'bool'].includes(node.$__type)) {
return !!parseInt(node['#text']);
}
if (['bin', 'binary'].includes(node.$__type)) {
return Buffer.from(node['#text'], 'hex');
}
if (['ip4', 'str', 'string'].includes(node.$__type)) {
return node['#text'];
}
if (node.$__type == 'time') {
return new Date(parseInt(node['#text']) * 1000);
}
return {
type: node.$__type,
value: node['#text'],
};
}
function toObject(node: Record<string, unknown> | unknown[] | string): any {
if (node instanceof Array) {
return node.map((v) => toObject(v as Record<string, unknown>));
}
if (typeof node === 'string') {
return {};
}
if ('$__type' in node) {
return parseValue(node as { $__type: string; $__count?: unknown });
}
const obj: object = {};
for (const key in node) {
if (key == '?xml') continue;
const value = node[key];
if (key.startsWith('$')) {
obj[key.substring(1)] = value;
continue;
}
obj[key] = toObject(node[key] as Record<string, unknown>);
}
return obj;
}
export function fromKBinXml(kbinxml: Buffer) {
const xml = to_xml(kbinxml).data;
const parsedXml = parser.parse(xml);
return toObject(parsedXml);
}
function serializeValue(value: any, type: string): string {
if (
[
's8',
'u8',
's16',
'u16',
's32',
'u32',
's64',
'u64',
'float',
'double',
].includes(type)
) {
return value.toString();
}
if (['b', 'bool'].includes(type)) {
return value ? '1' : '0';
}
if (['bin', 'binary'].includes(type)) {
if (!(value instanceof Buffer)) {
throw new Error('binary with non-buffer value');
}
return value.toString('hex');
}
if (['ip4', 'str', 'string'].includes(type)) {
return value;
}
if (type == 'time') {
if (value instanceof Date) {
return Math.floor(value.valueOf() / 1000).toString();
}
if (typeof value === 'number') {
return value.toString();
}
throw new Error('time with not date or number');
}
throw new Error(`unsupported type ${type}`);
}
function serializeObject(obj: Object, name: string, linePrefix: string = '') {
let output = `${linePrefix}<${name}`;
const entries = Object.entries(obj);
const attrs = entries
.filter(v => v[0].startsWith('$'))
.map(v => [v[0].substring(1), v[1]]);
// handle kxml value shit
const value = entries.find(v => v[0] == '__value');
if (value && value[1] instanceof Array) {
attrs.push(['__count', value[1].length]);
}
if (value && value[1] instanceof Buffer) {
attrs.push(['__size', value[1].length]);
}
// the kbinxml library had issue when serializing non-ordered stuff
attrs.sort((a, b) => a[0] < b[0] ? 1 : (a[0] > b[0] ? -1 : 0));
for (const attr of attrs) {
output += ` ${attr[0]}="${_.escape(attr[1])}"`;
}
output += '>';
if (value) {
const type = attrs.find(v => v[0] == '__type');
if (!type) {
// or normal xml innerXml?
throw new Error(`value with no type in ${name}`);
}
output += (value[1] instanceof Array ? value[1] : [value[1]])
.map(v => serializeValue(v, type[1]))
.join(' ');
} else {
const elements = entries
.filter(v => !v[0].startsWith('$'))
.sort((a, b) => a[0] < b[0] ? 1 : (a[0] > b[0] ? -1 : 0));
if (elements.length) {
output += '\n';
for (const element of elements) {
if (element[1] instanceof Array) {
for (const item of element[1])
output += serializeObject(item, element[0], linePrefix + ' ');
continue;
}
output += serializeObject(element[1], element[0], linePrefix + ' ');
}
output += linePrefix;
}
}
return output + `</${name}>\n`;
}
export function toKBinXml(topName: string, obj: unknown, encoding: 'UTF-8' | 'SHIFT_JIS' = 'UTF-8') {
const xml = `<?xml version="1.0" encoding="${encoding}"?>` + serializeObject(obj, topName);
return to_bin(xml);
}
+7
View File
@@ -0,0 +1,7 @@
import { createHash } from "crypto";
export function sha256(data: Buffer) {
return createHash('sha256')
.update(data)
.digest('hex');
}