commit 1c65f10e24b44b440213460ceb1f7622161b653c Author: bietiaop <1527109126@qq.com> Date: Mon Jul 8 13:13:22 2024 +0800 initial upload diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..0e98056 Binary files /dev/null and b/.DS_Store differ diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..7ba2b10 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,17 @@ +module.exports = { + env: { + es2021: true, + node: true, + }, + extends: [ + 'standard', + 'eslint:recommended', + 'standard', + 'plugin:prettier/recommended', + ], + rules: { + eqeqeq: ['off'], + 'prefer-const': ['off'], + 'arrow-body-style': 'off', + }, +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..66cfa01 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.css.map +config/**/*.* +!config/.gitkeep diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4d3ac9c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# 0.0.1 + +* 初始化项目 +* 支持 UID 绑定 +* 支持 note 查看 diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe1f07a --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# ZZZ-Plugin + +

+ ZZZ-Plugin +

+

ZZZ- Plugin

+

🚧Yunzai绝区零Bot插件🚧

+ +# 安装 + +进入Yunzai根目录运行以下命令进行安装: + +```bash +git clone --depth=1 https://github.com/ZZZure/ZZZ-Plugin.git ./plugins/ZZZ-Plugin +``` + +安装后重启Yunzai即可使用。 + +# 功能 + +待更新 + +# 贡献 + +请先 `fork` 本仓库,修改并测试完成后提交PR。 + +**请注意:** + +* 你的 CSS 必须是 `scss` 编写 diff --git a/apps/bind.js b/apps/bind.js new file mode 100644 index 0000000..75eebed --- /dev/null +++ b/apps/bind.js @@ -0,0 +1,26 @@ +import { rulePrefix } from '../lib/common.js'; +import { ZZZPlugin } from '../lib/plugin.js'; + +export class bind extends ZZZPlugin { + constructor() { + super({ + name: '[ZZZ-Plugin]Bind', + dsc: 'zzzbind', + event: 'message', + priority: 100, + rule: [ + { + reg: `^${rulePrefix}绑定(uid|UID)?(\\s)?[1-9][0-9]{7,9}$`, + fnc: 'bindUid', + }, + ], + }); + } + async bindUid() { + const uid = parseInt(this.e.msg.replace(/[^0-9]/gi, '')); + const user = this.e.user_id; + await redis.set(`ZZZ:UID:${user}`, uid); + this.reply(`绑定成功,当前绑定[zzz]uid:${uid}`, false); + return false; + } +} diff --git a/apps/note.js b/apps/note.js new file mode 100644 index 0000000..5b84fb6 --- /dev/null +++ b/apps/note.js @@ -0,0 +1,53 @@ +import { ZZZPlugin } from '../lib/plugin.js'; +import MysZZZApi from '../lib/mysapi.js'; +import { getCk } from '../lib/common.js'; +import _ from 'lodash'; +import render from '../lib/render.js'; +import { ZZZNoteResp } from '../model/note.js'; + +export class test extends ZZZPlugin { + constructor() { + super({ + name: '[ZZZ-Plugin]Note', + dsc: 'zzznote', + event: 'message', + priority: 100, + rule: [ + { + reg: `^#zzznote$`, + fnc: 'note', + }, + ], + }); + } + async note(e) { + const { api, deviceFp } = await this.getAPI(); + let userData = await api.getData('zzzUser'); + if (!userData?.data || _.isEmpty(userData.data.list)) { + await e.reply('[zzznote]玩家信息获取失败'); + return false; + } + userData = userData?.data?.list[0]; + let noteData = await api.getData('zzzNote', { deviceFp }); + noteData = await api.checkCode(e, noteData, 'zzzNote', {}); + if (!noteData || noteData.retcode !== 0) { + await e.reply('[zzznote]每日数据获取失败'); + return false; + } + noteData = noteData.data; + noteData = new ZZZNoteResp(noteData); + let avatar = this.e.bot.avatar; + // 头像 + if (this.e.member?.getAvatarUrl) { + avatar = await this.e.member.getAvatarUrl(); + } else if (this.e.friend?.getAvatarUrl) { + avatar = await this.e.friend.getAvatarUrl(); + } + const finalData = { + avatar, + player: userData, + note: noteData, + }; + await render(e, 'note/index.html', finalData); + } +} diff --git a/config/.gitkeep b/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/defSet/config.yaml b/defSet/config.yaml new file mode 100644 index 0000000..9cbe634 --- /dev/null +++ b/defSet/config.yaml @@ -0,0 +1,2 @@ +render: + scale: 100 diff --git a/index.js b/index.js new file mode 100644 index 0000000..61ac541 --- /dev/null +++ b/index.js @@ -0,0 +1,44 @@ +import fs from 'fs'; +import { appPath } from './lib/path.js'; + +logger.info('*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*'); +logger.info('ZZZ-Plugin 加载中'); +logger.info('仓库地址 https://github.com/ZZZure/ZZZ-plugin'); +logger.info('Created By ZZZure Project (MIHOMO)'); +logger.info('*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*'); + +if (!global.segment) global.segment = (await import('oicq')).segment; + +if (!global.core) { + try { + global.core = (await import('oicq')).core; + } catch (err) { + // ignore empty error + } +} + +const files = fs.readdirSync(appPath).filter(file => file.endsWith('.js')); + +const ret = []; + +files.forEach(file => { + ret.push(import(`./apps/${file}`)); +}); + +const retPromise = await Promise.allSettled(ret); + +const apps = {}; + +for (const i in files) { + const name = files[i].replace('.js', ''); + + if (retPromise[i].status != 'fulfilled') { + logger.error(`[ZZZ-Plugin] 载入模块${logger.red(name)}错误`); + logger.error(retPromise[i].reason); + continue; + } + + apps[name] = retPromise[i].value[Object.keys(retPromise[i].value)[0]]; +} + +export { apps }; diff --git a/lib/authkey.js b/lib/authkey.js new file mode 100644 index 0000000..277f745 --- /dev/null +++ b/lib/authkey.js @@ -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} + */ +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; +} diff --git a/lib/common.js b/lib/common.js new file mode 100644 index 0000000..8613eae --- /dev/null +++ b/lib/common.js @@ -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; +} diff --git a/lib/mysapi.js b/lib/mysapi.js new file mode 100644 index 0000000..4895650 --- /dev/null +++ b/lib/mysapi.js @@ -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; +} diff --git a/lib/mysapi/api.js b/lib/mysapi/api.js new file mode 100644 index 0000000..840b748 --- /dev/null +++ b/lib/mysapi/api.js @@ -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`; diff --git a/lib/mysapi/tool.js b/lib/mysapi/tool.js new file mode 100644 index 0000000..fbd57da --- /dev/null +++ b/lib/mysapi/tool.js @@ -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]; + }; +} diff --git a/lib/path.js b/lib/path.js new file mode 100644 index 0000000..53eff54 --- /dev/null +++ b/lib/path.js @@ -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'); diff --git a/lib/plugin.js b/lib/plugin.js new file mode 100644 index 0000000..66699e9 --- /dev/null +++ b/lib/plugin.js @@ -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 }; + } +} diff --git a/lib/render.js b/lib/render.js new file mode 100644 index 0000000..354eed8 --- /dev/null +++ b/lib/render.js @@ -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}${version.yunzai} & ${pluginName}${version.version}`, + createdby: `Created By ${pluginName} & Powered By ZZZure`, + }, + quality: 100, + }; + }, + }); +} + +export default render; diff --git a/lib/settings.js b/lib/settings.js new file mode 100644 index 0000000..e45143d --- /dev/null +++ b/lib/settings.js @@ -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(); diff --git a/lib/version.js b/lib/version.js new file mode 100644 index 0000000..dd53ed0 --- /dev/null +++ b/lib/version.js @@ -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, '$1'); + line = line.replace(/`\s*/g, ''); + line = line.replace(/\s*\*\*([^\*]+\*\*)/g, '$1'); + line = line.replace(/\*\*\s*/g, ''); + line = line.replace(/ⁿᵉʷ/g, ''); + 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; diff --git a/model/avatar.js b/model/avatar.js new file mode 100644 index 0000000..1c937fc --- /dev/null +++ b/model/avatar.js @@ -0,0 +1,218 @@ +/** + * @class + */ +export class Avatar { + /** + * @param {number} id + * @param {number} level + * @param {string} name_mi18n + * @param {string} full_name_mi18n + * @param {number} element_type + * @param {string} camp_name_mi18n + * @param {number} avatar_profession + * @param {string} rarity + * @param {string} group_icon_path + * @param {string} hollow_icon_path + * @param {number} rank + * @param {boolean} is_chosen + */ + constructor( + id, + level, + name_mi18n, + full_name_mi18n, + element_type, + camp_name_mi18n, + avatar_profession, + rarity, + group_icon_path, + hollow_icon_path, + rank, + is_chosen + ) { + this.id = id; + this.level = level; + this.name_mi18n = name_mi18n; + this.full_name_mi18n = full_name_mi18n; + this.element_type = element_type; + this.camp_name_mi18n = camp_name_mi18n; + this.avatar_profession = avatar_profession; + this.rarity = rarity; + this.group_icon_path = group_icon_path; + this.hollow_icon_path = hollow_icon_path; + this.rank = rank; + this.is_chosen = is_chosen; + } +} + +/** + * @class + */ +export class AvatarIconPaths { + /** + * @param {string} group_icon_path + * @param {string} hollow_icon_path + */ + constructor(group_icon_path, hollow_icon_path) { + this.group_icon_path = group_icon_path; + this.hollow_icon_path = hollow_icon_path; + } +} + +/** + * @class + */ +export class ZZZAvatarBasic { + /** + * @param {number} id + * @param {number} level + * @param {string} name_mi18n + * @param {string} full_name_mi18n + * @param {number} element_type + * @param {string} camp_name_mi18n + * @param {number} avatar_profession + * @param {string} rarity + * @param {AvatarIconPaths} icon_paths + * @param {number} rank + * @param {boolean} is_chosen + */ + constructor( + id, + level, + name_mi18n, + full_name_mi18n, + element_type, + camp_name_mi18n, + avatar_profession, + rarity, + icon_paths, + rank, + is_chosen + ) { + this.id = id; + this.level = level; + this.name_mi18n = name_mi18n; + this.full_name_mi18n = full_name_mi18n; + this.element_type = element_type; + this.camp_name_mi18n = camp_name_mi18n; + this.avatar_profession = avatar_profession; + this.rarity = rarity; + this.icon_paths = icon_paths; + this.rank = rank; + this.is_chosen = is_chosen; + } +} + +/** + * @class + */ +export class Rank { + /** + * @param {number} id + * @param {string} name + * @param {string} desc + * @param {number} pos + * @param {boolean} is_unlocked + */ + constructor(id, name, desc, pos, is_unlocked) { + this.id = id; + this.name = name; + this.desc = desc; + this.pos = pos; + this.is_unlocked = is_unlocked; + } +} + +/** + * @class + */ +export class ZZZAvatarInfo { + /** + * @param {number} id + * @param {number} level + * @param {string} name_mi18n + * @param {string} full_name_mi18n + * @param {number} element_type + * @param {string} camp_name_mi18n + * @param {number} avatar_profession + * @param {string} rarity + * @param {string} group_icon_path + * @param {string} hollow_icon_path + * @param {Equip[]} equip + * @param {Weapon} weapon + * @param {Property[]} properties + * @param {Skill[]} skills + * @param {number} rank + * @param {Rank[]} ranks + */ + constructor( + id, + level, + name_mi18n, + full_name_mi18n, + element_type, + camp_name_mi18n, + avatar_profession, + rarity, + group_icon_path, + hollow_icon_path, + equip, + weapon, + properties, + skills, + rank, + ranks + ) { + this.id = id; + this.level = level; + this.name_mi18n = name_mi18n; + this.full_name_mi18n = full_name_mi18n; + this.element_type = element_type; + this.camp_name_mi18n = camp_name_mi18n; + this.avatar_profession = avatar_profession; + this.rarity = rarity; + this.group_icon_path = group_icon_path; + this.hollow_icon_path = hollow_icon_path; + this.equip = equip; + this.weapon = weapon; + this.properties = properties; + this.skills = skills; + this.rank = rank; + this.ranks = ranks; + } +} + +/** + * @class + */ +export class ZZZUser { + /** + * @param {string} game_biz + * @param {string} region + * @param {string} game_uid + * @param {string} nickname + * @param {number} level + * @param {boolean} is_chosen + * @param {string} region_name + * @param {boolean} is_official + */ + constructor( + game_biz, + region, + game_uid, + nickname, + level, + is_chosen, + region_name, + is_official + ) { + this.game_biz = game_biz; + this.region = region; + this.game_uid = game_uid; + this.nickname = nickname; + this.level = level; + this.is_chosen = is_chosen; + this.region_name = region_name; + this.is_official = is_official; + } +} diff --git a/model/bangboo.js b/model/bangboo.js new file mode 100644 index 0000000..579ece9 --- /dev/null +++ b/model/bangboo.js @@ -0,0 +1,27 @@ +/** + * @class + */ +export class BangbooWiki { + /** + * @param {string} item_id + * @param {string} wiki_url + */ + constructor(item_id, wiki_url) { + this.item_id = item_id; + this.wiki_url = wiki_url; + } +} + +/** + * @class + */ +export class ZZZBangbooResp { + /** + * @param {Item[]} items + * @param {BangbooWiki} bangboo_wiki + */ + constructor(items, bangboo_wiki) { + this.items = items; + this.bangboo_wiki = bangboo_wiki; + } +} diff --git a/model/equip.js b/model/equip.js new file mode 100644 index 0000000..8597344 --- /dev/null +++ b/model/equip.js @@ -0,0 +1,133 @@ +/** + * @class + */ +export class EquipProperty { + /** + * @param {string} property_name + * @param {number} property_id + * @param {string} base + */ + constructor(property_name, property_id, base) { + this.property_name = property_name; + this.property_id = property_id; + this.base = base; + } +} + +/** + * @class + */ +export class EquipMainProperty { + /** + * @param {string} property_name + * @param {number} property_id + * @param {string} base + */ + constructor(property_name, property_id, base) { + this.property_name = property_name; + this.property_id = property_id; + this.base = base; + } +} + +/** + * @class + */ +export class EquipSuit { + /** + * @param {number} suit_id + * @param {string} name + * @param {number} own + * @param {string} desc1 + * @param {string} desc2 + */ + constructor(suit_id, name, own, desc1, desc2) { + this.suit_id = suit_id; + this.name = name; + this.own = own; + this.desc1 = desc1; + this.desc2 = desc2; + } +} + +/** + * @class + */ +export class Equip { + /** + * @param {number} id + * @param {number} level + * @param {string} name + * @param {string} icon + * @param {string} rarity + * @param {EquipProperty[]} properties + * @param {EquipMainProperty[]} main_properties + * @param {EquipSuit} equip_suit + * @param {number} equipment_type + */ + constructor( + id, + level, + name, + icon, + rarity, + properties, + main_properties, + equip_suit, + equipment_type + ) { + this.id = id; + this.level = level; + this.name = name; + this.icon = icon; + this.rarity = rarity; + this.properties = properties; + this.main_properties = main_properties; + this.equip_suit = equip_suit; + this.equipment_type = equipment_type; + } +} + +/** + * @class + */ +export class Weapon { + /** + * @param {number} id + * @param {number} level + * @param {string} name + * @param {number} star + * @param {string} icon + * @param {string} rarity + * @param {EquipProperty[]} properties + * @param {EquipMainProperty[]} main_properties + * @param {string} talent_title + * @param {string} talent_content + * @param {number} profession + */ + constructor( + id, + level, + name, + star, + icon, + rarity, + properties, + main_properties, + talent_title, + talent_content, + profession + ) { + this.id = id; + this.level = level; + this.name = name; + this.star = star; + this.icon = icon; + this.rarity = rarity; + this.properties = properties; + this.main_properties = main_properties; + this.talent_title = talent_title; + this.talent_content = talent_content; + this.profession = profession; + } +} diff --git a/model/gacha.js b/model/gacha.js new file mode 100644 index 0000000..5523112 --- /dev/null +++ b/model/gacha.js @@ -0,0 +1,63 @@ +/** + * @class + */ +export class SingleGachaLog { + /** + * @param {string} uid + * @param {string} gacha_id + * @param {string} gacha_type + * @param {string} item_id + * @param {string} count + * @param {string} time + * @param {string} name + * @param {string} lang + * @param {string} item_type + * @param {string} rank_type + * @param {string} id + */ + constructor( + uid, + gacha_id, + gacha_type, + item_id, + count, + time, + name, + lang, + item_type, + rank_type, + id + ) { + this.uid = uid; + this.gacha_id = gacha_id; + this.gacha_type = gacha_type; + this.item_id = item_id; + this.count = count; + this.time = time; + this.name = name; + this.lang = lang; + this.item_type = item_type; + this.rank_type = rank_type; + this.id = id; + } +} + +/** + * @class + */ +export class ZZZGachaLogResp { + /** + * @param {string} page + * @param {string} size + * @param {SingleGachaLog[]} list + * @param {string} region + * @param {number} region_time_zone + */ + constructor(page, size, list, region, region_time_zone) { + this.page = page; + this.size = size; + this.list = list; + this.region = region; + this.region_time_zone = region_time_zone; + } +} diff --git a/model/index.js b/model/index.js new file mode 100644 index 0000000..9558202 --- /dev/null +++ b/model/index.js @@ -0,0 +1,83 @@ +/** + * @class + */ +export class Buddy { + /** + * @param {number} id + * @param {string} name + * @param {string} rarity + * @param {number} level + * @param {number} star + */ + constructor(id, name, rarity, level, star) { + this.id = id; + this.name = name; + this.rarity = rarity; + this.level = level; + this.star = star; + } +} + +/** + * @class + */ +export class Stats { + /** + * @param {number} active_days + * @param {number} avatar_num + * @param {string} world_level_name + * @param {number} cur_period_zone_layer_count + * @param {number} buddy_num + */ + constructor( + active_days, + avatar_num, + world_level_name, + cur_period_zone_layer_count, + buddy_num + ) { + this.active_days = active_days; + this.avatar_num = avatar_num; + this.world_level_name = world_level_name; + this.cur_period_zone_layer_count = cur_period_zone_layer_count; + this.buddy_num = buddy_num; + } +} + +/** + * @class + */ +export class ZZZIndexResp { + /** + * @param {Stats} stats + * @param {Avatar[]} avatar_list + * @param {string} cur_head_icon_url + * @param {Buddy[]} buddy_list + */ + constructor(stats, avatar_list, cur_head_icon_url, buddy_list) { + this.stats = stats; + this.avatar_list = avatar_list; + this.cur_head_icon_url = cur_head_icon_url; + this.buddy_list = buddy_list; + } +} + +/** + * @class + */ +export class Item { + /** + * @param {number} id + * @param {string} name + * @param {string} rarity + * @param {number} level + * @param {number} star + */ + constructor(id, name, rarity, level, star) { + this.id = id; + this.name = name; + this.rarity = rarity; + this.level = level; + this.star = star; + } +} diff --git a/model/note.js b/model/note.js new file mode 100644 index 0000000..8e27ebc --- /dev/null +++ b/model/note.js @@ -0,0 +1,103 @@ +import { converSecondsToHM } from '../utils/time.js'; + +/** + * @class + */ +export class Vitality { + /** + * @param {number} max + * @param {number} current + */ + constructor(max, current) { + this.max = max; + this.current = current; + } + get finish() { + return this.max === this.current; + } +} + +/** + * @class + */ +export class VhsSale { + /** + * @param {string} sale_state + */ + constructor(sale_state) { + this.sale_state = sale_state; + } + get state() { + if (this.sale_state.includes('Doing')) { + return true; + } + return false; + } + get state_label() { + if (this.sale_state.includes('Doing')) { + return '正在营业'; + } + return '尚未营业'; + } +} + +/** + * @class + */ +export class EnergyProgress { + /** + * @param {number} max + * @param {number} current + */ + constructor(max, current) { + this.max = max; + this.current = current; + } +} + +/** + * @class + */ +export class Energy { + /** + * @param {EnergyProgress} progress + * @param {number} restore + */ + constructor(progress, restore) { + this.progress = progress; + this.restore = restore; + const leftHM = converSecondsToHM(restore); + this.progress.rest = `${leftHM[0]}小时${leftHM[1]}分钟`; + this.percent = parseInt((progress.current / progress.max) * 100); + } +} + +/** + * @class + */ +export class ZZZNoteResp { + /** + * @param {{ energy: Energy, vitality:Vitality, vhs_sale: VhsSale, card_sign: string }} data + */ + constructor(data) { + const { energy, vitality, vhs_sale, card_sign } = data; + this.energy = new Energy(energy.progress, energy.restore); + this.vitality = new Vitality(vitality.max, vitality.current); + this.vhs_sale = new VhsSale(vhs_sale.sale_state); + this.card_sign = card_sign; + } + + get sign() { + if (this.card_sign?.includes('Done')) { + return true; + } + return false; + } + + get sign_label() { + if (this.card_sign?.includes('Done')) { + return '已抽奖'; + } + return '未抽奖'; + } +} diff --git a/model/property.js b/model/property.js new file mode 100644 index 0000000..4bf41bb --- /dev/null +++ b/model/property.js @@ -0,0 +1,19 @@ +/** + * @class + */ +class Property { + /** + * @param {string} property_name + * @param {number} property_id + * @param {string} base + * @param {string} add + * @param {string} final + */ + constructor(property_name, property_id, base, add, final) { + this.property_name = property_name; + this.property_id = property_id; + this.base = base; + this.add = add; + this.final = final; + } +} diff --git a/model/skill.js b/model/skill.js new file mode 100644 index 0000000..53fff4a --- /dev/null +++ b/model/skill.js @@ -0,0 +1,29 @@ +/** + * @class + */ +export class SkillItem { + /** + * @param {string} title + * @param {string} text + */ + constructor(title, text) { + this.title = title; + this.text = text; + } +} + +/** + * @class + */ +export class Skill { + /** + * @param {number} level + * @param {number} skill_type + * @param {SkillItem[]} items + */ + constructor(level, skill_type, items) { + this.level = level; + this.skill_type = skill_type; + this.items = items; + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1f6b2ca --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "name": "zzz-plugin", + "type": "module", + "version": "0.0.1", + "description": "", + "main": "index.js", + "keywords": [], + "author": "ZZZure", + "license": "ISC" +} diff --git a/resources/common/fonts/ExQteFeverNum.ttf b/resources/common/fonts/ExQteFeverNum.ttf new file mode 100644 index 0000000..0f15585 Binary files /dev/null and b/resources/common/fonts/ExQteFeverNum.ttf differ diff --git a/resources/common/fonts/FC_Sound.ttf b/resources/common/fonts/FC_Sound.ttf new file mode 100644 index 0000000..8f9e14e Binary files /dev/null and b/resources/common/fonts/FC_Sound.ttf differ diff --git a/resources/common/fonts/Mont_FF-Heavy.otf b/resources/common/fonts/Mont_FF-Heavy.otf new file mode 100644 index 0000000..c776874 Binary files /dev/null and b/resources/common/fonts/Mont_FF-Heavy.otf differ diff --git a/resources/common/fonts/REEJI-CloudChaoCuHeiGBT.ttf b/resources/common/fonts/REEJI-CloudChaoCuHeiGBT.ttf new file mode 100644 index 0000000..0050bcf Binary files /dev/null and b/resources/common/fonts/REEJI-CloudChaoCuHeiGBT.ttf differ diff --git a/resources/common/fonts/RoGSanSrfStd-Bd.otf b/resources/common/fonts/RoGSanSrfStd-Bd.otf new file mode 100644 index 0000000..e8ed429 Binary files /dev/null and b/resources/common/fonts/RoGSanSrfStd-Bd.otf differ diff --git a/resources/common/fonts/SCDream9.otf b/resources/common/fonts/SCDream9.otf new file mode 100644 index 0000000..44f0e8b Binary files /dev/null and b/resources/common/fonts/SCDream9.otf differ diff --git a/resources/common/fonts/SourceHanSerifCN-Heavy.otf b/resources/common/fonts/SourceHanSerifCN-Heavy.otf new file mode 100644 index 0000000..014238c Binary files /dev/null and b/resources/common/fonts/SourceHanSerifCN-Heavy.otf differ diff --git a/resources/common/fonts/TiltWarp-Regular.ttf b/resources/common/fonts/TiltWarp-Regular.ttf new file mode 100644 index 0000000..8ed2730 Binary files /dev/null and b/resources/common/fonts/TiltWarp-Regular.ttf differ diff --git a/resources/common/fonts/impact.ttf b/resources/common/fonts/impact.ttf new file mode 100644 index 0000000..7b7956f Binary files /dev/null and b/resources/common/fonts/impact.ttf differ diff --git a/resources/common/fonts/inpinhongmengti.ttf b/resources/common/fonts/inpinhongmengti.ttf new file mode 100644 index 0000000..5115d0d Binary files /dev/null and b/resources/common/fonts/inpinhongmengti.ttf differ diff --git a/resources/common/images/bg.jpg b/resources/common/images/bg.jpg new file mode 100644 index 0000000..d1129f8 Binary files /dev/null and b/resources/common/images/bg.jpg differ diff --git a/resources/common/layout/index.css b/resources/common/layout/index.css new file mode 100644 index 0000000..2dcefb0 --- /dev/null +++ b/resources/common/layout/index.css @@ -0,0 +1,30 @@ +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +body { + font-family: "zzz", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + transform-origin: 0 0; + font-size: 24px; + width: 840px; +} + +.container { + background: url("../images/bg.jpg") no-repeat center center; + background-size: cover; + color: white; +} + +.copyright { + text-align: center; + font-size: 0.7em; + color: #797979; + padding-bottom: 2em; +} +.copyright .highlight { + color: #fff; +} + +/*# sourceMappingURL=index.css.map */ diff --git a/resources/common/layout/index.html b/resources/common/layout/index.html new file mode 100644 index 0000000..eb3016a --- /dev/null +++ b/resources/common/layout/index.html @@ -0,0 +1,22 @@ + + + + + + + + ZZZ-Plugin + + + {{block 'css'}} + {{/block}} + + + +
+ {{block 'main'}}{{/block}} + +
+ + + diff --git a/resources/common/layout/index.scss b/resources/common/layout/index.scss new file mode 100644 index 0000000..7d0830f --- /dev/null +++ b/resources/common/layout/index.scss @@ -0,0 +1,31 @@ +@charset "UTF-8"; + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +body { + font-family: 'zzz', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', + Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + transform-origin: 0 0; + font-size: 24px; + width: 840px; +} + +.container { + background: url('../images/bg.jpg') no-repeat center center; + background-size: cover; + color: white; +} + +.copyright { + text-align: center; + font-size: 0.7em; + color: #797979; + padding-bottom: 2em; + .highlight { + color: #fff; + } +} diff --git a/resources/common/style/index.css b/resources/common/style/index.css new file mode 100644 index 0000000..28eb92e --- /dev/null +++ b/resources/common/style/index.css @@ -0,0 +1,21 @@ +@font-face { + font-family: "zzz"; + src: url("../fonts/inpinhongmengti.ttf"); +} +@font-face { + font-family: "Source Han Serif"; + src: url("../fonts/SourceHanSerifCN-Heavy.otf"); +} +.zzz-font { + font-family: "zzz", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; +} + +.no-zzz-font { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; +} + +.zzz-serif { + font-family: "Source Han Serif", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; +} + +/*# sourceMappingURL=index.css.map */ diff --git a/resources/common/style/index.scss b/resources/common/style/index.scss new file mode 100644 index 0000000..33d3479 --- /dev/null +++ b/resources/common/style/index.scss @@ -0,0 +1,41 @@ +@charset "UTF-8"; + +@font-face { + font-family: 'zzz'; + // src: url('../fonts/RoGSanSrfStd-Bd.otf'); + src: url('../fonts/inpinhongmengti.ttf'); + // font-weight: 400; +} + +// @font-face { +// font-family: 'zzz'; +// src: url('../fonts/REEJI-CloudChaoCuHeiGBT.ttf'); +// font-weight: 900; +// } + +@font-face { + font-family: 'Source Han Serif'; + src: url('../fonts/SourceHanSerifCN-Heavy.otf'); +} + +.zzz-font { + font-family: 'zzz', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', + Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +} + +.no-zzz-font { + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +} + +// .zzz-title { +// font-family: 'zzz', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', +// Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +// font-weight: 900; +// } + +.zzz-serif { + font-family: 'Source Han Serif', system-ui, -apple-system, BlinkMacSystemFont, + 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', + sans-serif; +} diff --git a/resources/note/images/ActivityGeneralBtnBg02.png b/resources/note/images/ActivityGeneralBtnBg02.png new file mode 100644 index 0000000..0771e74 Binary files /dev/null and b/resources/note/images/ActivityGeneralBtnBg02.png differ diff --git a/resources/note/images/BgFrame.png b/resources/note/images/BgFrame.png new file mode 100644 index 0000000..0bdeb49 Binary files /dev/null and b/resources/note/images/BgFrame.png differ diff --git a/resources/note/images/IconStamina.png b/resources/note/images/IconStamina.png new file mode 100644 index 0000000..fff8259 Binary files /dev/null and b/resources/note/images/IconStamina.png differ diff --git a/resources/note/images/PetSelectBG.png b/resources/note/images/PetSelectBG.png new file mode 100644 index 0000000..e0d665a Binary files /dev/null and b/resources/note/images/PetSelectBG.png differ diff --git a/resources/note/images/SuitBg.png b/resources/note/images/SuitBg.png new file mode 100644 index 0000000..d178d26 Binary files /dev/null and b/resources/note/images/SuitBg.png differ diff --git a/resources/note/images/UIDBg.png b/resources/note/images/UIDBg.png new file mode 100644 index 0000000..658b404 Binary files /dev/null and b/resources/note/images/UIDBg.png differ diff --git a/resources/note/images/batteryBg.png b/resources/note/images/batteryBg.png new file mode 100644 index 0000000..0021657 Binary files /dev/null and b/resources/note/images/batteryBg.png differ diff --git a/resources/note/images/no.png b/resources/note/images/no.png new file mode 100644 index 0000000..9fc659d Binary files /dev/null and b/resources/note/images/no.png differ diff --git a/resources/note/images/yes.png b/resources/note/images/yes.png new file mode 100644 index 0000000..ed43900 Binary files /dev/null and b/resources/note/images/yes.png differ diff --git a/resources/note/index.css b/resources/note/index.css new file mode 100644 index 0000000..a8adda6 --- /dev/null +++ b/resources/note/index.css @@ -0,0 +1,253 @@ +.container { + padding-top: 3em; +} + +.card { + border-image: url("./images/BgFrame.png"); + border-image-slice: 325 0 162.5 0 fill; + border-image-width: 10em 0em 5em 0em; + border-image-repeat: round stretch; + min-height: 30em; + overflow: hidden; + padding: 5em 2.2em 2.2em 2.2em; +} +.card .user-info { + display: flex; + align-items: center; + margin-bottom: 1em; + margin-top: 1.7em; + gap: 1em; + padding: 0 1em; +} +.card .user-info .avatar { + width: 4em; + height: 4em; + border-radius: 50%; + border-image: url("./images/SuitBg.png"); + border-image-width: 0; + border-image-outset: 0.4em; + border-image-repeat: round; + border-image-slice: 1 fill; + overflow: hidden; +} +.card .user-info .avatar img { + width: 100%; + height: 100%; + object-fit: contain; + display: block; +} +.card .user-info .info-bar { + width: 100%; + display: flex; + flex-direction: column; + align-items: flex-start; + flex: 1; + gap: 0.2em; +} +.card .user-info .info-bar .info { + width: 100%; + display: flex; + overflow: hidden; + gap: 0.5em; + padding-left: 0.5em; +} +.card .user-info .info-bar .info .nickname { + font-size: 1.2em; + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.card .user-info .info-bar .info .label { + -webkit-clip-path: polygon(0.2em 0%, calc(100% - 0.2em) 0%, 100% 0.2em, 100% calc(100% - 0.2em), calc(100% - 0.2em) 100%, 0.2em 100%, 0% calc(100% - 0.2em), 0% 0.2em); + clip-path: polygon(0.2em 0%, calc(100% - 0.2em) 0%, 100% 0.2em, 100% calc(100% - 0.2em), calc(100% - 0.2em) 100%, 0.2em 100%, 0% calc(100% - 0.2em), 0% 0.2em); + padding: 0 0.4em; + font-size: 0.9em; + display: flex; + justify-content: center; + align-items: center; + color: rgb(43, 38, 40); + margin: 0.1em 0; +} +.card .user-info .info-bar .info .label.level { + background-color: rgb(243, 203, 69); +} +.card .user-info .info-bar .info .label.server { + background-color: rgb(81, 177, 253); +} +.card .user-info .info-bar .uid { + border-image: url("./images/UIDBg.png"); + border-image-slice: 0 100 0 100 fill; + border-image-width: 0em 1.6em 0em 1.6em; + border-image-outset: 0; + border-image-repeat: stretch stretch; + padding: 0.3em 1.6em; + font-size: 0.9em; + color: rgba(255, 255, 255, 0.6); +} +.card > .title { + width: 100%; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + display: flex; + align-items: stretch; + padding: 0 0.4em; + position: relative; + margin: 0.5em 0; +} +.card > .title .parallelograms { + display: flex; + flex-wrap: nowrap; + align-items: flex-end; + position: inherit; + padding-bottom: 0.4em; + z-index: 1; +} +.card > .title .parallelograms span { + width: 1.3em; + height: 80%; + background-color: rgb(246, 202, 69); + margin-right: -0.2em; + -webkit-clip-path: polygon(0.7em 0%, 100% 0%, calc(100% - 0.7em) 100%, 0% 100%); + clip-path: polygon(0.7em 0%, 100% 0%, calc(100% - 0.7em) 100%, 0% 100%); +} +.card > .title .bg-content { + position: absolute; + bottom: -0.1em; + left: 50%; + transform: translate(-50%, 0) skewX(-8deg); + color: rgba(255, 255, 255, 0.1); + font-size: 2.7em; +} +.card > .title .content { + flex-grow: 1; + flex-shrink: 1; + font-size: 1.5em; + text-align: center; + position: inherit; + z-index: 1; +} +.card .battery { + display: flex; + align-items: center; + gap: 1.5em; + padding: 0.5em; + border: solid 0.2em rgb(174, 174, 174); + border-radius: 0.7em; + color: rgb(167, 167, 167); + background: url("./images/batteryBg.png") no-repeat center center; + background-size: cover; + margin: 1.5em 0; +} +.card .battery .icon { + width: 7em; + height: 7em; + border-radius: 50%; + border-image: url("./images/PetSelectBG.png"); + border-image-width: 0; + border-image-outset: 0em; + border-image-repeat: round; + border-image-slice: 1 fill; + overflow: hidden; + padding: 1.4em; + flex-grow: 0; + flex-shrink: 0; +} +.card .battery .icon img { + width: 100%; + height: 100%; + object-fit: contain; + display: block; +} +.card .battery .info { + flex-grow: 1; + flex-shrink: 1; + margin-right: 1em; +} +.card .battery .info .bvalue { + display: flex; + align-items: flex-end; +} +.card .battery .info .bvalue .title { + font-size: 1.2em; +} +.card .battery .info .bvalue .value .big { + font-size: 1.5em; + color: rgb(253, 209, 73); + margin: 0 0.7em; +} +.card .battery .info .bleft { + display: flex; + gap: 0.2em; + font-size: 0.8em; +} +.card .battery .info .bleft .value { + color: white; +} +.card .battery .info .texture { + width: 100%; + border-image: url("./images/ActivityGeneralBtnBg02.png"); + border-image-slice: 30 40 30 24 fill; + border-image-width: 1em 1.5em 1em 0.9em; + border-image-repeat: stretch stretch; + padding: 1.1em 1.23em 1.1em 0.6em; + margin-top: 0.7em; +} +.card .battery .info .texture .progress { + height: 0.5em; + border-radius: 0.3em; + background-color: rgb(244, 198, 68); +} +.card .active-list { + font-size: 1.2em; + padding: 0 1em; + margin-top: -0.5em; +} +.card .active-list .active { + display: flex; + border-bottom: solid 0.07em rgba(255, 255, 255, 0.3); + align-items: center; + color: rgb(216, 216, 216); + gap: 0.5em; + padding-right: 1em; + padding-top: 1em; + padding-bottom: 0.2em; +} +.card .active-list .active .status { + width: 1.8em; + height: 1.8em; + border-radius: 50%; + background-color: rgb(34, 39, 37); + flex-grow: 0; + flex-shrink: 0; + background: url("./images/no.png") no-repeat center center; + background-size: contain; +} +.card .active-list .active .status.finish { + background: url("./images/yes.png") no-repeat center center; + background-size: contain; +} +.card .active-list .active .title { + flex-grow: 1; + padding: 0; + font-size: 1.1em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.card .active-list .active .value { + display: flex; + align-items: flex-end; + font-size: 1.2em; + color: rgb(253, 209, 73); + flex-grow: 0; + flex-shrink: 0; +} +.card .active-list .active .value .sub { + font-size: 0.8em; + color: rgb(255, 255, 255); + margin-left: 0.2em; +} + +/*# sourceMappingURL=index.css.map */ diff --git a/resources/note/index.html b/resources/note/index.html new file mode 100644 index 0000000..0828758 --- /dev/null +++ b/resources/note/index.html @@ -0,0 +1,98 @@ +{{extend defaultLayout}} + +{{block 'css'}} + +{{/block}} + +{{block 'main'}} +
+ +
+
+ STAMINA +
+
+ +
+
+ 电池电量 +
+
+ +
+
+ +
+
+ +
+
+
+
电量
+
{{note.energy.progress.current}}/{{note.energy.progress.max}}
+
+
+
剩余
+
{{note.energy.progress.rest}}
+
+
+
+
+
+
+
+
+ +
+
+ ACTIVE +
+
+ +
+
+ 每日情况 +
+
+ +
+
+ +
+
+
+
今日活跃度
+
+ {{note.vitality.current}} + /{{note.vitality.max}} +
+
+
+
+
录像店经营
+
+ {{note.vhs_sale.state_label}} +
+
+
+
+
刮刮卡
+
+ {{note.sign_label}} +
+
+
+
+{{/block}} diff --git a/resources/note/index.scss b/resources/note/index.scss new file mode 100644 index 0000000..36f9150 --- /dev/null +++ b/resources/note/index.scss @@ -0,0 +1,284 @@ +.container { + padding-top: 3em; +} +.card { + border-image: url('./images/BgFrame.png'); + border-image-slice: 325 0 162.5 0 fill; + border-image-width: 10em 0em 5em 0em; + border-image-repeat: round stretch; + min-height: 30em; + overflow: hidden; + padding: 5em 2.2em 2.2em 2.2em; + .user-info { + display: flex; + align-items: center; + margin-bottom: 1em; + margin-top: 1.7em; + gap: 1em; + padding: 0 1em; + .avatar { + width: 4em; + height: 4em; + border-radius: 50%; + border-image: url('./images/SuitBg.png'); + border-image-width: 0; + border-image-outset: 0.4em; + border-image-repeat: round; + border-image-slice: 1 fill; + overflow: hidden; + img { + width: 100%; + height: 100%; + object-fit: contain; + display: block; + } + } + .info-bar { + width: 100%; + display: flex; + flex-direction: column; + align-items: flex-start; + flex: 1; + gap: 0.2em; + .info { + width: 100%; + display: flex; + overflow: hidden; + gap: 0.5em; + padding-left: 0.5em; + .nickname { + font-size: 1.2em; + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .label { + $label-width: 0.2em; + -webkit-clip-path: polygon( + $label-width 0%, + calc(100% - $label-width) 0%, + 100% $label-width, + 100% calc(100% - $label-width), + calc(100% - $label-width) 100%, + $label-width 100%, + 0% calc(100% - $label-width), + 0% $label-width + ); + clip-path: polygon( + $label-width 0%, + calc(100% - $label-width) 0%, + 100% $label-width, + 100% calc(100% - $label-width), + calc(100% - $label-width) 100%, + $label-width 100%, + 0% calc(100% - $label-width), + 0% $label-width + ); + padding: 0 0.4em; + font-size: 0.9em; + display: flex; + justify-content: center; + align-items: center; + color: rgb(43, 38, 40); + margin: 0.1em 0; + + &.level { + background-color: rgb(243, 203, 69); + } + + &.server { + background-color: rgb(81, 177, 253); + } + } + } + .uid { + border-image: url('./images/UIDBg.png'); + border-image-slice: 0 100 0 100 fill; + border-image-width: 0em 1.6em 0em 1.6em; + border-image-outset: 0; + border-image-repeat: stretch stretch; + padding: 0.3em 1.6em; + font-size: 0.9em; + color: rgba(255, 255, 255, 0.6); + } + } + } + > .title { + width: 100%; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + display: flex; + align-items: stretch; + padding: 0 0.4em; + position: relative; + margin: 0.5em 0; + .parallelograms { + $pwidth: 0.7em; + display: flex; + flex-wrap: nowrap; + align-items: flex-end; + position: inherit; + padding-bottom: 0.4em; + z-index: 1; + span { + width: 1.3em; + height: 80%; + background-color: rgb(246, 202, 69); + margin-right: -0.2em; + -webkit-clip-path: polygon( + $pwidth 0%, + 100% 0%, + calc(100% - $pwidth) 100%, + 0% 100% + ); + clip-path: polygon( + $pwidth 0%, + 100% 0%, + calc(100% - $pwidth) 100%, + 0% 100% + ); + } + } + .bg-content { + position: absolute; + bottom: -0.1em; + left: 50%; + transform: translate(-50%, 0) skewX(-8deg); + color: rgba(255, 255, 255, 0.1); + font-size: 2.7em; + } + .content { + flex-grow: 1; + flex-shrink: 1; + font-size: 1.5em; + text-align: center; + position: inherit; + z-index: 1; + } + } + .battery { + display: flex; + align-items: center; + gap: 1.5em; + padding: 0.5em; + border: solid 0.2em rgb(174, 174, 174); + border-radius: 0.7em; + color: rgb(167, 167, 167); + background: url('./images/batteryBg.png') no-repeat center center; + background-size: cover; + margin: 1.5em 0; + .icon { + width: 7em; + height: 7em; + border-radius: 50%; + border-image: url('./images/PetSelectBG.png'); + border-image-width: 0; + border-image-outset: 0em; + border-image-repeat: round; + border-image-slice: 1 fill; + overflow: hidden; + padding: 1.4em; + flex-grow: 0; + flex-shrink: 0; + img { + width: 100%; + height: 100%; + object-fit: contain; + display: block; + } + } + .info { + flex-grow: 1; + flex-shrink: 1; + margin-right: 1em; + .bvalue { + display: flex; + align-items: flex-end; + .title { + font-size: 1.2em; + } + .value { + .big { + font-size: 1.5em; + color: rgb(253, 209, 73); + margin: 0 0.7em; + } + } + } + .bleft { + display: flex; + gap: 0.2em; + font-size: 0.8em; + .value { + color: white; + } + } + .texture { + width: 100%; + border-image: url('./images/ActivityGeneralBtnBg02.png'); + border-image-slice: 30 40 30 24 fill; + border-image-width: 1em 1.5em 1em 0.9em; + border-image-repeat: stretch stretch; + padding: 1.1em 1.23em 1.1em 0.6em; + margin-top: 0.7em; + .progress { + height: 0.5em; + border-radius: 0.3em; + background-color: rgb(244, 198, 68); + } + } + } + } + .active-list { + font-size: 1.2em; + padding: 0 1em; + margin-top: -0.5em; + .active { + display: flex; + border-bottom: solid 0.07em rgba(255, 255, 255, 0.3); + align-items: center; + color: rgb(216, 216, 216); + gap: 0.5em; + padding-right: 1em; + padding-top: 1em; + padding-bottom: 0.2em; + .status { + width: 1.8em; + height: 1.8em; + border-radius: 50%; + background-color: rgb(34, 39, 37); + flex-grow: 0; + flex-shrink: 0; + background: url('./images/no.png') no-repeat center center; + background-size: contain; + &.finish { + background: url('./images/yes.png') no-repeat center center; + background-size: contain; + } + } + .title { + flex-grow: 1; + padding: 0; + font-size: 1.1em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .value { + display: flex; + align-items: flex-end; + font-size: 1.2em; + color: rgb(253, 209, 73); + flex-grow: 0; + flex-shrink: 0; + .sub { + font-size: 0.8em; + color: rgb(255, 255, 255); + margin-left: 0.2em; + } + } + } + } +} diff --git a/utils/time.js b/utils/time.js new file mode 100644 index 0000000..6e5201e --- /dev/null +++ b/utils/time.js @@ -0,0 +1,6 @@ +export const converSecondsToHM = seconds => { + const d = new Date(seconds * 1000); + const hh = d.getUTCHours(); + const mm = d.getUTCMinutes(); + return [hh, mm]; +};