@ -29,6 +29,7 @@
|
||||
"@ccms/common": "^0.18.0",
|
||||
"@ccms/container": "^0.18.0",
|
||||
"@ccms/i18n": "^0.18.0",
|
||||
"crypto-js": "^4.1.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"yaml": "^1.10.2"
|
||||
}
|
||||
|
@ -35,7 +35,15 @@ export class PluginCommandManager {
|
||||
|
||||
private unregistryCommand(pluginInstance: plugin.Plugin) {
|
||||
let cmds = getPluginCommandMetadata(pluginInstance)
|
||||
cmds.forEach(cmd => this.CommandManager.off(pluginInstance, cmd.name))
|
||||
for (const cmd of cmds) {
|
||||
if (!this.ServerChecker.check(cmd.servers)) {
|
||||
console.debug(`[${pluginInstance.description.name}] ${cmd.target.constructor.name} incompatible command ${cmd.name} server(${cmd.servers}) ignore.`)
|
||||
continue
|
||||
}
|
||||
for (let command of [cmd.name, ...cmd.alias]) {
|
||||
this.CommandManager.off(pluginInstance, command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private generateAutoMainCommand(pluginInstance: plugin.Plugin, cmd: interfaces.CommandMetadata, tab: interfaces.CommandMetadata) {
|
||||
@ -46,17 +54,41 @@ export class PluginCommandManager {
|
||||
cmdExecutor = (sender: any, command: string, args: string[]) => {
|
||||
let subcommand = args[0]
|
||||
let cmdKey = 'cmd' + subcommand
|
||||
if (pluginInstance[cmdKey]) {
|
||||
let subcommandexec = pluginInstance[cmdKey]
|
||||
if (!subcommandexec) {
|
||||
args.shift()
|
||||
return pluginInstance[cmdKey].apply(pluginInstance, [sender, ...args])
|
||||
} else if (pluginInstance['cmdmain']) {
|
||||
return pluginInstance['cmdmain'].apply(pluginInstance, [sender, ...args])
|
||||
subcommandexec = pluginInstance['cmdmain']
|
||||
}
|
||||
pluginInstance.logger.sender(sender, '§4未知的子命令: §c' + subcommand)
|
||||
pluginInstance['cmdhelp'] && pluginInstance.logger.sender(sender, `§6请执行 §b/${command} §ahelp §6查看帮助!`)
|
||||
if (!subcommandexec) {
|
||||
subcommand && pluginInstance.logger.sender(sender, '§4未知的子命令: §c' + subcommand)
|
||||
pluginInstance.logger.sender(
|
||||
sender,
|
||||
pluginInstance['cmdhelp'] ?
|
||||
`§6请执行 §b/${command} §ahelp §6查看帮助!` :
|
||||
`§b版本: §a ${pluginInstance.description.version}`
|
||||
)
|
||||
return
|
||||
}
|
||||
let permission: string
|
||||
if (typeof cmd.permission == "string") {
|
||||
permission = cmd.permission as string
|
||||
} else if (cmd.permission) {
|
||||
permission = `${pluginInstance.description.name.toLocaleLowerCase()}.${command}.${subcommand}`
|
||||
}
|
||||
if (sender.hasPermission && !sender.hasPermission(permission)) {
|
||||
return pluginInstance.logger.sender(sender, `§c你需要 ${permission} 权限 才可执行此命令.`)
|
||||
}
|
||||
return subcommandexec.apply(pluginInstance, [sender, ...args])
|
||||
}
|
||||
let originCompleter = cmdCompleter
|
||||
cmdCompleter = (sender: any, command: string, args: string[]) => {
|
||||
let permission: string
|
||||
if (typeof cmd.permission == "string") {
|
||||
permission = cmd.permission as string
|
||||
} else if (cmd.permission) {
|
||||
permission = `${pluginInstance.description.name.toLocaleLowerCase()}.${command}`
|
||||
}
|
||||
if (sender.hasPermission && !sender.hasPermission(permission)) { return [] }
|
||||
return (args.length == 1 ? cmdSubCache : []).concat(originCompleter?.apply(pluginInstance, [sender, command, args]) || [])
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,10 @@ export class PluginConfigManager {
|
||||
'save': { value: () => this.saveConfig0(plugin, metadata) },
|
||||
'reload': { value: () => this.loadConfig0(plugin, metadata) }
|
||||
})
|
||||
Object.defineProperty(plugin, metadata.variable, { value })
|
||||
Object.defineProperty(plugin, metadata.variable, {
|
||||
value,
|
||||
configurable: true
|
||||
})
|
||||
}
|
||||
|
||||
private loadConfig0(plugin: plugin.Plugin, metadata: interfaces.ConfigMetadata) {
|
||||
@ -86,37 +89,50 @@ export class PluginConfigManager {
|
||||
let defaultValue = metadata.default ?? plugin[metadata.variable]
|
||||
let configValue = defaultValue || {}
|
||||
if (defaultValue) {
|
||||
metadata.file = fs.concat(fs.file(plugin.description.loadMetadata.file).parent, plugin.description.name, metadata.filename)
|
||||
metadata.file = fs.concat(
|
||||
fs.file(plugin.description.loadMetadata.file).parent,
|
||||
plugin.description.name,
|
||||
metadata.filename
|
||||
)
|
||||
let configLoader = this.getConfigLoader(metadata.format)
|
||||
if (!fs.exists(metadata.file)) {
|
||||
base.save(metadata.file, configLoader.dump(defaultValue))
|
||||
console.i18n("ms.plugin.manager.config.save.default", { plugin: plugin.description.name, name: metadata.name, format: metadata.format })
|
||||
console.i18n("ms.plugin.manager.config.save.default", {
|
||||
plugin: plugin.description.name,
|
||||
name: metadata.name,
|
||||
format: metadata.format
|
||||
})
|
||||
} else {
|
||||
configValue = configLoader.load(base.read(metadata.file)) || {}
|
||||
if (defaultValue && this.setDefaultValue(configValue, defaultValue)) {
|
||||
if (defaultValue && this.setDefaultValue(configValue, defaultValue, !!metadata.default)) {
|
||||
base.save(metadata.file, configLoader.dump(configValue))
|
||||
}
|
||||
console.debug(`[${plugin.description.name}] Load Config ${metadata.variable} from file ${metadata.file} =>\n${JSON.stringify(configValue, undefined, 4).substr(0, 500)}`)
|
||||
console.debug(`[${plugin.description.name}] Load Config ${metadata.variable} from file ${metadata.file} =>
|
||||
${JSON.stringify(configValue, undefined, 4).substring(0, 500)}`)
|
||||
}
|
||||
}
|
||||
this.defienConfigProp(plugin, metadata, configValue)
|
||||
} catch (error: any) {
|
||||
console.i18n("ms.plugin.manager.config.load.error", { plugin: plugin.description.name, name: metadata.name, format: metadata.format, error })
|
||||
console.i18n("ms.plugin.manager.config.load.error", {
|
||||
plugin: plugin.description.name,
|
||||
name: metadata.name,
|
||||
format: metadata.format,
|
||||
error
|
||||
})
|
||||
console.ex(error)
|
||||
}
|
||||
}
|
||||
|
||||
private setDefaultValue(configValue, defaultValue) {
|
||||
private setDefaultValue(configValue, defaultValue, deepCopy) {
|
||||
let needSave = false
|
||||
for (const key of Object.keys(defaultValue)) {
|
||||
// 当配置文件不存在当前属性时才进行赋值
|
||||
if (!Object.prototype.hasOwnProperty.call(configValue, key) && key != '____deep_copy____') {
|
||||
if (!Object.prototype.hasOwnProperty.call(configValue, key)) {
|
||||
configValue[key] = defaultValue[key]
|
||||
needSave = true
|
||||
} else if (Object.prototype.toString.call(configValue[key]) == "[object Object]"
|
||||
&& Object.prototype.hasOwnProperty.call(defaultValue[key], '____deep_copy____')) {
|
||||
// 对象需要递归检测 如果对象内存在 ____deep_copy____ 那就忽略设置
|
||||
needSave ||= this.setDefaultValue(configValue[key], defaultValue[key])
|
||||
} else if (Object.prototype.toString.call(configValue[key]) == "[object Object]" && deepCopy) {
|
||||
// 对象需要递归检测
|
||||
needSave ||= this.setDefaultValue(configValue[key], defaultValue[key], deepCopy)
|
||||
}
|
||||
}
|
||||
return needSave
|
||||
@ -127,10 +143,16 @@ export class PluginConfigManager {
|
||||
metadata.file = fs.concat(fs.file(plugin.description.loadMetadata.file).parent, plugin.description.name, metadata.filename)
|
||||
let result = this.getConfigLoader(metadata.format).dump(plugin[metadata.variable])
|
||||
base.save(metadata.file, result)
|
||||
console.debug(`[${plugin.description.name}] Save Config ${metadata.variable} to file ${metadata.file} =>\n${result.substr(0, 500)}`)
|
||||
console.debug(`[${plugin.description.name}] Save Config ${metadata.variable} to file ${metadata.file} =>
|
||||
${result.substring(0, 500)}`)
|
||||
return true
|
||||
} catch (error: any) {
|
||||
console.i18n("ms.plugin.manager.config.save.error", { plugin: plugin.description.name, name: metadata.name, format: metadata.format, error })
|
||||
console.i18n("ms.plugin.manager.config.save.error", {
|
||||
plugin: plugin.description.name,
|
||||
name: metadata.name,
|
||||
format: metadata.format,
|
||||
error
|
||||
})
|
||||
console.ex(error)
|
||||
return false
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ export const METADATA_KEY = {
|
||||
tab: Symbol.for("@ccms/plugin:tab"),
|
||||
listener: Symbol.for("@ccms/plugin:listener"),
|
||||
config: Symbol.for("@ccms/plugin:config"),
|
||||
playerdata: Symbol.for("@ccms/plugin:playerdata"),
|
||||
stage: {
|
||||
load: Symbol.for("@ccms/plugin:stage:load"),
|
||||
enable: Symbol.for("@ccms/plugin:stage:enable"),
|
||||
|
@ -2,7 +2,7 @@ import { plugin as pluginApi } from "@ccms/api"
|
||||
import { injectable, decorate } from "@ccms/container"
|
||||
import { interfaces } from './interfaces'
|
||||
import { METADATA_KEY } from './constants'
|
||||
import { getPluginMetadatas, getPluginCommandMetadata, getPluginListenerMetadata, getPluginTabCompleterMetadata, getPluginConfigMetadata, getPluginStageMetadata, getPluginSources } from './utils'
|
||||
import { getPluginMetadatas, getPluginCommandMetadata, getPluginListenerMetadata, getPluginTabCompleterMetadata, getPluginConfigMetadata, getPluginStageMetadata, getPluginSources, getPluginPlayerDataMetadata } from './utils'
|
||||
|
||||
/**
|
||||
* MiaoScript plugin
|
||||
@ -84,6 +84,21 @@ export function config(metadata: interfaces.ConfigMetadata = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export function playerdata(metadata: interfaces.PlayerDataMetadata = {}) {
|
||||
return function (target: any, key: string) {
|
||||
metadata.name = metadata.name || key
|
||||
metadata.variable = key
|
||||
metadata.version = metadata.version ?? 1
|
||||
metadata.format = metadata.format ?? 'yml'
|
||||
metadata.autosave = metadata.autosave ?? false
|
||||
metadata.filename = metadata.filename ?? "username"
|
||||
metadata.dir = metadata.dir ?? "playerdata"
|
||||
let previousMetadata = getPluginPlayerDataMetadata(target)
|
||||
previousMetadata.set(metadata.name, metadata)
|
||||
Reflect.defineMetadata(METADATA_KEY.playerdata, previousMetadata, target.constructor)
|
||||
}
|
||||
}
|
||||
|
||||
function stage(stage: string) {
|
||||
return (metadata: interfaces.ExecMetadata = {}) => {
|
||||
return function (target: any, key: string, value: any) {
|
||||
|
@ -1,4 +1,6 @@
|
||||
import './scanner/file-scanner'
|
||||
import './scanner/js-scanner'
|
||||
import './scanner/mjs-scanner'
|
||||
|
||||
import './loader/ioc-loader'
|
||||
import './loader/basic-loader'
|
||||
|
||||
@ -12,5 +14,6 @@ export {
|
||||
cmd as Cmd,
|
||||
tab as Tab,
|
||||
listener as Listener,
|
||||
config as Config
|
||||
config as Config,
|
||||
playerdata as PlayerData
|
||||
} from './decorators'
|
||||
|
@ -59,6 +59,10 @@ export namespace interfaces {
|
||||
* 自动化主命令
|
||||
*/
|
||||
autoMain?: boolean
|
||||
/**
|
||||
* 子命令权限效验
|
||||
*/
|
||||
permission?: boolean | string
|
||||
}
|
||||
export interface ListenerMetadata extends ExecMetadata {
|
||||
/**
|
||||
@ -100,4 +104,38 @@ export namespace interfaces {
|
||||
*/
|
||||
file?: any
|
||||
}
|
||||
export interface PlayerDataMetadata extends plugin.BaseMetadata {
|
||||
/**
|
||||
* 配置文件版本号
|
||||
*/
|
||||
version?: number
|
||||
/**
|
||||
* 默认配置
|
||||
*/
|
||||
default?: any
|
||||
/**
|
||||
* 实体变量名称
|
||||
*/
|
||||
variable?: string
|
||||
/**
|
||||
* 配置文件格式 默认 yml
|
||||
*/
|
||||
format?: string
|
||||
/**
|
||||
* 自动保存 默认为 false
|
||||
*/
|
||||
autosave?: boolean
|
||||
/**
|
||||
* 配置文件名称 默认玩家名称
|
||||
*/
|
||||
filename?: "username" | "uuid"
|
||||
/**
|
||||
* 配置文件目录 默认 playerdata
|
||||
*/
|
||||
dir?: string
|
||||
/**
|
||||
* 配置文件子目录 默认为空
|
||||
*/
|
||||
subdir?: string
|
||||
}
|
||||
}
|
||||
|
@ -169,6 +169,7 @@ export class PluginManagerImpl implements plugin.PluginManager {
|
||||
private loadAndRequirePlugin(loadMetadata: plugin.PluginLoadMetadata) {
|
||||
let startTime = Date.now()
|
||||
let metadata = this.loadPlugin(loadMetadata.scanner.load(loadMetadata))
|
||||
if (!metadata) { throw new Error('load plugin metadata failed.') }
|
||||
console.i18n('ms.plugin.manager.build', {
|
||||
name: loadMetadata.metadata.name,
|
||||
version: loadMetadata.metadata.version,
|
||||
@ -184,9 +185,10 @@ export class PluginManagerImpl implements plugin.PluginManager {
|
||||
* 从文件加载插件
|
||||
* @param file java.io.File
|
||||
*/
|
||||
loadFromFile(file: string, scanner = this.sacnnerMap.get('file')): plugin.Plugin {
|
||||
if (!file) { throw new Error('plugin file can\'t be undefiend!') }
|
||||
if (!scanner) { throw new Error('plugin scanner can\'t be undefiend!') }
|
||||
loadFromFile(file: string, ext: any = 'js'): plugin.Plugin {
|
||||
if (!file) { throw new Error('plugin file can\'t be undefiend.') }
|
||||
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.load(plugin)
|
||||
@ -213,7 +215,7 @@ export class PluginManagerImpl implements plugin.PluginManager {
|
||||
reload(...args: any[]): void {
|
||||
this.checkAndGet(args[0]).forEach((pl: plugin.Plugin) => {
|
||||
this.disable(pl)
|
||||
this.loadFromFile(pl.description.loadMetadata.file, pl.description.loadMetadata.scanner)
|
||||
this.loadFromFile(pl.description.loadMetadata.file, pl.description.loadMetadata.scanner.type)
|
||||
})
|
||||
}
|
||||
|
||||
@ -235,12 +237,12 @@ export class PluginManagerImpl implements plugin.PluginManager {
|
||||
}
|
||||
|
||||
private checkAndGet(name: string | plugin.Plugin | undefined | any): Map<string, plugin.Plugin> | plugin.Plugin[] {
|
||||
if (name === undefined) throw new Error(`checkAndGet Plugin can't be undefiend!`)
|
||||
if (name === undefined) throw new Error(`checkAndGet Plugin can't be undefiend.`)
|
||||
if (name == this.instanceMap) { return this.instanceMap }
|
||||
if (typeof name == 'string' && this.instanceMap.has(name)) { return [this.instanceMap.get(name)] }
|
||||
if (name instanceof interfaces.Plugin) { return [name as plugin.Plugin] }
|
||||
if (name.description?.name) { return [name as plugin.Plugin] }
|
||||
throw new Error(`Plugin ${JSON.stringify(name)} not exist!`)
|
||||
throw new Error(`Plugin ${JSON.stringify(name)} not exist.`)
|
||||
}
|
||||
|
||||
private buildPlugins() {
|
||||
|
@ -2,7 +2,7 @@ import { plugin } from "@ccms/api"
|
||||
import * as fs from '@ccms/common/dist/fs'
|
||||
import { provideSingletonNamed } from "@ccms/container"
|
||||
|
||||
const SCANNER_TYPE_NAME = 'file'
|
||||
const SCANNER_TYPE_NAME = 'js'
|
||||
|
||||
@provideSingletonNamed(plugin.PluginScanner, SCANNER_TYPE_NAME)
|
||||
export class JSFileScanner implements plugin.PluginScanner {
|
@ -67,7 +67,13 @@ function getPluginConfigMetadata(target: any) {
|
||||
) || new Map<string, interfaces.ConfigMetadata>()
|
||||
return configMetadata
|
||||
}
|
||||
|
||||
function getPluginPlayerDataMetadata(target: any) {
|
||||
let playerdataMetadata: Map<string, interfaces.ConfigMetadata> = Reflect.getMetadata(
|
||||
METADATA_KEY.playerdata,
|
||||
target.constructor
|
||||
) || new Map<string, interfaces.ConfigMetadata>()
|
||||
return playerdataMetadata
|
||||
}
|
||||
function getPluginStageMetadata(target: any, stage: string) {
|
||||
let stageMetadata: interfaces.ExecMetadata[] = Reflect.getMetadata(
|
||||
METADATA_KEY.stage[stage],
|
||||
@ -86,5 +92,6 @@ export {
|
||||
getPluginTabCompleterMetadata,
|
||||
getPluginListenerMetadata,
|
||||
getPluginConfigMetadata,
|
||||
getPluginPlayerDataMetadata,
|
||||
getPluginStageMetadata
|
||||
}
|
||||
|
Reference in New Issue
Block a user