插件化目录初版提交
12
.idea/miao-plugin.iml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/miao-plugin.iml" filepath="$PROJECT_DIR$/.idea/miao-plugin.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
52
.idea/workspace.xml
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="8b7e4a51-6647-4b82-a102-b4b894d7928f" name="Changes" comment="">
|
||||
<change afterPath="$PROJECT_DIR$/apps/character.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apps/character/character.css" beforeDir="false" afterPath="$PROJECT_DIR$/resources/character/character.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apps/character/character.html" beforeDir="false" afterPath="$PROJECT_DIR$/resources/character/character.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apps/character/character.js" beforeDir="false" afterPath="$PROJECT_DIR$/config.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apps/character/h.jpg" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apps/character/w.jpg" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/index.js" beforeDir="false" afterPath="$PROJECT_DIR$/index.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/miao-plugin.js" beforeDir="false" afterPath="$PROJECT_DIR$/miao-plugin.js" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectId" id="26q2WrgqGMhiO9lAOk0fE9BWvtL" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">
|
||||
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
||||
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||
<property name="vue.rearranger.settings.migration" value="true" />
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="8b7e4a51-6647-4b82-a102-b4b894d7928f" name="Changes" comment="" />
|
||||
<created>1648136859374</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1648136859374</updated>
|
||||
<workItem from="1648136860539" duration="1102000" />
|
||||
<workItem from="1648140745824" duration="69000" />
|
||||
<workItem from="1648140890688" duration="1838000" />
|
||||
<workItem from="1648224805226" duration="302000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
525
apps/character.js
Normal file
@ -0,0 +1,525 @@
|
||||
import fetch from "node-fetch";
|
||||
import { segment } from "oicq";
|
||||
import lodash from "lodash";
|
||||
import fs from "fs";
|
||||
|
||||
let getUrl, getServer;
|
||||
|
||||
import { Character } from "../components/models.js";
|
||||
|
||||
|
||||
//角色昵称
|
||||
let nameID = "";
|
||||
let genshin = {};
|
||||
await init();
|
||||
|
||||
|
||||
export async function init(isUpdate = false) {
|
||||
let _path = "file://" + process.cwd();
|
||||
console.log(_path + "config/gen");
|
||||
let version = isUpdate ? new Date().getTime() : 0;
|
||||
|
||||
genshin = await import(_path + `/config/genshin/roleId.js?version=${version}`);
|
||||
nameID = "";
|
||||
}
|
||||
|
||||
|
||||
//#神里
|
||||
export async function character(e, { render, MysApi }) {
|
||||
let roleId = roleIdToName(e.msg.replace(/#|老婆|老公|[1|2|5][0-9]{8}/g, "").trim());
|
||||
|
||||
|
||||
let hutao = Character.get("胡桃");
|
||||
|
||||
console.log(hutao.a)
|
||||
|
||||
return true;
|
||||
|
||||
if (!roleId) return false;
|
||||
|
||||
getUrl = MysApi.getUrl;
|
||||
getServer = MysApi.getServer;
|
||||
|
||||
let uidRes = await getUid(e);
|
||||
|
||||
|
||||
if (!uidRes.uid && uidRes.isSelf) {
|
||||
e.reply("请先发送#+你游戏的uid");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(await limitGet(e))) return true;
|
||||
|
||||
let uid = uidRes.uid;
|
||||
|
||||
let res = await mysApi(e, uid, "character", {
|
||||
role_id: uid,
|
||||
server: getServer(uid),
|
||||
});
|
||||
|
||||
if (res.retcode == "-1") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (checkRetcode(res, uid, e)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let avatars = res.data.avatars;
|
||||
let length = avatars.length;
|
||||
|
||||
avatars = lodash.keyBy(avatars, "id");
|
||||
|
||||
if (roleId == 20000000) {
|
||||
if (avatars["10000005"]) {
|
||||
roleId = "10000005";
|
||||
}
|
||||
if (avatars["10000007"]) {
|
||||
roleId = "10000007";
|
||||
}
|
||||
}
|
||||
|
||||
if (!avatars[roleId]) {
|
||||
let name = lodash.truncate(e.sender.card, { length: 8 });
|
||||
if (length > 8) {
|
||||
e.reply([segment.at(e.user_id, name), `\n没有${e.msg}`]);
|
||||
} else {
|
||||
e.reply([segment.at(e.user_id, name), "\n请先在米游社展示该角色"]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
limitSet(e);
|
||||
|
||||
avatars = avatars[roleId];
|
||||
|
||||
let skill = await getSkill(e, uid, avatars);
|
||||
|
||||
let type = "character";
|
||||
|
||||
let base64 = await render("miao-plugin", type, {
|
||||
_plugin: true,
|
||||
save_id: uid,
|
||||
uid: uid,
|
||||
skill,
|
||||
...get_character(avatars),
|
||||
}, "png");
|
||||
|
||||
if (base64) {
|
||||
e.reply(segment.image(`base64://${base64}`));
|
||||
}
|
||||
|
||||
return true; //事件结束不再往下
|
||||
}
|
||||
|
||||
|
||||
//获取角色技能数据
|
||||
async function getSkill(e, uid, avatars) {
|
||||
|
||||
let skill = {};
|
||||
if (NoteCookie && NoteCookie[e.user_id] && NoteCookie[e.user_id].uid == uid && NoteCookie[e.user_id].cookie.includes("cookie_token")) {
|
||||
let skillres = await mysApi(e, uid, "detail", {
|
||||
role_id: uid,
|
||||
server: getServer(uid),
|
||||
avatar_id: avatars.id,
|
||||
});
|
||||
if (skillres.retcode == 0 && skillres.data && skillres.data.skill_list) {
|
||||
skill.id = avatars.id;
|
||||
let skill_list = lodash.orderBy(skillres.data.skill_list, ["id"], ["asc"]);
|
||||
for (let val of skill_list) {
|
||||
val.level_original = val.level_current;
|
||||
if (val.name.includes("普通攻击")) {
|
||||
skill.a = val;
|
||||
continue;
|
||||
}
|
||||
if (val.max_level >= 10 && !skill.e) {
|
||||
skill.e = val;
|
||||
continue;
|
||||
}
|
||||
if (val.max_level >= 10 && !skill.q) {
|
||||
skill.q = val;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (avatars.actived_constellation_num >= 3) {
|
||||
if (avatars.constellations[2].effect.includes(skill.e.name)) {
|
||||
skill.e.level_current += 3;
|
||||
} else if (avatars.constellations[2].effect.includes(skill.q.name)) {
|
||||
skill.q.level_current += 3;
|
||||
}
|
||||
}
|
||||
if (avatars.actived_constellation_num >= 5) {
|
||||
if (avatars.constellations[4].effect.includes(skill.e.name)) {
|
||||
skill.e.level_current += 3;
|
||||
} else if (avatars.constellations[4].effect.includes(skill.q.name)) {
|
||||
skill.q.level_current += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return skill;
|
||||
}
|
||||
|
||||
|
||||
function get_character(avatars) {
|
||||
let list = [];
|
||||
let set = {};
|
||||
let setArr = [];
|
||||
let text1 = "";
|
||||
let text2 = "";
|
||||
let bg = 2;
|
||||
|
||||
let weapon = {
|
||||
type: "weapon",
|
||||
name: avatars.weapon.name,
|
||||
showName: genshin.abbr[avatars.weapon.name] ? genshin.abbr[avatars.weapon.name] : avatars.weapon.name,
|
||||
level: avatars.weapon.level,
|
||||
affix_level: avatars.weapon.affix_level,
|
||||
};
|
||||
|
||||
for (let val of avatars.reliquaries) {
|
||||
if (set[val.set.name]) {
|
||||
set[val.set.name]++;
|
||||
|
||||
if (set[val.set.name] == 2) {
|
||||
if (text1) {
|
||||
text2 = "2件套:" + val.set.affixes[0].effect;
|
||||
} else {
|
||||
text1 = "2件套:" + val.set.affixes[0].effect;
|
||||
}
|
||||
}
|
||||
|
||||
if (set[val.set.name] == 4) {
|
||||
text2 = "4件套:" + val.set.name;
|
||||
}
|
||||
} else {
|
||||
set[val.set.name] = 1;
|
||||
}
|
||||
|
||||
list.push({
|
||||
type: "reliquaries",
|
||||
name: val.name,
|
||||
level: val.level,
|
||||
});
|
||||
}
|
||||
|
||||
for (let val of Object.keys(set)) {
|
||||
setArr.push({
|
||||
name: val,
|
||||
num: set[val],
|
||||
showName: genshin.abbr[val] ? genshin.abbr[val] : val,
|
||||
});
|
||||
}
|
||||
|
||||
if (avatars.reliquaries.length >= 2 && !text1) {
|
||||
text1 = "无套装效果";
|
||||
}
|
||||
|
||||
if (avatars.id == "10000005") {
|
||||
avatars.name = "空";
|
||||
} else if (avatars.id == "10000007") {
|
||||
avatars.name = "荧";
|
||||
}
|
||||
|
||||
let reliquaries = list[0];
|
||||
return {
|
||||
name: avatars.name,
|
||||
showName: genshin.abbr[avatars.name] ? genshin.abbr[avatars.name] : avatars.name,
|
||||
level: avatars.level,
|
||||
fetter: avatars.fetter,
|
||||
actived_constellation_num: avatars.actived_constellation_num,
|
||||
weapon,
|
||||
text1,
|
||||
text2,
|
||||
bg,
|
||||
reliquaries,
|
||||
set: setArr,
|
||||
};
|
||||
}
|
||||
|
||||
//获取uid
|
||||
async function getUid(e) {
|
||||
let res;
|
||||
let reg = /[1|2|5][0-9]{8}/g;
|
||||
|
||||
//从消息中获取
|
||||
if (e.msg) {
|
||||
res = e.msg.match(reg);
|
||||
if (res) {
|
||||
//redis保存uid
|
||||
redis.set(`genshin:uid:${e.user_id}`, res[0], { EX: 2592000 });
|
||||
return { isSelf: false, uid: res[0] };
|
||||
}
|
||||
}
|
||||
|
||||
//从群昵称获取
|
||||
res = e.sender.card.toString().match(reg);
|
||||
|
||||
if (res) {
|
||||
//redis保存uid
|
||||
redis.set(`genshin:uid:${e.user_id}`, res[0], { EX: 2592000 });
|
||||
|
||||
return { isSelf: true, uid: res[0] };
|
||||
}
|
||||
|
||||
//从redis获取
|
||||
res = await redis.get(`genshin:uid:${e.user_id}`);
|
||||
if (res) {
|
||||
redis.expire(`genshin:uid:${e.user_id}`, 2592000);
|
||||
return { isSelf: true, uid: res };
|
||||
}
|
||||
|
||||
return { isSelf: true, uid: false };
|
||||
}
|
||||
|
||||
async function mysApi(e, uid, type, data = {}) {
|
||||
if (BotConfig.mysCookies.length <= 0) {
|
||||
Bot.logger.error("请打开config.js,配置米游社cookie");
|
||||
return { retcode: -300 };
|
||||
}
|
||||
|
||||
let dayEnd = getDayEnd();
|
||||
|
||||
let cookie, index, isNew;
|
||||
let selfCookie = NoteCookie[e.user_id];
|
||||
|
||||
//私聊发送的cookie
|
||||
if (selfCookie && selfCookie.uid == uid) {
|
||||
cookie = selfCookie.cookie;
|
||||
}
|
||||
//配置里面的cookie
|
||||
else if (BotConfig.dailyNote && BotConfig.dailyNote[e.user_id] && BotConfig.dailyNote[e.user_id].uid == uid) {
|
||||
cookie = BotConfig.dailyNote[e.user_id].cookie;
|
||||
} else {
|
||||
//获取uid集合
|
||||
let uid_arr = await redis.get(`genshin:ds:qq:${e.user_id}`);
|
||||
|
||||
if (uid_arr) {
|
||||
uid_arr = JSON.parse(uid_arr);
|
||||
if (!uid_arr.includes(uid)) {
|
||||
uid_arr.push(uid);
|
||||
|
||||
await redis.set(`genshin:ds:qq:${e.user_id}`, JSON.stringify(uid_arr), {
|
||||
EX: dayEnd,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
uid_arr = [uid];
|
||||
|
||||
await redis.set(`genshin:ds:qq:${e.user_id}`, JSON.stringify(uid_arr), {
|
||||
EX: dayEnd,
|
||||
});
|
||||
}
|
||||
|
||||
if (uid_arr.length > e.groupConfig.mysUidLimit && !e.isMaster) {
|
||||
return { retcode: -200 };
|
||||
}
|
||||
|
||||
//限制无用uid查询
|
||||
if (uid < 100000050) {
|
||||
return { retcode: 10102, message: "Data is not public for the user" };
|
||||
}
|
||||
|
||||
isNew = false;
|
||||
index = await redis.get(`genshin:ds:uid:${uid}`);
|
||||
if (!index) {
|
||||
//获取没有到30次的index
|
||||
for (let i in BotConfig.mysCookies) {
|
||||
//跳过达到上限的cookie
|
||||
if (await redis.get(`genshin:ds:max:${i}}`)) {
|
||||
continue;
|
||||
}
|
||||
let count = await redis.sendCommand(["scard", `genshin:ds:index:${i}`]);
|
||||
if (count < 27) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//查询已达上限
|
||||
if (!index) {
|
||||
return { retcode: -100 };
|
||||
}
|
||||
isNew = true;
|
||||
}
|
||||
if (!BotConfig.mysCookies[index]) {
|
||||
return { retcode: -300 };
|
||||
}
|
||||
|
||||
if (!BotConfig.mysCookies[index].includes("ltoken")) {
|
||||
Bot.logger.error("米游社cookie错误,请重新配置");
|
||||
return { retcode: -400 };
|
||||
}
|
||||
}
|
||||
|
||||
let { url, headers, query, body } = getUrl(type, uid, data);
|
||||
headers.Cookie = cookie || BotConfig.mysCookies[index];
|
||||
|
||||
let param = {
|
||||
headers,
|
||||
timeout: 10000,
|
||||
};
|
||||
if (body) {
|
||||
param.method = "post";
|
||||
param.body = body;
|
||||
} else {
|
||||
param.method = "get";
|
||||
}
|
||||
|
||||
let response = {};
|
||||
try {
|
||||
response = await fetch(url, param);
|
||||
} catch (error) {
|
||||
Bot.logger.error(error);
|
||||
return false;
|
||||
}
|
||||
if (!response.ok) {
|
||||
Bot.logger.error(response);
|
||||
return false;
|
||||
}
|
||||
const res = await response.json();
|
||||
|
||||
if (!res) {
|
||||
Bot.logger.mark(`mys接口没有返回`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isNew) {
|
||||
await redis.sendCommand(["sadd", `genshin:ds:index:${index}`, uid]);
|
||||
redis.expire(`genshin:ds:index:${index}`, dayEnd);
|
||||
redis.set(`genshin:ds:uid:${uid}`, index, { EX: dayEnd });
|
||||
}
|
||||
|
||||
if (res.retcode != 0 && ![10102, 1008, -1].includes(res.retcode)) {
|
||||
let ltuid = headers.Cookie.match(/ltuid=(\w{0,9})/g)[0].replace(/ltuid=|;/g, "");
|
||||
|
||||
if (selfCookie && selfCookie.uid == uid) {
|
||||
Bot.logger.mark(`mys接口报错:${JSON.stringify(res)},体力配置cookie,ltuid:${ltuid}`);
|
||||
//体力cookie失效
|
||||
if (res.message == "Please login") {
|
||||
delete NoteCookie[e.user_id];
|
||||
}
|
||||
} else {
|
||||
Bot.logger.mark(`mys接口报错:${JSON.stringify(res)},第${Number(index) + 1}个cookie,ltuid:${ltuid}`);
|
||||
|
||||
//标记达到上限的cookie,自动切换下一个
|
||||
if ([10101].includes(res.retcode)) {
|
||||
redis.set(`genshin:ds:max:${index}`, "1", { EX: dayEnd });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function checkRetcode(res, uid, e) {
|
||||
let qqName = "";
|
||||
switch (res.retcode) {
|
||||
case 0:
|
||||
Bot.logger.debug(`mys查询成功:${uid}`);
|
||||
return false;
|
||||
case -1:
|
||||
break;
|
||||
case -100:
|
||||
e.reply("无法查询,已达上限\n请配置更多cookie");
|
||||
break;
|
||||
case -200:
|
||||
qqName = lodash.truncate(e.sender.card, { length: 8 });
|
||||
e.reply([segment.at(e.user_id, qqName), "\n今日查询已达上限"]);
|
||||
break;
|
||||
case -300:
|
||||
e.reply("尚未配置公共查询cookie,无法查询原神角色信息\n私聊发送【配置cookie】进行设置");
|
||||
break;
|
||||
case -400:
|
||||
e.reply("米游社cookie错误,请重新配置");
|
||||
break;
|
||||
case 1001:
|
||||
case 10001:
|
||||
case 10103:
|
||||
e.reply("米游社接口报错,暂时无法查询");
|
||||
break;
|
||||
case 1008:
|
||||
qqName = lodash.truncate(e.sender.card, { length: 8 });
|
||||
e.reply([segment.at(e.user_id, qqName), "\n请先去米游社绑定角色"]);
|
||||
break;
|
||||
case 10101:
|
||||
e.reply("查询已达今日上限");
|
||||
break;
|
||||
case 10102:
|
||||
if (res.message == "Data is not public for the user") {
|
||||
qqName = lodash.truncate(e.sender.card, { length: 8 });
|
||||
e.reply([segment.at(e.user_id, qqName), "\n米游社数据未公开"]);
|
||||
} else {
|
||||
e.reply(`id:${uid}请先去米游社绑定角色`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {角色昵称} keyword
|
||||
* @param {是否搜索角色默认名} search_val
|
||||
* @returns
|
||||
*/
|
||||
export function roleIdToName(keyword, search_val = false) {
|
||||
if (!keyword) {
|
||||
return false;
|
||||
}
|
||||
if (search_val) {
|
||||
return genshin.roleId[keyword][0] ? genshin.roleId[keyword][0] : "";
|
||||
}
|
||||
|
||||
if (!nameID) {
|
||||
nameID = new Map();
|
||||
for (let i in genshin.roleId) {
|
||||
for (let val of genshin.roleId[i]) {
|
||||
nameID.set(val, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
let name = nameID.get(keyword);
|
||||
return name ? name : "";
|
||||
}
|
||||
|
||||
async function limitGet(e) {
|
||||
if (!e.isGroup) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (e.isMaster) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let key = `genshin:limit:${e.user_id}`;
|
||||
let num = await redis.get(key);
|
||||
|
||||
if (num && num >= e.groupConfig.mysDayLimit - 1) {
|
||||
let name = lodash.truncate(e.sender.card, { length: 8 });
|
||||
e.reply([segment.at(e.user_id, name), "\n今日查询已达上限"]);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function limitSet(e) {
|
||||
if (!e.isGroup) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let key = `genshin:limit:${e.user_id}`;
|
||||
let dayEnd = getDayEnd();
|
||||
|
||||
await redis.incr(key);
|
||||
redis.expire(key, dayEnd);
|
||||
}
|
||||
|
||||
function getDayEnd() {
|
||||
let now = new Date();
|
||||
let dayEnd = new Date(now.getFullYear(), now.getMonth(), now.getDate(), "23", "59", "59").getTime() / 1000;
|
||||
|
||||
return dayEnd - parseInt(now.getTime() / 1000);
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
*{
|
||||
padding:0;
|
||||
margin:0;
|
||||
}
|
||||
html,body{
|
||||
width:100%;
|
||||
height:10px;
|
||||
|
||||
position:relative;
|
||||
}
|
||||
img.bg{
|
||||
width:100%;
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="character.css">
|
||||
<style>
|
||||
body {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
@media only screen and (orientation: landscape) {
|
||||
body {
|
||||
background-color: lightblue;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<title>test</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content" style="background-image:url(w.jpg)">
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Before Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 118 KiB |
97
components/Data.js
Normal file
@ -0,0 +1,97 @@
|
||||
import lodash from "lodash";
|
||||
import fs from "fs";
|
||||
|
||||
let Data = {
|
||||
|
||||
/*
|
||||
* 根据指定的path依次检查与创建目录
|
||||
* */
|
||||
createDir(rootPath = "", path, includeFile = false) {
|
||||
console.log(rootPath, path)
|
||||
let pathList = path.split("/"),
|
||||
nowPath = rootPath;
|
||||
pathList.forEach((name, idx) => {
|
||||
name = name.trim();
|
||||
if (!includeFile && idx < pathList.length - 1) {
|
||||
nowPath += name + "/";
|
||||
if (name) {
|
||||
if (!fs.existsSync(nowPath)) {
|
||||
console.log(nowPath)
|
||||
fs.mkdirSync(nowPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/*
|
||||
* 读取json
|
||||
* */
|
||||
readJSON(root, path) {
|
||||
if (!/\.json$/.test(path)) {
|
||||
path = path + ".json";
|
||||
}
|
||||
// 检查并创建目录
|
||||
Data.createDir(root, path, true);
|
||||
|
||||
let jsonRet = fs.readFileSync(`${root}/${path}`, "utf8");
|
||||
return JSON.parse(jsonRet);
|
||||
},
|
||||
|
||||
/*
|
||||
* 写JSON
|
||||
* */
|
||||
writeJson(path, file, data, space = "\t") {
|
||||
if (!/\.json$/.test(file)) {
|
||||
file = file + ".json";
|
||||
}
|
||||
|
||||
// 检查并创建目录
|
||||
Data.createDir(path, true);
|
||||
return fs.writeFileSync(`${path}/${file}`, JSON.stringify(data, null, space));
|
||||
},
|
||||
|
||||
/*
|
||||
* 返回一个从 target 中选中的属性的对象
|
||||
*
|
||||
* keyList : 获取字段列表,逗号分割字符串
|
||||
* key1, key2, toKey1:fromKey1, toKey2:fromObj.key
|
||||
*
|
||||
* defaultData: 当某个字段为空时会选取defaultData的对应内容
|
||||
* toKeyPrefix:返回数据的字段前缀,默认为空。defaultData中的键值无需包含toKeyPrefix
|
||||
*
|
||||
* */
|
||||
|
||||
getData(target, keyList = "", cfg = {}) {
|
||||
target = target || {};
|
||||
let defaultData = cfg.defaultData || {};
|
||||
let ret = {};
|
||||
// 分割逗号
|
||||
if (typeof (keyList) === "string") {
|
||||
keyList = keyList.split(",");
|
||||
}
|
||||
|
||||
lodash.forEach(keyList, (keyCfg) => {
|
||||
// 处理通过:指定 toKey & fromKey
|
||||
let _keyCfg = keyCfg.split(":");
|
||||
let keyTo = _keyCfg[0].trim(),
|
||||
keyFrom = (_keyCfg[1] || _keyCfg[0]).trim(),
|
||||
keyRet = keyTo;
|
||||
if (cfg.lowerFirstKey) {
|
||||
keyRet = lodash.lowerFirst(keyRet);
|
||||
console.log('keyRet', keyRet)
|
||||
}
|
||||
if (cfg.keyPrefix) {
|
||||
keyRet = cfg.keyPrefix + keyRet;
|
||||
}
|
||||
// 通过Data.getVal获取数据
|
||||
ret[keyRet] = Data.getVal(target, keyFrom, defaultData[keyTo], cfg);
|
||||
})
|
||||
return ret;
|
||||
},
|
||||
|
||||
getVal(target, keyFrom, defaultValue) {
|
||||
return lodash.get(target, keyFrom, defaultValue);
|
||||
}
|
||||
};
|
||||
export default Data;
|
158
components/MysApi.js
Normal file
@ -0,0 +1,158 @@
|
||||
import md5 from "md5";
|
||||
import lodash from 'lodash';
|
||||
import fetch from "node-fetch";
|
||||
|
||||
let MysApi = {
|
||||
getUrl(type, uid, data = {}) {
|
||||
let url = "https://api-takumi.mihoyo.com";
|
||||
let game_record = "/game_record/app/genshin/api/";
|
||||
let server = MysApi.getServer(uid);
|
||||
let query, body;
|
||||
|
||||
switch (type) {
|
||||
//首页宝箱
|
||||
case "index":
|
||||
url += game_record + "index";
|
||||
query = `role_id=${uid}&server=${server}`;
|
||||
break;
|
||||
//深渊
|
||||
case "spiralAbyss":
|
||||
url += game_record + "spiralAbyss";
|
||||
query = `role_id=${uid}&schedule_type=${data.schedule_type}&server=${server}`;
|
||||
break;
|
||||
//角色详情
|
||||
case "character":
|
||||
url += game_record + "character";
|
||||
body = JSON.stringify(data);
|
||||
break;
|
||||
//树脂每日任务(只能当前id)
|
||||
case "dailyNote":
|
||||
url += game_record + "dailyNote";
|
||||
query = `role_id=${uid}&server=${server}`;
|
||||
break;
|
||||
case "detail":
|
||||
url += "/event/e20200928calculate/v1/sync/avatar/detail";
|
||||
query = `uid=${uid}®ion=${server}&avatar_id=${data.avatar_id}`;
|
||||
break;
|
||||
case "getAnnouncement":
|
||||
url += "/game_record/card/wapi/getAnnouncement";
|
||||
break;
|
||||
case "getGameRecordCard":
|
||||
url += "/game_record/card/wapi/getGameRecordCard";
|
||||
query = `uid=${uid}`;//米游社id
|
||||
break;
|
||||
case "bbs_sign_info":
|
||||
url += "/event/bbs_sign_reward/info";
|
||||
query = `act_id=e202009291139501®ion=${server}&uid=${uid}`;
|
||||
break;
|
||||
case "bbs_sign_home":
|
||||
url += "/event/bbs_sign_reward/home";
|
||||
query = `act_id=e202009291139501®ion=${server}&uid=${uid}`;
|
||||
break;
|
||||
case "bbs_sign":
|
||||
url += "/event/bbs_sign_reward/sign";
|
||||
body = JSON.stringify({ act_id: "e202009291139501", region: server, uid: uid, });
|
||||
break;
|
||||
case "ys_ledger":
|
||||
url = "https://hk4e-api.mihoyo.com/event/ys_ledger/monthInfo";
|
||||
query = `month=${data.month}&bind_uid=${uid}&bind_region=${server}`;
|
||||
break;
|
||||
}
|
||||
|
||||
if (query) {
|
||||
url += "?" + query;
|
||||
}
|
||||
|
||||
let headers;
|
||||
if (type === "bbs_sign") {
|
||||
headers = MysApi.getHeaders_sign();
|
||||
} else {
|
||||
headers = MysApi.getHeaders(query, body);
|
||||
}
|
||||
|
||||
return { url, headers, query, body };
|
||||
},
|
||||
|
||||
getServer(uid) {
|
||||
switch (uid.toString()[0]) {
|
||||
case "1":
|
||||
case "2":
|
||||
return "cn_gf01"; //官服
|
||||
case "5":
|
||||
return "cn_qd01"; //B服
|
||||
}
|
||||
return "cn_gf01"; //官服
|
||||
},
|
||||
|
||||
//# Github-@lulu666lulu
|
||||
getDs(q = "", b = "") {
|
||||
let n = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs";
|
||||
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}`;
|
||||
},
|
||||
|
||||
//签到ds
|
||||
getDS_sign() {
|
||||
const n = "h8w582wxwgqvahcdkpvdhbh2w9casgfl";
|
||||
const t = Math.round(new Date().getTime() / 1000);
|
||||
const r = lodash.sampleSize("abcdefghijklmnopqrstuvwxyz0123456789", 6).join("");
|
||||
const DS = md5(`salt=${n}&t=${t}&r=${r}`);
|
||||
return `${t},${r},${DS}`;
|
||||
},
|
||||
|
||||
getHeaders(q = "", b = "") {
|
||||
return {
|
||||
"x-rpc-app_version": "2.20.1",
|
||||
"x-rpc-client_type": 5,
|
||||
DS: MysApi.getDs(q, b),
|
||||
};
|
||||
},
|
||||
|
||||
getHeaders_sign() {
|
||||
return {
|
||||
"x-rpc-app_version": "2.3.0",
|
||||
"x-rpc-client_type": 5,
|
||||
"x-rpc-device_id": MysApi.guid(),
|
||||
"User-Agent": " miHoYoBBS/2.3.0",
|
||||
DS: MysApi.getDS_sign(),
|
||||
};
|
||||
},
|
||||
|
||||
guid() {
|
||||
function S4() {
|
||||
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
||||
}
|
||||
|
||||
return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
|
||||
},
|
||||
|
||||
// 按type请求
|
||||
request: async function (type, cfg) {
|
||||
let { uid } = cfg;
|
||||
let { url, headers } = MysApi.getUrl(type, uid);
|
||||
return await MysApi.fetch(url, headers, cfg);
|
||||
},
|
||||
|
||||
// 发送请求
|
||||
fetch: async function (url, cfg) {
|
||||
let { cookie, error, success, headers, method } = cfg;
|
||||
headers = headers || {};
|
||||
method = method || "get";
|
||||
headers.Cookie = cookie;
|
||||
let response = await fetch(url, { method, headers });
|
||||
if (!response.ok) {
|
||||
return await error(-1, {
|
||||
msg: "米游社接口错误"
|
||||
})
|
||||
}
|
||||
let res = await response.json();
|
||||
if (res.retcode * 1 !== 0) {
|
||||
return await error(res.retcode * 1, res)
|
||||
}
|
||||
return await success(res.data, res)
|
||||
}
|
||||
}
|
||||
|
||||
export default MysApi;
|
295
components/User.js
Normal file
@ -0,0 +1,295 @@
|
||||
import UserModel from "./models/UserModel.js";
|
||||
import { segment } from "oicq";
|
||||
import fetch from "node-fetch";
|
||||
import { MysApi } from "./index.js";
|
||||
import md5 from "md5";
|
||||
|
||||
const getUidByToken = async function (token) {
|
||||
let ltoken = `ltoken=${token["ltoken"]}ltuid=${token["ltuid"]};`;
|
||||
let cookie_token = `cookie_token=${token["cookie_token"]} account_id=${token["ltuid"]};`;
|
||||
ltoken += cookie_token;
|
||||
let uid = 0;
|
||||
let url = host + "binding/api/getUserGameRolesByCookie?game_biz=hk4e_cn";
|
||||
|
||||
await MysApi.fetch(url, {
|
||||
method: "get",
|
||||
cookie: ltoken,
|
||||
error: async () => {
|
||||
throw `cookie错误:${res.message}`;
|
||||
},
|
||||
success: async (data) => {
|
||||
for (let val of data.list) {
|
||||
//米游社默认展示的角色
|
||||
if (val.is_chosen) {
|
||||
uid = val.game_uid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!uid) {
|
||||
uid = data.list[0].game_uid;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
return uid;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
let User = {};
|
||||
|
||||
/*
|
||||
* 在文本中检索uid,若未查找到则返回false
|
||||
* */
|
||||
User.matchUid = function (msg) {
|
||||
let ret = /[1|2|5][0-9]{8}/g.exec(msg);
|
||||
if (ret) {
|
||||
return ret[0];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* 返回需要绑定 cookie
|
||||
*
|
||||
* */
|
||||
User.replyNeedBind = function (e, replyMsg = "") {
|
||||
replyMsg = replyMsg || `您尚未绑定米游社cookie,无法进行操作`;
|
||||
let helpMsg = "获取cookie后发送至当前聊天窗口即可,Cookie获取方式:https://docs.qq.com/doc/DUWNVQVFTU3liTVlO";
|
||||
|
||||
if (e.isGroup) {
|
||||
replyMsg = segment.image(`file:///${_path}/resources/help/help.png`);
|
||||
e.reply([replyMsg, helpMsg]);
|
||||
} else {
|
||||
e.reply(replyMsg);
|
||||
e.reply(helpMsg);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/*
|
||||
* 获取当前用户消息所查询的目标用户
|
||||
*
|
||||
* 策略:优先级依次递减
|
||||
* 1. 消息里包含 uid
|
||||
* 2. 存在 msg.at,且msg.at 用户 是绑定用户
|
||||
* 3. 存在 msg.at 且msg.at 名片包含uid
|
||||
* 4. 当前用户为绑定用户
|
||||
* 5. 当前用户名片包含 uid
|
||||
* 6. 当前用户存在redis-uid 缓存
|
||||
* */
|
||||
User.getTargetUser = async function (e, selfUser) {
|
||||
let res;
|
||||
let reg = /[1|2|5][0-9]{8}/g;
|
||||
let msg = e.msg;
|
||||
|
||||
let targetId, targetUser;
|
||||
|
||||
/*-- 有指定的查询目标 --*/
|
||||
|
||||
/* 消息里包含 uid的话优先匹配 */
|
||||
if (e.msg) {
|
||||
targetId = getUid(e.msg);
|
||||
if (targetId) {
|
||||
// 根据targetId查找用户
|
||||
targetUser = await User.get({ uid: targetId });
|
||||
//存在则返回,不存在则将该uid绑定至当前用户
|
||||
if (targetUser) {
|
||||
return targetUser;
|
||||
}
|
||||
|
||||
let selfUserBindUid = await selfUser.getRegUid();
|
||||
// 当前用户未注册,则将uid绑定至当前用户
|
||||
if (!selfUser.isBind || selfUser.uid == targetId) {
|
||||
await selfUser.setRegUid(targetId)
|
||||
return selfUser;
|
||||
} else {
|
||||
// 当前用户为注册用户,返回 Draft
|
||||
return User.get({ uid: targetId }, true)
|
||||
}
|
||||
|
||||
|
||||
selfUser.setRegUid(targetId);
|
||||
return selfUser;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有at的用户,使用被at的用户
|
||||
if (!targetId && e.at) {
|
||||
targetUser = await User.get({ qq: e.at.qq });
|
||||
|
||||
// 识别at用户的名片结果。如果at用户无uid信息则使用此结果
|
||||
targetId = getUid(e.at.card.toString());
|
||||
|
||||
if (targetUser) {
|
||||
targetUser
|
||||
}
|
||||
}
|
||||
|
||||
if (targetUser) {
|
||||
let targetUserUid = await targetUser.getRegUid();
|
||||
targetUser.setRegUid(targetUserUid || targetId)
|
||||
if (!targetId && !targetUserUid) {
|
||||
// return false;
|
||||
}
|
||||
}
|
||||
|
||||
targetUser = selfUser;
|
||||
// 使用当前用户作为targetUser
|
||||
if (selfUser.isBind) {
|
||||
// 设置查询用户为当前用户
|
||||
targetUser = selfUser;
|
||||
|
||||
// 从当前用户的昵称中匹配uid
|
||||
targetId = getUid(e.sender.card.toString());
|
||||
} else if (false) {
|
||||
// selfUser.uid =
|
||||
}
|
||||
|
||||
selfUser.setRegUid(uid);
|
||||
|
||||
|
||||
// 存在查询用户,但无
|
||||
if (!targetUser && targetId) {
|
||||
// 根据uid创建的用户包含uid
|
||||
return User.get({ uid: targetId }, true);
|
||||
|
||||
} else if (targetUser && targetId) {
|
||||
// 存在目标用户,但不存在查询uid的话,赋值给targetUser
|
||||
if (!targetUser.uid && targetId) {
|
||||
targetUser.uid = targetId;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
if (targetUser) {
|
||||
targetUser.setLastQuery(targetUser.id);
|
||||
}
|
||||
|
||||
return targetUser;
|
||||
|
||||
|
||||
//从redis获取
|
||||
res = await redis.get(`genshin:uid:${e.user_id}`);
|
||||
if (res) {
|
||||
redis.expire(`genshin:uid:${e.user_id}`, 2592000);
|
||||
return { isSelf: true, uid: res };
|
||||
}
|
||||
return { isSelf: true, uid: false };
|
||||
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* 获取当前 MysApi 的最佳查询User
|
||||
*
|
||||
* 策略,优先级依次递减 ( sUid 在下方代指被查询的Uid )
|
||||
* 1. 如果 sUid 为绑定用户,优先使用绑定用户自身的 cookie( 在不允许跨系统调用时需传递 allowCrossUid = false )
|
||||
* 2. 如果 sUid 24小时内被查询过,优先使用曾经查询过该用户的 cookie
|
||||
* 3. 如果 当前查询用户为绑定用户,优先使用绑定用户自身的 cookie
|
||||
* 4. 使用系统cookie : 暂未接管bot逻辑,目前需要传入getBotCookie方法
|
||||
*
|
||||
* */
|
||||
User.getReqUser = async function (e, allowCrossUser = true, getBotCookie=false) {
|
||||
|
||||
// 当前用户
|
||||
let selfUser = User.get(e.user_id);
|
||||
|
||||
// 被查询用户
|
||||
let targetUser = await User.getTargetUser(e);
|
||||
|
||||
// 如果 sUid 为绑定用户,优先使用绑定用户自身的 cookie
|
||||
if (targetUser.isBind && allowCrossUser) {
|
||||
return targetUser;
|
||||
}
|
||||
|
||||
// 如果 sUid 24小时内被查询过,优先使用曾经查询过该用户的 cookie
|
||||
let lastQueryUser = targetUser.getSourceUser();
|
||||
if (lastQueryUser) {
|
||||
return lastQueryUser;
|
||||
}
|
||||
|
||||
// 如果 当前查询用户为绑定用户,优先使用绑定用户自身的 cookie
|
||||
if (selfUser.isBind) {
|
||||
await targetUser.setSourceUser(selfUser);
|
||||
return selfUser;
|
||||
}
|
||||
|
||||
// 使用系统 cookie
|
||||
// 将系统注册的cookie视作机器人,同样包装为 User 用户返回
|
||||
let botUser = User.getAvailableBot(e, true);
|
||||
if (botUser) {
|
||||
await targetUser.setSourceUser(botUser);
|
||||
return botUser;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/*
|
||||
* 对当前用户的类型进行检查,并对不符合条件的用户进行回复
|
||||
* type: all-不检查,bind-绑定用户(设置了有效的NoteCookie),master-管理员
|
||||
* replyMsg:不符合条件的消息
|
||||
* */
|
||||
User.check = async function (e, type = "all", checkParams = {}) {
|
||||
let self = User.get(e.user_id);
|
||||
|
||||
let { limit = true, action, replyMsg } = checkParams;
|
||||
|
||||
// 校验频度限制
|
||||
if (limit) {
|
||||
if (!(await limitGet(e))) return true;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'bind':
|
||||
// 需要是绑定用户
|
||||
if (!self.isBind) {
|
||||
if (!replyMsg) {
|
||||
action = action || "进行操作";
|
||||
replyMsg = "您尚未绑定米游社cookie,无法" + action;
|
||||
}
|
||||
User.replyNeedBind(e, replyMsg);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'master':
|
||||
if (!self.isMaster) {
|
||||
// 如果主动传递了replyMsg则进行回复,否则静默
|
||||
if (replyMsg) {
|
||||
e.reply(replyMsg)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case 'all':
|
||||
//不检查权限
|
||||
return self;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return self;
|
||||
};
|
||||
|
||||
/*
|
||||
* 获取可用的机器人,作为UserModel返回
|
||||
* noticeError: 在无可用机器人时是否 e.reply 错误信息
|
||||
* */
|
||||
// TODO: 待实现
|
||||
User.getAvailableBot = async function (e, noticeError = false) {
|
||||
let id = md5("BOT_" & md5('cookie'));
|
||||
|
||||
User.get(md5);
|
||||
User.bindCookie(cookie, {
|
||||
isBot: true
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export default User;
|
||||
|
1
components/index.js
Normal file
@ -0,0 +1 @@
|
||||
export Data from "./Data.js";
|
@ -1 +1,23 @@
|
||||
export default class Base{}
|
||||
import { Data } from "../index.js";
|
||||
|
||||
export default class Base {
|
||||
|
||||
constructor() {
|
||||
this.name = "";
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
getData(arrList = "", cfg = {}) {
|
||||
return Data.getData(this, arrList, cfg);
|
||||
}
|
||||
|
||||
// 获取指定值数据,支持通过
|
||||
getVal(key, defaultValue) {
|
||||
return Data.getVal(this, key, defaultValue);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,60 +1,165 @@
|
||||
import Base from "./Base.js";
|
||||
import { roleId, abbr } from "../../../config/genshin/roleId.js";
|
||||
import { roleId, abbr } from "../../../../config/genshin/roleId.js";
|
||||
import lodash from "lodash";
|
||||
import fs from "fs";
|
||||
import Data from "../Data.js";
|
||||
import request from "request";
|
||||
|
||||
|
||||
let characterMap = {};
|
||||
let characterAttr = {};
|
||||
|
||||
const characterMeta = JSON.parse(fs.readFileSync("../meta/characters.json", "utf8"));
|
||||
// 读取配置
|
||||
let characterMeta = Data.readJSON("./plugins/miao-plugin/components/meta", "characters.json")// JSON.parse(fs.readFileSync(__dirname + "../meta/characters.json", "utf8"));
|
||||
characterMeta = lodash.keyBy(characterMeta, (d) => d.Name);
|
||||
|
||||
const elemName = {
|
||||
pyro: "火",
|
||||
hydro: "水",
|
||||
dendro: "草",
|
||||
electro: "雷",
|
||||
anemo: "风",
|
||||
cryo: "冰",
|
||||
geo: "岩"
|
||||
};
|
||||
|
||||
lodash.forEach(characterMeta, (meta)=>{
|
||||
characterMap[meta.Name] = new Character(name, meta)
|
||||
});
|
||||
|
||||
lodash.forEach(roleId, function(names, id){
|
||||
if(characterMap[names[0]]){
|
||||
characterMap[names[0]].id = id;
|
||||
}
|
||||
});
|
||||
|
||||
class Character extends Base {
|
||||
|
||||
constructor(name, meta) {
|
||||
constructor(name) {
|
||||
super();
|
||||
let key = "".split();
|
||||
this._meta = meta;
|
||||
}
|
||||
get sortName() {
|
||||
return characterAttr[this.name] || this.name;
|
||||
this.name = name;
|
||||
this.sName = this.name;
|
||||
this.id = YunzaiApps.mysInfo['roleIdToName'](name);
|
||||
lodash.extend(this, getMetaData(name))
|
||||
}
|
||||
|
||||
get id(){
|
||||
for(let id in roleId){
|
||||
if(roleId[id] && role[id][0] === this.name){
|
||||
return id;
|
||||
}
|
||||
get id() {
|
||||
for (let id in roleId) {
|
||||
if (roleId[id] && role[id][0] === this.name) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getData(key){
|
||||
async checkImgCache(resDir) {
|
||||
// 处理img信息
|
||||
let chcheDir = resDir + "/cache/";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let cacheImgFile = async function (url, cacheDir) {
|
||||
let ret = /^https:\/\/(.*)$/.exec(url);
|
||||
if (ret && ret[1] && /\.(png|jpg|gif|jpeg|webp)$/.test(ret[1])) {
|
||||
let fileName = ret[1];
|
||||
Data.createDir(cacheDir, fileName);
|
||||
request(imgUrl).pipe(fs.createWriteStream(cacheDir + fileName));
|
||||
}
|
||||
}
|
||||
|
||||
// 获取指定角色的Meta信息
|
||||
let getMetaData = function (name) {
|
||||
if (!characterMeta[name]) {
|
||||
return {};
|
||||
}
|
||||
const metaCfg = { lowerFirstKey: true },
|
||||
meta = characterMeta[name];
|
||||
|
||||
// 处理基础信息
|
||||
let ret = Data.getData(meta, "Name,Title,desc:Description,astro:AstrolabeName", metaCfg);
|
||||
|
||||
// 处理图像信息
|
||||
ret.img = Data.getData(meta, "Weapon,Element,City,Profile,GachaCard,GachaSplash,Source", metaCfg);
|
||||
|
||||
// 处理元素
|
||||
let elemRet = /([a-z]*).png$/.exec(meta.Element);
|
||||
if (elemRet && elemRet[1]) {
|
||||
ret.elementType = ret[1];
|
||||
ret.element = elemName[ret.elementType];
|
||||
}
|
||||
|
||||
// 处理属性
|
||||
ret.stat = Data.getData(meta, "BaseHP,BaseATK,BaseDEF,aStat:AscensionStat,aStatValue:AscensionStatValue");
|
||||
ret.statPerLv = lodash.map(meta.CharStat, (d) => Data.getData(d, "Name,Values", metaCfg));
|
||||
|
||||
// 处理材料
|
||||
let itemKey = lodash.map("talent,boss,gemStone,Local,monster,weekly".split(","), (a) => `${a}:${lodash.upperFirst(a)}.Name`);
|
||||
ret.item = Data.getData(meta, itemKey, metaCfg)
|
||||
|
||||
// 处理天赋
|
||||
ret.talents = {
|
||||
a: getTalentData(meta.NormalAttack),
|
||||
e: getTalentData(meta.TalentE),
|
||||
q: getTalentData(meta.TalentQ),
|
||||
};
|
||||
|
||||
// 处理其他天赋
|
||||
ret.passiveTalents = lodash.map(meta.PassiveTalents, (d) => Data.getData(d, "Name,desc:Description,icon:Source", metaCfg))
|
||||
|
||||
// 处理命座信息
|
||||
let cons = {};
|
||||
lodash.forEach(meta.Constellation, (data, key) => {
|
||||
cons[key.replace("Constellation")] = Data.getData(data, "Name,icon:Source,desc:Description", metaCfg);
|
||||
})
|
||||
ret.cons = cons;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 获取Meta中的天赋信息
|
||||
const getTalentData = function (data) {
|
||||
let ret = Data.getData(data, "Name,icon:Source,desc:Description", { lowerFirstKey: true });
|
||||
let attr = [], table = [], tableKeys;
|
||||
|
||||
lodash.forEach(data.Table, (tr) => {
|
||||
let tmp = { name: tr.Name }, isTable = true, isDef = false, lastVal;
|
||||
|
||||
// 检查当前行是否是表格数据
|
||||
lodash.forEach(tr.Values, (v) => {
|
||||
// 如果为空则退出循环
|
||||
if (v === "") {
|
||||
isTable = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof (lastVal) === "undefined") {
|
||||
// 设定初始值
|
||||
lastVal = v;
|
||||
} else if (lastVal != v) {
|
||||
// 如果与初始值不一样,则标记退出循环
|
||||
isDef = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (isTable && isDef) {
|
||||
if (!tableKeys) {
|
||||
tableKeys = lodash.keys(tr.Values);
|
||||
}
|
||||
tmp.value = lodash.map(tableKeys, (k) => tr.Values[k])
|
||||
table.push(tmp);
|
||||
} else {
|
||||
tmp.value = lastVal;
|
||||
attr.push(tmp)
|
||||
}
|
||||
})
|
||||
ret.attr = attr;
|
||||
ret.table = table;
|
||||
ret.tableKeys = tableKeys;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
Character.get = function (val) {
|
||||
let name = YunzaiApps.mysInfo.roleIdToName(val);
|
||||
if(!name){
|
||||
let roleid = YunzaiApps.mysInfo['roleIdToName'](val);
|
||||
let name = YunzaiApps.mysInfo['roleIdToName'](roleid, true);
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
if(!characterMap[name]){
|
||||
//characterMap[name] =
|
||||
if (!characterMap[name]) {
|
||||
let character = new Character(name);
|
||||
characterMap[name] = character;
|
||||
}
|
||||
|
||||
|
||||
return characterMap[name];
|
||||
};
|
||||
|
||||
export default Character;
|
||||
|
273
components/models/UserModel.js
Normal file
@ -0,0 +1,273 @@
|
||||
/*
|
||||
* UserModel Class
|
||||
* 提供用户实例相关的操作方法
|
||||
*
|
||||
* * TODO:将与具体用户操作相关的方法逐步迁移到UserModel中,外部尽量只调用实例方法
|
||||
* 以确保逻辑收敛且维护性更强
|
||||
* */
|
||||
import BaseModel from "./BaseModel.js"
|
||||
import lodash from "lodash";
|
||||
import md5 from "md5";
|
||||
import { MysApi, Data } from "../index.js";
|
||||
|
||||
const _path = process.cwd();
|
||||
const redisPrefix = "cache";
|
||||
|
||||
const userInstanceReclaimTime = 60;
|
||||
let userMap = {};
|
||||
|
||||
// Redis相关操作方法
|
||||
const Cache = {
|
||||
prefix: "genshin",
|
||||
async get(type, key) {
|
||||
return await redis.get(`${Cache.prefix}:${type}:${key}`);
|
||||
},
|
||||
async set(type, key, val, exp = 2592000) {
|
||||
return await redis.set(`${Cache.prefix}:${type}:${key}`, val, { EX: exp });
|
||||
},
|
||||
async del(type, key) {
|
||||
return await redis.del(`${Cache.prefix}:${type}:${key}`);
|
||||
}
|
||||
};
|
||||
const saveCookieFile = function () {
|
||||
Data.writeJson("./data/NoteCookie/", "NoteCookie", NoteCookie);
|
||||
};
|
||||
|
||||
// UserModel class
|
||||
class UserModel extends BaseModel {
|
||||
|
||||
// 初始化用户
|
||||
constructor(id) {
|
||||
super();
|
||||
// 一个id对应一个用户,根据id检索用户信息
|
||||
this.id = id;
|
||||
|
||||
// 检索是否存在NoteCookie信息
|
||||
let data = NoteCookie[id];
|
||||
|
||||
if (data) {
|
||||
this._data = data;
|
||||
this.isPush = data.isPush;
|
||||
this.isSignAuto = data.isSignAuto;
|
||||
this.uid = data.uid;
|
||||
} else {
|
||||
this._data = {};
|
||||
}
|
||||
}
|
||||
|
||||
// 是绑定的cookie用户
|
||||
// 需要存在NoteCookie记录且存在 cookie 与 uid 才认为是正确记录
|
||||
get isBind() {
|
||||
let dbData = NoteCookie[this.id];
|
||||
return !!(dbData && dbData.cookie && dbData.uid);
|
||||
}
|
||||
|
||||
// 是否是管理员
|
||||
// TODO
|
||||
get isMaster() {
|
||||
return !this.isBot && BotConfig.masterQQ && BotConfig.masterQQ.includes(Number(this.id));
|
||||
}
|
||||
|
||||
get isBot() {
|
||||
// todo
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取当前用户cookie
|
||||
get cookie() {
|
||||
return this._data.cookie;
|
||||
}
|
||||
|
||||
// 获取当前用户uid
|
||||
get uid() {
|
||||
return this._uid || this._data.uid || this._reg_uid;
|
||||
}
|
||||
|
||||
set uid(uid) {
|
||||
this._uid = uid;
|
||||
this._reg_uid = uid;
|
||||
}
|
||||
|
||||
// 保存用户信息
|
||||
/*
|
||||
async _save() {
|
||||
// todo
|
||||
return
|
||||
|
||||
let data = NoteCookie[this.id] || this._data || {};
|
||||
|
||||
// 将信息更新至 NoteCookie
|
||||
data.id = this.id;
|
||||
data.uid = this._uid || this._data.uid;
|
||||
data.cookie = this._cookie || this._data.cookie;
|
||||
data.isPush = this.isPush;
|
||||
data.isAutoSign = !!this.isAutoSign;
|
||||
|
||||
// 保存信息
|
||||
NoteCookie[this.id] = data;
|
||||
this._data = data;
|
||||
saveCookieFile();
|
||||
|
||||
// 建立当前用户相关缓存
|
||||
await this.refreshCache();
|
||||
return this;
|
||||
}
|
||||
*/
|
||||
|
||||
// 设置&更新用户缓存
|
||||
async refreshCache() {
|
||||
// 设置缓存
|
||||
await Cache.set("id-uid", this.qq, this.uid);
|
||||
await Cache.set("uid-id", this.uid, this.id);
|
||||
Bot.logger.mark(`绑定用户:QQ${this.id},UID${this.uid}`);
|
||||
}
|
||||
|
||||
// 删除用户缓存
|
||||
async delCache() {
|
||||
await Cache.del("id-uid", this.id);
|
||||
await Cache.del("uid-id", this.uid);
|
||||
}
|
||||
|
||||
|
||||
// 获取曾经查询过当前用户的人
|
||||
async getSourceUser() {
|
||||
let lastQuery = await Cache.get("id-source", this.id);
|
||||
|
||||
if (lastQuery) {
|
||||
return UserModel.get(lastQuery);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置曾经查询过当前用户的人,缓存23小时
|
||||
async setSourceUser(user) {
|
||||
await Cache.set("id-source", this.id, user.id, 3600 * 23);
|
||||
}
|
||||
|
||||
// 删除曾经查询过当前用户的人
|
||||
async delSourceUser() {
|
||||
await Cache.del("id-source", this.id);
|
||||
}
|
||||
|
||||
|
||||
/* 获取当前用户注册的uid
|
||||
*
|
||||
* 1. 如果是绑定用户,优先返回当前绑定的uid(cookie 对应uid)
|
||||
* 2. 返回redis中存储的uid
|
||||
*
|
||||
* 注:redis uid需要主动调用一次 getRegUid 才能被this.uid访问到
|
||||
*
|
||||
* */
|
||||
async getRegUid() {
|
||||
if (this.isBind) {
|
||||
return this.uid;
|
||||
}
|
||||
if (!this._reg_uid) {
|
||||
let uid = await Cache.get('id-regUid', this.id);
|
||||
if (uid) {
|
||||
this._reg_uid = uid;
|
||||
}
|
||||
}
|
||||
return this._reg_uid;
|
||||
}
|
||||
|
||||
async setRegUid(uid) {
|
||||
// 只有非绑定用户才设置 注册uid
|
||||
if (!this.isBind) {
|
||||
this._reg_uid = uid;
|
||||
Cache.set('id-regUid', this.id, uid);
|
||||
Cache.set('regUid-id', this.uid, this.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* UserModel static function */
|
||||
|
||||
/*
|
||||
* 获取用户实例
|
||||
* query为获取条件,默认为 id
|
||||
*
|
||||
* */
|
||||
UserModel.get = async function (query, getDraftWhenNotFound = false) {
|
||||
let user = await getUser(query, getDraftWhenNotFound);
|
||||
|
||||
user._reclaimFn && clearTimeout(user._reclaimFn);
|
||||
user._reclaimFn = setTimeout(() => {
|
||||
delete userMap[user.id];
|
||||
}, userInstanceReclaimTime);
|
||||
userMap[user.id] = user;
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
// 格式化查询
|
||||
const formatQuery = function (query) {
|
||||
if (typeof (query) === "string") {
|
||||
return { id: query };
|
||||
}
|
||||
return query;
|
||||
};
|
||||
|
||||
let getUser = async function (query, getDraftWhenNotFound = false) {
|
||||
query = formatQuery(query);
|
||||
|
||||
let id = "";
|
||||
// 根据id获取用户
|
||||
if (query.id) {
|
||||
id = query.id;
|
||||
} else if (query.uid) {
|
||||
// 根据uid检索id
|
||||
id = await Cache.get("uid-id", query.uid);
|
||||
if (!id) {
|
||||
// 如未查找到,则从注册uid中检索
|
||||
id = await Cache.get("regUid-id", query.uid)
|
||||
}
|
||||
} else if (query.token) {
|
||||
// 根据token检索id
|
||||
// 不常用,仅用在机器人绑定环节
|
||||
id = await Cache.get("token-id", query.token);
|
||||
}
|
||||
|
||||
// 已有实例优先使用已有的
|
||||
if (userMap[id]) {
|
||||
return userMap[id];
|
||||
}
|
||||
|
||||
// 如果是注册用户,则返回新instance
|
||||
if (NoteCookie[id]) {
|
||||
return new UserModel(id);
|
||||
}
|
||||
|
||||
// 如果允许返回Draft,则生成并返回
|
||||
if (getDraftWhenNotFound) {
|
||||
return getDraft(query);
|
||||
}
|
||||
|
||||
// 未查询到用户则返回false
|
||||
return false;
|
||||
}
|
||||
|
||||
let getDraft = function (query) {
|
||||
let id = '';
|
||||
if (query.id) {
|
||||
id = query.id;
|
||||
} else if (query.uid) {
|
||||
id = '_UID_' + query.uid;
|
||||
} else if (query.token) {
|
||||
id = "_CK_" + md5(query.token);
|
||||
}
|
||||
|
||||
let user = new UserModel(id);
|
||||
user.id = query.id;
|
||||
user.uid = query.uid;
|
||||
user.cookie = query.cookie;
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export default UserModel;
|
13
index.js
@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
export const rule = {
|
||||
character: {
|
||||
reg: "^#(.*)$",
|
||||
priority: 208,
|
||||
describe: "【#刻晴】角色详情",
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export { character } from "./apps/character.js";
|
||||
|
304
resources/character/character.css
Normal file
@ -0,0 +1,304 @@
|
||||
@font-face {
|
||||
font-family: "HWZhongSong";
|
||||
src: url("../../font/华文中宋.TTF");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "tttgbnumber";
|
||||
src: url("../../font/tttgbnumber.ttf");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "NZBZ";
|
||||
src: url("../font/NZBZ.ttf");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
font-family: "tttgbnumber";
|
||||
transform: scale(1.40);
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
background-color: #1234;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.container img.bg {
|
||||
width: 100%;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.info {
|
||||
position: absolute;
|
||||
background: rgba(0, 0, 0, .5);
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
box-shadow: 0 -5px 10px 0 #000;
|
||||
padding:35px 10px 10px 35px;
|
||||
}
|
||||
|
||||
.role_box {
|
||||
padding: 5px 10px;
|
||||
background-repeat: no-repeat;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.role_name {
|
||||
font-family: "NZBZ";
|
||||
font-size: 80px;
|
||||
letter-spacing: 5px;
|
||||
line-height: 90px;
|
||||
height: 100px;
|
||||
text-shadow: 0 0 1px #000, 3px 3px 6px #000;
|
||||
display: inline-block;
|
||||
position:absolute;
|
||||
top:-70px;
|
||||
left:20px;
|
||||
}
|
||||
|
||||
.role_name:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
background-image: linear-gradient(to right, rgba(255, 255, 255, 0.5) 20%, rgba(255, 255, 255, 0.5) 80%, rgba(255, 255, 255, 0) 100%);
|
||||
right: -50px;
|
||||
min-width: 360px;
|
||||
height: 1px;
|
||||
bottom: 0;
|
||||
left: -50px;
|
||||
opacity: 1;
|
||||
transition: width 0.3s 0.1s, opacity 0.3s 0.1s;
|
||||
}
|
||||
|
||||
|
||||
.weapon {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
padding: 3px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 5px;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
right:15px;
|
||||
top:45px;
|
||||
}
|
||||
|
||||
.lv {
|
||||
font-family: "tttgbnumber";
|
||||
font-size: 26px;
|
||||
margin: 10px 5px 2px 5px;
|
||||
}
|
||||
|
||||
|
||||
.weapon .num {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
font-size: 12px;
|
||||
border-radius: 5px;
|
||||
padding: 1px 5px;
|
||||
background-color: rgba(0, 0, 0, var(--bg-opacity));
|
||||
--bg-opacity: 0.75;
|
||||
}
|
||||
|
||||
.weapon_num {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
background: #000;
|
||||
border-radius: 5px;
|
||||
padding: 1px 3px;
|
||||
}
|
||||
|
||||
.weapon img {
|
||||
width: 100%;
|
||||
transform: scale(1.2, 1.2);
|
||||
}
|
||||
|
||||
.equiv .img_box {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border: 1px solid #d3bc8d;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.equiv img {
|
||||
width: 100%;
|
||||
transform: scale(1.2, 1.2);
|
||||
}
|
||||
|
||||
|
||||
.detail {
|
||||
margin-left: 5px;
|
||||
width: 300px;
|
||||
margin-top: 3px;
|
||||
padding: 5px 0;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.skill {
|
||||
margin-left: 5px;
|
||||
width: 300px;
|
||||
padding-bottom: 6px;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.text_box::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 0.5) 20%,
|
||||
rgba(255, 255, 255, 0.5) 80%,
|
||||
rgba(255, 255, 255, 0) 100%
|
||||
);
|
||||
width: 0%;
|
||||
height: 1px;
|
||||
top: 0;
|
||||
left: -15px;
|
||||
width: 300px;
|
||||
opacity: 1;
|
||||
transition: width 0.3s 0.1s, opacity 0.3s 0.1s;
|
||||
}
|
||||
|
||||
.text_box::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 0.5) 20%,
|
||||
rgba(255, 255, 255, 0.5) 80%,
|
||||
rgba(255, 255, 255, 0) 100%
|
||||
);
|
||||
width: 0%;
|
||||
height: 1px;
|
||||
bottom: 0;
|
||||
left: -15px;
|
||||
width: 300px;
|
||||
opacity: 1;
|
||||
transition: width 0.3s 0.1s, opacity 0.3s 0.1s;
|
||||
}
|
||||
|
||||
.detail p,
|
||||
.skill p {
|
||||
margin-right: 4px;
|
||||
line-height: 16px;
|
||||
width: 90px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.no_skill {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.star {
|
||||
width: 16px;
|
||||
vertical-align: -2px;
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
.equiv {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/*margin-bottom: 5px;*/
|
||||
}
|
||||
|
||||
.equiv {
|
||||
margin: 0 10px 8px 10px;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
padding: 3px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 5px;
|
||||
height: 52px;
|
||||
width: 52px;
|
||||
position: relative;
|
||||
display:flex;
|
||||
}
|
||||
|
||||
.equiv .num {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
font-size: 12px;
|
||||
/*background: rgba(0,0,0,.6);*/
|
||||
border-radius: 5px;
|
||||
padding: 1px 5px;
|
||||
background-color: rgba(0, 0, 0, var(--bg-opacity));
|
||||
--bg-opacity: 0.75;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
|
||||
.equiv .img_box {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border: 1px solid #d3bc8d;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.equiv img {
|
||||
width: 100%;
|
||||
transform: scale(1.2, 1.2);
|
||||
}
|
||||
|
||||
.equiv_info {
|
||||
display: inline-block;
|
||||
font-size: 15px;
|
||||
padding: 5px 5px 1px 7px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), rgba(114, 102, 104, 0.3);
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.equiv_info .text {
|
||||
margin-bottom: 5px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
55
resources/character/character.html
Normal file
@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
|
||||
<link rel="shortcut icon" href="#"/>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{{_res_path}}/MiaoPlugin/character/character.css?v=1.0"/>
|
||||
<link rel="preload" href="{{_res_path}}/font/tttgbnumber.ttf" as="font">
|
||||
<link rel="preload" href="{{_res_path}}/font/华文中宋.TTF" as="font">
|
||||
<link rel="preload" href="{{_res_path}}/genshin/logo/bg/{{name}}1.png" as="image">
|
||||
<link rel="preload" href="{{_res_path}}/genshin/logo/bg/{{name}}2.png" as="image">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container" id="container">
|
||||
<div class="info">
|
||||
<div class="role_name">
|
||||
{{"神里凌华"}}
|
||||
</div>
|
||||
<div class="lv">ID:{{uid}} Lv.{{level}} ❤{{fetter}}</div>
|
||||
|
||||
<div class="weapon">
|
||||
<div class="img_box">
|
||||
<img src="{{_res_path}}/genshin/logo/weapon/{{weapon.name}}.png"/>
|
||||
</div>
|
||||
<p class="num">lv{{weapon.level}}</p>
|
||||
<p class="weapon_num">{{weapon.affix_level}}</p>
|
||||
</div>
|
||||
|
||||
{{ if skill.a }}
|
||||
<div class="skill">
|
||||
<p> 爆发:<span>{{ skill.q.level_current}}</span></p>
|
||||
<p> 战技:<span>{{ skill.e.level_current}}</span></p>
|
||||
<p> 普攻:<span>{{ skill.a.level_current}}</span></p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="equiv">
|
||||
<div class="item">
|
||||
<div class="img_box">
|
||||
<img src="{{_res_path}}/genshin/logo/{{reliquaries.type}}/{{reliquaries.name}}.png"/>
|
||||
</div>
|
||||
<p class="num">+{{reliquaries.level}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="equiv_info">
|
||||
<div class="text">{{text1}}</div>
|
||||
<div class="text">{{text2}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<img src="{{_res_path}}/MiaoPlugin/characterImg/刻晴/1.jpg" class="bg"></div>
|
||||
</div>
|
||||
</body>
|
||||
<script type="text/javascript"></script>
|
||||
</html>
|
BIN
resources/character/星星.png
Normal file
After Width: | Height: | Size: 344 B |
BIN
resources/characterImg/七七/1.jpg
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
resources/characterImg/七七/10.jpg
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
resources/characterImg/七七/2.jpg
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
resources/characterImg/七七/3.jpg
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
resources/characterImg/七七/4.jpg
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
resources/characterImg/七七/5.jpg
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
resources/characterImg/七七/6.jpg
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
resources/characterImg/七七/7.jpg
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
resources/characterImg/七七/8.jpg
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
resources/characterImg/七七/9.jpg
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
resources/characterImg/丽莎/1.jpg
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
resources/characterImg/丽莎/2.jpg
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
resources/characterImg/九条裟罗/1.jpg
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
resources/characterImg/九条裟罗/2.jpg
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
resources/characterImg/云堇/1.jpg
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
resources/characterImg/云堇/10.jpg
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
resources/characterImg/云堇/11.jpg
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
resources/characterImg/云堇/12.jpg
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
resources/characterImg/云堇/13.jpg
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
resources/characterImg/云堇/14.jpg
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
resources/characterImg/云堇/15.jpg
Normal file
After Width: | Height: | Size: 83 KiB |
BIN
resources/characterImg/云堇/16.jpg
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
resources/characterImg/云堇/2.jpg
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
resources/characterImg/云堇/3.jpg
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
resources/characterImg/云堇/4.jpg
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
resources/characterImg/云堇/5.jpg
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
resources/characterImg/云堇/6.jpg
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
resources/characterImg/云堇/7.jpg
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
resources/characterImg/云堇/8.jpg
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
resources/characterImg/云堇/9.jpg
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
resources/characterImg/五郎/1.jpg
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
resources/characterImg/五郎/2.jpg
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
resources/characterImg/优菈/1.jpg
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
resources/characterImg/优菈/10.jpg
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
resources/characterImg/优菈/2.jpg
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
resources/characterImg/优菈/3.jpg
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
resources/characterImg/优菈/4.jpg
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
resources/characterImg/优菈/5.jpg
Normal file
After Width: | Height: | Size: 83 KiB |
BIN
resources/characterImg/优菈/6.jpg
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
resources/characterImg/优菈/7.jpg
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
resources/characterImg/优菈/8.jpg
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
resources/characterImg/优菈/9.jpg
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
resources/characterImg/八重神子/1.jpg
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
resources/characterImg/八重神子/10.jpg
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
resources/characterImg/八重神子/11.jpg
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
resources/characterImg/八重神子/12.jpg
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
resources/characterImg/八重神子/13.jpg
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
resources/characterImg/八重神子/14.jpg
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
resources/characterImg/八重神子/15.jpg
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
resources/characterImg/八重神子/16.jpg
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
resources/characterImg/八重神子/17.jpg
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
resources/characterImg/八重神子/18.jpg
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
resources/characterImg/八重神子/19.jpg
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
resources/characterImg/八重神子/2.jpg
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
resources/characterImg/八重神子/20.jpg
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
resources/characterImg/八重神子/21.jpg
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
resources/characterImg/八重神子/22.jpg
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
resources/characterImg/八重神子/23.jpg
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
resources/characterImg/八重神子/24.jpg
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
resources/characterImg/八重神子/3.jpg
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
resources/characterImg/八重神子/5.jpg
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
resources/characterImg/八重神子/6.jpg
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
resources/characterImg/八重神子/7.jpg
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
resources/characterImg/八重神子/8.jpg
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
resources/characterImg/八重神子/9.jpg
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
resources/characterImg/凝光/1.jpg
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
resources/characterImg/凝光/2.jpg
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
resources/characterImg/凝光/3.jpg
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
resources/characterImg/凯亚/1.jpg
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
resources/characterImg/凯亚/2.jpg
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
resources/characterImg/凯亚/3.jpg
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
resources/characterImg/凯亚/4.jpg
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
resources/characterImg/凯亚/5.jpg
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
resources/characterImg/刻晴/1.jpg
Normal file
After Width: | Height: | Size: 5.6 MiB |
BIN
resources/characterImg/刻晴/10.jpg
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
resources/characterImg/刻晴/2.jpg
Normal file
After Width: | Height: | Size: 8.0 MiB |
BIN
resources/characterImg/刻晴/3.jpg
Normal file
After Width: | Height: | Size: 48 KiB |