ZZZ-Plugin/lib/plugin.js

286 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import MysZZZApi from './mysapi.js';
import { getCk } from './common.js';
import _ from 'lodash';
import NoteUser from '../../genshin/model/mys/NoteUser.js';
import settings from '../lib/settings.js';
import request from '../utils/request.js';
import path from 'path';
import { pluginName, resourcesPath } from './path.js';
import version from './version.js';
export class ZZZPlugin extends plugin {
/**
* 获取用户 UID如果需要同时获取API可以直接调用 getAPI
* @returns {Promise<string | boolean>}
*/
async getUID() {
// 默认为当前用户
let user = this.e;
// 获取配置
const query = settings.getConfig('config').query;
const allow = _.get(query, 'others', true);
// 如果 at 存在且允许查看其他用户
if (this.e.at && allow) {
// 将当前用户的 user_id 设置为 at 对象的 user_id
this.e.user_id = this.e.at;
// 将当前用户设置为 at 对象
user = this.e.at;
}
// 获取用户信息(米游社),因此这里会导致查询一次米游社的信息
this.User = await NoteUser.create(user);
// 获取用户 UID
const uid = this.User?.getUid('zzz');
// 如果 UID 不存在,说明没有绑定 cookie
if (!uid) {
await this.reply(
'uid为空需要CK的功能请先绑定CK或者#扫码登录需要SK的功能请#扫码登录若不清楚需要CK或SK请查看%帮助'
);
throw new Error('UID为空');
}
// 返回 UID
return uid;
}
/**
* 获取用户 ltuid
* @returns {Promise<string | boolean>}
* @memberof ZZZPlugin
* @description 获取用户 ltuid
* @returns {Promise<string | boolean>}
*/
async getLtuid() {
const uid = await this.getUID();
const ck = await getCk(this.e);
if (!ck || Object.keys(ck).filter(k => ck[k].ck).length === 0) {
await this.reply('尚未绑定cookie请先绑定cookie或者#扫码登录');
throw new Error('CK为空');
}
const currentCK = Object.values(ck).find(item => {
return item.ck && item.uid === uid;
});
return currentCK?.ltuid || '';
}
/**
* 获取米游社 API
* @returns {Promise<{api: MysZZZApi, uid: string, deviceFp: string}>}
*/
async getAPI() {
// 直接调用获取 UID
const uid = await this.getUID();
// 获取用户的 cookie
const ck = await getCk(this.e);
// 如果 cookie 不存在或者 cookie 为空,说明没有绑定 cookie
if (!ck || Object.keys(ck).filter(k => ck[k].ck).length === 0) {
await this.reply('尚未绑定cookie请先绑定cookie或者#扫码登录');
throw new Error('CK为空');
}
// 创建米游社 API 对象
const api = new MysZZZApi(uid, ck, {
handler: this.e?.runtime?.handler || {},
});
const currentCK = Object.values(ck).find(item => {
return item.ck && item.uid === uid;
});
const ltuid = currentCK?.ltuid || '';
if (!ltuid) {
this.reply('ltuid为空请重新绑定CK');
throw new Error('ltuid为空');
}
// 获取设备指纹
let deviceFp;
let bindInfo = await redis.get(`ZZZ:DEVICE_FP:${ltuid}:BIND`);
if (bindInfo) {
deviceFp = await redis.get(`ZZZ:DEVICE_FP:${ltuid}:FP`);
let data = {
deviceFp,
};
try {
bindInfo = JSON.parse(bindInfo);
data = {
productName: bindInfo?.deviceProduct,
deviceType: bindInfo?.deviceName,
modelName: bindInfo?.deviceModel,
oaid: bindInfo?.oaid,
deviceInfo: bindInfo?.deviceFingerprint,
board: bindInfo?.deviceBoard,
};
} catch (error) {
bindInfo = null;
}
if (!deviceFp) {
const sdk = api.getUrl('getFp', data);
const res = await request(sdk.url, {
headers: sdk.headers,
method: 'POST',
body: sdk.body,
});
const fpRes = await res.json();
logger.debug(`[米游社][设备指纹]${JSON.stringify(fpRes)}`);
deviceFp = fpRes?.data?.device_fp;
if (!deviceFp) {
return { api: null, uid: null, deviceFp: null };
}
await redis.set(`ZZZ:DEVICE_FP:${ltuid}:FP`, deviceFp, {
EX: 86400 * 7,
});
data['deviceFp'] = deviceFp;
const deviceLogin = api.getUrl('deviceLogin', data);
const saveDevice = api.getUrl('saveDevice', data);
if (!!deviceLogin && !!saveDevice) {
logger.debug(`[米游社][设备登录]保存设备信息`);
try {
logger.debug(`[米游社][设备登录]${JSON.stringify(deviceLogin)}`);
const login = await request(deviceLogin.url, {
headers: deviceLogin.headers,
method: 'POST',
body: deviceLogin.body,
});
const save = await request(saveDevice.url, {
headers: saveDevice.headers,
method: 'POST',
body: saveDevice.body,
});
const result = await Promise.all([login.json(), save.json()]);
logger.debug(`[米游社][设备登录]${JSON.stringify(result)}`);
} catch (error) {
logger.error(`[米游社][设备登录]${error.message}`);
}
}
}
} else {
deviceFp = await redis.get(`ZZZ:DEVICE_FP:${uid}`);
if (!deviceFp) {
const sdk = api.getUrl('getFp');
const res = await request(sdk.url, {
headers: sdk.headers,
method: 'POST',
body: sdk.body,
});
const fpRes = await res.json();
deviceFp = fpRes?.data?.device_fp;
if (deviceFp) {
await redis.set(`ZZZ:DEVICE_FP:${uid}`, deviceFp, {
EX: 86400 * 7,
});
}
}
}
// 返回数据API、UID、设备指纹
return { api, uid, deviceFp };
}
/**
* 获取玩家信息(当调用此方法时,会获取用户的玩家信息,并将其保存到`e.playerCard`中,方便渲染用户信息(此部分请查阅`lib/render.js`中两个模块的作用))
* @returns {Promise<boolean | object>}
*/
async getPlayerInfo() {
// 获取 米游社 API
const { api, uid } = await this.getAPI();
// 获取用户信息
let userData = await api.getFinalData('zzzUser').catch(e => {
this.reply(e.message);
throw e;
});
if (!userData) throw new Error('获取用户数据失败');
// 取第一个用户信息
userData =
userData?.list?.find(item => item.game_uid == uid) || userData?.list?.[0];
// 获取用户头像
let avatar = '';
if (this.e.member?.getAvatarUrl) {
avatar = await this.e.member.getAvatarUrl();
} else if (this.e.friend?.getAvatarUrl) {
avatar = await this.e.friend.getAvatarUrl();
} else {
avatar = this.e?.bot?.avatar;
}
// 写入数据
this.e.playerCard = {
avatar: avatar,
player: userData,
};
// 返回数据
return userData;
}
/**
* 渲染函数
* @param {string} renderPath 渲染路径
* @param {object} renderData 渲染数据
* @param {object} cfg 配置
* @returns {Promise<any>}
*/
render(renderPath, renderData = {}, cfg = {}) {
const e = this.e || cfg?.e;
// 判断是否存在e.runtime
if (!e.runtime) {
logger.error('未找到e.runtime请升级至最新版Yunzai');
}
// 获取渲染精度配置
const renderCfg = _.get(settings.getConfig('config'), 'render', {});
const scaleCfg = _.get(renderCfg, 'scale', 100);
// 计算配置缩放比例
const scaleCfgValue = Math.min(2, Math.max(0.5, scaleCfg / 100)) * 2;
// 将函数参数中的缩放比例与配置缩放比例相乘
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 {
// 玩家信息
player: e?.playerCard?.player,
// 玩家头像
avatar: e?.playerCard?.avatar,
// 传入的数据
...data,
// 资源路径
_res_path: resPath,
// 布局路径
_layout_path: layoutPath,
// 默认布局路径
defaultLayout: path.join(layoutPathFull, 'index.html'),
// 系统配置
sys: {
// 缩放比例
scale: pct,
// 资源路径
resourcesPath: resPath,
// 当前渲染的路径
currentPath: renderPathFull,
/**
* 下面两个模块的作用在于,你可以在你的布局文件中使用这两个模块,就可以显示用户信息和特殊标题,使用方法如下:
* 1. 展示玩家信息:首先你要在查询的时候调用`this.getPlayerInfo()`,这样,玩家数据就会保存在`e.playerCard`中,然后你就可以在布局文件中使用`{{include sys.playerInfo}}`来展示玩家信息。
* 2. 展示特殊标题:你可以在布局文件中使用`<% include(sys.specialTitle, {en: 'PROPERTY' , cn: '属性' , count: 6 }) %>`来展示特殊标题,其中`count`为可选参数默认为9。
*/
// 玩家信息模块
playerInfo: path.join(layoutPathFull, 'playerinfo.html'),
// 特殊标题模块
specialTitle: path.join(layoutPathFull, 'specialtitle.html'),
// 版权信息
copyright: `Created By ${version.name}<span class="version">${version.yunzai}</span> & ${pluginName}<span class="version">${version.version}</span>`,
// 版权信息(简化版)
createdby: `Created By <div class="highlight"><span>${pluginName}</span><div class="version">${version.version}</div></div> & Powered By <div class="highlight">ZZZure</div>`,
},
quality: 100,
};
},
});
}
}