From 8d96062db4a136800c11ae81f10850b4af279679 Mon Sep 17 00:00:00 2001 From: CodePwn <77036672+CodePwn2021@users.noreply.github.com> Date: Mon, 29 Aug 2022 12:46:16 +0800 Subject: [PATCH] 0.2.0 --- README.md | 25 ++++- lib/client.ts | 246 +++++++++++++++++++++++++++++++++++++++++-------- lib/index.ts | 3 +- lib/player.ts | 163 ++++++++++++++++++++++++++++++++ package.json | 4 +- test/index.cjs | 20 +++- 6 files changed, 413 insertions(+), 48 deletions(-) create mode 100644 lib/player.ts diff --git a/README.md b/README.md index 365d17f..6f70c46 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,30 @@ [![npm version](https://img.shields.io/npm/v/light-omega/latest.svg)](https://www.npmjs.com/package/light-omega) [![dm](https://shields.io/npm/dm/light-omega)](https://www.npmjs.com/package/light-omega) [![node engine](https://img.shields.io/node/v/light-omega/latest.svg)](https://nodejs.org) -## Warning -这个项目仍然处于测试阶段,请勿用于生产环境。 ## Install: ```bash > npm i light-omega # yarn add light-omega ``` ## Example -详见 [这里](/test/index.cjs) +```js +const { createClient } = require('light-omega'); + +// client的大部分函数需要在ready事件之后才能执行 +omg.on('omega.ready', onReady); + +async function onReady() { + // 在这里随便干点什么 + const res = await omg.sendPacket('get_players_list', {}); + console.log(JSON.stringify(res)); + const player = await omg.getPlayer(omg.bot_name); + console.log(player); + const echo = await omg.sendPacket('echo', { + message: 'hello, light-omega!' + }); + console.log(echo); +} +``` +## Docs +coming soon... +## Chat +[![group:766363535](https://img.shields.io/badge/group-766363535-blue)](https://qm.qq.com/cgi-bin/qm/qr?k=Lq7eF60RsxhVPWCKvlSWWuimHR4bHHz-) diff --git a/lib/client.ts b/lib/client.ts index 17ec657..e454f4e 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -2,92 +2,143 @@ import {WebSocket} from 'ws'; import {EventEmitter} from 'events'; import {OmegaRequestPacket, OmegaResponsePacket} from './omega_packet'; import log4js from 'log4js'; +import {Player} from "./player"; const pkg = require('../package.json'); const DEFAULT_WS_URL: string = 'ws://localhost:24011/omega_side'; /** 一个客户端 */ export class OmegaClient extends EventEmitter { - bot_name: String = 'Unknown' - private connected: Boolean = false - private wsClient: WebSocket = new WebSocket(null) - logger:log4js.Logger = log4js.getLogger('[LightOmega]') + /** 机器人的昵称 + * @readonly + */ + get bot_name() { + return this.#bot_name; + } + + #bot_name: String = 'Unknown' + + /** 是否已连接 + * @readonly + */ + get connected() { + return this.#connected; + } - constructor(host: string, level:LogLevel = 'mark') { + #connected: Boolean = false + /** 连接 Omega WebSocket 的实例 + * @private + */ + #wsClient: WebSocket + /** 日志记录器 + * @private + */ + #logger: log4js.Logger = log4js.getLogger('[LightOmega]') + + constructor(host: string, level: logLevel = 'mark') { super(); - this.wsClient = new WebSocket(host); + this.#wsClient = new WebSocket(host); /** 注册&处理ws client的事件 */ - this.wsClient.on('open', async () => { - this.connected = true; + this.#wsClient.on('open', async () => { + this.#connected = true; // 处理omega的包 - this.wsClient.on('message', (message) => { + this.#wsClient.on('message', (message) => { const obj_message = JSON.parse(message.toString('utf-8')); /** omega 响应数据包 */ if (obj_message.client !== 0) { /** 发送的数据包有问题 */ - if(obj_message.violate) { + if (obj_message.violate) { throw Error(JSON.stringify(obj_message)) } this.emit('omega.response', obj_message); return; } /** omega 主动推送数据包 */ - this.packetCount = obj_message.client; - this.emit('omega.push', obj_message); + switch (obj_message.type) { + /** sub: 玩家登录 */ + case 'playerLogin': + this.emit('omega.playerLogin', obj_message); + break; + /** sub: 玩家登出 */ + case 'playerLogout': + this.emit('omega.playerLogout', obj_message); + break; + /** sub: 菜单选项被触发 */ + case 'menuTriggered': + this.emit('omega.menuTriggered', obj_message); + break; + /** sub: 接收到订阅的数据包 */ + case 'mcPkt': + this.emit('omega.mcPkt', obj_message); + break; + /** sub: 方块更新 */ + case 'blockUpdate': + this.emit('omega.blockUpdate', obj_message); + break; + /** 通用 */ + default: + this.emit('omega.push', obj_message); + break; + } }); /** 获取自己(机器人)的昵称 */ const get_name_packet = await this.execCmd('tell @s @s'); - this.bot_name = get_name_packet.data.result.OutputMessages[0].Parameters[0]; + this.#bot_name = get_name_packet.data.result.OutputMessages[0].Parameters[0]; /** 初始化logger */ - this.logger = log4js.getLogger(`[LightOmega:${this.bot_name}]`); - this.logger.level = level; - this.logger.mark('┌────────────────────────────────────────────────────────────────┐'); - this.logger.mark(`| version: ${pkg.version} (publish on ${pkg.publish_time}) |`); - this.logger.mark('| changelog: https://github.com/CodePwn2021/light-omega/releases |'); - this.logger.mark('└────────────────────────────────────────────────────────────────┘'); + this.#logger = log4js.getLogger(`[LightOmega:${this.#bot_name}]`); + this.#logger.level = level; + this.#logger.mark('┌────────────────────────────────────────────────────────────────┐'); + this.#logger.mark(`| version: ${pkg.version} (publish on ${pkg.publish_time}) |`); + this.#logger.mark('| changelog: https://github.com/CodePwn2021/light-omega/releases |'); + this.#logger.mark('└────────────────────────────────────────────────────────────────┘'); + + /** 注册必要的监听 */ + await registerPushPacket(this); /** 准备就绪 */ this.emit('omega.ready'); }); /** 错误提示 */ - this.wsClient.on('error', (err) => { - console.error('ERROR !!!!!!!!!!!!!!'); - console.error(err); - console.error('ERROR !!!!!!!!!!!!!!'); + this.#wsClient.on('error', (err) => { + this.#logger.error('WebSocket ERROR!!', err); }); } - private packetCount: number = 0 + /** 发包编号 + * @private + */ + #packetCount: number = 0 /** * 发送一个数据包 * @function sendPacket * @param {string} func 请求的数据类型(也可理解成函数名) * @param {any} param 参数 + * @returns */ - async sendPacket(func: string, param: any): Promise { - if(!this.connected) throw Error('WebSocket not connected!!!\nPlease use sendPacket() in client.on("omega.ready") !!!'); - if(this.packetCount === 24011 || this.packetCount === 0) { - this.packetCount = 1; + async sendPacket(func: packetFuncName, param: any): Promise { + if (!this.#connected) throw Error('WebSocket not connected!!!\nPlease use sendPacket() in client.on("omega.ready") !!!'); + if (this.#packetCount === 24011 || this.#packetCount === 0) { + this.#packetCount = 1; } else { - this.packetCount++; + this.#packetCount++; } const packet: OmegaRequestPacket = { - client: this.packetCount, + client: this.#packetCount, 'function': func, args: param }; - this.wsClient.send(JSON.stringify(packet), (err) => { + this.#wsClient.send(JSON.stringify(packet), (err) => { if (err) { console.error(err); } }); return new Promise((resolve) => { - this.once('omega.response', (res:OmegaResponsePacket) => { + this.once('omega.response', (res: OmegaResponsePacket) => { resolve(res); }); }); @@ -96,23 +147,104 @@ export class OmegaClient extends EventEmitter { /** * 执行一个命令 * @function execCmd + * @description 请不要用于执行没有返回值的命令,比如 "say",否则会导致函数流程卡住 * @param {string} cmd 命令 * @param {boolean} isWebSocket (可选) 是否以游戏内 WebSocket 的身份执行 + * @returns {OmegaResponsePacket} + * @see execWriteOnlyCmd + */ + async execCmd(cmd: string, isWebSocket: boolean = false): Promise { + return await this.sendPacket(isWebSocket ? 'send_ws_cmd' : 'send_player_cmd', { + cmd + }); + } + + /** + * 执行一个 setting 命令 + * @function execWriteOnlyCmd + * @description 通常以此方式执行的命令可能会无效,但是可用于执行一个永远没有返回值的命令,比如 "say" + * @param {string} cmd 命令 + * @returns {void} + * @see execCmd */ - async execCmd(cmd:string, isWebSocket:boolean = false):Promise { - return await this.sendPacket(isWebSocket?'send_ws_cmd':'send_player_cmd', { + async execWriteOnlyCmd(cmd: string): Promise { + await this.sendPacket('send_wo_cmd', { cmd }); + return; } + + /** + * 获取一个玩家对象 + * @function getPlayer + * @param {string} playerName 玩家昵称 + * @returns {Player} + */ + async getPlayer(playerName: string): Promise { + const playerListPacket = await this.sendPacket('get_players_list', {}); + let targetIndex: number = 0; + const isPlayerExists: boolean = playerListPacket.data.some((value: any, index: number) => { + targetIndex = index; + return playerName === value.name; + }); + if (isPlayerExists) { + return new Player(this, playerListPacket.data[targetIndex]); + } + throw Error('Player NOT exists!!!'); + } + + /** + * 发送QQ信息(会调用群服互通,若未开启群服互通则会导致不可预料的异常) + * @function sendQQMsg + * @param {string} msg 要发送的信息 + * @returns {void} + */ + async sendQQMsg(msg: string): Promise { + await this.sendPacket('send_qq_msg', { + msg + }); + return; + } + + /** + * 注册一个菜单 + * @function registerMenu + * @param {string[]} triggers + * @param {string} argument_hint + * @param {string} usage + * @param {string} sub_id + */ + async registerMenu(triggers: string[], argument_hint: string, usage: string, sub_id: string): Promise { + await this.sendPacket('reg_menu', { + triggers, + argument_hint, + usage, + sub_id + }); + return; + } +} + +/** + * 注册必要的推送数据包 + * @function registerPushPacket + * @param {OmegaClient} client 客户端 + * @returns {void} + */ +async function registerPushPacket(client: OmegaClient): Promise { + await client.sendPacket('reg_login', {}); + await client.sendPacket('reg_logout', {}); + return; } /** * 创建一个客户端 * @function createClient * @param {string} host (可选) 自定义连接的ip/域名和端口 - * @param {LogLevel} level (可选) 日志等级,参考log4js + * @param {logLevel} level (可选) 日志等级,参考log4js + * @returns {OmegaClient} */ -export function createClient(host?: string, level?: LogLevel) { +export function createClient(host?: string, level?: logLevel): OmegaClient { let _host; if (host) { _host = `ws://${host}/omega_side`; @@ -125,4 +257,44 @@ export function createClient(host?: string, level?: LogLevel) { // define /** 日志等级 */ -export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'mark' | 'off' +export type logLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'mark' | 'off' + +/** 数据包func名称 */ +export type packetFuncName = + 'echo' + | 'regMCPKt' + | 'reg_mc_packet' + | 'query_packet_name' + | 'send_ws_cmd' + | 'send_player_cmd' + | 'send_wo_cmd' + | 'send_fb_cmd' + | 'get_uqholder' + | 'get_players_list' + | 'reg_menu' + | 'player.next_input' + | 'player.say_to' + | 'player.title_to' + | 'player.subtitle_to' + | 'player.actionbar_to' + | 'player.pos' + | 'player.set_data' + | 'player.get_data' + | 'reg_login' + | 'reg_logout' + | 'reg_block_update' + | 'query_item_mapping' + | 'query_block_mapping' + | 'query_memory_scoreboard' + | 'send_qq_msg' + | 'data.get_root_dir' + +/** 可订阅事件 */ +export type clientEvents = + 'omega.response' + | 'omega.push' + | 'omega.playerLogin' + | 'omega.playerLogout' + | 'omega.menuTriggered' + | 'omega.mcPkt' + | 'omega.blockUpdate' diff --git a/lib/index.ts b/lib/index.ts index 2a8613f..6473e91 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1 +1,2 @@ -export { createClient } from './client' +export { createClient, OmegaClient, logLevel, packetFuncName, clientEvents } from './client' +export { Player, playerInfo } from './player' diff --git a/lib/player.ts b/lib/player.ts new file mode 100644 index 0000000..6822f96 --- /dev/null +++ b/lib/player.ts @@ -0,0 +1,163 @@ +import {OmegaClient} from "./client"; + +export class Player { + + /** + * 客户端实例 + * + * 注意:请不要调用此处的客户端 + * @private + */ + #client: OmegaClient + + name: string = "Unknown" + uuid: string = "00000000-0000-0000-0000-000000000000" + uniqueID: number = 0 + + constructor(client: OmegaClient, playerInfo: playerInfo) { + this.#client = client; + this.name = playerInfo.name; + Object.freeze(this.name); + this.uuid = playerInfo.uuid; + Object.freeze(this.uuid); + this.uniqueID = playerInfo.uniqueID; + Object.freeze(this.uniqueID); + } + + /** + * 获取玩家的下一句话 + * @param {string} hint 提示语 + * @returns {string} + */ + async requestNextInput(hint:string):Promise { + const nextInputPacket = await this.#client.sendPacket('player.next_input', { + player: this.name, + hint + }); + if(nextInputPacket.data.success && nextInputPacket.data.input !== undefined) { + return nextInputPacket.data.input; + } + throw Error(`Cannot get player's next input, Because: ${nextInputPacket.data.err}`); + } + + /** + * 向该玩家发送一个信息 + * @function sayTo + * @param {string} message 信息 + * @returns {void} + */ + async sayTo(message:string):Promise { + await this.#client.sendPacket('player.say_to', { + player: this.name, + msg: message + }); + return; + } + + /** + * 向该玩家发送一个title信息 + * @function titleTo + * @param {string} message 信息 + * @returns {void} + */ + async titleTo(message:string):Promise { + await this.#client.sendPacket('player.title_to', { + player: this.name, + msg: message + }); + return; + } + + /** + * 向该玩家发送一个subtitle信息 + * @function subtitleTo + * @param {string} message 信息 + * @returns {void} + */ + async subtitleTo(message:string):Promise { + await this.#client.sendPacket('player.subtitle_to', { + player: this.name, + msg: message + }); + return; + } + + /** + * 向该玩家发送一个actionbar信息 + * @function actionbarTo + * @param {string} message 信息 + * @returns {void} + */ + async actionbarTo(message:string):Promise { + await this.#client.sendPacket('player.actionbar_to', { + player: this.name, + msg: message + }); + return; + } + + /** + * 获取玩家当前坐标 + * @param {string} limit 选择器,例如@a[name=[player],tag=abc] + * @returns {number[]} 坐标数组[x,y,z] + */ + async getPos(limit:string):Promise { + const playerPosPacket = await this.#client.sendPacket('player.pos', { + player: this.name, + limit + }); + if(playerPosPacket.data.success) { + return playerPosPacket.data.pos; + } + throw Error("Unable to get the player's position, is the player offline/in another dimension???"); + } + + /** + * 给该玩家设置一个数据 + * @function setData + * @param {string} key 键 + * @param {any} value 值 + * @returns {void} + */ + async setData(key:string, value:any):Promise { + await this.#client.sendPacket('player.set_data', { + player: this.name, + entry: key, + data: value + }); + } + + /** + * 通过键来获取该玩家的指定数据 + * @function getData + * @param {string} key 键 + * @returns {any} 对应数据 + */ + async getData(key:string):Promise { + const getDataPacket = await this.#client.sendPacket('player.get_data', { + player: this.name, + entry: key + }); + if(getDataPacket.data.found) { + return getDataPacket.data.data; + } + throw Error(`Unable to get the player's data: ${key}`); + } + + /** + * 将该玩家踢出 + * @function kick + * @param {string} reason 理由 + * @returns {void} + */ + async kick(reason?:string):Promise { + await this.#client.execCmd(reason?`kick ${this.name} ${reason}`:`kick ${this.name}`); + } +} + +export type playerInfo = { + name: string, + runtimeID: number, + uuid: string, + uniqueID: number +} diff --git a/package.json b/package.json index d5c7ab1..2a35272 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "light-omega", "author": "codepwn", "description": "Omega ws client in FastBuilder.", - "version": "0.1.2", - "publish_time": "2022-8-26", + "version": "0.2.0", + "publish_time": "2022-8-29", "main": "./lib/index.js", "typings": "./lib/index.d.ts", "repository": "https://github.com/CodePwn2021/light-omega", diff --git a/test/index.cjs b/test/index.cjs index c598fd7..33888fa 100644 --- a/test/index.cjs +++ b/test/index.cjs @@ -1,17 +1,27 @@ +/** + * 测试用,请勿参照学习 + */ async function init() { - const omgs = await import('../lib/client.js'); + const omg_client = await import('../lib/client.js'); - const omg = omgs.createClient(); + const omg = omg_client.createClient(); omg.on('omega.push', (res) => { console.log('OmegaPush', res); }); - // register packet + // client的大部分函数需要在ready事件之后才能执行 omg.on('omega.ready', onReady); async function onReady() { - const res = await omg.execCmd('list'); - console.log('Execute Cmd!!!!!', res); + // 在这里随便干点什么 + const res = await omg.sendPacket('get_players_list', {}); + console.log(JSON.stringify(res)); + const player = await omg.getPlayer(omg.bot_name); + console.log(player); + const echo = await omg.sendPacket('echo', { + message: 'hello, light-omega!' + }); + console.log(echo); } }