mirror of
https://github.com/ZZZure/ZZZ-Plugin.git
synced 2025-12-16 13:17:32 +00:00
initial upload
This commit is contained in:
commit
1c65f10e24
58 changed files with 2533 additions and 0 deletions
53
lib/authkey.js
Normal file
53
lib/authkey.js
Normal 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
36
lib/common.js
Normal 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
246
lib/mysapi.js
Normal 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
21
lib/mysapi/api.js
Normal 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
81
lib/mysapi/tool.js
Normal 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®ion=${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®ion=${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
29
lib/path.js
Normal 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
65
lib/plugin.js
Normal 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
56
lib/render.js
Normal 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
195
lib/settings.js
Normal 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
101
lib/version.js
Normal 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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue