initial upload

This commit is contained in:
bietiaop 2024-07-08 13:13:22 +08:00
commit 1c65f10e24
58 changed files with 2533 additions and 0 deletions

53
lib/authkey.js Normal file
View file

@ -0,0 +1,53 @@
// import User from "../../xiaoyao-cvs-plugin/model/user.js";
import fetch from 'node-fetch';
import MysZZZApi from './mysapi.js';
let User;
try {
User = (await import('../../xiaoyao-cvs-plugin/model/user.js')).default;
} catch (e) {
logger.warn('建议安装逍遥插件以获得更佳体验');
}
/**
* 此方法依赖逍遥插件
* @returns {Promise<void>}
*/
export async function getAuthKey(e, srUid, authAppid = 'csc') {
if (!User) {
throw new Error('未安装逍遥插件,无法自动刷新抽卡链接');
}
let user = new User(e);
// set genshin uid
await user.getCookie(e);
let ck = await user.getStoken(e.user_id);
ck = `stuid=${ck.stuid};stoken=${ck.stoken};mid=${ck.mid};`;
let api = new MysZZZApi(srUid, ck);
let type = 'zzzPayAuthKey';
switch (authAppid) {
case 'csc': {
type = 'zzzPayAuthKey';
break;
}
default:
}
const { url, headers, body } = api.getUrl(type);
let res = await fetch(url, {
method: 'POST',
headers,
body,
});
res = await res.json();
return res?.data?.authkey;
}
export async function getStoken(e) {
if (!User) {
throw new Error('未安装逍遥插件,无法自动刷新抽卡链接');
}
let user = new User(e);
// set uid
await user.getCookie(e);
let ck = await user.getStoken(e.user_id);
ck = `stuid=${ck.stuid};stoken=${ck.stoken};mid=${ck.mid};`;
return ck;
}

36
lib/common.js Normal file
View file

@ -0,0 +1,36 @@
import User from '../../genshin/model/user.js';
import { getStoken } from './authkey.js';
export const rulePrefix = '((#|\\*)?(zzz|ZZZ|绝区零)|\\*|)';
export async function getCk(e, s = false) {
e.isSr = true;
let stoken = '';
let user = new User(e);
if (s) {
stoken = await getStoken(e);
}
if (typeof user.getCk === 'function') {
let ck = user.getCk();
Object.keys(ck).forEach(k => {
if (ck[k].ck) {
ck[k].ck = `${stoken}${ck[k].ck}`;
}
});
return ck;
}
let mysUser = (await user.user()).getMysUser('zzz');
let ck;
if (mysUser) {
ck = {
default: {
ck: `${stoken}${mysUser.ck}`,
uid: mysUser.getUid('zzz'),
qq: '',
ltuid: mysUser.ltuid,
device_id: mysUser.device,
},
};
}
return ck;
}

246
lib/mysapi.js Normal file
View file

