添加#椰奶帮助
This commit is contained in:
45
components/Cfg.js
Normal file
45
components/Cfg.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import fs from 'fs'
|
||||
import lodash from 'lodash'
|
||||
|
||||
const _path = process.cwd()
|
||||
const _cfgPath = `${_path}/plugins/yenai-plugin/components/`
|
||||
let cfg = {}
|
||||
|
||||
try {
|
||||
if (fs.existsSync(_cfgPath + 'cfg.json')) {
|
||||
cfg = JSON.parse(fs.readFileSync(_cfgPath + 'cfg.json', 'utf8')) || {}
|
||||
}
|
||||
} catch (e) {
|
||||
// do nth
|
||||
}
|
||||
|
||||
let Cfg = {
|
||||
get(rote, def = '') {
|
||||
return lodash.get(cfg, rote, def)
|
||||
},
|
||||
set(rote, val) {
|
||||
lodash.set(cfg, rote, val)
|
||||
fs.writeFileSync(_cfgPath + 'cfg.json', JSON.stringify(cfg, null, '\t'))
|
||||
},
|
||||
del(rote) {
|
||||
lodash.set(cfg, rote, undefined)
|
||||
fs.writeFileSync(_cfgPath + 'cfg.json', JSON.stringify(cfg, null, '\t'))
|
||||
},
|
||||
scale(pct = 1) {
|
||||
let scale = Cfg.get('sys.scale', 100)
|
||||
scale = Math.min(2, Math.max(0.5, scale / 100))
|
||||
pct = pct * scale
|
||||
return `style=transform:scale(${pct})`
|
||||
},
|
||||
isDisable(e, rote) {
|
||||
if (Cfg.get(rote, true)) {
|
||||
return false
|
||||
}
|
||||
if (/^#*小飞/.test(e.msg || '')) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export default Cfg
|
||||
13
components/Common.js
Normal file
13
components/Common.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import Cfg from './Cfg.js'
|
||||
import render from './common-lib/render.js'
|
||||
|
||||
function sleep (ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
export default {
|
||||
render,
|
||||
cfg: Cfg.get,
|
||||
isDisable: Cfg.isDisable,
|
||||
sleep
|
||||
}
|
||||
107
components/Config.js
Normal file
107
components/Config.js
Normal file
@@ -0,0 +1,107 @@
|
||||
import YAML from 'yaml'
|
||||
import chokidar from 'chokidar'
|
||||
import fs from 'node:fs'
|
||||
|
||||
const Path = process.cwd();
|
||||
const Plugin_Name = 'yenai-plugin'
|
||||
const Plugin_Path = `${Path}/plugins/${Plugin_Name}`;
|
||||
class Config {
|
||||
constructor() {
|
||||
this.config = {}
|
||||
|
||||
/** 监听文件 */
|
||||
this.watcher = {}
|
||||
|
||||
this.ignore = []
|
||||
}
|
||||
|
||||
/**
|
||||
* @param app 功能
|
||||
* @param name 配置文件名称
|
||||
*/
|
||||
getdefSet(app, name) {
|
||||
return this.getYaml(app, name, 'defSet')
|
||||
}
|
||||
|
||||
/** 用户配置 */
|
||||
getConfig(app, name) {
|
||||
return this.getYaml(app, name, 'config')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置yaml
|
||||
* @param app 功能
|
||||
* @param name 名称
|
||||
* @param type 默认跑配置-defSet,用户配置-config
|
||||
*/
|
||||
getYaml(app, name, type) {
|
||||
let file = this.getFilePath(app, name, type)
|
||||
let key = `${app}.${name}`
|
||||
|
||||
if (this.config[type][key]) return this.config[type][key]
|
||||
|
||||
try {
|
||||
this.config[type][key] = YAML.parse(
|
||||
fs.readFileSync(file, 'utf8')
|
||||
)
|
||||
} catch (error) {
|
||||
logger.error(`[${app}][${name}] 格式错误 ${error}`)
|
||||
return false
|
||||
}
|
||||
|
||||
this.watch(file, app, name, type)
|
||||
|
||||
return this[type][key]
|
||||
}
|
||||
|
||||
getFilePath(app, name, type) {
|
||||
if (!this.config[type]) {
|
||||
this.config[type] = {};
|
||||
}
|
||||
|
||||
if (!this.watcher[type]) {
|
||||
this.watcher[type] = {};
|
||||
}
|
||||
|
||||
let config_path = `${Plugin_Path}/${type}/`;
|
||||
let file = `${config_path}${app}.${name}.yaml`;
|
||||
try {
|
||||
if (!fs.existsSync(file)) {
|
||||
let default_file = `${config_path}default/${app}.${name}.yaml`;
|
||||
fs.copyFileSync(default_file, file);
|
||||
}
|
||||
} catch (err) { }
|
||||
return file;
|
||||
}
|
||||
|
||||
/** 监听配置文件 */
|
||||
watch(file, app, name, type = 'defSet') {
|
||||
let key = `${app}.${name}`
|
||||
|
||||
if (this.watcher[type][key]) return
|
||||
|
||||
const watcher = chokidar.watch(file)
|
||||
watcher.on('change', path => {
|
||||
delete this[type][key]
|
||||
logger.mark(`[修改配置文件][${type}][${app}][${name}]`)
|
||||
if (this[`change_${app}${name}`]) {
|
||||
this[`change_${app}${name}`]()
|
||||
}
|
||||
})
|
||||
|
||||
this.watcher[type][key] = watcher
|
||||
}
|
||||
|
||||
|
||||
save(app, name, type) {
|
||||
let file = this.getFilePath(app, name, type)
|
||||
if (lodash.isEmpty(data)) {
|
||||
fs.existsSync(file) && fs.unlinkSync(file)
|
||||
} else {
|
||||
let yaml = YAML.stringify(data)
|
||||
fs.writeFileSync(file, yaml, 'utf8')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
export default new Config()
|
||||
224
components/Data.js
Normal file
224
components/Data.js
Normal file
@@ -0,0 +1,224 @@
|
||||
import lodash from 'lodash'
|
||||
import fs from 'fs'
|
||||
|
||||
const _path = process.cwd()
|
||||
const plugin = 'yenai-plugin'
|
||||
const getRoot = (root = '') => {
|
||||
if (root === 'root' || root === 'yunzai') {
|
||||
root = `${_path}/`
|
||||
} else if (!root) {
|
||||
root = `${_path}/plugins/${plugin}/`
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
let Data = {
|
||||
|
||||
/*
|
||||
* 根据指定的path依次检查与创建目录
|
||||
* */
|
||||
createDir(path = '', root = '', includeFile = false) {
|
||||
root = getRoot(root)
|
||||
let pathList = path.split('/')
|
||||
let nowPath = root
|
||||
pathList.forEach((name, idx) => {
|
||||
name = name.trim()
|
||||
if (!includeFile && idx <= pathList.length - 1) {
|
||||
nowPath += name + '/'
|
||||
if (name) {
|
||||
if (!fs.existsSync(nowPath)) {
|
||||
fs.mkdirSync(nowPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/*
|
||||
* 读取json
|
||||
* */
|
||||
readJSON(file = '', root = '') {
|
||||
root = getRoot(root)
|
||||
if (fs.existsSync(`${root}/${file}`)) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(`${root}/${file}`, 'utf8'))
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
|
||||
/*
|
||||
* 写JSON
|
||||
* */
|
||||
writeJSON(file, data, space = '\t', root = '') {
|
||||
// 检查并创建目录
|
||||
Data.createDir(file, root, true)
|
||||
root = getRoot(root)
|
||||
delete data._res
|
||||
return fs.writeFileSync(`${root}/${file}`, JSON.stringify(data, null, space))
|
||||
},
|
||||
|
||||
async getCacheJSON(key) {
|
||||
try {
|
||||
let txt = await redis.get(key)
|
||||
if (txt) {
|
||||
return JSON.parse(txt)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
return {}
|
||||
},
|
||||
|
||||
async setCacheJSON(key, data, EX = 3600 * 24 * 90) {
|
||||
await redis.set(key, JSON.stringify(data), { EX })
|
||||
},
|
||||
|
||||
async importModule(file, root = '') {
|
||||
root = getRoot(root)
|
||||
if (!/\.js$/.test(file)) {
|
||||
file = file + '.js'
|
||||
}
|
||||
if (fs.existsSync(`${root}/${file}`)) {
|
||||
try {
|
||||
let data = await import(`file://${root}/${file}?t=${new Date() * 1}`)
|
||||
return data || {}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
|
||||
async importDefault(file, root) {
|
||||
let ret = await Data.importModule(file, root)
|
||||
return ret.default || {}
|
||||
},
|
||||
|
||||
async import(name) {
|
||||
return await Data.importModule(`components/optional-lib/${name}.js`)
|
||||
},
|
||||
|
||||
async importCfg(key) {
|
||||
let sysCfg = await Data.importModule(`config/system/${key}_system.js`)
|
||||
let diyCfg = await Data.importModule(`config/${key}.js`)
|
||||
if (diyCfg.isSys) {
|
||||
console.error(`miao-plugin: config/${key}.js无效,已忽略`)
|
||||
console.error(`如需配置请复制config/${key}_default.js为config/${key}.js,请勿复制config/system下的系统文件`)
|
||||
diyCfg = {}
|
||||
}
|
||||
return {
|
||||
sysCfg,
|
||||
diyCfg
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* 返回一个从 target 中选中的属性的对象
|
||||
*
|
||||
* keyList : 获取字段列表,逗号分割字符串
|
||||
* key1, key2, toKey1:fromKey1, toKey2:fromObj.key
|
||||
*
|
||||
* defaultData: 当某个字段为空时会选取defaultData的对应内容
|
||||
* toKeyPrefix:返回数据的字段前缀,默认为空。defaultData中的键值无需包含toKeyPrefix
|
||||
*
|
||||
* */
|
||||
|
||||
getData(target, keyList = '', cfg = {}) {
|
||||
target = target || {}
|
||||
let defaultData = cfg.defaultData || {}
|
||||
let ret = {}
|
||||
// 分割逗号
|
||||
if (typeof (keyList) === 'string') {
|
||||
keyList = keyList.split(',')
|
||||
}
|
||||
|
||||
lodash.forEach(keyList, (keyCfg) => {
|
||||
// 处理通过:指定 toKey & fromKey
|
||||
let _keyCfg = keyCfg.split(':')
|
||||
let keyTo = _keyCfg[0].trim()
|
||||
let keyFrom = (_keyCfg[1] || _keyCfg[0]).trim()
|
||||
let keyRet = keyTo
|
||||
if (cfg.lowerFirstKey) {
|
||||
keyRet = lodash.lowerFirst(keyRet)
|
||||
}
|
||||
if (cfg.keyPrefix) {
|
||||
keyRet = cfg.keyPrefix + keyRet
|
||||
}
|
||||
// 通过Data.getVal获取数据
|
||||
ret[keyRet] = Data.getVal(target, keyFrom, defaultData[keyTo], cfg)
|
||||
})
|
||||
return ret
|
||||
},
|
||||
|
||||
getVal(target, keyFrom, defaultValue) {
|
||||
return lodash.get(target, keyFrom, defaultValue)
|
||||
},
|
||||
|
||||
// 异步池,聚合请求
|
||||
async asyncPool(poolLimit, array, iteratorFn) {
|
||||
const ret = [] // 存储所有的异步任务
|
||||
const executing = [] // 存储正在执行的异步任务
|
||||
for (const item of array) {
|
||||
// 调用iteratorFn函数创建异步任务
|
||||
const p = Promise.resolve().then(() => iteratorFn(item, array))
|
||||
// 保存新的异步任务
|
||||
ret.push(p)
|
||||
|
||||
// 当poolLimit值小于或等于总任务个数时,进行并发控制
|
||||
if (poolLimit <= array.length) {
|
||||
// 当任务完成后,从正在执行的任务数组中移除已完成的任务
|
||||
const e = p.then(() => executing.splice(executing.indexOf(e), 1))
|
||||
executing.push(e) // 保存正在执行的异步任务
|
||||
if (executing.length >= poolLimit) {
|
||||
// 等待较快的任务执行完成
|
||||
await Promise.race(executing)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.all(ret)
|
||||
},
|
||||
|
||||
// sleep
|
||||
sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
},
|
||||
|
||||
// 获取默认值
|
||||
def() {
|
||||
for (let idx in arguments) {
|
||||
if (!lodash.isUndefined(arguments[idx])) {
|
||||
return arguments[idx]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 循环字符串回调
|
||||
eachStr: (arr, fn) => {
|
||||
if (lodash.isString(arr)) {
|
||||
arr = arr.replace(/\s*(;|;|、|,)\s*/, ',')
|
||||
arr = arr.split(',')
|
||||
} else if (lodash.isNumber(arr)) {
|
||||
arr = [arr.toString()]
|
||||
}
|
||||
lodash.forEach(arr, (str, idx) => {
|
||||
if (!lodash.isUndefined(str)) {
|
||||
fn(str.trim ? str.trim() : str, idx)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
regRet(reg, txt, idx) {
|
||||
if (reg && txt) {
|
||||
let ret = reg.exec(txt)
|
||||
if (ret && ret[idx]) {
|
||||
return ret[idx]
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export default Data
|
||||
100
components/Version.js
Normal file
100
components/Version.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import fs from 'fs'
|
||||
import lodash from 'lodash'
|
||||
import cfg from '../../../lib/config/config.js'
|
||||
const Plugin_Path = `${process.cwd()}/plugins/yenai-plugin`;
|
||||
const README_path = `${Plugin_Path}/README.md`
|
||||
const CHANGELOG_path = `${Plugin_Path}/CHANGELOG.md`
|
||||
const yunzai_ver = `v${cfg.package.version}`;
|
||||
|
||||
let logs = {}
|
||||
let changelogs = []
|
||||
let currentVersion
|
||||
let versionCount = 6
|
||||
|
||||
const getLine = function (line) {
|
||||
line = line.replace(/(^\s*\*|\r)/g, '')
|
||||
line = line.replace(/\s*`([^`]+`)/g, '<span class="cmd">$1')
|
||||
line = line.replace(/`\s*/g, '</span>')
|
||||
line = line.replace(/\s*\*\*([^\*]+\*\*)/g, '<span class="strong">$1')
|
||||
line = line.replace(/\*\*\s*/g, '</span>')
|
||||
line = line.replace(/ⁿᵉʷ/g, '<span class="new"></span>')
|
||||
return line
|
||||
}
|
||||
|
||||
try {
|
||||
if (fs.existsSync(CHANGELOG_path)) {
|
||||
logs = fs.readFileSync(CHANGELOG_path, 'utf8') || ''
|
||||
logs = logs.replace(/\t/g,' ').split('\n')
|
||||
let temp = {};
|
||||
let lastLine = {}
|
||||
lodash.forEach(logs, (line) => {
|
||||
if (versionCount <= -1) {
|
||||
return false
|
||||
}
|
||||
let versionRet = /^#\s*([0-9a-zA-Z\\.~\s]+?)\s*$/.exec(line.trim())
|
||||
if (versionRet && versionRet[1]) {
|
||||
let v = versionRet[1].trim()
|
||||
if (!currentVersion) {
|
||||
currentVersion = v
|
||||
} else {
|
||||
changelogs.push(temp)
|
||||
if (/0\s*$/.test(v) && versionCount > 0) {
|
||||
//versionCount = 0
|
||||
versionCount--
|
||||
} else {
|
||||
versionCount--
|
||||
}
|
||||
}
|
||||
temp = {
|
||||
version: v,
|
||||
logs: []
|
||||
}
|
||||
} else {
|
||||
if (!line.trim()) {
|
||||
return
|
||||
}
|
||||
if (/^\*/.test(line)) {
|
||||
lastLine = {
|
||||
title: getLine(line),
|
||||
logs: []
|
||||
}
|
||||
if(!temp.logs){
|
||||
temp = {
|
||||
version: line,
|
||||
logs: []
|
||||
}
|
||||
}
|
||||
temp.logs.push(lastLine)
|
||||
} else if (/^\s{2,}\*/.test(line)) {
|
||||
lastLine.logs.push(getLine(line))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
// do nth
|
||||
}
|
||||
|
||||
try{
|
||||
if(fs.existsSync(README_path)){
|
||||
let README = fs.readFileSync(README_path, 'utf8') || ''
|
||||
let reg = /版本:(.*)/.exec(README)
|
||||
if(reg){
|
||||
currentVersion = reg[1]
|
||||
}
|
||||
}
|
||||
}catch(err){}
|
||||
|
||||
let Version = {
|
||||
get ver () {
|
||||
return currentVersion;
|
||||
},
|
||||
get yunzai(){
|
||||
return yunzai_ver;
|
||||
},
|
||||
get logs(){
|
||||
return changelogs;
|
||||
}
|
||||
}
|
||||
export default Version
|
||||
15
components/cfg.json
Normal file
15
components/cfg.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"sys": {
|
||||
"help": true,
|
||||
"scale": 100
|
||||
},
|
||||
"char": {
|
||||
"wife": false,
|
||||
"char": true
|
||||
},
|
||||
"wiki": {
|
||||
"abyss": true,
|
||||
"wiki": false,
|
||||
"stat": true
|
||||
}
|
||||
}
|
||||
53
components/common-lib/render.js
Normal file
53
components/common-lib/render.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Data, Version, Plugin_Name } from '../index.js'
|
||||
import Cfg from '../Cfg.js'
|
||||
import fs from 'fs'
|
||||
import puppeteer from '../../../../lib/puppeteer/puppeteer.js'
|
||||
|
||||
const _path = process.cwd()
|
||||
|
||||
export default async function (path, params, cfg) {
|
||||
let [app, tpl] = path.split('/')
|
||||
let { e } = cfg
|
||||
let layoutPath = process.cwd() + `/plugins/${Plugin_Name}/resources/common/layout/`
|
||||
let resPath = `../../../../../plugins/${Plugin_Name}/resources/`
|
||||
Data.createDir(`data/html/${Plugin_Name}/${app}/${tpl}`, 'root')
|
||||
let data = {
|
||||
...params,
|
||||
_plugin: Plugin_Name,
|
||||
saveId: params.saveId || params.save_id || tpl,
|
||||
tplFile: `./plugins/${Plugin_Name}/resources/${app}/${tpl}.html`,
|
||||
pluResPath: resPath,
|
||||
_res_path: resPath,
|
||||
_layout_path: layoutPath,
|
||||
_tpl_path: process.cwd() + `/plugins/${Plugin_Name}/resources/common/tpl/`,
|
||||
defaultLayout: layoutPath + 'default.html',
|
||||
elemLayout: layoutPath + 'elem.html',
|
||||
pageGotoParams: {
|
||||
waitUntil: 'networkidle0'
|
||||
},
|
||||
sys: {
|
||||
scale: Cfg.scale(cfg.scale || 1),
|
||||
copyright: `Created By Yunzai-Bot<span class="version">${Version.yunzai}</span> & yenai-Plugin<span class="version">V1.0.0</span>`
|
||||
},
|
||||
quality: 100
|
||||
}
|
||||
|
||||
|
||||
if (process.argv.includes('web-debug')) {
|
||||
// debug下保存当前页面的渲染数据,方便模板编写与调试
|
||||
// 由于只用于调试,开发者只关注自己当时开发的文件即可,暂不考虑app及plugin的命名冲突
|
||||
let saveDir = _path + '/data/ViewData/'
|
||||
if (!fs.existsSync(saveDir)) {
|
||||
fs.mkdirSync(saveDir)
|
||||
}
|
||||
let file = saveDir + tpl + '.json'
|
||||
data._app = app
|
||||
fs.writeFileSync(file, JSON.stringify(data))
|
||||
}
|
||||
let base64 = await puppeteer.screenshot(`${Plugin_Name}/${app}/${tpl}`, data)
|
||||
let ret = true
|
||||
if (base64) {
|
||||
ret = await e.reply(base64)
|
||||
}
|
||||
return cfg.retMsgId ? ret : true
|
||||
}
|
||||
10
components/index.js
Normal file
10
components/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const Path = process.cwd();
|
||||
const Plugin_Name = 'yenai-plugin'
|
||||
const Plugin_Path = `${Path}/plugins/${Plugin_Name}`;
|
||||
import Version from './Version.js'
|
||||
import Data from './Data.js'
|
||||
import Cfg from './Cfg.js'
|
||||
import Common from './Common.js'
|
||||
import Config from './Config.js'
|
||||
|
||||
export { Cfg, Common, Config, Data, Version, Path, Plugin_Name, Plugin_Path }
|
||||
Reference in New Issue
Block a user