@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user