@ -0,0 +1,246 @@
import MysApi from '../../genshin/model/mys/mysApi.js';
import md5 from 'md5';
import _ from 'lodash';
import crypto from 'crypto';
import ZZZApiTool from './mysapi/tool.js';
// const DEVICE_ID = randomString(32).toUpperCase()
const DEVICE_NAME = randomString(_.random(1, 10));
export default class MysZZZApi extends MysApi {
constructor(uid, cookie, option = {}) {
super(uid, cookie, option, true);
this.uid = uid;
this.server = this.getServer();
// this.isSr = true
// this.server = 'hkrpg_cn'
this.apiTool = new ZZZApiTool(uid, this.server);
if (typeof this.cookie != 'string' && this.cookie) {
let ck =
this.cookie[Object.keys(this.cookie).filter(k => this.cookie[k].ck)[0]];
this._device = ck?.device_id || ck?.device;
this.cookie = ck?.ck;
}
if (!this._device) {
this._device = crypto.randomUUID();
}
}
getServer() {
switch (String(this.uid).slice(0, -8)) {
case '1':
case '2':
return 'prod_gf_cn'; // 官服
case '5':
return 'prod_qd_cn'; // B服
case '6':
return 'prod_official_usa'; // 美服
case '7':
return 'prod_official_euro'; // 欧服
case '8':
case '18':
return 'prod_official_asia'; // 亚服
case '9':
return 'prod_official_cht'; // 港澳台服
}
return 'prod_gf_cn';
}
getUrl(type, data = {}) {
data.deviceId = this._device;
let urlMap = this.apiTool.getUrlMap(data);
if (!urlMap[type]) return false;
let {
url,
query = '',
body = '',
noDs = false,
dsSalt = '',
} = urlMap[type];
if (query) url += `?${query}`;
if (body) body = JSON.stringify(body);
let headers = this.getHeaders(query, body);
if (data.deviceFp) {
headers['x-rpc-device_fp'] = data.deviceFp;
// 兼容喵崽
this._device_fp = { data: { device_fp: data.deviceFp } };
}
headers.cookie = this.cookie;
if (this._device) {
headers['x-rpc-device_id'] = this._device;
}
switch (dsSalt) {
case 'web': {
headers.DS = this.getDS2();
break;
}
default:
}
if (type === 'zzzPayAuthKey') {
let extra = {
'x-rpc-app_version': '2.40.1',
'User-Agent': 'okhttp/4.8.0',
'x-rpc-client_type': '5',
Referer: 'https://app.mihoyo.com',
Origin: 'https://webstatic.mihoyo.com',
// Cookie: this.cookies,
// DS: this.getDS2(),
'x-rpc-sys_version': '12',
'x-rpc-channel': 'mihoyo',
'x-rpc-device_id': this._device,
'x-rpc-device_name': DEVICE_NAME,
'x-rpc-device_model': 'Mi 10',
Host: 'api-takumi.mihoyo.com',
};
headers = Object.assign(headers, extra);
} else {
headers.DS = this.getDs(query, body);
}
if (noDs) {
delete headers.DS;
if (this._device) {
body = JSON.parse(body);
body.device_id = this._device;
body = JSON.stringify(body);
}
}
return { url, headers, body };
}
getDs(q = '', b = '') {
let n = '';
if (['prod_gf_cn', 'prod_qd_cn'].includes(this.server)) {
n = 'xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs';
} else if (/official/.test(this.server)) {
n = 'okr4obncj8bw5a65hbnn5oo6ixjc3l9w';
}
let t = Math.round(new Date().getTime() / 1000);
let r = Math.floor(Math.random() * 900000 + 100000);
let DS = md5(`salt=${n}&t=${t}&r=${r}&b=${b}&q=${q}`);
return `${t},${r},${DS}`;
}
getDS2() {
let t = Math.round(new Date().getTime() / 1000);
let r = randomString(6);
let sign = md5(`salt=jEpJb9rRARU2rXDA9qYbZ3selxkuct9a&t=${t}&r=${r}`);
return `${t},${r},${sign}`;
}
getHeaders(query = '', body = '') {
const cn = {
app_version: '2.44.1',
User_Agent:
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.44.1',
client_type: '5',
Origin: 'https://webstatic.mihoyo.com',
X_Requested_With: 'com.mihoyo.hyperion',
Referer: 'https://webstatic.mihoyo.com/',
};
const os = {
app_version: '2.55.0',
User_Agent:
'Mozilla/5.0 (Linux; Android 11; J9110 Build/55.2.A.4.332; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/124.0.6367.179 Mobile Safari/537.36 miHoYoBBSOversea/2.55.0',
client_type: '2',
Origin: 'https://act.hoyolab.com',
X_Requested_With: 'com.mihoyo.hoyolab',
Referer: 'https://act.hoyolab.com/',
};
let client;
if (/official/.test(this.server)) {
client = os;
} else {
client = cn;
}
return {
'x-rpc-app_version': client.app_version,
'x-rpc-client_type': client.client_type,
// 'x-rpc-page': '3.1.3_#/rpg',
'User-Agent': client.User_Agent,
Referer: client.Referer,
DS: this.getDs(query, body),
Origin: client.Origin,
};
}
/**
* 校验状态码
* @param e 消息e
* @param res 请求返回
* @param type 请求类型 srNote
* @param data 查询请求的数据
* @returns {Promise<*|boolean>}
*/
async checkCode(e, res, type, data = {}) {
if (!res || !e) {
this.e.reply('米游社接口请求失败,暂时无法查询');
return false;
}
this.e = e;
this.e.isSr = true;
res.retcode = Number(res.retcode);
switch (res.retcode) {
case 0:
break;
case 10035:
case 1034: {
let handler = this.e.runtime?.handler || {};
// 如果有注册的mys.req.err调用
if (handler.has('mys.req.err')) {
logger.mark(
`[米游社zzz查询失败][uid:${this.uid}][qq:${this.userId}] 遇到验证码,尝试调用 Handler mys.req.err`
);
res =
(await handler.call('mys.req.err', this.e, {
mysApi: this,
type,
res,
data,
mysInfo: this,
})) || res;
}
if (!res || res?.retcode === 1034 || res?.retcode === 10035) {
logger.mark(
`[米游社zzz查询失败][uid:${this.uid}][qq:${this.userId}] 遇到验证码`
);
this.e.reply('米游社zzz查询遇到验证码请稍后再试');
}
break;
}
default:
if (/(登录|login)/i.test(res.message)) {
logger.mark(`[ck失效][uid:${this.uid}]`);
this.e.reply(`UID:${this.uid}米游社cookie已失效`);
} else {
this.e.reply(
`米游社接口报错,暂时无法查询:${res.message || 'error'}`
);
}
break;
}
if (res.retcode !== 0) {
logger.mark(
`[米游社zzz接口报错]${JSON.stringify(res)}uid${this.uid}`
);
}
return res;
}
}
export function randomString(length) {
let randomStr = '';
for (let i = 0; i < length; i++) {
randomStr += _.sample('abcdefghijklmnopqrstuvwxyz0123456789');
}
return randomStr;
}
export function generateSeed(length = 16) {
const characters = '0123456789abcdef';
let result = '';
for (let i = 0; i < length; i++) {
result += characters[Math.floor(Math.random() * characters.length)];
}
return result;
}

21
lib/mysapi/api.js Normal file
View file

@ -0,0 +1,21 @@
export const OLD_URL = 'https://api-takumi.mihoyo.com',
NEW_URL = 'https://api-takumi-record.mihoyo.com';
export const ZZZ_API = `${NEW_URL}/event/game_record_zzz/api/zzz`,
ZZZ_INDEX_API = `${ZZZ_API}/index`,
ZZZ_NOTE_API = `${ZZZ_API}/note`,
ZZZ_BUDDY_INFO_API = `${ZZZ_API}/buddy/info`,
ZZZ_AVATAR_BASIC_API = `${ZZZ_API}/avatar/basic`,
ZZZ_AVATAR_INFO_API = `${ZZZ_API}/avatar/info`,
ZZZ_CHALLENGE_API = `${ZZZ_API}/challenge`,
ZZZ_BIND_API = `${OLD_URL}/binding/api`,
ZZZ_GAME_INFO_API = `${ZZZ_BIND_API}/getUserGameRolesByCookie?game_biz=nap_cn`;
// Resource
export const ZZZ_RES = 'https://act-webstatic.mihoyo.com/game_record/zzz',
ZZZ_SQUARE_AVATAR = `${ZZZ_RES}/role_square_avatar`,
ZZZ_SQUARE_BANGBOO = `${ZZZ_RES}/bangboo_rectangle_avatar`;
export const PUBLIC_API = 'https://public-operation-nap.mihoyo.com',
PUBILC_GACHA_LOG_API = `${PUBLIC_API}/common/gacha_record/api`,
ZZZ_GET_GACHA_LOG_API = `${PUBILC_GACHA_LOG_API}/getGachaLog`;

81
lib/mysapi/tool.js Normal file
View file

