diff --git a/packages/plugins/src/MiaoConsole.ts b/packages/plugins/src/MiaoConsole.ts index 38e22e38..7b93a0c7 100644 --- a/packages/plugins/src/MiaoConsole.ts +++ b/packages/plugins/src/MiaoConsole.ts @@ -4,12 +4,10 @@ import { plugin as pluginApi, server, task } from '@ms/api' import { plugin, interfaces, cmd } from '@ms/plugin' -import { inject, Container, ContainerInstance, postConstruct } from '@ms/container' +import { inject, Container } from '@ms/container' import * as reflect from '@ms/common/dist/reflect' +import { Server as SocketIOServer, Socket as SocketIOSocket } from '@ms/websocket' -let clients: any[] = [] -let SPLIT_LINE = '\\M\\W\\S|T|S|S/L/T/' -let MessageHandle = Symbol.for('MiaoConsole:MessageHandle') const refList: Array<{ server: string, future: string }> = [ { server: 'an', future: 'g' },//spigot 1.12.2 { server: 'getServerConnection', future: 'f' },//after spigot 1.14.4 @@ -18,13 +16,6 @@ const refList: Array<{ server: string, future: string }> = [ @plugin({ name: 'MiaoConsole', version: '1.0.0', author: 'MiaoWoo', servers: ['!nukkit'], source: __filename }) export class MiaoConsole extends interfaces.Plugin { - public static GlobalContainer: Container - public static GlobalLogger: Console - - @inject(ContainerInstance) - private container: Container - @inject(pluginApi.PluginManager) - private PluginManager: pluginApi.PluginManager @inject(server.ServerType) private ServerType: string @inject(server.Server) @@ -32,31 +23,15 @@ export class MiaoConsole extends interfaces.Plugin { @inject(task.TaskManager) private Task: task.TaskManager - private pipeline: any + private pipeline: any; + private socketIOServer: SocketIOServer; @cmd() mconsole(sender: any, command: string, args: string[]) { - switch (args[0]) { - case "reload": - this.PluginManager.reload(this) - break - default: - } - } - - enable() { - MiaoConsole.GlobalLogger = this.logger - MiaoConsole.GlobalContainer = this.container } disable() { - if (this.pipeline) { - if (this.pipeline.names().contains('miao_detect')) { - this.pipeline.remove('miao_detect') - } - clients.forEach(c => c.close()) - this.container.unbind(MessageHandle) - } + this.socketIOServer?.close() } bukkitenable() { @@ -86,165 +61,44 @@ export class MiaoConsole extends interfaces.Plugin { }).later(300).timer(500).submit() } - reflectPromise(consoleServer) { + reflectPromise(consoleServer: any) { for (const ref of refList) { try { return reflect.on(consoleServer).call(ref.server).get(ref.future).get().get(0) } catch (error) { } } } - reflectChannel(promise) { + reflectChannel(promise: any) { if (!promise) { throw Error(`Can't found ServerConnection or ChannelFuture !`) } this.pipeline = reflect.on(promise).get('channel').get().pipeline() } injectMiaoDetect() { - let MiaoDetectHandler = getMiaoDetectHandler(); - this.pipeline.addFirst('miao_detect', new MiaoDetectHandler()) - this.container.bind(MessageHandle).toFunction(this.onmessage.bind(this)) + this.socketIOServer = new SocketIOServer(this.pipeline, { + path: '/ws' + }); + let namespace = this.socketIOServer.of('/MiaoConsole') + namespace.on('connect', (client: SocketIOSocket) => { + global.setGlobal('client', client); + client.on('type', (fn) => { + this.logger.console(`§6客户端 §b${client.id} §a新建连接...`) + fn && fn(this.ServerType) + client.emit('log', `Currect Server Version is ${this.Server.getVersion()}`) + }) + client.on('command', (cmd) => { + setTimeout(() => this.Server.dispatchConsoleCommand(cmd), 0) + client.emit('log', `§6命令: §b${cmd} §a执行成功!`) + }) + client.on('exec', (code) => { + try { + client.emit('log', this.Task.callSyncMethod(() => eval(code)) + '') + } catch (ex) { + client.emit('log', '§4代码执行异常\n' + console.stack(ex).join('\n')) + } + }) + client.on('disconnect', () => { + this.logger.console(`§6客户端 §b${client.id} §c断开连接...`) + }) + }) this.logger.info('Netty Channel Pipeline Inject MiaoDetectHandler Successful!') } - - onmessage(ctx: any, msg: any) { - let text: string = msg.text() - const [type, content] = text.split(SPLIT_LINE) - try { - var result = this[type](ctx, content) - } catch (ex) { - var result = '§4代码执行异常\n' + console.stack(ex).join('\n') - } - result && this.sendResult(ctx, "log", result) - } - - execCommand(ctx: any, cmd: string) { - setTimeout(() => this.Server.dispatchConsoleCommand(cmd), 0) - return `§6命令: §b${cmd} §a执行成功!` - } - - execCode(ctx: any, code: string) { - return this.Task.callSyncMethod(() => eval(code)) + ''; - } - - execDetect(ctx: any, cmd: string) { - switch (cmd) { - case "type": - this.sendResult(ctx, "type", this.ServerType) - return `Currect Server Version is ${this.Server.getVersion()}` - } - } - - sendResult(ctx: any, type: string, msg: string) { - let TextWebSocketFrame = getTextWebSocketFrame() - ctx.writeAndFlush(new TextWebSocketFrame(`${type}${SPLIT_LINE}${msg}`)) - } -} - -function getMiaoDetectHandler() { - const ChannelInboundHandlerAdapter = Java.type('io.netty.channel.ChannelInboundHandlerAdapter') - const CharsetUtil = Java.type('io.netty.util.CharsetUtil') - const MiaoDetectHandler = Java.extend(ChannelInboundHandlerAdapter, { - channelRead: (ctx: any, channel: any) => { - channel.pipeline().addFirst('miaowebsocket', new WebSocketHandler()) - ctx.fireChannelRead(channel) - } - }) - const TypeParameterMatcher = Java.type('io.netty.util.internal.TypeParameterMatcher') - const DefaultHttpResponse = Java.type('io.netty.handler.codec.http.DefaultHttpResponse') - const DefaultFullHttpResponse = Java.type('io.netty.handler.codec.http.DefaultFullHttpResponse') - const HttpHeaders = Java.type('io.netty.handler.codec.http.HttpHeaders') - const HttpVersion = Java.type('io.netty.handler.codec.http.HttpVersion') - const HttpResponseStatus = Java.type('io.netty.handler.codec.http.HttpResponseStatus') - const LastHttpContent = Java.type('io.netty.handler.codec.http.LastHttpContent') - const HttpServerCodec = Java.type('io.netty.handler.codec.http.HttpServerCodec') - const ChunkedWriteHandler = Java.type('io.netty.handler.stream.ChunkedWriteHandler') - const HttpObjectAggregator = Java.type('io.netty.handler.codec.http.HttpObjectAggregator') - const WebSocketServerProtocolHandler = Java.type('io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler') - const SimpleChannelInboundHandler = Java.type('io.netty.channel.SimpleChannelInboundHandler') - const FullHttpRequestMatcher = TypeParameterMatcher.get(base.getClass('io.netty.handler.codec.http.FullHttpRequest')) - const File = Java.type('java.io.File') - const Runnable = Java.type('java.lang.Runnable') - const RandomAccessFile = Java.type('java.io.RandomAccessFile') - const DefaultFileRegion = Java.type('io.netty.channel.DefaultFileRegion') - const ChannelFutureListener = Java.type('io.netty.channel.ChannelFutureListener') - const HttpRequestHandler = Java.extend(SimpleChannelInboundHandler, { - acceptInboundMessage: (msg: any) => { - return FullHttpRequestMatcher.match(msg) - }, - channelRead0: (ctx: any, request: any) => { - if ('/ws' == request.getUri()) { - ctx.fireChannelRead(request.retain()) - } else { - ctx.executor().execute(new Runnable({ - run: () => { - if (HttpHeaders.is100ContinueExpected(request)) { - ctx.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE)) - } - let filename = request.getUri().split('?')[0].substr(1) - let file = new File('/home/project/WebWorkSpace/MiaoConsole', filename || 'index.html') - if (!file.exists() || !file.isFile()) { - ctx.write(new DefaultHttpResponse(request.getProtocolVersion(), HttpResponseStatus.NOT_FOUND)) - ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT).addListener(ChannelFutureListener.CLOSE) - return - } - let response = new DefaultHttpResponse(request.getProtocolVersion(), HttpResponseStatus.OK) - response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html charset=UTF-8") - let raf = new RandomAccessFile(file, 'r') - let keepAlive = HttpHeaders.isKeepAlive(request) - if (keepAlive) { - response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, file.length()) - response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE) - } - ctx.write(response) - ctx.write(new DefaultFileRegion(raf.getChannel(), 0, raf.length())) - let future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) - if (!keepAlive) { - future.addListener(ChannelFutureListener.CLOSE) - } - } - })) - } - } - }) - const TextWebSocketFrameMatcher = TypeParameterMatcher.get(base.getClass('io.netty.handler.codec.http.websocketx.TextWebSocketFrame')) - const TextWebSocketFrameHandler = Java.extend(SimpleChannelInboundHandler, { - userEventTriggered: (ctx: any, evt: any) => { - if (evt == 'HANDSHAKE_COMPLETE') { - clients.push(ctx.channel()) - MiaoConsole.GlobalLogger.console(`new client §b${ctx.channel().id()} §aconnected...`) - } - }, - acceptInboundMessage: (msg: any) => { - return TextWebSocketFrameMatcher.match(msg) - }, - channelRead0: (ctx: any, msg: any) => { - MiaoConsole.GlobalContainer.get(MessageHandle)(ctx, msg) - } - }) - const WebSocketHandler = Java.extend(ChannelInboundHandlerAdapter, { - channelRead: function(ctx: any, msg: any) { - msg.markReaderIndex() - let message: string = msg.toString(CharsetUtil.UTF_8) - let channel = ctx.channel() - let pipeline = channel.pipeline() - if (message.indexOf('HTTP/1.1') > 0) { - pipeline.names().forEach(f => { - if (f == 'miaowebsocket' || f.indexOf('DefaultChannelPipeline') > -1) { return } - pipeline.remove(f) - }) - pipeline.addLast('http', new HttpServerCodec()) - pipeline.addLast('chunk', new ChunkedWriteHandler()) - pipeline.addLast('httpobj', new HttpObjectAggregator(64 * 1024)) - pipeline.addLast('http_request', new HttpRequestHandler()) - pipeline.addLast('websocket', new WebSocketServerProtocolHandler("/ws")) - pipeline.addLast('websocket_handler', new TextWebSocketFrameHandler()) - } - pipeline.remove('miaowebsocket') - msg.resetReaderIndex() - ctx.fireChannelRead(msg) - } - }) - return MiaoDetectHandler; -} - -function getTextWebSocketFrame() { - return Java.type('io.netty.handler.codec.http.websocketx.TextWebSocketFrame') } diff --git a/packages/plugins/src/MiaoScriptPackageManager.ts b/packages/plugins/src/MiaoScriptPackageManager.ts index a819603c..92c94b3a 100644 --- a/packages/plugins/src/MiaoScriptPackageManager.ts +++ b/packages/plugins/src/MiaoScriptPackageManager.ts @@ -1,9 +1,12 @@ import { plugin as pluginApi, task, server } from '@ms/api' -import * as fs from '@ms/common/dist/fs' +import { Translate } from '@ms/i18n' import { inject } from '@ms/container'; import { interfaces, plugin, cmd, tab } from '@ms/plugin' +import * as fs from '@ms/common/dist/fs' +import http from '@ms/common/dist/http' + let help = [ '§6========= §6[§aMiaoScriptPackageManager§6] 帮助 §aBy §b喵♂呜 §6=========', '§6/mpm §ainstall §e<插件名称> §6- §3安装插件', @@ -17,6 +20,20 @@ let help = [ '§6/mpm §crestart §6- §4重启MiaoScript脚本引擎' ]; +let langMap = { + 'list.header.install': '§6当前 §bMiaoScript §6已安装下列插件:', + 'list.header': '§6当前 §bMiaoScriptPackageCenter §6中存在下列插件:', + 'list.body': '§6插件名称: §b{name} §6版本: §a{version} §6作者: §3{author}', + 'plugin.not.exists': '§6插件 §b{name} §c不存在!', + 'plugin.unload.finish': '§6插件 §b{name} §a已卸载!', + 'plugin.reload.finish': '§6插件 §b{name} §a重载完成!', + 'plugin.name.empty': '§c请输入插件名称!', + 'cloud.update.finish': '§6成功从 §aMiaoScriptPackageCenter §6获取到 §a{length} §6个插件!', + 'cloud.not.exists': '§6当前 §aMiaoScriptPackageCenter §c不存在 §a{name} §c插件!' +} + +let fallbackMap = langMap + @plugin({ name: 'MiaoScriptPackageManager', prefix: 'PM', version: '1.0.0', author: 'MiaoWoo', source: __filename }) export class MiaoScriptPackageManager extends interfaces.Plugin { @inject(pluginApi.PluginManager) @@ -29,11 +46,16 @@ export class MiaoScriptPackageManager extends interfaces.Plugin { private server: server.Server private packageCache: any[] = []; - private packageNameCache: any[] = []; + private packageNameCache: string[] = []; + + private translate: Translate; load() { - this.taskManager.create(() => { - }).async().submit(); + this.translate = new Translate({ + langMap, + fallbackMap + }) + this.updateRepo(this.server.getConsoleSender()) } @cmd() @@ -41,134 +63,96 @@ export class MiaoScriptPackageManager extends interfaces.Plugin { this.taskManager.create(() => this.main(sender, command, args)).async().submit(); } + i18n(sender: any, name: string, params?: any) { + this.logger.sender(sender, this.translate.translate(name, params)) + } + main(sender: any, command: string, args: string[]) { - if (!args[0] || args[1] === 'help') { + let cmdKey = 'cmd' + args[0] + if (!this[cmdKey] || args[0] === 'help') { this.logger.sender(sender, help); return; } - switch (args[0]) { - case "list": - if (args[1]) { - this.logger.sender(sender, '§6当前 §bMiaoScript §6已安装下列插件:'); - this.pluginManager.getPlugins().forEach((plugin) => { - var desc = plugin.description; - this.logger.sender(sender, `§6插件名称: §b${desc.name} §6版本: §a${desc.version || '1.0'} §6作者: §3${desc.author || '未知'}`) - }) - } else { - this.logger.sender(sender, '§6当前 §bMiaoScriptPackageCenter §6中存在下列插件:'); - for (var pkgName in this.packageCache) { - var pkg = this.packageCache[pkgName]; - // console.sender(sender, '§6插件名称: §b%s §6版本: §a%s §6作者: §3%s'.format(pkg.name, pkg.version || '1.0', pkg.author || '未知')) - } - } - break; - case "install": - // if (args.length > 1) { - // download(sender, args[1]); - // } else { - // console.sender(sender, '§c请输入插件名称!') - // } - break; - case "uninstall": - break; - case "update": - // if (args.length > 1) { - // update(sender, args[1]); - // } else { - // load(); - // console.sender(sender, "§a仓库缓存刷新成功 共存在 §b" + Object.keys(packageCache).length + " §a个插件!") - // } - break; - case "upgrade": - if (args[3] === "engine") { - fs.del(fs.concat(root, '', '')) - } - break; - case "delete": - // if (args.length > 1) { - // del(sender, args[1]); - // } else { - // console.sender(sender, '§c请输入插件名称!') - // } - break; - case "load": - if (args.length > 1) { - var pname = args[1]; - if (!this.pluginManager.getPlugins().has(pname)) { - this.logger.sender(sender, `§6插件 §b${pname} §c不存在!`) - return - } - this.pluginManager.load(pname) - this.pluginManager.enable(pname) - this.logger.sender(sender, `§6插件 §b${pname} §a已加载!`) - } - break; - case "unload": - if (args.length > 1) { - var pname = args[1]; - if (!this.pluginManager.getPlugins().has(pname)) { - this.logger.sender(sender, `§6插件 §b${pname} §c不存在!`) - return - } - this.pluginManager.disable(pname) - this.logger.sender(sender, `§6插件 §b${pname} §a已卸载!`) - } - break; - case "reload": - if (args.length > 1) { - var pname = args[1]; - if (!this.pluginManager.getPlugins().has(pname)) { - this.logger.sender(sender, `§6插件 §b${pname} §c不存在!`) - return - } - this.pluginManager.reload(pname); - this.logger.sender(sender, `§6插件 §b${pname} §a重载完成!`) - } - break; - case "restart": - if (this.serverType === "sponge") { - setTimeout(() => this.server.dispatchConsoleCommand('sponge plugins reload'), 0) - return - } - try { - this.logger.sender(sender, '§6Reloading §3MiaoScript Engine...'); - ScriptEngineContextHolder.disableEngine(); - ScriptEngineContextHolder.enableEngine(); - this.logger.sender(sender, '§3MiaoScript Engine §6Reload §aSuccessful...'); - } catch (ex) { - this.logger.sender(sender, "§3MiaoScript Engine §6Reload §cError! ERR: " + ex); - this.logger.sender(sender, this.logger.stack(ex)); - } - break; - case "run": - args.shift(); - try { - var script = args.join(' '); - this.logger.sender(sender, '§b运行脚本:§r', script); - this.logger.sender(sender, '§a返回结果:§r', eval(script) || '§4没有返回结果!'); - } catch (ex) { - this.logger.sender(sender, this.logger.stack(ex)); - } - break; - case "create": - this.logger.sender(sender, `§4当前暂不支持生成插件模板!`); - // var name = args[1]; - // if (!name) { - // this.logger.sender(sender, '§4参数错误 /mpm create <插件名称> [作者] [版本] [主命令]'); - // return; - // } - // // var result = template.create(http.get(self.config.template)).render({ - // // name: name, - // // author: args[2] || 'MiaoWoo', - // // version: args[3] || '1.0', - // // command: args[4] || name.toLowerCase(), - // // }); - // // fs.save(fs.file(__dirname, name + '.js'), result); - // this.logger.sender(sender, '§6插件 §a' + name + ' §6已生成到插件目录...'); - break; - default: - this.logger.sender(sender, help); - break; + args.shift() + this[cmdKey](sender, ...args); + } + + cmdlist(sender, type: string = 'cloud') { + if (type == "install") { + this.i18n(sender, 'list.header.install') + this.pluginManager.getPlugins().forEach((plugin) => { + this.i18n(sender, 'list.body', plugin.description); + }) + } else { + this.i18n(sender, 'list.header') + for (var pkgName in this.packageCache) { + this.i18n(sender, 'list.body', this.packageCache[pkgName]); + } + } + } + + cmdinstall(sender: any, name: string) { + if (!name) { return this.i18n(sender, 'plugin.name.empty') } + this.download(sender, name); + } + + cmdupdate(sender: any, name: string) { + if (name) { + this.update(sender, name); + } else { + this.updateRepo(sender) + } + } + + cmdunload(sender: any, name: string) { + if (this.checkPlugin(sender, name)) { + this.pluginManager.disable(name) + this.i18n(sender, 'plugin.unload.finish', { name }) + } + } + + cmdreload(sender: any, name: string) { + if (this.checkPlugin(sender, name)) { + this.pluginManager.reload(name); + this.i18n(sender, 'plugin.reload.finish', { name }) + } + } + + checkPlugin(sender: any, name: string) { + if (name && this.pluginManager.getPlugins().has(name)) { return true } + this.i18n(sender, 'plugin.not.exists', { name }) + return false + } + + cmdrestart(sender: any) { + if (this.serverType === "sponge") { + setTimeout(() => this.server.dispatchConsoleCommand('sponge plugins reload'), 0) + return + } + try { + this.logger.sender(sender, '§6Reloading §3MiaoScript Engine...'); + ScriptEngineContextHolder.disableEngine(); + ScriptEngineContextHolder.enableEngine(); + this.logger.sender(sender, '§3MiaoScript Engine §6Reload §aSuccessful...'); + } catch (ex) { + this.logger.sender(sender, "§3MiaoScript Engine §6Reload §cError! ERR: " + ex); + this.logger.sender(sender, this.logger.stack(ex)); + } + } + + cmdrun(sender, ...args: any[]) { + try { + var script = args.join(' '); + this.logger.sender(sender, '§b运行脚本:§r', script); + this.logger.sender(sender, '§a返回结果:§r', eval(script) || '§4没有返回结果!'); + } catch (ex) { + this.logger.sender(sender, this.logger.stack(ex)); + } + } + + update(sender: any, name: string) { + if (!this.packageNameCache.includes(name)) { + this.i18n(sender, 'cloud.not.exists', { name }) } } @@ -177,6 +161,8 @@ export class MiaoScriptPackageManager extends interfaces.Plugin { if (args.length === 1) return ['list', 'install', 'update', 'upgrade', 'reload', 'restart', 'run', 'help', 'create']; if (args.length > 1) { switch (args[0]) { + case "list": + return ["install", "cloud"] case "install": return this.packageNameCache; case "update": @@ -188,4 +174,19 @@ export class MiaoScriptPackageManager extends interfaces.Plugin { } } } + + updateRepo(sender: any) { + this.taskManager.create(() => { + let result = http.get('http://ms.yumc.pw/api/plugin'); + for (const pl of result.data) { + this.packageCache[pl.name] = pl; + } + this.packageNameCache = Object.keys(this.packageCache); + this.i18n(sender, 'cloud.update.finish', { length: this.packageNameCache.length }) + }).async().submit(); + } + + download(sender, name) { + + } }