mirror of
https://github.com/Jerryplusy/crystelf-plugin.git
synced 2025-10-14 13:49:18 +00:00
Compare commits
3 Commits
ac0eb92efd
...
ea7b3103f0
Author | SHA1 | Date | |
---|---|---|---|
ea7b3103f0 | |||
39008cda53 | |||
1e3598e399 |
110
apps/poke.js
110
apps/poke.js
@ -3,10 +3,8 @@ import tool from '../components/tool.js';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import configControl from '../lib/config/configControl.js';
|
import configControl from '../lib/config/configControl.js';
|
||||||
|
|
||||||
const replyText = configControl.get('poke')?.replyText;
|
const replyPoke = configControl.get('poke')?.replyPoke;
|
||||||
const replyVoice = configControl.get('poke')?.replyVoice;
|
const nickName = configControl.get('profile')?.nickName;
|
||||||
const mutePick = configControl.get('poke')?.mutePick;
|
|
||||||
const muteTime = configControl.get('poke')?.muteTime;
|
|
||||||
|
|
||||||
export default class ChuochuoPlugin extends plugin {
|
export default class ChuochuoPlugin extends plugin {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -61,113 +59,25 @@ async function masterPoke(e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleBotPoke(e) {
|
async function handleBotPoke(e) {
|
||||||
const randomNum = Math.random();
|
|
||||||
|
|
||||||
if (randomNum < replyText) {
|
|
||||||
try {
|
try {
|
||||||
const coreUrl = configControl.get(`coreConfig`)?.coreUrl;
|
const coreUrl = configControl.get(`coreConfig`)?.coreUrl;
|
||||||
const targetUrl = `${coreUrl}/api/words/getText`;
|
const targetUrl = `${coreUrl}/api/words/getText`;
|
||||||
const res = await axios.post(targetUrl, {
|
const res = await axios.post(targetUrl, {
|
||||||
type: 'poke',
|
type: 'poke',
|
||||||
id: 'poke',
|
id: 'poke',
|
||||||
|
name: nickName,
|
||||||
});
|
});
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
return await e.reply(res.data.data);
|
await e.reply(res.data.data);
|
||||||
|
if (Math.random() < replyPoke) {
|
||||||
|
await tool.sleep(1000);
|
||||||
|
await e.bot.sendApi('group_poke', { group_id: e.group_id, user_id: e.operator_id });
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return await e.reply(`戳一戳出错了!${configControl.get('nickName')}不知道该说啥好了..`);
|
await e.reply(`戳一戳出错了!${configControl.get('profile')?.nickName}不知道该说啥好了..`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('戳一戳请求失败', err);
|
logger.error('戳一戳请求失败', err);
|
||||||
return await e.reply(`戳一戳出错了!${configControl.get('nickName')}不知道该说啥好了..`);
|
await e.reply(`戳一戳出错了!${configControl.get('profile')?.nickName}不知道该说啥好了..`);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (randomNum < replyText + replyVoice) {
|
|
||||||
try {
|
|
||||||
const coreUrl = configControl.get(`coreConfig`)?.coreUrl;
|
|
||||||
const targetUrl = `${coreUrl}/api/words/getText`;
|
|
||||||
const res = await axios.post(targetUrl, {
|
|
||||||
type: 'poke',
|
|
||||||
id: 'poke',
|
|
||||||
});
|
|
||||||
if (res.data.success) {
|
|
||||||
const message = res.data.data.toString();
|
|
||||||
//let message = cleanText(res.data.data.toString());
|
|
||||||
//logger.info(message);
|
|
||||||
return await e.bot.sendApi('get_ai_record', {
|
|
||||||
group_id: e.group_id,
|
|
||||||
character: 'lucy-voice-hoige',
|
|
||||||
text: message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger.error('语音生成失败', err);
|
|
||||||
return await e.reply(`戳一戳出错了!${configControl.get('nickName')}不知道该说啥好了..`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (randomNum < replyText + replyVoice + mutePick) {
|
|
||||||
let mutetype = Math.ceil(Math.random() * 4);
|
|
||||||
|
|
||||||
const botInfo = await Bot.pickMember(e.group_id, e.bot.uin).getInfo();
|
|
||||||
const isAdmin = botInfo.role === 'admin' || botInfo.role === 'owner';
|
|
||||||
if (!isAdmin) mutetype = 5;
|
|
||||||
|
|
||||||
switch (mutetype) {
|
|
||||||
case 1:
|
|
||||||
await e.reply('我生气了!砸挖撸多!木大!木大木大!');
|
|
||||||
await tool.sleep(1000);
|
|
||||||
return await tryMute(e, 60 * muteTime);
|
|
||||||
case 2:
|
|
||||||
await e.reply('不!!');
|
|
||||||
await tool.sleep(1000);
|
|
||||||
await e.reply('准!!');
|
|
||||||
await tool.sleep(1000);
|
|
||||||
await e.reply('戳!!');
|
|
||||||
await tool.sleep(1000);
|
|
||||||
await tryMute(e, 60 * muteTime);
|
|
||||||
await tool.sleep(1000);
|
|
||||||
return await e.reply('!!');
|
|
||||||
case 3:
|
|
||||||
await e.reply('吃我10068拳!');
|
|
||||||
await tool.sleep(1000);
|
|
||||||
await e.bot.sendApi('group_poke', { group_id: e.group_id, user_id: e.operator_id });
|
|
||||||
await tryMute(e, 60 * muteTime);
|
|
||||||
return;
|
|
||||||
case 4:
|
|
||||||
await e.reply('哼,我可是会还手的哦——');
|
|
||||||
await tool.sleep(1000);
|
|
||||||
await e.bot.sendApi('group_poke', { group_id: e.group_id, user_id: e.operator_id });
|
|
||||||
return await tryMute(e, 60 * muteTime);
|
|
||||||
case 5:
|
|
||||||
await e.reply('哼,唔啊啊啊啊啊啊!');
|
|
||||||
await tool.sleep(1000);
|
|
||||||
return await e.bot.sendApi('group_poke', { group_id: e.group_id, user_id: e.operator_id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const returnType = Math.round(Math.random() * 3);
|
|
||||||
const replies = [
|
|
||||||
'吃我一拳喵!',
|
|
||||||
'你刚刚是不是戳我了,你是坏蛋!我要戳回去,哼!!!',
|
|
||||||
'是不是要本萝莉揍你一顿才开心啊!!!',
|
|
||||||
];
|
|
||||||
if (replies[returnType]) {
|
|
||||||
await e.reply(replies[returnType]);
|
|
||||||
await tool.sleep(1000);
|
|
||||||
return await e.bot.sendApi('group_poke', { group_id: e.group_id, user_id: e.operator_id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanText(inputText) {
|
|
||||||
return inputText.replace(/[^\w\s,.!?]/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function tryMute(e, duration) {
|
|
||||||
try {
|
|
||||||
await e.group.muteMember(e.operator_id, duration);
|
|
||||||
} catch (err) {
|
|
||||||
logger.warn(`禁言失败: ${err}`);
|
|
||||||
await e.reply('气死我了!禁言不了你');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ export class ZWA extends plugin {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async www(e) {
|
async www(e) {
|
||||||
const currentHour = getCurrentHour();
|
const currentHour = getCurrentHour();
|
||||||
if (currentHour >= 20 && currentHour <= 23) {
|
if (currentHour >= 20 && currentHour <= 23) {
|
||||||
|
@ -1,44 +1,3 @@
|
|||||||
let date = {
|
let date = {}; //咕咕咕
|
||||||
/**
|
|
||||||
* 格式化日期时间
|
|
||||||
* @param {Date|number|string} [date=new Date()] - 可接收Date对象、时间戳或日期字符串
|
|
||||||
* @param {string} [format='YYYY-MM-DD HH:mm:ss'] - 格式模板,支持:
|
|
||||||
* YYYY-年, MM-月, DD-日,
|
|
||||||
* HH-时, mm-分, ss-秒
|
|
||||||
* @returns {string} 格式化后的日期字符串
|
|
||||||
* @example
|
|
||||||
* fc.formatDate(new Date(), 'YYYY年MM月DD日') // "2023年08月15日"
|
|
||||||
*/
|
|
||||||
formatDate(date = new Date(), format = 'YYYY-MM-DD HH:mm:ss') {
|
|
||||||
const d = new Date(date);
|
|
||||||
const pad = (n) => n.toString().padStart(2, '0');
|
|
||||||
|
|
||||||
return format
|
|
||||||
.replace(/YYYY/g, pad(d.getFullYear()))
|
|
||||||
.replace(/MM/g, pad(d.getMonth() + 1))
|
|
||||||
.replace(/DD/g, pad(d.getDate()))
|
|
||||||
.replace(/HH/g, pad(d.getHours()))
|
|
||||||
.replace(/mm/g, pad(d.getMinutes()))
|
|
||||||
.replace(/ss/g, pad(d.getSeconds()));
|
|
||||||
},
|
|
||||||
|
|
||||||
formatDuration(seconds) {
|
|
||||||
const days = Math.floor(seconds / 86400);
|
|
||||||
const hours = Math.floor((seconds % 86400) / 3600);
|
|
||||||
const mins = Math.floor((seconds % 3600) / 60);
|
|
||||||
const secs = seconds % 60;
|
|
||||||
|
|
||||||
return (
|
|
||||||
[
|
|
||||||
days > 0 ? `${days}天` : '',
|
|
||||||
hours > 0 ? `${hours}小时` : '',
|
|
||||||
mins > 0 ? `${mins}分钟` : '',
|
|
||||||
secs > 0 ? `${secs}秒` : '',
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(' ') || '0秒'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default date;
|
export default date;
|
||||||
|
@ -2,13 +2,14 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import Version from '../lib/system/version.js';
|
import Version from '../lib/system/version.js';
|
||||||
|
|
||||||
|
const fsp = fs.promises;
|
||||||
const Plugin_Name = Version.name;
|
const Plugin_Name = Version.name;
|
||||||
|
|
||||||
const _path = process.cwd();
|
const _path = process.cwd();
|
||||||
const getRoot = (root = '') => {
|
const getRoot = (root = '') => {
|
||||||
if (root === 'root' || root === 'yunzai') {
|
if (root === 'root' || root === 'yunzai') {
|
||||||
root = `${_path}/`;
|
root = `${_path}/`;
|
||||||
} else if (!root) {
|
} else if (!root || root === '') {
|
||||||
root = `${_path}/plugins/${Plugin_Name}/`;
|
root = `${_path}/plugins/${Plugin_Name}/`;
|
||||||
}
|
}
|
||||||
return root;
|
return root;
|
||||||
@ -17,231 +18,97 @@ const getRoot = (root = '') => {
|
|||||||
let fc = {
|
let fc = {
|
||||||
/**
|
/**
|
||||||
* 递归创建目录结构
|
* 递归创建目录结构
|
||||||
* @param {string} [path=""] - 要创建的相对路径,支持多级目录(如 "dir1/dir2")
|
|
||||||
* @param {string} [root=""] - 基础根目录,可选值:
|
|
||||||
* - "root" 或 "yunzai": 使用 Yunzai 根目录
|
|
||||||
* - 空值: 使用插件目录
|
|
||||||
* @param {boolean} [includeFile=false] - 是否包含最后一级作为文件名
|
|
||||||
* @example
|
|
||||||
* fc.createDir("config/deepseek", "root") // 在 Yunzai 根目录创建 config/deepseek 目录
|
|
||||||
*/
|
*/
|
||||||
createDir(path = '', root = '', includeFile = false) {
|
async createDir(p = '', root = '', includeFile = false) {
|
||||||
root = getRoot(root);
|
root = getRoot(root);
|
||||||
let pathList = path.split('/');
|
const pathList = p.split('/');
|
||||||
let nowPath = root;
|
let nowPath = root;
|
||||||
pathList.forEach((name, idx) => {
|
|
||||||
name = name.trim();
|
for (let idx = 0; idx < pathList.length; idx++) {
|
||||||
|
const name = pathList[idx].trim();
|
||||||
if (!includeFile && idx <= pathList.length - 1) {
|
if (!includeFile && idx <= pathList.length - 1) {
|
||||||
nowPath += name + '/';
|
nowPath += name + '/';
|
||||||
if (name) {
|
if (name) {
|
||||||
if (!fs.existsSync(nowPath)) {
|
try {
|
||||||
fs.mkdirSync(nowPath);
|
await fsp.access(nowPath);
|
||||||
|
} catch {
|
||||||
|
await fsp.mkdir(nowPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 读取JSON文件
|
* 读取json文件
|
||||||
* @param {string} [file=""] - JSON文件路径(相对路径)
|
* @param filePath 绝对路径
|
||||||
* @param {string} [root=""] - 基础根目录(同 createDir)
|
* @returns {Promise<{}|any>}
|
||||||
* @returns {object} 解析后的JSON对象,如文件不存在或解析失败返回空对象
|
|
||||||
* @example
|
|
||||||
* const config = fc.readJSON("config.json", "root")
|
|
||||||
*/
|
*/
|
||||||
readJSON(file = '', root = '') {
|
async readJSON(filePath) {
|
||||||
root = getRoot(root);
|
|
||||||
if (fs.existsSync(`${root}/${file}`)) {
|
|
||||||
try {
|
try {
|
||||||
return JSON.parse(fs.readFileSync(`${root}/${file}`, 'utf8'));
|
const data = await fsp.readFile(filePath, 'utf8');
|
||||||
|
return JSON.parse(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {};
|
return {};
|
||||||
},
|
|
||||||
|
|
||||||
statSync(file = '', root = '') {
|
|
||||||
root = getRoot(root);
|
|
||||||
try {
|
|
||||||
return fs.statSync(`${root}/${file}`);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 写入JSON文件(完全覆盖)
|
* 写入
|
||||||
* @param {string} file - 目标文件路径
|
* @param filePath 绝对路径
|
||||||
* @param {object} data - 要写入的JSON数据
|
* @param data
|
||||||
* @param {string} [root=""] - 基础根目录(同 createDir)
|
* @returns {Promise<void>}
|
||||||
* @param {number} [space=4] - JSON格式化缩进空格数
|
|
||||||
* @returns {boolean} 是否写入成功
|
|
||||||
* @warning 此方法会完全覆盖目标文件原有内容
|
|
||||||
* @example
|
|
||||||
* fc.writeJSON("config.json", {key: "value"}, "root", 4)
|
|
||||||
*/
|
*/
|
||||||
writeJSON(file, data, root = '', space = 4) {
|
async writeJSON(filePath, data) {
|
||||||
fc.createDir(file, root, true);
|
await fsp.writeFile(filePath, JSON.stringify(data, null, 4), 'utf8');
|
||||||
root = getRoot(root);
|
|
||||||
try {
|
|
||||||
fs.writeFileSync(`${root}/${file}`, JSON.stringify(data, null, space));
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 安全写入JSON文件(合并模式)
|
* 合并配置
|
||||||
* @param {string} file - 目标文件路径
|
* @param base 基准配置
|
||||||
* @param {object} data - 要合并的数据
|
* @param addon 额外配置
|
||||||
* @param {string} [root=""] - 基础根目录(同 createDir)
|
* @returns {*}
|
||||||
* @param {number} [space=4] - JSON格式化缩进空格数
|
|
||||||
* @returns {boolean} 是否写入成功
|
|
||||||
* @description
|
|
||||||
* - 如果目标文件不存在,创建新文件
|
|
||||||
* - 如果目标文件存在,深度合并新旧数据
|
|
||||||
* - 如果目标文件损坏,会创建新文件并记录警告
|
|
||||||
* @example
|
|
||||||
* fc.safewriteJSON("config.json", {newKey: "value"})
|
|
||||||
*/
|
*/
|
||||||
safeWriteJSON(file, data, root = '', space = 4) {
|
mergeConfig(base, addon) {
|
||||||
fc.createDir(file, root, true);
|
const result = { ...base };
|
||||||
root = getRoot(root);
|
for (const [key, value] of Object.entries(addon)) {
|
||||||
const filePath = `${root}/${file}`;
|
if (!(key in result)) {
|
||||||
|
result[key] = value;
|
||||||
try {
|
} else if (
|
||||||
let existingData = {};
|
typeof result[key] === 'object' &&
|
||||||
if (fs.existsSync(filePath)) {
|
typeof value === 'object' &&
|
||||||
try {
|
!Array.isArray(result[key]) &&
|
||||||
existingData = JSON.parse(fs.readFileSync(filePath, 'utf8')) || {};
|
!Array.isArray(value)
|
||||||
} catch (e) {
|
|
||||||
logger.warn(`无法解析现有JSON文件 ${filePath},将创建新文件`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergedData = this.deepMerge(existingData, data);
|
|
||||||
|
|
||||||
fs.writeFileSync(filePath, JSON.stringify(mergedData, null, space));
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`写入JSON文件失败 ${filePath}:`, err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 深度合并两个对象
|
|
||||||
* @param {object} target - 目标对象(将被修改)
|
|
||||||
* @param {object} source - 源对象
|
|
||||||
* @returns {object} 合并后的目标对象
|
|
||||||
* @description
|
|
||||||
* - 递归合并嵌套对象
|
|
||||||
* - 对于非对象属性直接覆盖
|
|
||||||
* - 不会合并数组(数组会被直接覆盖)
|
|
||||||
* @example
|
|
||||||
* const merged = fc.deepMerge({a: 1}, {b: {c: 2}})
|
|
||||||
* // 返回 {a: 1, b: {c: 2}}
|
|
||||||
*/
|
|
||||||
deepMerge(target, source) {
|
|
||||||
for (const key in source) {
|
|
||||||
if (source.hasOwnProperty(key)) {
|
|
||||||
if (
|
|
||||||
source[key] &&
|
|
||||||
typeof source[key] === 'object' &&
|
|
||||||
target[key] &&
|
|
||||||
typeof target[key] === 'object'
|
|
||||||
) {
|
) {
|
||||||
this.deepMerge(target[key], source[key]);
|
result[key] = this.mergeConfig(result[key], value);
|
||||||
} else {
|
|
||||||
target[key] = source[key];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return result;
|
||||||
return target;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 递归读取目录中的特定扩展名文件
|
* 异步递归读取目录中的特定扩展名文件
|
||||||
* @param {string} directory - 要搜索的目录路径
|
|
||||||
* @param {string} extension - 文件扩展名(不带点)
|
|
||||||
* @param {string} [excludeDir] - 要排除的目录名
|
|
||||||
* @returns {string[]} 匹配的文件相对路径数组
|
|
||||||
* @description
|
|
||||||
* - 自动跳过以下划线开头的文件
|
|
||||||
* - 结果包含子目录中的文件
|
|
||||||
* @example
|
|
||||||
* const jsFiles = fc.readDirRecursive("./plugins", "js", "node_modules")
|
|
||||||
*/
|
*/
|
||||||
readDirRecursive(directory, extension, excludeDir) {
|
async readDirRecursive(directory, extension, excludeDir) {
|
||||||
let files = fs.readdirSync(directory);
|
const entries = await fsp.readdir(directory, { withFileTypes: true });
|
||||||
|
let files = [];
|
||||||
|
|
||||||
let jsFiles = files.filter(
|
for (const entry of entries) {
|
||||||
(file) => path.extname(file) === `.${extension}` && !file.startsWith('_')
|
if (entry.isFile()) {
|
||||||
|
if (path.extname(entry.name) === `.${extension}` && !entry.name.startsWith('_')) {
|
||||||
|
files.push(entry.name);
|
||||||
|
}
|
||||||
|
} else if (entry.isDirectory()) {
|
||||||
|
if (entry.name === excludeDir) continue;
|
||||||
|
const subFiles = await this.readDirRecursive(
|
||||||
|
path.join(directory, entry.name),
|
||||||
|
extension,
|
||||||
|
excludeDir
|
||||||
);
|
);
|
||||||
|
files.push(...subFiles.map((fileName) => path.join(entry.name, fileName)));
|
||||||
files
|
|
||||||
.filter((file) => fs.statSync(path.join(directory, file)).isDirectory())
|
|
||||||
.forEach((subdirectory) => {
|
|
||||||
if (subdirectory === excludeDir) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subdirectoryPath = path.join(directory, subdirectory);
|
|
||||||
jsFiles.push(
|
|
||||||
...fc
|
|
||||||
.readDirRecursive(subdirectoryPath, extension, excludeDir)
|
|
||||||
.map((fileName) => path.join(subdirectory, fileName))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return jsFiles;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 深度克隆对象(支持基本类型/数组/对象/Date/RegExp)
|
|
||||||
* @param {*} source - 要克隆的数据
|
|
||||||
* @returns {*} 深度克隆后的副本
|
|
||||||
* @description
|
|
||||||
* - 处理循环引用
|
|
||||||
* - 保持原型链
|
|
||||||
* - 支持特殊对象类型(Date/RegExp等)
|
|
||||||
* @example
|
|
||||||
* const obj = { a: 1, b: [2, 3] };
|
|
||||||
* const cloned = fc.deepClone(obj);
|
|
||||||
*/
|
|
||||||
deepClone(source) {
|
|
||||||
const cache = new WeakMap();
|
|
||||||
|
|
||||||
const clone = (value) => {
|
|
||||||
if (value === null || typeof value !== 'object') {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cache.has(value)) {
|
|
||||||
return cache.get(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value instanceof Date) return new Date(value);
|
|
||||||
if (value instanceof RegExp) return new RegExp(value);
|
|
||||||
|
|
||||||
const target = new value.constructor();
|
|
||||||
cache.set(value, target);
|
|
||||||
|
|
||||||
for (const key in value) {
|
|
||||||
if (value.hasOwnProperty(key)) {
|
|
||||||
target[key] = clone(value[key]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return files;
|
||||||
return target;
|
|
||||||
};
|
|
||||||
|
|
||||||
return clone(source);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default fc;
|
export default fc;
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
import Version from '../lib/system/version.js';
|
|
||||||
|
|
||||||
const Plugin_Name = Version.name;
|
|
||||||
|
|
||||||
const _path = process.cwd();
|
|
||||||
const getRoot = (root = '') => {
|
|
||||||
if (root === 'root' || root === 'yunzai') {
|
|
||||||
root = `${_path}/`;
|
|
||||||
} else if (!root) {
|
|
||||||
root = `${_path}/plugins/${Plugin_Name}/`;
|
|
||||||
}
|
|
||||||
return root;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mc = {
|
|
||||||
/**
|
|
||||||
* 动态导入JS模块
|
|
||||||
* @param {string} file - 模块文件路径(可省略.js后缀)
|
|
||||||
* @param {string} [root=""] - 基础根目录(同 createDir)
|
|
||||||
* @returns {Promise<object>} 模块导出对象,如导入失败返回空对象
|
|
||||||
* @description
|
|
||||||
* - 自动添加时间戳参数防止缓存
|
|
||||||
* - 自动补全.js后缀
|
|
||||||
* @example
|
|
||||||
* const module = await fc.importModule("utils/helper")
|
|
||||||
*/
|
|
||||||
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 {};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 动态导入JS模块的默认导出
|
|
||||||
* @param {string} file - 模块文件路径
|
|
||||||
* @param {string} [root=""] - 基础根目录(同 createDir)
|
|
||||||
* @returns {Promise<object>} 模块的默认导出,如失败返回空对象
|
|
||||||
* @example
|
|
||||||
* const defaultExport = await fc.importDefault("components/Header")
|
|
||||||
*/
|
|
||||||
async importDefault(file, root) {
|
|
||||||
let ret = await fc.importModule(file, root);
|
|
||||||
return ret.default || {};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default mc;
|
|
@ -1,10 +1,8 @@
|
|||||||
let tools = {
|
let tools = {
|
||||||
/**
|
/**
|
||||||
* 异步延时函数
|
* 延时函数
|
||||||
* @param {number} ms - 等待的毫秒数
|
* @param {number} ms - 等待的毫秒数
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @example
|
|
||||||
* await fc.sleep(1000) // 等待1秒
|
|
||||||
*/
|
*/
|
||||||
sleep(ms) {
|
sleep(ms) {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
@ -12,114 +10,12 @@ let tools = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成指定范围内的随机整数
|
* 生成指定范围内的随机整数
|
||||||
* @param {number} min - 最小值(包含)
|
* @param {number} min - 最小值
|
||||||
* @param {number} max - 最大值(包含)
|
* @param {number} max - 最大值
|
||||||
* @returns {number} 范围内的随机整数
|
|
||||||
* @example
|
|
||||||
* const randomNum = fc.randomInt(1, 10) // 可能返回 5
|
|
||||||
*/
|
*/
|
||||||
randomInt(min, max) {
|
randomInt(min, max) {
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 防抖函数
|
|
||||||
* @param {Function} fn - 要执行的函数
|
|
||||||
* @param {number} [delay=300] - 延迟时间(毫秒)
|
|
||||||
* @param {boolean} [immediate=false] - 是否立即执行
|
|
||||||
* @returns {Function} 防抖处理后的函数
|
|
||||||
* @description
|
|
||||||
* 1. immediate=true时:先立即执行,后续调用在delay时间内被忽略
|
|
||||||
* 2. immediate=false时:延迟执行,重复调用会重置计时器
|
|
||||||
* @example
|
|
||||||
* window.addEventListener('resize', fc.debounce(() => {
|
|
||||||
* console.log('resize end');
|
|
||||||
* }, 500));
|
|
||||||
*/
|
|
||||||
debounce(fn, delay = 300, immediate = false) {
|
|
||||||
let timer = null;
|
|
||||||
return function (...args) {
|
|
||||||
if (immediate && !timer) {
|
|
||||||
fn.apply(this, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(timer);
|
|
||||||
timer = setTimeout(() => {
|
|
||||||
if (!immediate) {
|
|
||||||
fn.apply(this, args);
|
|
||||||
}
|
|
||||||
timer = null;
|
|
||||||
}, delay);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 异步重试机制
|
|
||||||
* @param {Function} asyncFn - 返回Promise的异步函数
|
|
||||||
* @param {number} [maxRetries=3] - 最大重试次数
|
|
||||||
* @param {number} [delay=1000] - 重试间隔(毫秒)
|
|
||||||
* @param {Function} [retryCondition] - 重试条件函数(err => boolean)
|
|
||||||
* @returns {Promise} 最终成功或失败的结果
|
|
||||||
* @example
|
|
||||||
* await fc.retry(fetchData, 5, 2000, err => err.status !== 404);
|
|
||||||
*/
|
|
||||||
async retry(asyncFn, maxRetries = 3, delay = 1000, retryCondition = () => true) {
|
|
||||||
let attempt = 0;
|
|
||||||
let lastError;
|
|
||||||
|
|
||||||
while (attempt <= maxRetries) {
|
|
||||||
try {
|
|
||||||
return await asyncFn();
|
|
||||||
} catch (err) {
|
|
||||||
lastError = err;
|
|
||||||
if (attempt === maxRetries || !retryCondition(err)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
attempt++;
|
|
||||||
await this.sleep(delay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw lastError;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将对象转换为URL查询字符串
|
|
||||||
* @param {object} params - 参数对象
|
|
||||||
* @param {boolean} [encode=true] - 是否进行URL编码
|
|
||||||
* @returns {string} 查询字符串(不带问号)
|
|
||||||
* @example
|
|
||||||
* fc.objectToQuery({a: 1, b: 'test'}) // "a=1&b=test"
|
|
||||||
*/
|
|
||||||
objectToQuery(params, encode = true) {
|
|
||||||
return Object.entries(params)
|
|
||||||
.map(([key, val]) => {
|
|
||||||
const value = val === null || val === undefined ? '' : val;
|
|
||||||
return `${key}=${encode ? encodeURIComponent(value) : value}`;
|
|
||||||
})
|
|
||||||
.join('&');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从错误堆栈中提取简洁的错误信息
|
|
||||||
* @param {Error} error - 错误对象
|
|
||||||
* @param {number} [depth=3] - 保留的堆栈深度
|
|
||||||
* @returns {string} 格式化后的错误信息
|
|
||||||
* @example
|
|
||||||
* try { ... } catch(err) {
|
|
||||||
* logger.error(fc.formatError(err));
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
formatError(error, depth = 3) {
|
|
||||||
if (!(error instanceof Error)) return String(error);
|
|
||||||
|
|
||||||
const stack = error.stack?.split('\n') || [];
|
|
||||||
const message = `${error.name}: ${error.message}`;
|
|
||||||
|
|
||||||
if (stack.length <= 1) return message;
|
|
||||||
|
|
||||||
return [message, ...stack.slice(1, depth + 1).map((line) => line.trim())].join('\n at ');
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default tools;
|
export default tools;
|
||||||
|
24
config/ai.json
Normal file
24
config/ai.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"mode": "deepseek",
|
||||||
|
"modelType": "deepseek-ai/DeepSeek-V3",
|
||||||
|
"historyLength": 3,
|
||||||
|
"maxLength": 3,
|
||||||
|
"chatTemperature": 1,
|
||||||
|
"pluginTemperature": 0.5,
|
||||||
|
"nickName": "寄气人",
|
||||||
|
"checkChat": {
|
||||||
|
"rdNum": 2,
|
||||||
|
"masterReply": true,
|
||||||
|
"userId": [
|
||||||
|
114514
|
||||||
|
],
|
||||||
|
"blackGroups": [
|
||||||
|
114,
|
||||||
|
514
|
||||||
|
],
|
||||||
|
"enableGroups": [
|
||||||
|
11115
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"maxMessageLength": 100
|
||||||
|
}
|
5
config/config.json
Normal file
5
config/config.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"debug": true,
|
||||||
|
"core": true,
|
||||||
|
"maxFeed": 10
|
||||||
|
}
|
@ -1,61 +0,0 @@
|
|||||||
### 参考以下注释进行配置,不要带注释粘贴,也不要改动`default.json`
|
|
||||||
|
|
||||||
配置文件位于`yunzai`根目录`/data/crystelf下`
|
|
||||||
|
|
||||||
参考注释:
|
|
||||||
|
|
||||||
``` yaml
|
|
||||||
{
|
|
||||||
"debug": true,\\是否启用调试模式
|
|
||||||
"core": true,\\是否启用晶灵核心相关功能
|
|
||||||
"coreConfig": { //晶灵核心配置
|
|
||||||
"coreUrl": "", //核心网址,需要加https://前缀
|
|
||||||
"wsUrl": "", //ws连接地址如ws://
|
|
||||||
"wsClientId": "",//端id
|
|
||||||
"wsSecret": "", wsmiy
|
|
||||||
"wsReConnectInterval": "5000",
|
|
||||||
"token": ""//postAPI调用密钥
|
|
||||||
},
|
|
||||||
"maxFeed": 10,//最大缓存rss流
|
|
||||||
"feeds": [//rss相关配置,无需手动更改
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"targetGroups": [114,154],
|
|
||||||
"screenshot": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fanqieConfig": {//番茄小说功能
|
|
||||||
"url": "http://127.0.0.1:6868",
|
|
||||||
"outDir": "/home/user/debian/cache/Downloads"
|
|
||||||
},
|
|
||||||
"poke": {//戳一戳概率,加起来不超过1,余下的概率为反击概率
|
|
||||||
"replyText": 0.4,
|
|
||||||
"replyVoice": 0.2,
|
|
||||||
"mutePick": 0.1,
|
|
||||||
""muteTime": 2"
|
|
||||||
},
|
|
||||||
"mode": "deepseek",//deepseekORopenai
|
|
||||||
"modelType": "deepseek-ai/DeepSeek-V3",//无需更改
|
|
||||||
"historyLength": 3,
|
|
||||||
"maxLength": 3,
|
|
||||||
"chatTemperature": 1,
|
|
||||||
"pluginTemperature": 0.5,
|
|
||||||
"nickName": "寄气人",//昵称
|
|
||||||
"checkChat": {
|
|
||||||
"rdNum": 2,//随机数,0-100
|
|
||||||
"masterReply": true,//主人回复
|
|
||||||
"userId": [ //一定回复的人
|
|
||||||
114514
|
|
||||||
],
|
|
||||||
"blackGroups": [//不许使用的群聊
|
|
||||||
114,
|
|
||||||
514
|
|
||||||
],
|
|
||||||
"enableGroups": [//一定回复的群聊
|
|
||||||
11115
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"maxMessageLength": 100//最大上下文
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
4
config/coreConfig.json
Normal file
4
config/coreConfig.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"coreUrl": "",
|
||||||
|
"token": ""
|
||||||
|
}
|
@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"debug": true,
|
|
||||||
"core": true,
|
|
||||||
"coreConfig": {
|
|
||||||
"coreUrl": "",
|
|
||||||
"token": ""
|
|
||||||
},
|
|
||||||
"maxFeed": 10,
|
|
||||||
"feeds": [
|
|
||||||
{
|
|
||||||
"url": "",
|
|
||||||
"targetGroups": [114,154],
|
|
||||||
"screenshot": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"poke": {
|
|
||||||
"replyText": 0.4,
|
|
||||||
"replyVoice": 0.2,
|
|
||||||
"mutePick": 0.1,
|
|
||||||
"muteTime": 2
|
|
||||||
},
|
|
||||||
"fanqieConfig": {
|
|
||||||
"url": "http://127.0.0.1:6868",
|
|
||||||
"outDir": "/home/user/debian/cache/Downloads"
|
|
||||||
},
|
|
||||||
"mode": "deepseek",
|
|
||||||
"modelType": "deepseek-ai/DeepSeek-V3",
|
|
||||||
"historyLength": 3,
|
|
||||||
"maxLength": 3,
|
|
||||||
"chatTemperature": 1,
|
|
||||||
"pluginTemperature": 0.5,
|
|
||||||
"nickName": "寄气人",
|
|
||||||
"checkChat": {
|
|
||||||
"rdNum": 2,
|
|
||||||
"masterReply": true,
|
|
||||||
"userId": [
|
|
||||||
114514
|
|
||||||
],
|
|
||||||
"blackGroups": [
|
|
||||||
114,
|
|
||||||
514
|
|
||||||
],
|
|
||||||
"enableGroups": [
|
|
||||||
11115
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"maxMessageLength": 100
|
|
||||||
}
|
|
4
config/fanqieConfig.json
Normal file
4
config/fanqieConfig.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"url": "http://127.0.0.1:6868",
|
||||||
|
"outDir": "/home/user/debian/cache/Downloads"
|
||||||
|
}
|
7
config/feeds.json
Normal file
7
config/feeds.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"url": "",
|
||||||
|
"targetGroups": [114,154],
|
||||||
|
"screenshot": true
|
||||||
|
}
|
||||||
|
]
|
3
config/poke.json
Normal file
3
config/poke.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"replyPoke": 0.4
|
||||||
|
}
|
3
config/profile.json
Normal file
3
config/profile.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"nickName": "鸡气人"
|
||||||
|
}
|
@ -6,11 +6,13 @@ const __filename = url.fileURLToPath(import.meta.url);
|
|||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
const rootDir = path.join(__dirname, '..');
|
const rootDir = path.join(__dirname, '..');
|
||||||
|
|
||||||
|
//这里储存的都是绝对路径
|
||||||
const Path = {
|
const Path = {
|
||||||
root: rootDir,
|
root: rootDir,
|
||||||
apps: path.join(rootDir, 'apps'),
|
apps: path.join(rootDir, 'apps'),
|
||||||
components: path.join(rootDir, 'components'),
|
components: path.join(rootDir, 'components'),
|
||||||
defaultConfig: path.join(rootDir, 'config/default.json'),
|
defaultConfig: path.join(rootDir, 'config/config.json'),
|
||||||
|
defaultConfigPath: path.join(rootDir, 'config'),
|
||||||
config: path.resolve(rootDir, '../../data/crystelf'),
|
config: path.resolve(rootDir, '../../data/crystelf'),
|
||||||
constants: path.join(rootDir, 'constants'),
|
constants: path.join(rootDir, 'constants'),
|
||||||
lib: path.join(rootDir, 'lib'),
|
lib: path.join(rootDir, 'lib'),
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
const relativelyPath = {
|
|
||||||
config: '/data/crystelf/config.json',
|
|
||||||
data: '/data/crystelf/data/',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default relativelyPath;
|
|
7
index.js
7
index.js
@ -6,16 +6,17 @@ import { crystelfInit } from './lib/system/init.js';
|
|||||||
import updater from './lib/system/updater.js';
|
import updater from './lib/system/updater.js';
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
chalk.rgb(134, 142, 204)(`crystelf-plugin ${Version.ver} 初始化 ~ by ${Version.author}`)
|
chalk.rgb(134, 142, 204)(`crystelf-plugin ${Version.ver} 初始化~ by ${Version.author}`)
|
||||||
);
|
);
|
||||||
|
|
||||||
updater.checkAndUpdate().catch((err) => {
|
updater.checkAndUpdate().catch((err) => {
|
||||||
logger.err(err);
|
logger.err(err);
|
||||||
});
|
});
|
||||||
await crystelfInit.CSH();
|
//不要加await!!!
|
||||||
|
crystelfInit.CSH().then(logger.mark('[crystelf-plugin] crystelf-plugin 完成初始化'));
|
||||||
|
|
||||||
const appPath = Path.apps;
|
const appPath = Path.apps;
|
||||||
const jsFiles = fc.readDirRecursive(appPath, 'js');
|
const jsFiles = await fc.readDirRecursive(appPath, 'js');
|
||||||
|
|
||||||
let ret = jsFiles.map((file) => {
|
let ret = jsFiles.map((file) => {
|
||||||
return import(`./apps/${file}`);
|
return import(`./apps/${file}`);
|
||||||
|
@ -1,55 +1,82 @@
|
|||||||
import Path, { defaultConfig } from '../../constants/path.js';
|
import Path, { defaultConfig } from '../../constants/path.js';
|
||||||
import fc from '../../components/json.js';
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import relativelyPath from '../../constants/relativelyPath.js';
|
import fc from '../../components/json.js';
|
||||||
|
|
||||||
const configPath = Path.config;
|
const fsp = fs.promises;
|
||||||
const dataPath = Path.data;
|
const pluginConfigPath = Path.defaultConfigPath;
|
||||||
const configFile = path.join(configPath, 'config.json');
|
const dataConfigPath = Path.config;
|
||||||
const configDir = relativelyPath.config;
|
const configFile = path.join(dataConfigPath, 'config.json');
|
||||||
|
let configCache = {}; // 缓存
|
||||||
|
|
||||||
let configCache = {};
|
/**
|
||||||
let lastModified = 0;
|
* 初始化配置
|
||||||
|
*/
|
||||||
function init() {
|
async function init() {
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(configPath)) {
|
try {
|
||||||
fs.mkdirSync(configPath, { recursive: true });
|
await fsp.access(dataConfigPath);
|
||||||
fs.mkdirSync(dataPath, { recursive: true });
|
} catch {
|
||||||
logger.mark(`crystelf-plugin 配置文件夹创建成功,位于 ${configPath}..`);
|
await fsp.mkdir(dataConfigPath, { recursive: true });
|
||||||
|
logger.mark(`[crystelf-plugin] 配置目录创建成功: ${dataConfigPath}`);
|
||||||
}
|
}
|
||||||
|
const pluginDefaultFile = path.join(pluginConfigPath, 'config.json');
|
||||||
|
try {
|
||||||
|
await fsp.access(configFile);
|
||||||
|
} catch {
|
||||||
|
await fsp.copyFile(pluginDefaultFile, configFile);
|
||||||
|
logger.mark(`[crystelf-plugin] 默认配置复制成功: ${configFile}`);
|
||||||
|
}
|
||||||
|
const pluginFiles = (await fsp.readdir(pluginConfigPath)).filter((f) => f.endsWith('.json'));
|
||||||
|
for (const file of pluginFiles) {
|
||||||
|
const pluginFilePath = path.join(pluginConfigPath, file);
|
||||||
|
const dataFilePath = path.join(dataConfigPath, file);
|
||||||
|
try {
|
||||||
|
await fsp.access(dataFilePath);
|
||||||
|
} catch {
|
||||||
|
await fsp.copyFile(pluginFilePath, dataFilePath);
|
||||||
|
logger.mark(`[crystelf-plugin] 配置文件缺失,已复制: ${file}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const files = (await fsp.readdir(dataConfigPath)).filter((f) => f.endsWith('.json'));
|
||||||
|
let baseConfig = {};
|
||||||
|
configCache = {};
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(dataConfigPath, file);
|
||||||
|
const name = path.basename(file, '.json');
|
||||||
|
try {
|
||||||
|
let data = await fc.readJSON(filePath);
|
||||||
|
const pluginFilePath = path.join(pluginConfigPath, file);
|
||||||
|
try {
|
||||||
|
await fsp.access(pluginFilePath);
|
||||||
|
const pluginData = await fc.readJSON(pluginFilePath);
|
||||||
|
data = fc.mergeConfig(data, pluginData);
|
||||||
|
await fc.writeJSON(filePath, data);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
if (!fs.existsSync(configFile)) {
|
if (name === 'config') {
|
||||||
fs.writeFileSync(configFile, JSON.stringify(defaultConfig, null, 4), 'utf8');
|
baseConfig = data;
|
||||||
logger.mark('crystelf-plugin 配置文件创建成功..');
|
|
||||||
} else {
|
} else {
|
||||||
const cfgFile = fs.readFileSync(configFile, 'utf8');
|
configCache[name] = data;
|
||||||
const loadedConfig = JSON.parse(cfgFile);
|
}
|
||||||
const cfg = { ...defaultConfig, ...loadedConfig };
|
} catch (e) {
|
||||||
|
logger.warn(`[crystelf-plugin] 读取配置文件 ${file} 失败:`, e);
|
||||||
if (JSON.stringify(cfg) !== JSON.stringify(loadedConfig)) {
|
|
||||||
fs.writeFileSync(configFile, JSON.stringify(cfg, null, 4), 'utf8');
|
|
||||||
logger.mark('crystelf-plugin 配置文件已更新,补充配置项..');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
configCache = { ...baseConfig, ...configCache };
|
||||||
const stats = fc.statSync(configDir, 'root');
|
configCache = fc.mergeConfig(configCache, defaultConfig);
|
||||||
configCache = fc.readJSON(configDir, 'root');
|
|
||||||
lastModified = stats.mtimeMs;
|
|
||||||
|
|
||||||
if (configCache.debug) {
|
if (configCache.debug) {
|
||||||
logger.info('crystelf-plugin 配置模块初始化成功..');
|
logger.info('[crystelf-plugin] 配置模块初始化成功..');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn('crystelf-plugin 初始化配置失败,使用空配置..', err);
|
logger.warn('[crystelf-plugin] 配置初始化失败,使用空配置..', err);
|
||||||
configCache = {};
|
configCache = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const configControl = {
|
const configControl = {
|
||||||
async init() {
|
async init() {
|
||||||
init();
|
await init();
|
||||||
},
|
},
|
||||||
|
|
||||||
get(key) {
|
get(key) {
|
||||||
@ -58,11 +85,31 @@ const configControl = {
|
|||||||
|
|
||||||
async set(key, value) {
|
async set(key, value) {
|
||||||
configCache[key] = value;
|
configCache[key] = value;
|
||||||
return fc.safeWriteJSON(configDir, configCache, 'root', 4);
|
const filePath = path.join(dataConfigPath, `${key}.json`);
|
||||||
|
try {
|
||||||
|
await fsp.access(filePath);
|
||||||
|
await fc.writeJSON(filePath, value);
|
||||||
|
} catch {
|
||||||
|
const cfg = await fc.readJSON(configFile);
|
||||||
|
cfg[key] = value;
|
||||||
|
await fc.writeJSON(configFile, cfg);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
return fc.safeWriteJSON(configDir, configCache, 'root', 4);
|
const cfg = await fc.readJSON(configFile);
|
||||||
|
for (const [key, value] of Object.entries(configCache)) {
|
||||||
|
const filePath = path.join(dataConfigPath, `${key}.json`);
|
||||||
|
try {
|
||||||
|
await fsp.access(filePath);
|
||||||
|
await fc.writeJSON(filePath, value);
|
||||||
|
} catch {
|
||||||
|
if (key !== 'config') {
|
||||||
|
cfg[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await fc.writeJSON(configFile, cfg);
|
||||||
},
|
},
|
||||||
|
|
||||||
async reload() {
|
async reload() {
|
||||||
|
@ -4,6 +4,7 @@ import path from 'path';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import configControl from '../config/configControl.js';
|
import configControl from '../config/configControl.js';
|
||||||
|
|
||||||
|
const fsp = fs.promises;
|
||||||
const redis = global.redis;
|
const redis = global.redis;
|
||||||
const cachePath = path.join(paths.rssCache, 'rss_cache.json');
|
const cachePath = path.join(paths.rssCache, 'rss_cache.json');
|
||||||
const MAX_CACHE = configControl.get('maxFeed');
|
const MAX_CACHE = configControl.get('maxFeed');
|
||||||
@ -77,13 +78,13 @@ const rssCache = {
|
|||||||
let localData = {};
|
let localData = {};
|
||||||
try {
|
try {
|
||||||
if (fs.existsSync(cachePath)) {
|
if (fs.existsSync(cachePath)) {
|
||||||
localData = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
|
localData = JSON.parse(await fsp.readFile(cachePath, 'utf-8'));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`本地rss缓存读取失败..`, err);
|
logger.error(`本地rss缓存读取失败..`, err);
|
||||||
}
|
}
|
||||||
localData[url] = latestLinks;
|
localData[url] = latestLinks;
|
||||||
fs.writeFileSync(cachePath, JSON.stringify(localData, null, 2), 'utf-8');
|
await fsp.writeFile(cachePath, JSON.stringify(localData, null, 2), 'utf-8');
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -94,7 +95,7 @@ const rssCache = {
|
|||||||
if (!fs.existsSync(cachePath)) return;
|
if (!fs.existsSync(cachePath)) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
|
const data = JSON.parse(await fsp.readFile(cachePath, 'utf-8'));
|
||||||
for (const [url, value] of Object.entries(data)) {
|
for (const [url, value] of Object.entries(data)) {
|
||||||
const key = this.urlToKey(url);
|
const key = this.urlToKey(url);
|
||||||
const safeArray = Array.isArray(value) ? value : [value];
|
const safeArray = Array.isArray(value) ? value : [value];
|
||||||
|
@ -5,6 +5,5 @@ export const crystelfInit = {
|
|||||||
async CSH() {
|
async CSH() {
|
||||||
await configControl.init();
|
await configControl.init();
|
||||||
await rssCache.init();
|
await rssCache.init();
|
||||||
logger.mark('crystelf-plugin 完成初始化');
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -9,25 +9,25 @@ const GIT_DIR = path.join(Path.root, '.git');
|
|||||||
const execStr = (cmd) => child_process.execSync(cmd, { cwd: Path.root }).toString().trim();
|
const execStr = (cmd) => child_process.execSync(cmd, { cwd: Path.root }).toString().trim();
|
||||||
|
|
||||||
const Updater = {
|
const Updater = {
|
||||||
isGitRepo() {
|
async isGitRepo() {
|
||||||
return fs.existsSync(GIT_DIR);
|
return fs.existsSync(GIT_DIR);
|
||||||
},
|
},
|
||||||
|
|
||||||
getBranch() {
|
async getBranch() {
|
||||||
return execStr('git symbolic-ref --short HEAD');
|
return execStr('git symbolic-ref --short HEAD');
|
||||||
},
|
},
|
||||||
|
|
||||||
getLocalHash() {
|
async getLocalHash() {
|
||||||
return execStr('git rev-parse HEAD');
|
return execStr('git rev-parse HEAD');
|
||||||
},
|
},
|
||||||
|
|
||||||
getRemoteHash(branch = 'main') {
|
async getRemoteHash(branch = 'main') {
|
||||||
return execStr(`git rev-parse origin/${branch}`);
|
return execStr(`git rev-parse origin/${branch}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
async hasUpdate() {
|
async hasUpdate() {
|
||||||
try {
|
try {
|
||||||
const branch = this.getBranch();
|
const branch = await this.getBranch();
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
child_process.exec('git fetch', { cwd: Path.root }, (err) => {
|
child_process.exec('git fetch', { cwd: Path.root }, (err) => {
|
||||||
@ -39,8 +39,8 @@ const Updater = {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const local = this.getLocalHash();
|
const local = await this.getLocalHash();
|
||||||
const remote = this.getRemoteHash(branch);
|
const remote = await this.getRemoteHash(branch);
|
||||||
|
|
||||||
return local !== remote;
|
return local !== remote;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -59,7 +59,7 @@ const Updater = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async checkAndUpdate() {
|
async checkAndUpdate() {
|
||||||
if (!this.isGitRepo()) {
|
if (!(await this.isGitRepo())) {
|
||||||
logger.warn('[crystelf-plugin] 当前目录不是 Git 仓库,自动更新功能已禁用');
|
logger.warn('[crystelf-plugin] 当前目录不是 Git 仓库,自动更新功能已禁用');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user