@ -0,0 +1,81 @@
import { generateSeed } from '../mysapi.js';
import crypto from 'crypto';
/**
* derived from miao-yunzai
*/
export default class ZZZApiTool {
/**
*
* @param {uid} uid
* @param {server} server
*/
constructor(uid, server) {
this.uid = uid;
this.isSr = true;
this.server = server;
this.game = 'zzz';
this.uuid = crypto.randomUUID();
}
getUrlMap = (data = {}) => {
let host, hostRecord, hostPublicData;
if (['prod_gf_cn', 'prod_qd_cn'].includes(this.server)) {
host = 'https://api-takumi.mihoyo.com/';
hostRecord = 'https://api-takumi-record.mihoyo.com/';
hostPublicData = 'https://public-data-api.mihoyo.com/';
} else if (/official/.test(this.server)) {
host = 'https://sg-public-api.hoyolab.com/';
hostRecord = 'https://bbs-api-os.hoyolab.com/';
hostPublicData = 'https://sg-public-data-api.hoyoverse.com/';
}
let urlMap = {
zzz: {
...(['prod_gf_cn', 'prod_qd_cn'].includes(this.server)
? {
zzzUser: {
url: `${host}binding/api/getUserGameRolesByCookie`,
query: `game_biz=nap_cn&region=${this.server}&game_uid=${this.uid}`,
},
getFp: {
url: `${hostPublicData}device-fp/api/getFp`,
body: {
seed_id: `${generateSeed(16)}`,
device_id: data.deviceId,
platform: '1',
seed_time: new Date().getTime() + '',
ext_fields: `{"ramCapacity":"3746","hasVpn":"0","proxyStatus":"0","screenBrightness":"0.550","packageName":"com.miHoYo.mhybbs","romRemain":"100513","deviceName":"iPhone","isJailBreak":"0","magnetometer":"-160.495300x-206.488358x58.534348","buildTime":"1706406805675","ramRemain":"97","accelerometer":"-0.419876x-0.748367x-0.508057","cpuCores":"6","cpuType":"CPU_TYPE_ARM64","packageVersion":"2.20.1","gyroscope":"0.133974x-0.051780x-0.062961","batteryStatus":"45","appUpdateTimeDiff":"1707130080397","appMemory":"57","screenSize":"414×896","vendor":"--","model":"iPhone12,5","IDFV":"${data.deviceId.toUpperCase()}","romCapacity":"488153","isPushEnabled":"1","appInstallTimeDiff":"1696756955347","osVersion":"17.2.1","chargeStatus":"1","isSimInserted":"1","networkType":"WIFI"}`,
app_name: 'account_cn',
device_fp: '38d7f0fa36179',
},
noDs: true,
},
}
: {
zzzUser: {
url: `${host}binding/api/getUserGameRolesByCookie`,
query: `game_biz=nap_global&region=${this.server}&game_uid=${this.uid}`,
},
getFp: {
url: `${hostPublicData}device-fp/api/getFp`,
body: {
seed_id: `${this.uuid}`,
device_id: '35315696b7071100',
hoyolab_device_id: `${this.uuid}`,
platform: '2',
seed_time: new Date().getTime() + '',
ext_fields: `{"proxyStatus":1,"isRoot":1,"romCapacity":"512","deviceName":"Xperia 1","productName":"J9110","romRemain":"483","hostname":"BuildHost","screenSize":"1096x2434","isTablet":0,"model":"J9110","brand":"Sony","hardware":"qcom","deviceType":"J9110","devId":"REL","serialNumber":"unknown","sdCapacity":107433,"buildTime":"1633631032000","buildUser":"BuildUser","simState":1,"ramRemain":"98076","appUpdateTimeDiff":1716545162858,"deviceInfo":"Sony\/J9110\/J9110:11\/55.2.A.4.332\/055002A004033203408384484:user\/release-keys","buildType":"user","sdkVersion":"30","ui_mode":"UI_MODE_TYPE_NORMAL","isMockLocation":0,"cpuType":"arm64-v8a","isAirMode":0,"ringMode":2,"app_set_id":"${this.uuid}","chargeStatus":1,"manufacturer":"Sony","emulatorStatus":0,"appMemory":"512","adid":"${this.uuid}","osVersion":"11","vendor":"unknown","accelerometer":"-0.9233304x7.574181x6.472585","sdRemain":97931,"buildTags":"release-keys","packageName":"com.mihoyo.hoyolab","networkType":"WiFi","debugStatus":1,"ramCapacity":"107433","magnetometer":"-9.075001x-27.300001x-3.3000002","display":"55.2.A.4.332","appInstallTimeDiff":1716489549794,"packageVersion":"","gyroscope":"0.027029991x-0.04459185x0.032222193","batteryStatus":45,"hasKeyboard":0,"board":"msmnile"}`,
app_name: 'bbs_oversea',
device_fp: '38d7f2352506c',
},
noDs: true,
},
}),
zzzNote: {
url: `${hostRecord}event/game_record_zzz/api/zzz/note`,
query: `role_id=${this.uid}&server=${this.server}`,
},
},
};
return urlMap[this.game];
};
}

