diff --git a/package-lock.json b/package-lock.json index 6f8022f..c327659 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,22 @@ { - "name": "node-typescript-boilerplate", + "name": "@laochan/ea3", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "node-typescript-boilerplate", + "name": "@laochan/ea3", "version": "0.0.0", - "license": "Apache-2.0", + "license": "MIT", "dependencies": { "@cordisjs/logger": "^0.1.3", "@kamyu/kbinxml": "^2.0.2", - "arc4": "^3.4.0", + "arc4": "^2.1.4", "dedent-js": "^1.0.1", + "dotenv": "^16.4.5", "fast-xml-parser": "^4.3.2", "koa": "^2.15.0", + "lodash": "^4.17.21", "pug": "^3.0.2", "reflect-metadata": "^0.2.1", "tslib": "~2.6", @@ -23,6 +25,7 @@ "devDependencies": { "@types/jest": "~29.5", "@types/koa": "^2.13.12", + "@types/lodash": "^4.14.202", "@types/node": "~20", "@types/pug": "^2.0.10", "@typescript-eslint/eslint-plugin": "~6.15", @@ -2042,6 +2045,12 @@ "@types/koa": "*" } }, + "node_modules/@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", + "dev": true + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz", @@ -2443,14 +2452,11 @@ } }, "node_modules/arc4": { - "version": "3.4.0", - "resolved": "https://registry.npmmirror.com/arc4/-/arc4-3.4.0.tgz", - "integrity": "sha512-zFQmSbTwSTrICYV8kldrD3YreCtlLIKEZk+lmlNSlpUKU/cF9o746WFEy9eNqLrqSyUgAAjB3gnD9UXdK23beA==", - "dependencies": { - "lodash": "4.17.4" - }, + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/arc4/-/arc4-2.1.4.tgz", + "integrity": "sha512-yYR7Ragh8qYiPMRipZmtoP6BVtENdOmFo7+sdBX7VD/F+hzblmsZjr56ir7G3485AEh3EWBscLv0h4iE+bWzwQ==", "engines": { - "node": ">=4" + "node": ">=0.10" } }, "node_modules/arg": { @@ -3118,6 +3124,14 @@ "resolved": "https://registry.npmmirror.com/doctypes/-/doctypes-1.1.0.tgz", "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==" }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -5110,9 +5124,9 @@ } }, "node_modules/lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha512-6X37Sq9KCpLSXEh8uM12AKYlviHPNNk4RxiGBn4cmKGJinbXBneWIV7iE/nXkM928O7ytHcHb6+X6Svl0f4hXg==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.memoize": { "version": "4.1.2", diff --git a/package.json b/package.json index cbecd3f..cdcc0b8 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "devDependencies": { "@types/jest": "~29.5", "@types/koa": "^2.13.12", + "@types/lodash": "^4.14.202", "@types/node": "~20", "@types/pug": "^2.0.10", "@typescript-eslint/eslint-plugin": "~6.15", @@ -43,10 +44,12 @@ "dependencies": { "@cordisjs/logger": "^0.1.3", "@kamyu/kbinxml": "^2.0.2", - "arc4": "^3.4.0", + "arc4": "^2.1.4", "dedent-js": "^1.0.1", + "dotenv": "^16.4.5", "fast-xml-parser": "^4.3.2", "koa": "^2.15.0", + "lodash": "^4.17.21", "pug": "^3.0.2", "reflect-metadata": "^0.2.1", "tslib": "~2.6", diff --git a/src/decorators/render-kxml.ts b/src/decorators/render-kxml.ts index 5a54947..c35b870 100644 --- a/src/decorators/render-kxml.ts +++ b/src/decorators/render-kxml.ts @@ -1,7 +1,7 @@ import pug from 'pug'; import { to_bin } from '@kamyu/kbinxml'; -export function kxml( +export function kxmlTemplate( templatePath: string, encoding: 'UTF-8' | 'SHIFT_JIS' = 'UTF-8', ): MethodDecorator { diff --git a/src/decorators/to-kxml.ts b/src/decorators/to-kxml.ts new file mode 100644 index 0000000..54d2c02 --- /dev/null +++ b/src/decorators/to-kxml.ts @@ -0,0 +1,152 @@ +import _ from 'lodash'; +import { to_bin } from '@kamyu/kbinxml'; + +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]]); + + for (const attr of attrs) { + output += ` ${attr[0]}="${_.escape(attr[1])}"`; + } + + // handle kxml value shit + const value = entries.find(v => v[0] == '__value'); + + if (value && value[1] instanceof Array) { + output += ` __count="${value[1].length}"`; + } + + 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('$')); + + 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 + `\n`; +} + +export function kxml(topName: string = 'response', 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 = + `` + serializeObject(obj, topName); + const kxml = to_bin(xml); + + return Buffer.from(kxml.data); + } 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')) + diff --git a/src/functions/default.ts b/src/functions/default.ts index 723c3b1..7f7d7e0 100644 --- a/src/functions/default.ts +++ b/src/functions/default.ts @@ -1,22 +1,18 @@ import { singleton } from 'tsyringe'; import { Context, ILaochanService } from '../types.js'; -import dedent from 'dedent-js'; -import { to_bin } from '@kamyu/kbinxml'; +import { kxml } from '../decorators/to-kxml.js'; @singleton() export class DefaultService implements ILaochanService { - async process(ctx: Context): Promise { - const tmp = ctx.service.split('.'); - const module = tmp[0]; - const method = tmp[1]; + @kxml() + async process(ctx: Context) { + const [module, method] = ctx.service.split('.'); - const xml = dedent` - - - <${module} method="${method}" status="0"/> - - `; - - return Buffer.from(to_bin(xml).data); + return { + [module]: { + $method: method, + status: '0', + } + } } } diff --git a/src/functions/facility/get.ts b/src/functions/facility/get.ts index 0cce88d..a22bd36 100644 --- a/src/functions/facility/get.ts +++ b/src/functions/facility/get.ts @@ -1,10 +1,10 @@ -import { kxml } from '../../decorators/render-kxml.js'; +import { kxmlTemplate } from '../../decorators/render-kxml.js'; import { ILaochanService } from '../../types.js'; import { singleton } from 'tsyringe'; @singleton() export default class implements ILaochanService { - @kxml('facility/get') + @kxmlTemplate('facility/get') async process(): Promise { return { status: 0, diff --git a/src/functions/message/get.ts b/src/functions/message/get.ts index 8fd36a8..682601d 100644 --- a/src/functions/message/get.ts +++ b/src/functions/message/get.ts @@ -1,10 +1,10 @@ -import { kxml } from '../../decorators/render-kxml.js'; +import { kxmlTemplate } from '../../decorators/render-kxml.js'; import { ILaochanService } from '../../types.js'; import { singleton } from 'tsyringe'; @singleton() export default class implements ILaochanService { - @kxml('message/get') + @kxmlTemplate('message/get') async process(): Promise { return { status: 0, diff --git a/src/functions/pcbtracker/alive.ts b/src/functions/pcbtracker/alive.ts index c6d54aa..75c74f6 100644 --- a/src/functions/pcbtracker/alive.ts +++ b/src/functions/pcbtracker/alive.ts @@ -1,16 +1,19 @@ -import { kxml } from '../../decorators/render-kxml.js'; +import { kxml } from '../../decorators/to-kxml.js'; import { ILaochanService } from '../../types.js'; import { singleton } from 'tsyringe'; @singleton() export default class implements ILaochanService { - @kxml('pcbtracker/alive') + @kxml() async process(): Promise { return { - status: 0, - expire: 3600, - time: Math.floor(new Date().valueOf() / 1000), - ecEnable: 1, + pcbtracker: { + $ecenable: 1, + $expire: 3600, + $method: 'alive', + $status: 0, + $time: Math.floor(new Date().valueOf() / 1000), + } }; } } diff --git a/src/functions/services/get.ts b/src/functions/services/get.ts index 1103027..9a8d46e 100644 --- a/src/functions/services/get.ts +++ b/src/functions/services/get.ts @@ -1,16 +1,41 @@ -import { kxml } from '../../decorators/render-kxml.js'; import { ILaochanService } from '../../types.js'; import { singleton } from 'tsyringe'; +import config from '../../utils/config.js'; +import { kxml } from '../../decorators/to-kxml.js'; @singleton() export default class implements ILaochanService { - @kxml('services/get') - async process(): Promise { + @kxml() + async process() { + const selfServices = [ + 'services', 'dlstatus', 'cardmng', + 'eacoin', 'userdata', 'userid', 'pcbtracker', 'pcbevent', + 'message', 'facility', 'apsmanager', 'sidmgr', 'package', + 'uploaders', 'local', 'local2', 'lobby', + ] + return { - keepaliveUrl: - 'ping://127.0.0.1/?pa=127.0.0.1&ia=127.0.0.1&ga=127.0.0.1ma=127.0.0.1&t1=2&t2=15&rt=2', - ntpUrl: 'ntp://pool.ntp.org', - selfUrl: 'http://localhost:10573', - }; + services: { + $expire: 43200, + $method: 'get', + $mode: 'operation', + $product_domain: 1, + $statuc: 0, + item: [ + { + $name: 'keepalive', + $url: 'ping://127.0.0.1/?pa=127.0.0.1&ia=127.0.0.1&ga=127.0.0.1ma=127.0.0.1&t1=2&t2=15&rt=2', + }, + { + $name: 'ntp', + $url: config.ntpUrl, + }, + ...selfServices.map(v => ({ + $name: v, + $url: config.selfUrl, + })) + ] + } + } } } diff --git a/src/utils/config.ts b/src/utils/config.ts new file mode 100644 index 0000000..a159e7e --- /dev/null +++ b/src/utils/config.ts @@ -0,0 +1,27 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +class Config { + get port() { + return process.env.PORT ? parseInt(process.env.PORT) : 10573; + } + + get selfUrl() { + return process.env.SELF_URL ?? 'http://127.0.0.1:10573'; + } + + get ntpUrl() { + return process.env.NTP_URL ?? 'ntp://pool.ntp.org'; + } + + get mongoUrl() { + return process.env.MONGO_URL ?? 'mongodb://127.0.0.1:27017'; + } + + get isDev() { + return process.env.NODE_ENV !== 'production'; + } +} + +export const config = new Config(); +export default config;