@@ -133,7 +133,12 @@ export namespace event {
 | 
			
		||||
            // @ts-ignore
 | 
			
		||||
            let executor = exec.name || exec.executor || '[anonymous]'
 | 
			
		||||
            // noinspection JSUnusedGlobalSymbols
 | 
			
		||||
            var listener = this.register(eventCls, this.execute(name, exec, eventCls), priority, ignoreCancel)
 | 
			
		||||
            var listener = this.register(
 | 
			
		||||
                eventCls,
 | 
			
		||||
                this.execute(name, exec, eventCls),
 | 
			
		||||
                priority,
 | 
			
		||||
                ignoreCancel
 | 
			
		||||
            )
 | 
			
		||||
            var listenerMap = this.listenerMap
 | 
			
		||||
            // add to cache Be used for close plugin to close event
 | 
			
		||||
            if (!listenerMap[name]) listenerMap[name] = []
 | 
			
		||||
@@ -141,11 +146,21 @@ export namespace event {
 | 
			
		||||
                if (off['offed']) return
 | 
			
		||||
                off['offed'] = true
 | 
			
		||||
                this.unregister(eventCls, listener)
 | 
			
		||||
                console.debug(i18n.translate("ms.api.event.unregister", { name, event: this.class2Name(eventCls), exec: executor }))
 | 
			
		||||
                console.debug(i18n.translate("ms.api.event.unregister", {
 | 
			
		||||
                    name,
 | 
			
		||||
                    event: this.class2Name(eventCls),
 | 
			
		||||
                    exec: executor
 | 
			
		||||
                }))
 | 
			
		||||
            }
 | 
			
		||||
            listenerMap[name].push(off)
 | 
			
		||||
            // noinspection JSUnresolvedVariable
 | 
			
		||||
            console.debug(i18n.translate("ms.api.event.register", { name, event: this.class2Name(eventCls), exec: executor }))
 | 
			
		||||
            console.debug(i18n.translate("ms.api.event.register", {
 | 
			
		||||
                name,
 | 
			
		||||
                event: this.class2Name(eventCls),
 | 
			
		||||
                exec: executor,
 | 
			
		||||
                priority,
 | 
			
		||||
                ignore: ignoreCancel
 | 
			
		||||
            }))
 | 
			
		||||
            return off
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
function exit() {
 | 
			
		||||
    var http = require('@ccms/common/dist/http').default
 | 
			
		||||
    function upgradeModules(core) {
 | 
			
		||||
        if (base.version && global.ScriptEngineVersion != core['dist-tags']['latest']) {
 | 
			
		||||
            var Paths = Java.type('java.nio.file.Paths')
 | 
			
		||||
            base.save(Paths.get(root, "upgrade"), core['dist-tags']['latest'])
 | 
			
		||||
            console.info('@ccms/core found new version ' + core['dist-tags']['latest'] + ' will upgrade after reboot!')
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    upgradeModules(http.get('https://registry.npmmirror.com/@ccms/core'))
 | 
			
		||||
    console.debug('exit finish!')
 | 
			
		||||
}
 | 
			
		||||
exit()
 | 
			
		||||
@@ -50,14 +50,7 @@ class MiaoScriptCore {
 | 
			
		||||
        this.pluginManager.disable(this.pluginManager.getPlugins())
 | 
			
		||||
        this.taskManager.disable()
 | 
			
		||||
        process.emit('core.after.disable')
 | 
			
		||||
        try {
 | 
			
		||||
            engineLoad({
 | 
			
		||||
                script: http.get("https://ms.yumc.pw/api/plugin/download/name/exit"),
 | 
			
		||||
                name: 'core/exit.js'
 | 
			
		||||
            })
 | 
			
		||||
        } catch (error: any) {
 | 
			
		||||
            console.debug(error)
 | 
			
		||||
        }
 | 
			
		||||
        loadCoreScript('exit')
 | 
			
		||||
        process.emit('core.before.exit')
 | 
			
		||||
        process.exit(0)
 | 
			
		||||
        console.i18n("ms.core.engine.disable.finish", {
 | 
			
		||||
@@ -98,18 +91,27 @@ function detectServer(): constants.ServerType {
 | 
			
		||||
    throw Error('Unknow Server Type...')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initialize() {
 | 
			
		||||
    process.emit('core.before.initialize')
 | 
			
		||||
    global.ScriptSlowExecuteTime = 30
 | 
			
		||||
    global.ScriptEngineVersion = require('../package.json').version
 | 
			
		||||
function loadCoreScript(name) {
 | 
			
		||||
    try {
 | 
			
		||||
        let scriptname = name + (global.debug ? '-debug' : '')
 | 
			
		||||
        engineLoad({
 | 
			
		||||
            script: http.get("https://ms.yumc.pw/api/plugin/download/name/initialize"),
 | 
			
		||||
            name: 'core/initialize.js'
 | 
			
		||||
            script: http.get(`https://ms.yumc.pw/api/plugin/download/name/${scriptname}`),
 | 
			
		||||
            name: `core/${scriptname}.js`
 | 
			
		||||
        })
 | 
			
		||||
    } catch (error: any) {
 | 
			
		||||
        console.debug(error)
 | 
			
		||||
        if (global.debug) {
 | 
			
		||||
            console.debug(error)
 | 
			
		||||
            console.ex(error)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initialize() {
 | 
			
		||||
    process.emit('core.before.initialize')
 | 
			
		||||
    global.ScriptSlowExecuteTime = 50
 | 
			
		||||
    global.ScriptEngineVersion = require('../package.json').version
 | 
			
		||||
    global.setGlobal('loadCoreScript', loadCoreScript)
 | 
			
		||||
    loadCoreScript('initialize')
 | 
			
		||||
    try {
 | 
			
		||||
        let corePackageStartTime = new Date().getTime()
 | 
			
		||||
        container.bind(ContainerInstance).toConstantValue(container)
 | 
			
		||||
@@ -141,8 +143,12 @@ function initialize() {
 | 
			
		||||
        process.emit('core.after.initialize')
 | 
			
		||||
        return disable
 | 
			
		||||
    } catch (error: any) {
 | 
			
		||||
        console.i18n("ms.core.initialize.error", { error })
 | 
			
		||||
        console.ex(error)
 | 
			
		||||
        if (console.console) {
 | 
			
		||||
            console.i18n("ms.core.initialize.error", { error })
 | 
			
		||||
            console.ex(error)
 | 
			
		||||
        } else {
 | 
			
		||||
            error.printStackTrace()
 | 
			
		||||
        }
 | 
			
		||||
        return () => console.i18n('ms.core.engine.disable.abnormal')
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,68 +0,0 @@
 | 
			
		||||
global.initialize = function () {
 | 
			
		||||
    var mspmc = 'https://ms.yumc.pw/api/plugin/download/name/'
 | 
			
		||||
    var artifact = 'https://ci.yumc.pw/job/Minecraft/job/MiaoScript/lastSuccessfulBuild/artifact'
 | 
			
		||||
 | 
			
		||||
    var fs = require('@ccms/common/dist/fs')
 | 
			
		||||
    var http = require('@ccms/common/dist/http').default
 | 
			
		||||
 | 
			
		||||
    function updateJar() {
 | 
			
		||||
        var DocumentBuilderFactory = Java.type('javax.xml.parsers.DocumentBuilderFactory')
 | 
			
		||||
        var URLDecoder = Java.type('java.net.URLDecoder')
 | 
			
		||||
        var pom = DocumentBuilderFactory.newInstance().newDocumentBuilder()
 | 
			
		||||
            .parse(artifact + "/pom.xml")
 | 
			
		||||
        var latestVersion = pom.getElementsByTagName("version").item(0).getTextContent()
 | 
			
		||||
        if (base.version != latestVersion) {
 | 
			
		||||
            var pluginFolder = fs.file(fs.concat(root, '..'))
 | 
			
		||||
            var updateFolder = fs.concat(pluginFolder, 'update')
 | 
			
		||||
            fs.mkdirs(updateFolder)
 | 
			
		||||
            var filePath = base.getInstance().class.classLoader.getURLs()[0]
 | 
			
		||||
            var pluginFile = fs.file(URLDecoder.decode(filePath.getFile(), "UTF-8"))
 | 
			
		||||
            var updateFile = fs.file(updateFolder, pluginFile.getName())
 | 
			
		||||
            http.download(artifact + "/target/MiaoScript.jar", updateFile.getAbsolutePath())
 | 
			
		||||
            console.info('MiaoScript found new version ' + latestVersion + ' will upgrade after reboot!')
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function upgradeModules(core) {
 | 
			
		||||
        if (base.version && global.ScriptEngineVersion != core['dist-tags']['latest']) {
 | 
			
		||||
            var Paths = Java.type('java.nio.file.Paths')
 | 
			
		||||
            base.save(Paths.get(root, "upgrade"), core['dist-tags']['latest'])
 | 
			
		||||
            console.info('@ccms/core found new version ' + core['dist-tags']['latest'] + ' will upgrade after reboot!')
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    var pluginFolder = fs.concat(root, 'plugins')
 | 
			
		||||
    var updateFolder = fs.concat(pluginFolder, 'update')
 | 
			
		||||
    var pluginFile = fs.concat(pluginFolder, 'MiaoScriptPackageManager.js')
 | 
			
		||||
    if (!fs.exists(pluginFile)) {
 | 
			
		||||
        fs.mkdirs(pluginFile)
 | 
			
		||||
        base.save(fs.concat(updateFolder, 'MiaoScriptPackageManager.auto.install'), '.')
 | 
			
		||||
    }
 | 
			
		||||
    fs.list(updateFolder).forEach(function (path) {
 | 
			
		||||
        var file = path.toFile()
 | 
			
		||||
        if (file.exists()) {
 | 
			
		||||
            var filename = file.getName()
 | 
			
		||||
            if (filename.endsWith(".auto.install")) {
 | 
			
		||||
                var pluginName = filename.replace('.auto.install', '')
 | 
			
		||||
                var pluginFile = fs.concat(pluginFolder, pluginName + '.js')
 | 
			
		||||
                if (!fs.exists(pluginFile)) {
 | 
			
		||||
                    var pluginTemp = pluginFile + '.tmp'
 | 
			
		||||
                    http.download(mspmc + pluginName, pluginTemp)
 | 
			
		||||
                    fs.move(pluginTemp, pluginFile, true)
 | 
			
		||||
                }
 | 
			
		||||
                base.delete(file)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
    try {
 | 
			
		||||
        Java.type("org.bukkit.Bukkit")
 | 
			
		||||
        updateJar()
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        console.debug(error)
 | 
			
		||||
        if (global.debug) {
 | 
			
		||||
            console.ex(error)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    upgradeModules(http.get('https://registry.npmmirror.com/@ccms/core'))
 | 
			
		||||
    console.debug('initialize finish!')
 | 
			
		||||
}
 | 
			
		||||
global.initialize()
 | 
			
		||||
delete global.initialize
 | 
			
		||||
@@ -12,6 +12,7 @@ ms.core.plugin.initialize: "Initialization MiaoScript Plugin System. Please wait
 | 
			
		||||
ms.core.plugin.completed: "MiaoScript Plugin System loading completed({time}s)!"
 | 
			
		||||
ms.core.engine.completed: "MiaoScript ScriptEngine loading completed... Done({time}s)!"
 | 
			
		||||
ms.core.engine.disable: "Disable MiaoScript Engine..."
 | 
			
		||||
ms.core.engine.disable.finish: "MiaoScript framework {loader} engine {version} 关闭完成... 耗时({time}s)!"
 | 
			
		||||
ms.core.engine.disable.abnormal: "abnormal Initialization MiaoScript Engine. Skip disable step..."
 | 
			
		||||
 | 
			
		||||
ms.api.event.resource.not.found: "Can't Mapping Event Because not found Resources {resource}!"
 | 
			
		||||
@@ -21,16 +22,20 @@ ms.api.event.not.found: "§6Plugin §b{name} §6register {event} error. event no
 | 
			
		||||
ms.api.event.execute.slow: "§cWARN! §6Plugin §b{name} §6execute §d{event} §6evnet §ccost §4{cost}ms !"
 | 
			
		||||
ms.api.event.execute.error: "§6Plugin §b{name} §6execute §d{event} §6event error §4{ex}"
 | 
			
		||||
ms.api.event.listen.plugin.name.empty: "Plugin name can't be empty!"
 | 
			
		||||
ms.api.event.register: "[{name}] register event {event}"
 | 
			
		||||
ms.api.event.register: "[{name}] register event {event} priority {priority} ignoreCancelled {ignore}"
 | 
			
		||||
ms.api.event.unregister: "[{name}] unregister event {event}"
 | 
			
		||||
 | 
			
		||||
ms.api.command.register.input.error: "CommandExec Must be a function... Input: {exec}"
 | 
			
		||||
ms.api.command.register: "[{plugin}] register command {name}({cmd})..."
 | 
			
		||||
ms.api.command.unregister: "[{plugin}] unregister command {name}..."
 | 
			
		||||
ms.api.command.execute.slow: "§cWarn. §6Player §a{player} §6exec §b{plugin} §6Plugin §d{command} {args} §6Command §cCost §4{cost}ms !"
 | 
			
		||||
ms.api.command.execute.error: "§6Player {player} §6exec §b{plugin} §6Plugin Command §d{command} {args} §6error §4{ex}"
 | 
			
		||||
ms.api.command.tab.completer.slow: "§cWarn. §6Player §a{player} §6exec §b{plugin} §6Plugin §d{command} {args} §6TabComplete §cCost §4{cost}ms !"
 | 
			
		||||
ms.api.command.tab.completer.error: "§6Player {player} §6exec §b{plugin} §6Plugin TabComplete §d{command} {args} §6error §4{ex}"
 | 
			
		||||
 | 
			
		||||
ms.plugin.initialize: "Initialization MiaoScript Plugin System: Plugin: {plugin} Loader: {loader}..."
 | 
			
		||||
ms.plugin.event.map: "Total {count} {type} Event Mapping Complate..."
 | 
			
		||||
ms.plugin.event.map.error: "Mapping {type} Event Failed. Error: {error}"
 | 
			
		||||
ms.plugin.manager.scan: "Scanner {scanner} Scanning Plugins in {folder} ..."
 | 
			
		||||
ms.plugin.manager.scan.finish: "Scanner {scanner} Found {size} Plugins in {folder} Start Build..."
 | 
			
		||||
ms.plugin.manager.initialize.error: "§6Plugin §b{name} §6initialize error §4{ex}"
 | 
			
		||||
 
 | 
			
		||||
@@ -22,8 +22,9 @@ ms.api.event.not.found: "§6插件 §b{name} §6注册事件 §c{event} §6失
 | 
			
		||||
ms.api.event.execute.slow: "§c注意! §6插件 §b{name} §6处理 §d{event} §6事件 §c耗时 §4{cost}ms !"
 | 
			
		||||
ms.api.event.execute.error: "§6插件 §b{name} §6处理 §d{event} §6事件时发生异常 §4{ex}"
 | 
			
		||||
ms.api.event.listen.plugin.name.empty: "插件名称为空 请检查传入参数!"
 | 
			
		||||
ms.api.event.register: "[{name}] 注册事件 {event} => 执行器 {exec}"
 | 
			
		||||
ms.api.event.register: "[{name}] 注册事件 {event} => 执行器 {exec} 优先级 {priority} 忽略取消 {ignore}"
 | 
			
		||||
ms.api.event.unregister: "[{name}] 注销事件 {event} => 执行器 {exec}"
 | 
			
		||||
 | 
			
		||||
ms.api.command.register.input.error: "CommandExec 必须为一个函数... 输入: {exec}"
 | 
			
		||||
ms.api.command.register: "[{plugin}] 注册命令 {name}({cmd})..."
 | 
			
		||||
ms.api.command.unregister: "[{plugin}] 注销命令 {name}..."
 | 
			
		||||
@@ -34,6 +35,7 @@ ms.api.command.tab.completer.error: "§6玩家 §a{player} §6执行 §b{plugin}
 | 
			
		||||
 | 
			
		||||
ms.plugin.initialize: "初始化 MiaoScript 插件系统: 实例: {plugin} 加载器: {loader}..."
 | 
			
		||||
ms.plugin.event.map: "总计 {count} 个 {type} 事件 映射完成..."
 | 
			
		||||
ms.plugin.event.map.error: "映射 {type} 事件 异常 将无法使用事件简称. Error: {error}"
 | 
			
		||||
ms.plugin.manager.scan: "扫描器 {scanner} 扫描 {folder} 中的插件..."
 | 
			
		||||
ms.plugin.manager.scan.finish: "扫描器 {scanner} 在 {folder} 中 发现 {size} 个插件 开始构建..."
 | 
			
		||||
ms.plugin.manager.initialize.error: "§6插件 §b{name} §6初始化错误 §4{ex}"
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@
 | 
			
		||||
        "test": "echo \"Error: run tests from root\" && exit 1"
 | 
			
		||||
    },
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
        "@types/crypto-js": "^4.1.1",
 | 
			
		||||
        "@types/js-yaml": "^4.0.5",
 | 
			
		||||
        "reflect-metadata": "^0.1.13",
 | 
			
		||||
        "rimraf": "^3.0.2",
 | 
			
		||||
@@ -30,7 +31,6 @@
 | 
			
		||||
        "@ccms/container": "^0.19.0",
 | 
			
		||||
        "@ccms/i18n": "^0.19.0",
 | 
			
		||||
        "crypto-js": "^4.1.1",
 | 
			
		||||
        "js-yaml": "^4.1.0",
 | 
			
		||||
        "yaml": "^1.10.2"
 | 
			
		||||
        "js-yaml": "^4.1.0"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -54,37 +54,34 @@ export class PluginCommandManager {
 | 
			
		||||
            cmdExecutor = (sender: any, command: string, args: string[]) => {
 | 
			
		||||
                let subcommand = args[0]
 | 
			
		||||
                let cmdKey = 'cmd' + subcommand
 | 
			
		||||
                let subcommandexec = pluginInstance[cmdKey]
 | 
			
		||||
                if (!subcommandexec) {
 | 
			
		||||
                    subcommandexec = pluginInstance['cmdmain']
 | 
			
		||||
                    subcommand = 'main'
 | 
			
		||||
                } else {
 | 
			
		||||
                    args.shift()
 | 
			
		||||
                }
 | 
			
		||||
                if (!subcommandexec) {
 | 
			
		||||
                    subcommand && pluginInstance.logger.sender(sender, '§4未知的子命令: §c' + subcommand)
 | 
			
		||||
                    pluginInstance.logger.sender(
 | 
			
		||||
                        sender,
 | 
			
		||||
                        pluginInstance['cmdhelp'] ?
 | 
			
		||||
                            `§6请执行 §b/${command} §ahelp §6查看帮助!` :
 | 
			
		||||
                            [
 | 
			
		||||
                                `§6插件: §b${pluginInstance.description.name}`,
 | 
			
		||||
                                `§6版本: §a${pluginInstance.description.version}`
 | 
			
		||||
                            ]
 | 
			
		||||
                    )
 | 
			
		||||
                if (!cmdSubCache.includes(subcommand)) {
 | 
			
		||||
                    if (!pluginInstance[cmd.executor].apply(pluginInstance, [sender, command, args])) {
 | 
			
		||||
                        subcommand && pluginInstance.logger.sender(sender, '§4未知的子命令: §c' + subcommand)
 | 
			
		||||
                        pluginInstance.logger.sender(
 | 
			
		||||
                            sender,
 | 
			
		||||
                            pluginInstance['cmdhelp'] ?
 | 
			
		||||
                                `§6请执行 §b/${command} §ahelp §6查看帮助!` :
 | 
			
		||||
                                [
 | 
			
		||||
                                    `§6插件: §b${pluginInstance.description.name}`,
 | 
			
		||||
                                    `§6版本: §a${pluginInstance.description.version}`
 | 
			
		||||
                                ]
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                    return
 | 
			
		||||
                }
 | 
			
		||||
                let subcommandexec = pluginInstance[cmdKey]
 | 
			
		||||
                let permission: string
 | 
			
		||||
                if (cmd.permission && sender.hasPermission) {
 | 
			
		||||
                    if (typeof cmd.permission == "string") {
 | 
			
		||||
                        permission = cmd.permission as string
 | 
			
		||||
                    } else {
 | 
			
		||||
                        permission = `${pluginInstance.description.name.toLocaleLowerCase()}.${command}.${subcommand}`
 | 
			
		||||
                        permission = `${pluginInstance.description.name.toLocaleLowerCase()}.${command}.${subcommand || 'main'}`
 | 
			
		||||
                    }
 | 
			
		||||
                    if (!sender.hasPermission(permission)) {
 | 
			
		||||
                        return pluginInstance.logger.sender(sender, `§c你需要 ${permission} 权限 才可执行此命令.`)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                args.shift()
 | 
			
		||||
                return subcommandexec.apply(pluginInstance, [sender, ...args])
 | 
			
		||||
            }
 | 
			
		||||
            let originCompleter = cmdCompleter
 | 
			
		||||
 
 | 
			
		||||
@@ -1,59 +1,36 @@
 | 
			
		||||
import * as yaml from 'js-yaml'
 | 
			
		||||
import * as fs from '@ccms/common/dist/fs'
 | 
			
		||||
import { plugin } from '@ccms/api'
 | 
			
		||||
import { provideSingleton } from '@ccms/container'
 | 
			
		||||
import { Autowired, Container, ContainerInstance, postConstruct, provideSingleton } from '@ccms/container'
 | 
			
		||||
 | 
			
		||||
import * as fs from '@ccms/common/dist/fs'
 | 
			
		||||
 | 
			
		||||
import { interfaces } from './interfaces'
 | 
			
		||||
import { getPluginConfigMetadata } from './utils'
 | 
			
		||||
 | 
			
		||||
export interface PluginConfigLoader {
 | 
			
		||||
    load(content: string): any
 | 
			
		||||
    dump(variable: any): string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class YamlPluginConfig implements PluginConfigLoader {
 | 
			
		||||
    load(content: string) {
 | 
			
		||||
        return yaml.load(content)
 | 
			
		||||
    }
 | 
			
		||||
    dump(variable: any): string {
 | 
			
		||||
        return yaml.dump(variable, { skipInvalid: true, lineWidth: 120 })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class JsonPluginConfig implements PluginConfigLoader {
 | 
			
		||||
    load(content: string) {
 | 
			
		||||
        return JSON.parse(content)
 | 
			
		||||
    }
 | 
			
		||||
    dump(variable: any): string {
 | 
			
		||||
        return JSON.stringify(variable, undefined, 4)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PluginConfig {
 | 
			
		||||
    /**
 | 
			
		||||
     * Save Config to File
 | 
			
		||||
     */
 | 
			
		||||
    readonly save?: () => void
 | 
			
		||||
    /**
 | 
			
		||||
     * Reload Config from File
 | 
			
		||||
     */
 | 
			
		||||
    readonly reload?: () => void
 | 
			
		||||
    [key: string]: any
 | 
			
		||||
}
 | 
			
		||||
import { PluginConfigLoader } from './config/interfaces'
 | 
			
		||||
import './config/loader/json-loader'
 | 
			
		||||
import './config/loader/yaml-loader'
 | 
			
		||||
 | 
			
		||||
@provideSingleton(PluginConfigManager)
 | 
			
		||||
export class PluginConfigManager {
 | 
			
		||||
    @Autowired(ContainerInstance)
 | 
			
		||||
    private container: Container
 | 
			
		||||
 | 
			
		||||
    private configLoaderMap = new Map<string, PluginConfigLoader>()
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.configLoaderMap.set("json", new JsonPluginConfig())
 | 
			
		||||
        let yaml = new YamlPluginConfig()
 | 
			
		||||
        this.configLoaderMap.set("yml", yaml)
 | 
			
		||||
        this.configLoaderMap.set("yaml", yaml)
 | 
			
		||||
        process.on('plugin.before.load', this.loadConfig.bind(this))
 | 
			
		||||
        process.on('plugin.after.disable', this.saveConfig.bind(this))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @postConstruct()
 | 
			
		||||
    initialize() {
 | 
			
		||||
        let configLoader = this.container.getAll<PluginConfigLoader>(PluginConfigLoader)
 | 
			
		||||
        configLoader.forEach((scanner) => {
 | 
			
		||||
            console.debug(`loading config loader ${scanner.type}...`)
 | 
			
		||||
            this.configLoaderMap.set(scanner.type, scanner)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getConfigLoader(format: string) {
 | 
			
		||||
        if (!this.configLoaderMap.has(format)) { throw new Error(`Unsupport config format ${format} !`) }
 | 
			
		||||
        return this.configLoaderMap.get(format)
 | 
			
		||||
@@ -73,13 +50,17 @@ export class PluginConfigManager {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private defienConfigProp(plugin: plugin.Plugin, metadata: interfaces.ConfigMetadata, value: any) {
 | 
			
		||||
    createConfig(plugin: plugin.Plugin, metadata: interfaces.ConfigMetadata, value: any) {
 | 
			
		||||
        Object.defineProperties(value, {
 | 
			
		||||
            'save': { value: () => this.saveConfig0(plugin, metadata) },
 | 
			
		||||
            'reload': { value: () => this.loadConfig0(plugin, metadata) }
 | 
			
		||||
        })
 | 
			
		||||
        return value
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private defienConfigProp(plugin: plugin.Plugin, metadata: interfaces.ConfigMetadata, value: any) {
 | 
			
		||||
        Object.defineProperty(plugin, metadata.variable, {
 | 
			
		||||
            value,
 | 
			
		||||
            value: this.createConfig(plugin, metadata, value),
 | 
			
		||||
            configurable: true
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										65
									
								
								packages/plugin/src/config/file-config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								packages/plugin/src/config/file-config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
import * as fs from '@ccms/common/dist/fs'
 | 
			
		||||
 | 
			
		||||
import { PluginConfig, PluginConfigLoader } from './interfaces'
 | 
			
		||||
 | 
			
		||||
export class PluginFileConfig implements PluginConfig {
 | 
			
		||||
    private loader: PluginConfigLoader
 | 
			
		||||
    private file: string
 | 
			
		||||
    constructor(loader: PluginConfigLoader, file: string, def = {}) {
 | 
			
		||||
        this.loader = loader
 | 
			
		||||
        this.file = file
 | 
			
		||||
        if (fs.exists(file)) {
 | 
			
		||||
            this.reload()
 | 
			
		||||
        } else {
 | 
			
		||||
            Object.assign(this, def)
 | 
			
		||||
        }
 | 
			
		||||
        this.initialize()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initialize() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    save() {
 | 
			
		||||
        base.save(this.file, this.loader.dump(this))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    reload() {
 | 
			
		||||
        Object.assign(this, this.loader.load(base.read(this.file)))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class PluginConfigFolder {
 | 
			
		||||
    private loader: PluginConfigLoader
 | 
			
		||||
    private folder: string
 | 
			
		||||
 | 
			
		||||
    private configCache = new Map<string, PluginFileConfig>()
 | 
			
		||||
 | 
			
		||||
    constructor(loader: PluginConfigLoader, folder: string) {
 | 
			
		||||
        this.loader = loader
 | 
			
		||||
        this.folder = folder
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createConfig(path: string, def = {}) {
 | 
			
		||||
        return new PluginFileConfig(this.loader, path, def)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getConfig(name: string, def = {}) {
 | 
			
		||||
        let path = fs.concat(this.folder, name)
 | 
			
		||||
        if (!this.configCache.has(path)) {
 | 
			
		||||
            this.configCache.set(path, this.createConfig(path, def))
 | 
			
		||||
        }
 | 
			
		||||
        return this.configCache.get(path)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    clear() {
 | 
			
		||||
        this.configCache.clear()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    save() {
 | 
			
		||||
        this.configCache.forEach((config) => config.save())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    reload() {
 | 
			
		||||
        this.configCache.forEach((config) => config.reload())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								packages/plugin/src/config/interfaces.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								packages/plugin/src/config/interfaces.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
export const PluginConfigLoader = Symbol.for('PluginConfigLoader')
 | 
			
		||||
export interface PluginConfigLoader {
 | 
			
		||||
    type: string
 | 
			
		||||
    load(content: string): any
 | 
			
		||||
    dump(variable: any): string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PluginConfig {
 | 
			
		||||
    /**
 | 
			
		||||
     * Save Config to File
 | 
			
		||||
     */
 | 
			
		||||
    readonly save?: () => void
 | 
			
		||||
    /**
 | 
			
		||||
     * Reload Config from File
 | 
			
		||||
     */
 | 
			
		||||
    readonly reload?: () => void
 | 
			
		||||
    [key: string]: any
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								packages/plugin/src/config/loader/json-loader.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								packages/plugin/src/config/loader/json-loader.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
import { provideSingletonNamed } from '@ccms/container'
 | 
			
		||||
 | 
			
		||||
import { PluginConfigLoader } from '../interfaces'
 | 
			
		||||
 | 
			
		||||
const LOADER_TYPE_NAME = 'json'
 | 
			
		||||
 | 
			
		||||
@provideSingletonNamed(PluginConfigLoader, LOADER_TYPE_NAME)
 | 
			
		||||
export class JsonPluginConfig implements PluginConfigLoader {
 | 
			
		||||
    type: string = LOADER_TYPE_NAME
 | 
			
		||||
 | 
			
		||||
    load(content: string) {
 | 
			
		||||
        return JSON.parse(content)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dump(variable: any): string {
 | 
			
		||||
        return JSON.stringify(variable, undefined, 4)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								packages/plugin/src/config/loader/yaml-loader.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								packages/plugin/src/config/loader/yaml-loader.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
import * as yaml from 'js-yaml'
 | 
			
		||||
import { provideSingletonNamed } from '@ccms/container'
 | 
			
		||||
 | 
			
		||||
import { PluginConfigLoader } from '../interfaces'
 | 
			
		||||
 | 
			
		||||
const LOADER_TYPE_NAME = 'yml'
 | 
			
		||||
 | 
			
		||||
@provideSingletonNamed(PluginConfigLoader, LOADER_TYPE_NAME)
 | 
			
		||||
export class YamlPluginConfig implements PluginConfigLoader {
 | 
			
		||||
    type: string = LOADER_TYPE_NAME
 | 
			
		||||
 | 
			
		||||
    load(content: string) {
 | 
			
		||||
        return yaml.load(content)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dump(variable: any): string {
 | 
			
		||||
        return yaml.dump(variable, { skipInvalid: true, lineWidth: 120 })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import './scanner/ms-scanner'
 | 
			
		||||
import './scanner/js-scanner'
 | 
			
		||||
import './scanner/mjs-scanner'
 | 
			
		||||
 | 
			
		||||
import './loader/ioc-loader'
 | 
			
		||||
import './loader/basic-loader'
 | 
			
		||||
 
 | 
			
		||||
@@ -64,9 +64,13 @@ export class PluginManagerImpl implements plugin.PluginManager {
 | 
			
		||||
    initialize() {
 | 
			
		||||
        if (this.pluginInstance === undefined) { throw new Error("Can't found Plugin Instance!") }
 | 
			
		||||
        if (this.initialized !== true) {
 | 
			
		||||
            process.emit('plugin.manager.before.initialize')
 | 
			
		||||
            process.emit('plugin.manager.before.initialize', this)
 | 
			
		||||
            console.i18n('ms.plugin.initialize', { plugin: this.pluginInstance, loader: Thread.currentThread().contextClassLoader })
 | 
			
		||||
            console.i18n('ms.plugin.event.map', { count: this.eventManager.mapEventName(), type: this.serverType })
 | 
			
		||||
            try {
 | 
			
		||||
                console.i18n('ms.plugin.event.map', { count: this.eventManager.mapEventName(), type: this.serverType })
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                console.i18n('ms.plugin.event.map.error', { error })
 | 
			
		||||
            }
 | 
			
		||||
            let pluginScanner = this.container.getAll<plugin.PluginScanner>(plugin.PluginScanner)
 | 
			
		||||
            pluginScanner.forEach((scanner) => {
 | 
			
		||||
                console.debug(`loading plugin sacnner ${scanner.type}...`)
 | 
			
		||||
@@ -78,14 +82,14 @@ export class PluginManagerImpl implements plugin.PluginManager {
 | 
			
		||||
                this.loaderMap.set(loader.type, loader)
 | 
			
		||||
            })
 | 
			
		||||
            this.initialized = true
 | 
			
		||||
            process.emit('plugin.manager.after.initialize')
 | 
			
		||||
            process.emit('plugin.manager.after.initialize', this)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    scan(folder: string): void {
 | 
			
		||||
        if (!folder) { throw new Error('plugin scan folder can\'t be empty!') }
 | 
			
		||||
        this.initialize()
 | 
			
		||||
        process.emit('plugin.manager.before.scan', folder)
 | 
			
		||||
        process.emit('plugin.manager.before.scan', folder, this)
 | 
			
		||||
        for (const [, scanner] of this.sacnnerMap) {
 | 
			
		||||
            try {
 | 
			
		||||
                console.i18n('ms.plugin.manager.scan', { scanner: scanner.type, folder })
 | 
			
		||||
@@ -104,13 +108,13 @@ export class PluginManagerImpl implements plugin.PluginManager {
 | 
			
		||||
                console.ex(error)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        process.emit('plugin.manager.after.scan', folder)
 | 
			
		||||
        process.emit('plugin.manager.after.scan', folder, this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    build(): void {
 | 
			
		||||
        process.emit('plugin.manager.before.build')
 | 
			
		||||
        process.emit('plugin.manager.before.build', this)
 | 
			
		||||
        this.buildPlugins()
 | 
			
		||||
        process.emit('plugin.manager.after.build')
 | 
			
		||||
        process.emit('plugin.manager.after.build', this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private logStage(plugin: plugin.Plugin, stage: string) {
 | 
			
		||||
@@ -144,7 +148,6 @@ export class PluginManagerImpl implements plugin.PluginManager {
 | 
			
		||||
            console.i18n("ms.plugin.manager.initialize.error", { name: loadMetadata.file, ex: error })
 | 
			
		||||
            console.ex(error)
 | 
			
		||||
        }
 | 
			
		||||
        console.console(`§6scanner: §b${loadMetadata.scanner.type} §ccan\'t load §6file §b${loadMetadata.file}. §eskip!`)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private loaderRequirePlugin(loadMetadata: plugin.PluginLoadMetadata, loader: plugin.PluginLoader) {
 | 
			
		||||
@@ -195,7 +198,9 @@ export class PluginManagerImpl implements plugin.PluginManager {
 | 
			
		||||
        let scanner = this.sacnnerMap.get(ext)
 | 
			
		||||
        if (!scanner) { throw new Error(`plugin scanner ${ext} can't found in sacnnerMap.`) }
 | 
			
		||||
        let metadata = this.loadAndRequirePlugin(scanner.read(file))
 | 
			
		||||
        let plugin = this.buildPlugin(metadata)
 | 
			
		||||
        this.buildPlugin(metadata)
 | 
			
		||||
        let plugin = metadata.target
 | 
			
		||||
        if (!plugin) { throw new Error(`plugin scanner ${ext} can't found in sacnnerMap.`) }
 | 
			
		||||
        this.load(plugin)
 | 
			
		||||
        this.enable(plugin)
 | 
			
		||||
        return plugin
 | 
			
		||||
@@ -255,10 +260,10 @@ export class PluginManagerImpl implements plugin.PluginManager {
 | 
			
		||||
            if (metadata?.depends?.length) {
 | 
			
		||||
                this.lazyMetadataMap.set(key, metadata)
 | 
			
		||||
            } else {
 | 
			
		||||
                this.buildPlugin(metadata)
 | 
			
		||||
                this.tryBuildPlugin(metadata)
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        this.lazyMetadataMap.forEach((metadata, key) => this.buildPlugin(metadata))
 | 
			
		||||
        this.lazyMetadataMap.forEach((metadata, key) => this.tryBuildPlugin(metadata))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private checkDepends(depends: string | string[]) {
 | 
			
		||||
@@ -273,24 +278,30 @@ export class PluginManagerImpl implements plugin.PluginManager {
 | 
			
		||||
        for (const depend of depends) { if (!this.nativePluginManager.has(depend)) loseDepends.push(depend) }
 | 
			
		||||
        return loseDepends
 | 
			
		||||
    }
 | 
			
		||||
    private buildPlugin(metadata: plugin.PluginMetadata) {
 | 
			
		||||
        process.emit(`plugin.before.build`, metadata)
 | 
			
		||||
 | 
			
		||||
    private tryBuildPlugin(metadata: plugin.PluginMetadata) {
 | 
			
		||||
        try {
 | 
			
		||||
            if (this.instanceMap.has(metadata.name)) { throw new Error(`Plugin ${metadata.name} is already load from ${metadata.source}...`) }
 | 
			
		||||
            if (!this.loaderMap.has(metadata.type)) { throw new Error(`§4无法加载插件 §b${metadata.name} §4请检查 §c${metadata.type} §4加载器是否正常启用!`) }
 | 
			
		||||
            if (!this.serverChecker.check(metadata.servers)) { throw new Error(`§6插件 §b${metadata.name} §c服务器类型不兼容(${metadata.servers.join(',')}) §6忽略加载...`) }
 | 
			
		||||
            let loseDepends = this.checkDepends(metadata.depends) || []
 | 
			
		||||
            if (loseDepends.length) { throw new Error(`§4无法加载插件 §b${metadata.name} §4请检查脚本依赖 §3[${loseDepends.join(',')}] §4是否安装完整!`) }
 | 
			
		||||
            let loseNativeDepends = this.checkNativeDepends(metadata.nativeDepends) || []
 | 
			
		||||
            if (loseNativeDepends.length) { throw new Error(`§4无法加载插件 §b${metadata.name} §4请检查插件依赖 §3[${loseNativeDepends.join(',')}] §4是否安装完整!`) }
 | 
			
		||||
            let pluginInstance = this.loaderMap.get(metadata.type).build(metadata)
 | 
			
		||||
            if (!pluginInstance) { throw new Error(`§4加载器 §c${metadata.type} §4加载插件 §c${metadata.name} §4失败!`) }
 | 
			
		||||
            this.instanceMap.set(metadata.name, pluginInstance)
 | 
			
		||||
            process.emit(`plugin.after.build`, metadata, pluginInstance)
 | 
			
		||||
            return pluginInstance
 | 
			
		||||
            return this.buildPlugin(metadata)
 | 
			
		||||
        } catch (error: any) {
 | 
			
		||||
            console.console(`§4无法加载插件 §b${metadata.name} §4构建插件失败!`)
 | 
			
		||||
            console.ex(error)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private buildPlugin(metadata: plugin.PluginMetadata) {
 | 
			
		||||
        process.emit(`plugin.before.build`, metadata)
 | 
			
		||||
        if (this.instanceMap.has(metadata.name)) { throw new Error(`Plugin ${metadata.name} is already load from ${metadata.source}...`) }
 | 
			
		||||
        if (!this.loaderMap.has(metadata.type)) { throw new Error(`§4无法加载插件 §b${metadata.name} §4请检查 §c${metadata.type} §4加载器是否正常启用!`) }
 | 
			
		||||
        if (!this.serverChecker.check(metadata.servers)) { throw new Error(`§6插件 §b${metadata.name} §c服务器类型不兼容(${metadata.servers.join(',')}) §6忽略加载...`) }
 | 
			
		||||
        let loseDepends = this.checkDepends(metadata.depends) || []
 | 
			
		||||
        if (loseDepends.length) { throw new Error(`§4无法加载插件 §b${metadata.name} §4请检查脚本依赖 §3[${loseDepends.join(',')}] §4是否安装完整!`) }
 | 
			
		||||
        let loseNativeDepends = this.checkNativeDepends(metadata.nativeDepends) || []
 | 
			
		||||
        if (loseNativeDepends.length) { throw new Error(`§4无法加载插件 §b${metadata.name} §4请检查插件依赖 §3[${loseNativeDepends.join(',')}] §4是否安装完整!`) }
 | 
			
		||||
        let pluginInstance = this.loaderMap.get(metadata.type).build(metadata)
 | 
			
		||||
        if (!pluginInstance) { throw new Error(`§4加载器 §c${metadata.type} §4加载插件 §c${metadata.name} §4失败!`) }
 | 
			
		||||
        metadata.target = pluginInstance
 | 
			
		||||
        this.instanceMap.set(metadata.name, pluginInstance)
 | 
			
		||||
        process.emit(`plugin.after.build`, metadata, pluginInstance)
 | 
			
		||||
        return pluginInstance
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,131 +0,0 @@
 | 
			
		||||
import { plugin } from "@ccms/api"
 | 
			
		||||
import http from '@ccms/common/dist/http'
 | 
			
		||||
import { provideSingletonNamed } from "@ccms/container"
 | 
			
		||||
 | 
			
		||||
import * as fs from '@ccms/common/dist/fs'
 | 
			
		||||
 | 
			
		||||
import * as CryptoJS from "crypto-js"
 | 
			
		||||
 | 
			
		||||
const SCANNER_TYPE_NAME = 'mjs'
 | 
			
		||||
 | 
			
		||||
@provideSingletonNamed(plugin.PluginScanner, SCANNER_TYPE_NAME)
 | 
			
		||||
export class SecretJSFileScanner implements plugin.PluginScanner {
 | 
			
		||||
    type: string = SCANNER_TYPE_NAME
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        global.setGlobal('MiaoScriptPackageCenterTokenCache', {})
 | 
			
		||||
        global.setGlobal('MiaoScriptPackageCenterQQCache', {})
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    scan(target: any): plugin.PluginLoadMetadata[] {
 | 
			
		||||
        return this.scanFolder(fs.concat(root, target)).map((file) => this.read(file))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    read(file: any): plugin.PluginLoadMetadata {
 | 
			
		||||
        return { file, type: this.type, scanner: this, loaded: false }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    load(metadata: plugin.PluginLoadMetadata): plugin.PluginLoadMetadata {
 | 
			
		||||
        if (metadata.type !== this.type) { return }
 | 
			
		||||
        this.updatePlugin(metadata.file)
 | 
			
		||||
        //@ts-ignore load plugin not use cache
 | 
			
		||||
        metadata.instance = require(metadata.file.toString(), {
 | 
			
		||||
            cache: false,
 | 
			
		||||
            hook: (origin) => {
 | 
			
		||||
                let info = JSON.parse(origin)
 | 
			
		||||
                if (!info || !info.encrypt) {
 | 
			
		||||
                    return this.exportErrorPlugin(
 | 
			
		||||
                        metadata.file,
 | 
			
		||||
                        `console.console('§4无效的加密插件: §c${metadata.file}')`
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                let qq = info.qq
 | 
			
		||||
                let encrypt = info.encrypt
 | 
			
		||||
                let token = this.decrypt(info.token, qq, info.timestamp)
 | 
			
		||||
                global.MiaoScriptPackageCenterQQCache[info.name] = qq
 | 
			
		||||
                global.MiaoScriptPackageCenterTokenCache[info.name] = token
 | 
			
		||||
                try {
 | 
			
		||||
                    let result = http.post('https://ms.yumc.pw/api/plugin/check', `{
 | 
			
		||||
                        "pid": ${info.pid},
 | 
			
		||||
                        "qq": ${qq},
 | 
			
		||||
                        "token": "${token}",
 | 
			
		||||
                        "source": true
 | 
			
		||||
                    }`)
 | 
			
		||||
                    if (result.code != 200) {
 | 
			
		||||
                        return this.exportErrorPlugin(
 | 
			
		||||
                            info.name,
 | 
			
		||||
                            `console.console('§6[§b圈云授权系统§6] §6插件: §b${info.name} §c授权效验失败: §4${result.msg}')`
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                    if (!result.data) {
 | 
			
		||||
                        return this.exportErrorPlugin(
 | 
			
		||||
                            info.name,
 | 
			
		||||
                            `console.console('§6[§b圈云授权系统§6] §6插件: §b${info.name} §c服务器返回异常数据.')`
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                    base.save(metadata.file, result.data.encrypt)
 | 
			
		||||
                    return result.data.source
 | 
			
		||||
                } catch (error) {
 | 
			
		||||
                }
 | 
			
		||||
                if (Date.now() / 1000 - info.timestamp > 7 * 24 * 60 * 60) {
 | 
			
		||||
                    return this.exportErrorPlugin(
 | 
			
		||||
                        info.name,
 | 
			
		||||
                        `console.console('§6[§b圈云授权系统§6] §6插件: §b${info.name} §c离线授权已过期.')`
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                return this.decrypt(encrypt, qq, token)
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        return metadata
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private decrypt(encrypt, key, iv) {
 | 
			
		||||
        return CryptoJS.AES.decrypt(encrypt,
 | 
			
		||||
            CryptoJS.enc.Utf8.parse(`${key}`.padEnd(16, "\0").substring(0, 16)), {
 | 
			
		||||
            iv: CryptoJS.enc.Utf8.parse(`${iv}`.padEnd(16, "\0").substring(0, 16))
 | 
			
		||||
        }).toString(CryptoJS.enc.Utf8)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private exportErrorPlugin(name, src) {
 | 
			
		||||
        return `module.exports = {
 | 
			
		||||
            description: {
 | 
			
		||||
                name: "${name}",
 | 
			
		||||
                version: "未授权",
 | 
			
		||||
                author: "未授权",
 | 
			
		||||
                type: "basic",
 | 
			
		||||
                source: __filename,
 | 
			
		||||
                target: this
 | 
			
		||||
            },
 | 
			
		||||
            load: function () { ${src} }
 | 
			
		||||
        }`
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private scanFolder(folder: any): string[] {
 | 
			
		||||
        var files = []
 | 
			
		||||
        this.checkUpdateFolder(folder)
 | 
			
		||||
        // must check file is exist maybe is a illegal symbolic link file
 | 
			
		||||
        fs.list(folder).forEach((path: any) => {
 | 
			
		||||
            let file = path.toFile()
 | 
			
		||||
            if (file.exists() && (file.getName().endsWith(".mjs.json") || file.getName().endsWith(".mjs"))) {
 | 
			
		||||
                files.push(file)
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        return files
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private checkUpdateFolder(path: any) {
 | 
			
		||||
        var update = fs.file(path, "update")
 | 
			
		||||
        if (!update.exists()) {
 | 
			
		||||
            update.mkdirs()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private updatePlugin(file: any) {
 | 
			
		||||
        var update = fs.file(fs.file(fs.file(file).parentFile, 'update'), file.name)
 | 
			
		||||
        if (update.exists()) {
 | 
			
		||||
            console.i18n("ms.plugin.manager.build.update", { name: file.name })
 | 
			
		||||
            fs.move(update, file, true)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										132
									
								
								packages/plugin/src/scanner/ms-scanner.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								packages/plugin/src/scanner/ms-scanner.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
			
		||||
import { plugin } from "@ccms/api"
 | 
			
		||||
import http from '@ccms/common/dist/http'
 | 
			
		||||
import { provideSingletonNamed } from "@ccms/container"
 | 
			
		||||
 | 
			
		||||
import * as fs from '@ccms/common/dist/fs'
 | 
			
		||||
 | 
			
		||||
import * as Utf8 from "crypto-js/enc-utf8"
 | 
			
		||||
import * as AES from "crypto-js/aes"
 | 
			
		||||
 | 
			
		||||
const SCANNER_TYPE_NAME = 'ms'
 | 
			
		||||
 | 
			
		||||
@provideSingletonNamed(plugin.PluginScanner, SCANNER_TYPE_NAME)
 | 
			
		||||
export class SecretJSFileScanner implements plugin.PluginScanner {
 | 
			
		||||
    type: string = SCANNER_TYPE_NAME
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        global.setGlobal('MiaoScriptPackageCenterQQCache', {})
 | 
			
		||||
        global.setGlobal('MiaoScriptPackageCenterInfoCache', {})
 | 
			
		||||
        global.setGlobal('MiaoScriptPackageCenterTokenCache', {})
 | 
			
		||||
        global.setGlobal('MiaoScriptPackageCenterResultCache', {})
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    scan(target: any): plugin.PluginLoadMetadata[] {
 | 
			
		||||
        return this.scanFolder(fs.concat(root, target)).map((file) => this.read(file))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    read(file: any): plugin.PluginLoadMetadata {
 | 
			
		||||
        return { file, type: this.type, scanner: this, loaded: false }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    load(metadata: plugin.PluginLoadMetadata): plugin.PluginLoadMetadata {
 | 
			
		||||
        if (metadata.type !== this.type) { return }
 | 
			
		||||
        this.updatePlugin(metadata.file)
 | 
			
		||||
        let encryptPlugin: any
 | 
			
		||||
        // @ts-ignore load plugin not use cache
 | 
			
		||||
        metadata.instance = require(metadata.file.toString(), {
 | 
			
		||||
            cache: false,
 | 
			
		||||
            beforeCompile: (source: string) => {
 | 
			
		||||
                try {
 | 
			
		||||
                    encryptPlugin = JSON.parse(source)
 | 
			
		||||
                } catch (error) {
 | 
			
		||||
                    encryptPlugin = require(metadata.file.toString())
 | 
			
		||||
                }
 | 
			
		||||
                if (!encryptPlugin || !encryptPlugin.encrypt) {
 | 
			
		||||
                    let filename = metadata.file.toString()
 | 
			
		||||
                    filename = filename.startsWith(root) ? filename.split(root)[1] : filename
 | 
			
		||||
                    throw new Error(`无效的加密插件: ${filename}.`)
 | 
			
		||||
                }
 | 
			
		||||
                let qq = encryptPlugin.qq
 | 
			
		||||
                let token = this.decrypt(encryptPlugin.token, qq, encryptPlugin.timestamp)
 | 
			
		||||
                global.MiaoScriptPackageCenterQQCache[encryptPlugin.name] = qq
 | 
			
		||||
                global.MiaoScriptPackageCenterInfoCache[encryptPlugin.name] = encryptPlugin
 | 
			
		||||
                global.MiaoScriptPackageCenterTokenCache[encryptPlugin.name] = token
 | 
			
		||||
                let encrypt = encryptPlugin.encrypt
 | 
			
		||||
                try {
 | 
			
		||||
                    let needUpdate = Date.now() / 1000 - encryptPlugin.timestamp > 3 * 24 * 60 * 60
 | 
			
		||||
                    console.console(`§6[§b圈云授权系统§6] §6加密插件: §b${encryptPlugin.name} §e授权效验中...`)
 | 
			
		||||
                    let result = http.post('https://ms.yumc.pw/api/plugin/check', {
 | 
			
		||||
                        "pid": encryptPlugin.pid,
 | 
			
		||||
                        "qq": qq,
 | 
			
		||||
                        "token": token,
 | 
			
		||||
                        "source": needUpdate
 | 
			
		||||
                    })
 | 
			
		||||
                    if (!result) { throw new Error(`授权服务器请求失败.`) }
 | 
			
		||||
                    global.MiaoScriptPackageCenterResultCache[encryptPlugin.name] = result
 | 
			
		||||
                    if (result.code != 200) {
 | 
			
		||||
                        throw new Error(`授权效验失败: ${result.msg}.`)
 | 
			
		||||
                    }
 | 
			
		||||
                    console.console(`§6[§b圈云授权系统§6] §6加密插件: §b${encryptPlugin.name} §a授权效验通过 正在构建插件...`)
 | 
			
		||||
                    if (needUpdate) {
 | 
			
		||||
                        if (!result.data) {
 | 
			
		||||
                            throw new Error(`授权效验失败: 服务器返回异常数据.`)
 | 
			
		||||
                        }
 | 
			
		||||
                        base.save(metadata.file, result.data.encrypt)
 | 
			
		||||
                        return result.data.source
 | 
			
		||||
                    }
 | 
			
		||||
                    return this.decrypt(encrypt, qq, token)
 | 
			
		||||
                } catch (error) {
 | 
			
		||||
                    console.console(`§6[§b圈云授权系统§6] §6加密插件: §b${encryptPlugin.name} §c授权效验失败: §4${error}.`)
 | 
			
		||||
                }
 | 
			
		||||
                if (Date.now() / 1000 - encryptPlugin.timestamp > 7 * 24 * 60 * 60) {
 | 
			
		||||
                    throw new Error(`授权效验失败: 离线授权已过期.`)
 | 
			
		||||
                }
 | 
			
		||||
                console.console(`§6[§b圈云授权系统§6] §6加密插件: §b${encryptPlugin.name} §e离线授权效验通过 正在解密插件...`)
 | 
			
		||||
                let decrypt = this.decrypt(encrypt, qq, token)
 | 
			
		||||
                console.console(`§6[§b圈云授权系统§6] §6加密插件: §b${encryptPlugin.name} §a离线解密成功 正在构建插件...`)
 | 
			
		||||
                return decrypt
 | 
			
		||||
            },
 | 
			
		||||
            afterCompile: () => {
 | 
			
		||||
                delete global.MiaoScriptPackageCenterResultCache[encryptPlugin.name].data
 | 
			
		||||
                global.MiaoScriptPackageCenterResultCache[encryptPlugin.name].timestamp = Date.now()
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        return metadata
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private decrypt(encrypt: string, key: string, iv: string) {
 | 
			
		||||
        return AES.decrypt(encrypt,
 | 
			
		||||
            Utf8.parse(`${key}`.padEnd(16, "\0").substring(0, 16)), {
 | 
			
		||||
            iv: Utf8.parse(`${iv}`.padEnd(16, "\0").substring(0, 16))
 | 
			
		||||
        }).toString(Utf8)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private scanFolder(folder: any): string[] {
 | 
			
		||||
        var files = []
 | 
			
		||||
        this.checkUpdateFolder(folder)
 | 
			
		||||
        // must check file is exist maybe is a illegal symbolic link file
 | 
			
		||||
        fs.list(folder).forEach((path: any) => {
 | 
			
		||||
            let file = path.toFile()
 | 
			
		||||
            if (file.exists() && file.getName().endsWith(".ms")) {
 | 
			
		||||
                files.push(file)
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        return files
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private checkUpdateFolder(path: any) {
 | 
			
		||||
        var update = fs.file(path, "update")
 | 
			
		||||
        if (!update.exists()) {
 | 
			
		||||
            update.mkdirs()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private updatePlugin(file: any) {
 | 
			
		||||
        var update = fs.file(fs.file(fs.file(file).parentFile, 'update'), file.name)
 | 
			
		||||
        if (update.exists()) {
 | 
			
		||||
            console.i18n("ms.plugin.manager.build.update", { name: file.name })
 | 
			
		||||
            fs.move(update, file, true)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -2,7 +2,6 @@
 | 
			
		||||
 * Module dependencies.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import * as server from "../server"
 | 
			
		||||
// const http = require("http")
 | 
			
		||||
// const Server = require("./server")
 | 
			
		||||
import { Server } from './server'
 | 
			
		||||
@@ -18,7 +17,7 @@ import { Server } from './server'
 | 
			
		||||
 | 
			
		||||
function attach(srv, options) {
 | 
			
		||||
    const engine = new Server(options)
 | 
			
		||||
    engine.attach(server.attach(srv, options), options)
 | 
			
		||||
    engine.attach(srv, options)
 | 
			
		||||
    return engine
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
/// <reference types="@ccms/nashorn" />
 | 
			
		||||
/// <reference types="@javatypes/tomcat-websocket-api" />
 | 
			
		||||
 | 
			
		||||
import * as server from './server'
 | 
			
		||||
import { Server, ServerOptions } from './socket.io'
 | 
			
		||||
 | 
			
		||||
interface SocketIOStatic {
 | 
			
		||||
@@ -38,9 +39,21 @@ interface SocketIOStatic {
 | 
			
		||||
 | 
			
		||||
type SocketStatic = SocketIOStatic & { Instance?: symbol }
 | 
			
		||||
 | 
			
		||||
let singletonServer: Server
 | 
			
		||||
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
let io: SocketStatic = function (pipeline: any, options: Partial<ServerOptions>) {
 | 
			
		||||
    return new Server(pipeline, options)
 | 
			
		||||
let io: SocketStatic = function io(pipeline: any, options: Partial<JavaServerOptions>, singleton = true) {
 | 
			
		||||
    if (singleton) {
 | 
			
		||||
        if (!singletonServer) {
 | 
			
		||||
            singletonServer = new Server(server.attach(pipeline, options), options)
 | 
			
		||||
            process.emit('websocket.create', singletonServer)
 | 
			
		||||
            process.on('exit', () => {
 | 
			
		||||
                singletonServer.close()
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        return singletonServer
 | 
			
		||||
    }
 | 
			
		||||
    return new Server(server.attach(pipeline, options), options)
 | 
			
		||||
}
 | 
			
		||||
io.Instance = Symbol("@ccms/websocket")
 | 
			
		||||
export default io
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ export enum ServerEvent {
 | 
			
		||||
export interface JavaServerOptions extends ServerOptions {
 | 
			
		||||
    event?: EventEmitter
 | 
			
		||||
    root?: string
 | 
			
		||||
    httpRequestHandler?: (ctx, request) => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export abstract class WebSocketServer extends EventEmitter {
 | 
			
		||||
@@ -30,7 +31,6 @@ export abstract class WebSocketServer extends EventEmitter {
 | 
			
		||||
        this.instance = instance
 | 
			
		||||
        this.options = options
 | 
			
		||||
        this.clients = new Map()
 | 
			
		||||
        console.debug('create websocket server from ' + this.constructor.name)
 | 
			
		||||
        this.initialize()
 | 
			
		||||
    }
 | 
			
		||||
    protected onconnect(handler: any) {
 | 
			
		||||
@@ -56,7 +56,7 @@ export abstract class WebSocketServer extends EventEmitter {
 | 
			
		||||
        if (this.clients.has(id)) {
 | 
			
		||||
            this.clients.has(id) && callback(this.clients.get(id))
 | 
			
		||||
        } else {
 | 
			
		||||
            console.debug('ignore execute', handler, 'callback', callback)
 | 
			
		||||
            console.trace('ignore execute', handler, 'callback', callback)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    public close() {
 | 
			
		||||
@@ -75,7 +75,7 @@ export const attach = (instance, options) => {
 | 
			
		||||
    options = Object.assign({
 | 
			
		||||
        event: new EventEmitter(),
 | 
			
		||||
        path: '/ws',
 | 
			
		||||
        root: root + '/wwwroot',
 | 
			
		||||
        root: root + Java.type("java.io.File").separatorChar + 'wwwroot',
 | 
			
		||||
    }, options)
 | 
			
		||||
    let WebSocketServerImpl = undefined
 | 
			
		||||
    if (instance.class.name.startsWith('io.netty.channel')) {
 | 
			
		||||
@@ -83,5 +83,6 @@ export const attach = (instance, options) => {
 | 
			
		||||
    } else {
 | 
			
		||||
        WebSocketServerImpl = require("./tomcat").TomcatWebSocketServer
 | 
			
		||||
    }
 | 
			
		||||
    console.debug('create websocket server from ' + WebSocketServerImpl.name)
 | 
			
		||||
    return new WebSocketServerImpl(instance, options)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,40 +23,46 @@ export class HttpRequestHandler extends HttpRequestHandlerAdapter {
 | 
			
		||||
        super()
 | 
			
		||||
        this.root = options.root
 | 
			
		||||
        this.ws = options.path
 | 
			
		||||
        if (options.httpRequestHandler) {
 | 
			
		||||
            this.httpRequestHandler = options.httpRequestHandler
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    channelRead0(ctx: any, request: any) {
 | 
			
		||||
        if (request.getUri().startsWith(this.ws)) {
 | 
			
		||||
            ctx.channel().attr(AttributeKeys.Request).set(request)
 | 
			
		||||
            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(this.root, 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)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }))
 | 
			
		||||
            this.httpRequestHandler(ctx, request)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    httpRequestHandler(ctx: any, request: any) {
 | 
			
		||||
        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(this.root, 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)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user