29
lib/path.js Normal file
View file

@ -0,0 +1,29 @@
import path from 'path';
import { fileURLToPath } from 'url';
// 获取当前模块的 URL
const metaUrl = import.meta.url;
// 将 URL 转换为文件路径
const metaPath = fileURLToPath(new URL(metaUrl));
// 插件路径
export const pluginPath = path.join(metaPath, '../../');
// 插件名
export const pluginName = path.basename(pluginPath);
// apps 路径
export const appPath = path.join(pluginPath, 'apps');
// resources
export const resourcesPath = path.join(pluginPath, 'resources');
// config 路径
export const configPath = path.join(pluginPath, 'config');
// 默认配置路径
export const defPath = path.join(pluginPath, 'defSet');
// data 路径
export const dataPath = path.join(pluginPath, 'data');

65
lib/plugin.js Normal file
View file

@ -0,0 +1,65 @@
import MysZZZApi from './mysapi.js';
import { getCk } from './common.js';
import _ from 'lodash';
export class ZZZPlugin extends plugin {
async miYoSummerGetUid() {
const key = `ZZZ:UID:${this.e.user_id}`;
const ck = await getCk(this.e);
if (!ck) return false;
let api = new MysZZZApi('', ck);
let userData = await api.getData('zzzUser');
if (!userData?.data || _.isEmpty(userData.data.list)) return false;
userData = userData.data.list[0];
let { game_uid: gameUid } = userData;
await redis.set(key, gameUid);
await redis.setEx(
`ZZZ:userData:${gameUid}`,
60 * 60,
JSON.stringify(userData)
);
return userData;
}
async getAPI() {
let user = this.e.user_id;
let ats = this.e.message.filter(m => m.type === 'at');
if (ats.length > 0 && !e.atBot) {
user = ats[0].qq;
this.e.user_id = user;
this.User = new User(this.e);
}
let uid = this.e.msg.match(/\d+/)?.[0];
await this.miYoSummerGetUid();
uid =
uid || (await redis.get(`ZZZ:UID:${user}`)) || this.e.user?.getUid('zzz');
if (!uid) {
await this.reply('尚未绑定uid,请发送#zzz绑定uid进行绑定');
return false;
}
const ck = await getCk(this.e);
if (!ck || Object.keys(ck).filter(k => ck[k].ck).length === 0) {
await this.reply('尚未绑定cookie请先使用逍遥插件绑定cookie');
return false;
}
const api = new MysZZZApi(uid, ck);
let deviceFp = await redis.get(`ZZZ:DEVICE_FP:${uid}`);
if (!deviceFp) {
let sdk = api.getUrl('getFp');
let res = await fetch(sdk.url, {
headers: sdk.headers,
method: 'POST',
body: sdk.body,
});
let fpRes = await res.json();
deviceFp = fpRes?.data?.device_fp;
if (deviceFp) {
await redis.set(`ZZZ:DEVICE_FP:${uid}`, deviceFp, {
EX: 86400 * 7,
});
}
}
return { api, uid, deviceFp };
}
}

56
lib/render.js Normal file
View file

