commit ff123e05466bb98acd9df83c67e64a4f7facff6c Author: Freddie Date: Wed May 27 21:00:53 2020 +0100 first release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ec29da --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vscode + +node_modules +package-lock.json \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..396f222 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Asphyxia CORE Community Plugins + +These plugins are provided by community members and considered officially supported by Team Asphyxia. \ No newline at end of file diff --git a/asphyxia-core.d.ts b/asphyxia-core.d.ts new file mode 100644 index 0000000..f0309ff --- /dev/null +++ b/asphyxia-core.d.ts @@ -0,0 +1,899 @@ +/// + +declare type KNumberType = + | 's8' + | 'u8' + | 's16' + | 'u16' + | 's32' + | 'u32' + | 'time' + | 'ip4' + | 'float' + | 'double' + | 'bool'; +declare type KBigIntType = 's64' | 'u64'; +declare type KNumberGroupType = + | '2s8' + | '2u8' + | '2s16' + | '2u16' + | '2s32' + | '2u32' + | '2f' + | '2d' + | '3s8' + | '3u8' + | '3s16' + | '3u16' + | '3s32' + | '3u32' + | '3f' + | '3d' + | '4s8' + | '4u8' + | '4s16' + | '4u16' + | '4s32' + | '4u32' + | '4f' + | '4d' + | '2b' + | '3b' + | '4b' + | 'vb'; +declare type KBigIntGroupType = + | '2s64' + | '2u64' + | '3s64' + | '3u64' + | '4s64' + | '4u64' + | 'vs8' + | 'vu8' + | 'vs16' + | 'vu16'; + +/** + * Attribute object + */ +declare type KAttrMap = { [key: string]: string }; + +/** + * Supported response encoding + */ +declare type KEncoding = 'shift_jis' | 'utf8' | 'euc-jp' | 'ascii' | 'iso-8859-1'; + +/** + * Information about requester + */ +declare interface EamuseInfo { + module: string; + method: string; + model: string; +} + +/** + * Detail of a config + */ +declare interface CONFIG_OPTIONS { + /** Provide a name to display in webui. If not provided, webui will use key as the name. */ + name?: string; + /** Provide a description for the option */ + desc?: string; + /** Type of the option */ + type: 'string' | 'integer' | 'float' | 'boolean'; + /** Only applies to 'integer' and 'float' */ + range?: [number, number]; + /** Validator for notify user about invalid values. return `true` to pass the validation. return a string to send a error message to WebUI. */ + validator?: (data: string) => true | string; + /** Only applies to 'string', provide options in a dropdown menu. */ + options?: string[]; + /** Indicate whether user need to restart CORE to see changes. */ + needRestart?: boolean; + /** Default value of the option */ + default: any; +} + +/** + * Response options + */ +declare interface EamuseSendOption { + status?: number; + + /** + * Encode response with specified encoding + * Default: 'SHIFT_JIS' + */ + encoding?: KEncoding; + + /** + * Replace response root tag name. + * Default to child tag name of request tag, + * which is usually the case and don't need to be replaced. + */ + rootName?: string; + + compress?: boolean; + kencode?: boolean; + encrypt?: boolean; +} + +declare interface EamuseSend { + /** + * Send empty response with status code 0 + */ + success: (options?: EamuseSendOption) => Promise; + + /** + * Send empty response with status code 1 + */ + deny: (options?: EamuseSendOption) => Promise; + + /** + * Send empty response with custom status code + */ + status: (code: number, options?: EamuseSendOption) => Promise; + + /** + * Send plain javascript object. + * When constructing objects, make sure to use helper [[K]]: + * ``` + * { + * outter: K.ATTR({ status: "1" }, { + * inner: K.ITEM("s32", 1) + * }) + * } + * ``` + * + * Or follow xml-like format manually: + * ``` + * { + * outter: { + * "@attr": { status: "1" }, + * inner: { + * "@attr": { __type: "s32" }, + * "@content": [1] + * } + * } + * } + * ``` + * @param res xml-like formatted javascript object + * @param options Response options. See: [[EamuseSendOption]] + */ + object: (res: any, options?: EamuseSendOption) => Promise; + + /** + * Send xml data using ejs template system. + * + * @param res xml string as the template + * @param data Render template with specified data, + * pass null or undefined to render static xml + * @param options Response options. See: [[EamuseSendOption]] + */ + xml: (res: string, data?: any, options?: EamuseSendOption) => Promise; + + /** + * Send xml data using pug template system. + * + * @param res pug string as the template + * @param data Render template with specified data, + * pass null or undefined to render static xml + * @param options Response options. See: [[EamuseSendOption]] + */ + pug: (res: string, data?: any, options?: EamuseSendOption) => Promise; + + /** + * Render and send ejs template from a file + * + * @param file Filename of the template + * @param data Render template with specified data, + * pass null or undefined to render static xml + * @param options Response options. See: [[EamuseSendOption]] + */ + xmlFile: (file: string, data?: any, options?: EamuseSendOption) => Promise; + + /** + * Render and send pug template from a file + * + * @param file Filename of the template + * @param data Render template with specified data, + * pass null or undefined to render static xml + * @param options Response options. See: [[EamuseSendOption]] + */ + pugFile: (file: string, data?: any, options?: EamuseSendOption) => Promise; +} + +/** + * Helper type for typing your custom route. + */ +declare type EamusePluginRoute = (req: EamuseInfo, data: any, send: EamuseSend) => Promise; + +/** + * Helper type for typing your custom route. + * + * Alias for [[EamusePluginRoute]] + */ +declare type EPR = EamusePluginRoute; + +/** + * R stands for `Register` + * + * These functions can only be called in plugins' `register()` function. + */ +declare namespace R { + /** + * Register your custom route. + * + * You should only call this from your plugin's `register()` function. + * + * @param method Method name of your target route, + * usually looks like `"module.get"` + * @param handler Your custom route function/method following the type [[EamusePluginRoute]]. + * A boolean can be passed if you don't need any processing: + * - `true`: Sending empty response with status code 0 + * - `false`: Sending empty response with status code 1 + */ + function Route(method: string, handler: EamusePluginRoute | boolean): void; + + /** + * Register all unhandled routes for a game. + * + * You should only call this from your plugin's `register()` function. + * + * @param handler Your custom route function/method following the type [[EamusePluginRoute]]. + * If undefined, the router will apply a default handler that prints method names. + */ + function Unhandled(handler?: EamusePluginRoute): void; + + /** + * Register a target game code to your plugin for checking savedata. + * + * You should only call this from your plugin's `register()` function. + * + * @param code Model code of your target machine, + * usually __three capital letters__ + * + */ + function GameCode(code: string): void; + + /** + * Register a contributor. + * + * Contributors will show up in WebUI. + * + * @param name Contributor's name + * @param link Contributor's homepage + */ + function Contributor(name: string, link?: string): void; + + /** + * Register a configuration option. + * + * @param key config key + * @param options See [[CONFIG_OPTIONS]] + * + * __NOTE__: `options.validator` will only notify user about invalid value. It wouldn't stop user from saving invalid value. + */ + function Config(key: string, options: CONFIG_OPTIONS): void; + + /** + * Register a WebUI event callback + * + * Which can be called in WebUI using `emit(event)` function or a post message to `/emit/` + * + * Callback can be async function if you want to use await for your DB operations. + */ + function WebUIEvent(event: string, callback: (data: any) => void | Promise): void; +} + +/** + * A warpper of javascript object for reading xml-like formatted data. + */ +declare class KDataReader { + /** + * Wrapped javascript object + */ + public obj: any; + constructor(obj: any); + + /** + * Get attrubutes for a tag + * + * Example: + * ```xml + * + * + * 1 + * 2 + * + * + * ``` + * ```javascript + * const data = { + * tag: K.ATTR({ status: "1" }, { + * inner: [ + * K.ITEM("s32", 1), + * K.ITEM("s32", 2) + * ] + * }) + * } + * ``` + * + * Evals: + * ```javascript + * $(data).attr("tag") // { status: "1" } + * $(data).element("tag").attr().status // "1" + * $(data).attr("tag.inner.0").__type // "s32" + * ``` + */ + attr(path?: string): KAttrMap; + + /** + * Get a bigint value from a tag, convert to bigint if applicable. + * + * Example: + * ```xml + * + * 1 + * 2 + * abc + * + * ``` + * ```javascript + * const data = { + * inner: [ + * K.ITEM("s64", 1n), + * K.ITEM("s32", 2) + * ], + * invalid: K.ITEM("str", "abc") + * } + * ``` + * + * Evals: + * ```javascript + * $(data).element("inner").bigint() // 1n + * $(data).bigint("inner.1") // 2n + * $(data).bigint("invalid", 3n) // 3n + * ``` + * + * @param def Default return value when target path does + * not exists or is not valid. + */ + bigint(path: string, def?: bigint): bigint; + + /** + * Get a bigint array from a tag. Only returns valid arrays + * when target tag has a type of [[KBigIntType]] or [[KBigIntGroupType]] + * + * Example: + * ```xml + * + * 1 2 + * 3 4 + * + * ``` + * ```javascript + * const data = { + * inner: K.ARRAY("s64", [1n, 2n]), + * invalid: K.ARRAY("s32", [3, 4]) + * } + * ``` + * + * Evals: + * ```javascript + * $(data).bigints("inner") // [1n, 2n] + * $(data).bigints("invalid") // undefined + * ``` + */ + bigints(path: string, def?: bigint[]): bigint[]; + + /** + * Get a boolean value from a tag, return true only if value in the tag is number and **greater than zero** + * + * Example: + * ```xml + * + * 0 + * 2 + * + * ``` + * ```javascript + * const data = { + * inner: [ + * K.ITEM("bool", false), + * K.ITEM("s32", 2) + * ] + * } + * ``` + * + * Evals: + * ```javascript + * $(data).bool("inner.0") // false + * $(data).bool("inner.1") // true + * $(data).bool("invalid") // false + * ``` + */ + bool(path: string): boolean; + + /** + * Get a Buffer object from a tag, Only returns valid Buffer + * when target tag has a type of "bin" + * + * Example: + * ```xml + * + * 00ff + * 1 2 3 + * + * ``` + * ```javascript + * const data = { + * inner: K.ITEM("bin", Buffer.from([0x00, 0xff])), + * invalid: K.ARRAY("u8", [1, 2, 3]) + * } + * ``` + * + * Evals: + * ```javascript + * $(data).buffer("inner") // + * $(data).buffer("invalid") // undefined + * ``` + */ + buffer(path: string, def?: Buffer): Buffer; + + /** + * Get raw content representation regardless of tag type + * + * Example: + * ```xml + * + * 1 + * 1 2 3 + * abc + * + * ``` + * ```javascript + * const data = { + * number: K.ITEM("s32", 1), + * array: K.ARRAY("u8", [1, 2, 3]), + * string: K.ITEM("str", "abc") + * } + * ``` + * + * Evals: + * ```javascript + * $(data).content("number") // [1] + * $(data).content("array") // [1, 2, 3] + * $(data).content("string") // "abc" + * ``` + */ + content(path: string, def?: any): any; + + /** + * Get first element named **path** inside a tag + * + * Example: + * ```xml + * + * + * 1 + * + * + * 1 + * + * + * ``` + * ```javascript + * const data = { + * inner: [ + * { id: K.ITEM("s32", 1) }, + * { id: K.ITEM("s32", 2) } + * ] + * } + * ``` + * + * Evals: + * ```javascript + * $(data).element("inner") // + * $(data).element("inner").obj // { id: [object] } + * $(data).element("inner").number("id") // 1 + * ``` + */ + element(path: string): KDataReader; + + /** + * Get array of all elements named **path** inside a tag + * + * Example: + * ```xml + * + * + * 1 + * + * + * 1 + * + * + * ``` + * ```javascript + * const data = { + * inner: [ + * { id: K.ITEM("s32", 1) }, + * { id: K.ITEM("s32", 2) } + * ] + * } + * ``` + * + * Evals: + * ```javascript + * $(data).elements("inner") // [, ] + * $(data).elements("inner")[1].number("id") // 2 + * ``` + */ + elements(path: string): KDataReader[]; + + /** + * Get a number value from a tag, convert to number if applicable. + * + * Example: + * ```xml + * + * 1 + * 2 + * abc + * + * ``` + * ```javascript + * const data = { + * inner: [ + * K.ITEM("s64", 1n), + * K.ITEM("s32", 2) + * ], + * invalid: K.ITEM("str", "abc") + * } + * ``` + * + * Evals: + * ```javascript + * $(data).element("inner").number() // 1 + * $(data).number("inner.1") // 2 + * $(data).number("invalid", 3) // 3 + * ``` + * + * @param def Default return value when target path does + * not exists or is not valid. + */ + number(path: string, def?: number): number; + + /** + * Get a number array from a tag. Only returns valid arrays + * when target tag has a type of [[KNumberType]] or [[KNumberGroupType]] + * + * Example: + * ```xml + * + * 1 2 + * 3 4 + * + * ``` + * ```javascript + * const data = { + * invalid: K.ARRAY("s64", [1n, 2n]), + * inner: K.ARRAY("s32", [3, 4]) + * } + * ``` + * + * Evals: + * ```javascript + * $(data).bigints("invalid") // undefined + * $(data).bigints("inner") // [3, 4] + * ``` + */ + numbers(path: string, def?: number[]): number[]; + + /** + * Get a string from a tag, Only returns valid string + * when target tag has a type of "str" + * + * Example: + * ```xml + * + * abc + * 1 + * + * ``` + * ```javascript + * const data = { + * inner: K.ITEM("str", "abc"), + * invalid: K.ITEM("s32", 1) + * } + * ``` + * + * Evals: + * ```javascript + * $(data).str("inner") // "abc" + * $(data).str("invalid") // undefined + * ``` + */ + str(path: string, def?: string): string; +} + +/** + * Helper for reading xml-like formatted data. + */ +declare function $(data: any): KDataReader; +declare namespace $ { + function ATTR(data: any, path?: string): KAttrMap; + function BIGINT(data: any, path: string, def?: bigint): bigint; + function BIGINTS(data: any, path: string, def?: bigint[]): bigint[]; + function BOOL(data: any, path: string): boolean; + function BUFFER(data: any, path: string, def?: Buffer): Buffer; + function CONTENT(data: any, path: string, def?: any): any; + function ELEMENT(data: any, path: string, def?: any): any; + function ELEMENTS(data: any, path: string, def?: any): any; + function NUMBER(data: any, path: string, def?: number): number; + function NUMBERS(data: any, path: string, def?: number[]): number[]; + function STR(data: any, path: string, def?: string): string; +} + +/** + * K stands for `Konstruct` + * + * Helper for constructing xml-like javascript object. + */ +declare namespace K { + /** + * Example: + * ``` + * { + * tag: K.ATTR({attr: "1"}, { + * inner: [{}, {}] + * }) + * } + * ``` + * Represents: + * ``` + * + * + * + * + * ``` + * @param attr Attribute map + * @param inner Inner tag/data + */ + function ATTR(attr: KAttrMap, inner?: any): any; + + /** + * Example: + * ``` + * { + * tag: K.ITEM('s32', 1, {attr: "2"}) + * } + * ``` + * Represents: + * ``` + * 1 + * ``` + * @param type ____type__ attribute, which is used during encoding and compression + * @param content data of specified type + * @param attr attribute map in addition to **__type** + */ + function ITEM(type: 'str', content: string, attr?: KAttrMap): any; + function ITEM(type: 'bin', content: Buffer, attr?: KAttrMap): any; + function ITEM(type: 'ip4', content: string, attr?: KAttrMap): any; + function ITEM(type: 'bool', content: boolean, attr?: KAttrMap): any; + function ITEM(type: KNumberType, content: number, attr?: KAttrMap): any; + function ITEM(type: KBigIntType, content: bigint, attr?: KAttrMap): any; + function ITEM(type: KNumberGroupType, content: number[], attr?: KAttrMap): any; + function ITEM(type: KBigIntGroupType, content: bigint[], attr?: KAttrMap): any; + + /** + * Example: + * ``` + * { + * tag: K.ARRAY('s32', [1, 2, 3], {attr: "4"}) + * } + * ``` + * Represents: + * ``` + * 1 2 3 + * ``` + * @param type ____type__ attribute, which is used during encoding and compression + * @param content array of data, ____count__ attribute will be automatically set to `content.length` + * @param attr attribute map in addition to **__type** and **__count** + */ + function ARRAY(type: 'u8' | 's8', content: Buffer, attr?: KAttrMap): any; + function ARRAY(type: KNumberType, content: number[], attr?: KAttrMap): any; + function ARRAY(type: KBigIntType, content: bigint[], attr?: KAttrMap): any; +} + +/** + * Filesystem IO + * + * These are designed to match nodejs `fs` module. Along with custom filesystem implementation for reading compressed data. + * + * __DO NOT__ use IO for savedata. Please use [[DB]] namespace so your data can be managed by WebUI. + * + * Also, due to difference between operating systems, you should always prepare your files using ascii path. + * Both UTF-8 and local encodings will have cross-platform compatibility issues. + */ +declare namespace IO { + /** + * Resolve a relative path starting from your plugin directory to an absolute path. + */ + function Resolve(path: string): string; + + /** + * Asynchronously read a directory. + * @param path A path to a directory. + */ + function ReadDir(path: string): Promise<{ name: string; type: 'file' | 'dir' | 'unsupported' }[]>; + + /** + * Asynchronously writes data to a file, replacing the file if it already exists. + * @param path A path to a file. + * If a file descriptor is provided, the underlying file will _not_ be closed automatically. + * @param data The data to write. If something other than a Buffer or Uint8Array is provided, the value is coerced to a string. + * @param options Either the encoding for the file, or an object optionally specifying the encoding, file mode, and flag. + * If `encoding` is not supplied, the default of `'utf8'` is used. + * If `mode` is not supplied, the default of `0o666` is used. + * If `mode` is a string, it is parsed as an octal integer. + * If `flag` is not supplied, the default of `'w'` is used. + */ + function WriteFile( + path: string, + data: any, + options: { encoding?: string | null; mode?: number | string; flag?: string } | string | null + ): Promise; + + /** + * Asynchronously writes data to a file, replacing the file if it already exists. + * @param path A path to a file. + * If a file descriptor is provided, the underlying file will _not_ be closed automatically. + * @param data The data to write. If something other than a Buffer or Uint8Array is provided, the value is coerced to a string. + */ + function WriteFile(path: string, data: any): Promise; + + /** + * Asynchronously reads the entire contents of a file. + * @param path A path to a file. + * If a file descriptor is provided, the underlying file will _not_ be closed automatically. + * @param options An object that may contain an optional flag. + * If a flag is not provided, it defaults to `'r'`. + */ + function ReadFile( + path: string, + options: { encoding?: null; flag?: string } | undefined | null + ): Promise; + + /** + * Asynchronously reads the entire contents of a file. + * @param path A path to a file. + * If a file descriptor is provided, the underlying file will _not_ be closed automatically. + * @param options Either the encoding for the result, or an object that contains the encoding and an optional flag. + * If a flag is not provided, it defaults to `'r'`. + */ + function ReadFile( + path: string, + options: { encoding: string; flag?: string } | string + ): Promise; + + /** + * Asynchronously reads the entire contents of a file. + * @param path A path to a file. + * If a file descriptor is provided, the underlying file will _not_ be closed automatically. + * @param options Either the encoding for the result, or an object that contains the encoding and an optional flag. + * If a flag is not provided, it defaults to `'r'`. + */ + function ReadFile( + path: string, + options: { encoding?: string | null; flag?: string } | string | undefined | null + ): Promise; + + /** + * Asynchronously reads the entire contents of a file. + * @param path A path to a file. + * If a file descriptor is provided, the underlying file will _not_ be closed automatically. + */ + function ReadFile(path: string): Promise; +} + +/** + * U stands for `Utilities` + * + * You can find miscellaneous helpers here + */ +declare namespace U { + /** + * Convert json data to xml string. + * + * @param data xml-like javascript object + */ + function toXML(data: any): string; + + /** + * Convert xml string to javascript object. Output will always be plain javascript string. + * + * @param xml xml string + * @param simplify if true, the parser will ignore attributes and only generate string values. (default: true) + */ + function parseXML(xml: string, simplify?: boolean): any; + + /** + * Get config from user configuration file. + * @param key + */ + function GetConfig(key: string): any; +} + +/** + * Database operation. + * + * There are two pools of data for each plugin: ___PluginSpace___ and __ProfileSpace__ + * + * If `refid` is a string, query will match a specific profile data in __ProfileSpace__. + * + * If `refid` is null, query will match all profile data in __ProfileSpace__. + * (doesn't apply to [[DB.FindOne]] and [[DB.Insert]]) + * + * If `refid` is not provided, query will match data in ___PluginSpace___. + * + * --- + * + * **NOTE**: since WebUI can delete data in __ProfileSpace__, + * you should refrain from referencing refid in your document to prevent getting unclearable garbage data. + * + * If you need to make rival/friend feature, we recommend you to get all profile data by passing null to `refid`. + * There will be 16 profiles maximum which is small enough to manage. + */ +declare namespace DB { + function FindOne(refid: string, query: object): Promise; + function FindOne(query: object): Promise; + + function Find(refid: string | null, query: object): Promise; + function Find(query: object): Promise; + + function Insert(refid: string, doc: object): Promise; + function Insert(doc: object): Promise; + + function Remove(refid: string | null, query: object): Promise; + function Remove(query: object): Promise; + + function Update( + refid: string | null, + query: object, + update: object + ): Promise<{ + updated: number; + docs: any[]; + }>; + function Update( + query: object, + update: object + ): Promise<{ + updated: number; + docs: any[]; + }>; + + function Upsert( + refid: string | null, + query: object, + update: object + ): Promise<{ + updated: number; + docs: any[]; + upsert: boolean; + }>; + function Upsert( + query: object, + update: object + ): Promise<{ + updated: number; + docs: any[]; + upsert: boolean; + }>; + + function Count(refid: string | null, query: object): Promise; + function Count(query: object): Promise; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ee9b16b --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name": "asphyxia-plugins", + "version": "1.0.0", + "description": "Community Plugins for Asphyxia CORE", + "dependencies": {}, + "devDependencies": { + "@types/node": "^14.0.5" + } +} diff --git a/sdvx@asphyxia b/sdvx@asphyxia new file mode 160000 index 0000000..27f1a25 --- /dev/null +++ b/sdvx@asphyxia @@ -0,0 +1 @@ +Subproject commit 27f1a259b57ad4bc82b1fbdce7fb67a5be653a22 diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..cd9a68c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "esnext", + "noImplicitAny": false, + "moduleResolution": "node", + "types": ["./asphyxia-core"], + "sourceMap": false + } +} diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..4344abd --- /dev/null +++ b/typedoc.json @@ -0,0 +1,9 @@ +{ + "includeDeclarations": true, + "excludeExternals": true, + "externalPattern": ["!asphyxia-core.d.ts"], + "mode": "file", + "readme": "../docs/_typedoc.md", + "name": "Asphyxia CORE Module API", + "out": "../docs/typedoc" +}