插件化目录初版提交
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,33 +1,35 @@
|
|||||||
import Base from "./Base.js";
|
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 lodash from "lodash";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import Data from "../Data.js";
|
||||||
|
import request from "request";
|
||||||
|
|
||||||
|
|
||||||
let characterMap = {};
|
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 {
|
class Character extends Base {
|
||||||
|
constructor(name) {
|
||||||
constructor(name, meta) {
|
|
||||||
super();
|
super();
|
||||||
let key = "".split();
|
this.name = name;
|
||||||
this._meta = meta;
|
this.sName = this.name;
|
||||||
}
|
this.id = YunzaiApps.mysInfo['roleIdToName'](name);
|
||||||
get sortName() {
|
lodash.extend(this, getMetaData(name))
|
||||||
return characterAttr[this.name] || this.name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get id() {
|
get id() {
|
||||||
@ -38,23 +40,126 @@ class Character extends Base {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
Character.get = function (val) {
|
||||||
let name = YunzaiApps.mysInfo.roleIdToName(val);
|
let roleid = YunzaiApps.mysInfo['roleIdToName'](val);
|
||||||
|
let name = YunzaiApps.mysInfo['roleIdToName'](roleid, true);
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!characterMap[name]) {
|
if (!characterMap[name]) {
|
||||||
//characterMap[name] =
|
let character = new Character(name);
|
||||||
|
characterMap[name] = character;
|
||||||
}
|
}
|
||||||
|
return characterMap[name];
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Character;
|
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 |