@ -0,0 +1,56 @@
import path from 'path';
import { pluginName, resourcesPath } from './path.js';
import setting from './settings.js';
import version from './version.js';
/**
* 渲染函数
* @param {any} e 消息事件
* @param {string} renderPath 渲染路径
* @param {object} renderData 渲染数据
* @param {object} cfg 配置
* @returns
*/
function render(e, renderPath, renderData = {}, cfg = {}) {
// 判断是否存在e.runtime
if (!e.runtime) {
console.log('未找到e.runtime请升级至最新版Yunzai');
}
// 获取渲染精度配置
const scaleCfg = setting.getConfig('config')?.render?.scale || 100;
// 计算配置缩放比例
const scaleCfgValue = Math.min(2, Math.max(0.5, scaleCfg / 100));
// 将函数参数中的缩放比例与配置缩放比例相乘
const scale = (cfg?.scale || 1) * scaleCfgValue;
// 将缩放比例转换为style属性
const pct = `style='transform:scale(${scale})'`;
// 获取布局路径
const layoutPathFull = path.join(resourcesPath, 'common/layout/');
// 调用e.runtime.render方法渲染
return e.runtime.render(pluginName, renderPath, renderData, {
...cfg,
beforeRender({ data }) {
const resPath = data.pluResPath;
const layoutPath = data.pluResPath + 'common/layout/';
const renderPathFull = data.pluResPath + renderPath.split('/')[0] + '/';
return {
...data,
_res_path: resPath,
_layout_path: layoutPath,
defaultLayout: path.join(layoutPathFull, 'index.html'),
sys: {
scale: pct,
resourcesPath: resPath,
currentPath: renderPathFull,
copyright: `Created By ${version.name}<span class="version">${version.yunzai}</span> & ${pluginName}<span class="version">${version.version}</span>`,
createdby: `Created By <span class="highlight">${pluginName}</span> & Powered By <span class="highlight">ZZZure</span>`,
},
quality: 100,
};
},
});
}
export default render;

195
lib/settings.js Normal file
View file

@ -0,0 +1,195 @@
import YAML from 'yaml';
import chokidar from 'chokidar';
import fs from 'fs';
import path from 'path';
import { configPath, dataPath, defPath, pluginName } from './path.js';
class Setting {
constructor() {
/** 默认设置 */
this.defPath = defPath;
this.defSet = {};
/** 用户设置 */
this.configPath = configPath;
this.config = {};
this.dataPath = dataPath;
this.data = {};
/** 监听文件 */
this.watcher = { config: {}, defSet: {} };
this.initCfg();
}
/** 初始化配置 */
initCfg() {
const files = fs
.readdirSync(this.defPath)
.filter(file => file.endsWith('.yaml'));
for (let file of files) {
if (!fs.existsSync(path.join(this.configPath, file))) {
fs.copyFileSync(
path.join(this.defPath, file),
path.join(this.configPath, file)
);
}
this.watch(
path.join(this.configPath, file),
file.replace('.yaml', ''),
'config'
);
}
}
// 配置对象化 用于锅巴插件界面填充
merge() {
let sets = {};
let appsConfig = fs
.readdirSync(this.defPath)
.filter(file => file.endsWith('.yaml'));
for (let appConfig of appsConfig) {
// 依次将每个文本填入键
let filename = appConfig.replace(/.yaml/g, '').trim();
sets[filename] = this.getConfig(filename);
}
return sets;
}
// 配置对象分析 用于锅巴插件界面设置
analysis(config) {
for (let key of Object.keys(config)) {
this.setConfig(key, config[key]);
}
}
// 获取对应模块数据文件
getData(filepath, filename) {
filename = `${filename}.yaml`;
filepath = path.join(this.dataPath, filepath);
try {
if (!fs.existsSync(path.join(filepath, filename))) {
return false;
}
return YAML.parse(fs.readFileSync(path.join(filepath, filename), 'utf8'));
} catch (error) {
logger.error(`[${pluginName}] [${filename}] 读取失败 ${error}`);
return false;
}
}
// 写入对应模块数据文件
setData(filepath, filename, data) {
filename = `${filename}.yaml`;
filepath = path.join(this.dataPath, filepath);
try {
if (!fs.existsSync(filepath)) {
// 递归创建目录
fs.mkdirSync(filepath, { recursive: true });
}
fs.writeFileSync(
path.join(filepath, filename),
YAML.stringify(data),
'utf8'
);
} catch (error) {
logger.error(`[${pluginName}] [${filename}] 写入失败 ${error}`);
return false;
}
}
// 获取对应模块默认配置
getdefSet(app) {
return this.getYaml(app, 'defSet');
}
// 获取对应模块用户配置
getConfig(app) {
return { ...this.getdefSet(app), ...this.getYaml(app, 'config') };
// return this.mergeConfigObjectArray({...this.getdefSet(app)},{...this.getYaml(app, 'config')});
}
//合并两个对象 相同的数组对象 主要用于yml的列表属性合并 并去重 先备份一下方法
mergeConfigObjectArray(obj1, obj2) {
for (const key in obj2) {
if (Array.isArray(obj2[key]) && Array.isArray(obj1[key])) {
//合并两个对象中相同 数组属性
const uniqueElements = new Set([...obj1[key], ...obj2[key]]);
obj1[key] = [...uniqueElements];
} else {
//否则以obj2中的为准
obj1[key] = obj2[key];
}
}
return obj1;
}
// 设置对应模块用户配置
setConfig(app, Object) {
return this.setYaml(app, 'config', { ...this.getdefSet(app), ...Object });
}
// 将对象写入YAML文件
setYaml(app, type, Object) {
let file = this.getFilePath(app, type);
try {
fs.writeFileSync(file, YAML.stringify(Object), 'utf8');
} catch (error) {
logger.error(`[${app}] 写入失败 ${error}`);
return false;
}
}
// 读取YAML文件 返回对象
getYaml(app, type) {
let file = this.getFilePath(app, type);
if (this[type][app]) return this[type][app];
try {
this[type][app] = YAML.parse(fs.readFileSync(file, 'utf8'));
} catch (error) {
logger.error(`[${app}] 格式错误 ${error}`);
return false;
}
this.watch(file, app, type);
return this[type][app];
}
// 获取YAML文件目录
getFilePath(app, type) {
const appFilename = `${app}.yaml`;
if (type === 'defSet') return path.join(this.defPath, appFilename);
else {
try {
if (!fs.existsSync(path.join(this.configPath, appFilename))) {
fs.copyFileSync(
path.join(this.defPath, appFilename),
path.join(this.configPath, appFilename)
);
}
} catch (error) {
logger.error(`[${pluginName}]缺失默认文件[${app}]${error}`);
}
return path.join(this.configPath, `${app}.yaml`);
}
}
// 监听配置文件
watch(file, app, type = 'defSet') {
if (this.watcher[type][app]) return;
const watcher = chokidar.watch(file);
watcher.on('change', path => {
delete this[type][app];
logger.mark(`[${pluginName}][修改配置文件][${type}][${app}]`);
if (this[`change_${app}`]) {
this[`change_${app}`]();
}
});
this.watcher[type][app] = watcher;
}
}
export default new Setting();

101
lib/version.js Normal file
View file

@ -0,0 +1,101 @@
import fs from 'fs';
import lodash from 'lodash';
import path from 'path';
import { pluginPath } from './path.js';
const _logPath = path.join(pluginPath, 'CHANGELOG.md');
let logs = {};
let changelogs = [];
let currentVersion;
let versionCount = 4;
let packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
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(_logPath)) {
logs = fs.readFileSync(_logPath, 'utf8') || '';
logs = logs.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);
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;
} else {
versionCount--;
}
}
temp = {
version: v,
logs: [],
};
} else {
if (!line.trim()) {
return;
}
if (/^\*/.test(line)) {
lastLine = {
title: getLine(line),
logs: [],
};
temp.logs.push(lastLine);
} else if (/^\s{2,}\*/.test(line)) {
lastLine.logs.push(getLine(line));
}
}
});
}
} catch (e) {
// void error
}
const yunzaiVersion = packageJson.version;
const isV3 = yunzaiVersion[0] === '3';
let isMiao = false;
let name = 'Yunzai-Bot';
if (packageJson.name === 'miao-yunzai') {
isMiao = true;
name = 'Miao-Yunzai';
} else if (packageJson.name === 'trss-yunzai') {
isMiao = true;
name = 'TRSS-Yunzai';
}
const version = {
isV3,
isMiao,
name,
get version() {
return currentVersion;
},
get yunzai() {
return yunzaiVersion;
},
get changelogs() {
return changelogs;
},
};
export default version;