2
0
mirror of https://github.com/ctrlcvs/xiaoyao-cvs-plugin.git synced 2024-12-22 19:10:53 +08:00

初步兼容yunzaiV3版本

This commit is contained in:
ctrlcvs 2022-07-24 11:46:05 +08:00
parent cb9b06c6ef
commit a7294079e4
22 changed files with 2283 additions and 258 deletions

View File

@ -1,3 +1,6 @@
# 1.1.1
* 初步兼容V3版本
# 1.1.0
* 增加`#图鉴帮助`用于查看帮助命令
* 增加`#图鉴设置`用于设置图鉴相关功能

44
adapter/index.js Normal file
View File

@ -0,0 +1,44 @@
import plugin from '../../../lib/plugins/plugin.js'
import * as Atlas from '../apps/index.js'
import { render } from './render.js'
import { checkAuth, getMysApi } from './mys.js'
export class atlas extends plugin {
constructor () {
super({
name: 'xiaoyao-cvs-plugin',
desc: '图鉴插件',
event: 'message',
priority: 50,
rule: [{
reg: '.+',
fnc: 'dispatch'
}]
})
}
async dispatch (e) {
let msg = e.raw_message
e.checkAuth = async function (cfg) {
return await checkAuth(e, cfg)
}
e.getMysApi = async function (cfg) {
return await getMysApi(e, cfg)
}
msg = '#' + msg.replace('#', '')
for (let fn in Atlas.rule) {
let cfg = Atlas.rule[fn]
if (Atlas[fn] && new RegExp(cfg.reg).test(msg)) {
let ret = await Atlas[fn](e, {
render
})
if (ret === true) {
console.log('ret true')
return true
}
}
}
return false
}
}

114
adapter/mys.js Normal file
View File

@ -0,0 +1,114 @@
import MysInfo from '../../genshin/model/mys/mysInfo.js'
import lodash from 'lodash'
class User {
constructor (cfg) {
this.id = cfg.id
this.uid = cfg.uid
this.cookie = ''
}
// 保存用户配置
async setCfg (path, value) {
console.log(this.id)
let userCfg = await redis.get(`genshin:user-cfg:${this.id}`)
userCfg = userCfg ? JSON.parse(userCfg) : {}
lodash.set(userCfg, path, value)
await redis.set(`genshin:user-cfg:${this.id}`, JSON.stringify(userCfg))
}
/* 获取用户配置 */
async getCfg (path, defaultValue) {
let userCfg = await redis.get(`genshin:user-cfg:${this.id}`)
userCfg = userCfg ? JSON.parse(userCfg) : {}
return lodash.get(userCfg, path, defaultValue)
}
async getMysUser () {
return {
uid: this.uid
}
}
}
class Mys {
constructor (e, uid, MysApi) {
this.selfUser = new User({ id: e.user_id, uid })
this.targetUser = {
uid
}
this.e = e
this.MysApi = MysApi
e.targetUser = this.targetUser
e.selfUser = this.selfUser
}
async getData (api, data) {
if (!this.MysApi) {
return false
}
let ret = await MysInfo.get(this.e, api, data)
if (!ret) {
return false
}
return ret.data || ret
}
// 获取角色信息
async getCharacter () {
return await this.getData('character')
}
// 获取角色详情
async getAvatar (id) {
return await this.getData('detail', { avatar_id: id })
}
// 首页宝箱信息
async getIndex () {
return await this.getData('index')
}
// 获取深渊信息
async getSpiralAbyss (type = 1) {
return await this.getData('spiralAbyss', { schedule_type: type })
}
async getDetail (id) {
return await this.getData('detail', { avatar_id: id })
}
async getCompute (data) {
return await this.getData('compute', data)
}
async getAvatarSkill (id) {
return await this.getData('avatarSkill', { avatar_id: id })
}
get isSelfCookie () {
return true
}
}
export async function getMysApi (e, cfg) {
let { auth = 'all' } = cfg
let uid = await MysInfo.getUid(e)
if (!uid) return false
/* 检查user ck */
let isCookieUser = await MysInfo.checkUidBing(uid)
if (auth === 'cookie' && !isCookieUser) {
e.reply('尚未绑定Cookie...')
return false
}
let MysApi = await MysInfo.init(e, 'roleIndex')
if (!MysApi) {
return false
}
return new Mys(e, uid, MysApi)
}
export async function checkAuth (e, cfg) {
return new User({ id: e.user_id })
}

21
adapter/render.js Normal file
View File

@ -0,0 +1,21 @@
import lodash from 'lodash'
import Data from '../components/Data.js'
import puppeteer from '../../../lib/puppeteer/puppeteer.js'
const plugin = 'xiaoyao-cvs-plugin'
const _path = process.cwd()
export async function render (app = '', tpl = '', data = {}, imgType = 'jpeg') {
// 在data中保存plugin信息
data._plugin = plugin
if (lodash.isUndefined(data._res_path)) {
data._res_path = `../../../../../plugins/${plugin}/resources/`
}
Data.createDir(_path + '/data/', `html/${plugin}/${app}/${tpl}`)
data.saveId = data.saveId || data.save_id || tpl
data.tplFile = `./plugins/${plugin}/resources/${app}/${tpl}.html`
data.pluResPath = data._res_path
return await puppeteer.screenshot(`${plugin}/${app}/${tpl}`, data)
}

View File

@ -5,24 +5,20 @@ import fetch from "node-fetch";
import Common from "../components/Common.js";
import fs from "fs";
import format from "date-format";
import puppeteer from "puppeteer";
import { MysUser, User } from "../../../lib/components/Models.js";
import common from "../../../lib/common.js";
import { isV3 } from '../components/Changelog.js'
import MysInfo from '../model/mys/mysInfo.js'
// import { MysUser } from "../../../lib/components/Models.js";
// import common from "../../../lib/common.js";
import lodash from "lodash";
import { getPluginRender } from "../../../lib/render.js"
import { getPluginRender } from "./render.js";
import gsCfg from '../model/gsCfg.js'
import {
Cfg,
Data
} from "../components/index.js";
import moment from 'moment';
// import MysApi from "../components/MysApi.js"
import {
getUrl,
getHeaders
} from "../../../lib/app/mysApi.js";
import MysApi from "../model/mys/mysApi.js";
const _path = process.cwd();
let role_user = Data.readJSON(`${_path}/plugins/xiaoyao-cvs-plugin/resources/dailyNote/json/`, "dispatch_time");
@ -44,63 +40,64 @@ export async function Note(e, {
if (!Cfg.get("sys.Note")&&!poke) {
return false;
}
let cookie, uid;
if (NoteCookie[e.user_id]) {
cookie = NoteCookie[e.user_id].cookie;
uid = NoteCookie[e.user_id].uid;
} else if (BotConfig.dailyNote && BotConfig.dailyNote[e.user_id]) {
cookie = BotConfig.dailyNote[e.user_id].cookie;
uid = BotConfig.dailyNote[e.user_id].uid;
} else {
e.reply(`尚未配置,无法查询体力\n配置教程:${BotConfig.cookieDoc}`);
return true;
}
const response = await getDailyNote(uid, cookie);
if (!response.ok) {
e.reply("米游社接口错误");
return true;
}
const res = await response.json();
if (res.retcode == 10102) {
if (!e.openDailyNote) {
e.openDailyNote = true;
await openDailyNote(cookie); //自动开启
dailyNote(e);
let cookie, uid,res;
if(isV3){
res = await MysInfo.get(e, 'dailyNote')
if (!res || res.retcode !== 0) return false
}else{
if (NoteCookie[e.user_id]) {
cookie = NoteCookie[e.user_id].cookie;
uid = NoteCookie[e.user_id].uid;
} else if (BotConfig.dailyNote && BotConfig.dailyNote[e.user_id]) {
cookie = BotConfig.dailyNote[e.user_id].cookie;
uid = BotConfig.dailyNote[e.user_id].uid;
} else {
e.reply("请先开启实时便笺数据展示");
e.reply(`尚未配置,无法查询体力\n配置教程:${BotConfig.cookieDoc}`);
return true;
}
return true;
}
if (res.retcode != 0) {
if (res.message == "Please login") {
Bot.logger.mark(`体力cookie已失效`);
e.reply(`体力cookie已失效请重新配置\n注意退出米游社登录cookie将会失效`);
if (NoteCookie[e.user_id]) {
await MysUser.delNote(NoteCookie[e.user_id]);
delete NoteCookie[e.user_id];
saveJson();
const response = await getDailyNote(uid, cookie);
if (!response.ok) {
e.reply("米游社接口错误");
return true;
}
res = await response.json();
if (res.retcode == 10102) {
if (!e.openDailyNote) {
e.openDailyNote = true;
await openDailyNote(cookie); //自动开启
dailyNote(e);
} else {
e.reply("请先开启实时便笺数据展示");
}
} else {
e.reply(`体力查询错误:${res.message}`);
Bot.logger.mark(`体力查询错误:${JSON.stringify(res)}`);
return true;
}
if (res.retcode != 0) {
if (res.message == "Please login") {
Bot.logger.mark(`体力cookie已失效`);
e.reply(`体力cookie已失效请重新配置\n注意退出米游社登录cookie将会失效`);
if (NoteCookie[e.user_id]) {
// await MysUser.delNote(NoteCookie[e.user_id]);
delete NoteCookie[e.user_id];
saveJson();
}
} else {
e.reply(`体力查询错误:${res.message}`);
Bot.logger.mark(`体力查询错误:${JSON.stringify(res)}`);
}
return true;
}
//redis保存uid
redis.set(`genshin:uid:${e.user_id}`, uid, {
EX: 2592000
});
//更新
if (NoteCookie[e.user_id]) {
NoteCookie[e.user_id].maxTime = new Date().getTime() + res.data.resin_recovery_time * 1000;
saveJson();
}
return true;
}
//redis保存uid
redis.set(`genshin:uid:${e.user_id}`, uid, {
EX: 2592000
});
//更新
if (NoteCookie[e.user_id]) {
NoteCookie[e.user_id].maxTime = new Date().getTime() + res.data.resin_recovery_time * 1000;
saveJson();
}
let data = res.data;
@ -143,9 +140,9 @@ export async function Note(e, {
val.remained_time = new Date().getTime() + val.remained_time * 1000;
// console.log(val.remained_time)
var urls_avatar_side = val.avatar_side_icon.split("_");
let id = YunzaiApps.mysInfo.roleIdToName(urls_avatar_side[urls_avatar_side.length - 1].replace(
let id = gsCfg.roleIdToName(urls_avatar_side[urls_avatar_side.length - 1].replace(
/(.png|.jpg)/g, ""));
let name = YunzaiApps.mysInfo.roleIdToName(id, true);
let name = gsCfg.roleIdToName(id, true);
var time_cha = 20;
if (role_user["12"].includes(name)) {
time_cha = 15;
@ -287,14 +284,14 @@ async function dateTime_(time) {
time) < 19.5 ? "傍晚" : format("hh",
time) < 22 ? "晚上" : "深夜";
}
async function getDailyNote(uid, cookie) {
let mysApi = new MysApi(uid, cookie)
let {
url,
headers,
query,
body
} = getUrl("dailyNote", uid);
} = mysApi.getUrl("dailyNote", uid);
headers.Cookie = cookie;
const response = await fetch(url, {
method: "get",
@ -302,7 +299,6 @@ async function getDailyNote(uid, cookie) {
});
return response;
}
export async function saveJson() {
let path = "data/NoteCookie/NoteCookie.json";
fs.writeFileSync(path, JSON.stringify(NoteCookie, "", "\t"));
@ -337,7 +333,10 @@ export async function DailyNoteTask() {
};
e.reply = (msg) => {
common.relpyPrivate(user_id, msg);
Bot.pickUser(user_id*1).sendMsg(msg).catch((err) => {
logger.mark(err)
})
// common.relpyPrivate(user_id, msg);
};
//判断今天是否推送
if (cookie.maxTime && cookie.maxTime > 0 && new Date().getTime() > cookie.maxTime - (160 - sendResin) * 8 *

View File

@ -13,9 +13,7 @@ import {
Cfg
} from "../components/index.js";
import Common from "../components/Common.js";
import {
init
} from "../apps/xiaoyao_image.js"
const require = createRequire(
import.meta.url);
@ -156,14 +154,12 @@ export async function updateRes(e) {
}
let numRet = /(\d*) files changed,/.exec(stdout);
if (numRet && numRet[1]) {
init()
e.reply(`报告主人,更新成功,此次更新了${numRet[1]}个图片~`);
return true;
}
if (error) {
e.reply("更新失败!\nError code: " + error.code + "\n" + error.stack + "\n 请稍后重试。");
} else {
init()
e.reply("图片加量包更新成功~");
}
});
@ -176,7 +172,6 @@ export async function updateRes(e) {
if (error) {
e.reply("角色图片加量包安装失败!\nError code: " + error.code + "\n" + error.stack + "\n 请稍后重试。");
} else {
init()
e.reply("角色图片加量包安装成功!您后续也可以通过 #图鉴更新 命令来更新图像");
}
});

70
apps/index.js Normal file
View File

@ -0,0 +1,70 @@
import lodash from "lodash";
import {
AtlasAlias
} from "./xiaoyao_image.js";
import {
versionInfo,
help
} from "./help.js";
import {
Note,DailyNoteTask,
Note_appoint,pokeNote
} from "./Note.js";
import {
rule as adminRule,
updateRes,
sysCfg,
updateMiaoPlugin
} from "./admin.js";
import {
currentVersion
} from "../components/Changelog.js";
export {
updateRes,
updateMiaoPlugin,
versionInfo,
Note_appoint,pokeNote,
sysCfg,
help,DailyNoteTask,
AtlasAlias,
Note
};
let rule = {
versionInfo: {
reg: "^#图鉴版本$",
describe: "【#帮助】 图鉴版本介绍",
},
help: {
reg: "^#?(图鉴)?(命令|帮助|菜单|help|说明|功能|指令|使用说明)$",
describe: "查看插件的功能",
},
AtlasAlias: {
reg: "^(#(.*)|.*图鉴)$",
describe: "角色、食物、怪物、武器信息图鉴",
},
Note: {
reg: "^#*(体力|树脂|查询体力|便笺|便签)$",
describe: "体力",
},
Note_appoint: {
reg: "^#体力模板(设置(.*)|列表)$",
describe: "体力模板设置",
},
pokeNote: {
reg: "#poke#",
describe: "体力",
},
...adminRule
};
lodash.forEach(rule, (r) => {
r.priority = r.priority || 50;
r.prehash = true;
r.hashMark = true;
});
export {
rule
};

View File

@ -3,7 +3,7 @@ import fs from "fs";
import puppeteer from "puppeteer";
import lodash from "lodash";
import { Data } from "../../../lib/components/index.js";
import { Data } from "../components/index.js";
const _path = process.cwd();
//html模板
@ -33,6 +33,7 @@ let shoting = [];
* @param data 前端参数必传 data.save_id 用来区分模板
* @param imgType 图片类型 jpegpng清晰一点大小更大
*/
const plugin="xiaoyao-cvs-plugin"
async function render1(app = "", type = "", data = {}, imgType = "jpeg") {
if (lodash.isUndefined(data._res_path)) {
data._res_path = `../../../../../plugins/xiaoyao-cvs-plugin/resources/`;
@ -197,4 +198,29 @@ async function browserInit() {
}
}
export { render1, browserInit, renderNum };
function getPluginRender(plugin) {
return async function (app = "", type = "", data = {}, imgType = "jpeg") {
// 在data中保存plugin信息
data._plugin = plugin;
if (lodash.isUndefined(data._res_path)) {
data._res_path = `../../../../../plugins/${plugin}/resources/`;
}
if (lodash.isUndefined(data._sys_res_path)) {
data._sys_res_path = `../../../../../resources/`;
}
let tplKey = `${plugin}.${app}.${type}`;
let saveId = data.save_id;
let tplFile = _path + `/plugins/${plugin}/resources/${app}/${type}.html`;
Data.createDir(_path + `/data/`, `html/plugin_${plugin}/${app}/${type}`);
let savePath = _path + `/data/html/plugin_${plugin}/${app}/${type}/${saveId}.html`;
return await doRender(app, type, data, imgType, {
tplKey,
tplFile,
savePath,
saveId,
});
}
}
export { render1, browserInit, renderNum,getPluginRender };

View File

@ -8,10 +8,11 @@ import {
import Data from "../components/Data.js"
import path from 'path';
import fetch from "node-fetch";
import gsCfg from '../model/gsCfg.js'
const _path = process.cwd();
const __dirname = path.resolve();
const list = ["shiwu_tujian", "yuanmo_tujian", "mijin_tujian", "shengyiwu_tujian"]
const list = ["wuqi_tujian","shiwu_tujian", "yuanmo_tujian", "mijin_tujian", "shengyiwu_tujian"]
export async function AtlasAlias(e) {
if (!Cfg.get("Atlas.all")) {
return false;
@ -25,7 +26,7 @@ export async function AtlasAlias(e) {
}
if (await Atlas_list(e)) return true;
if (await roleInfo(e)) return true;
if (await weaponInfo(e)) return true;
// if (await weaponInfo(e)) return true;
// if (await foodInfo(e)) return true;
// if (await RelicsInfo(e)) return true;
// if (await monsterInfo(e)) return true;
@ -37,7 +38,7 @@ export async function AtlasAlias(e) {
export async function roleInfo(e) {
// let msg=e.msg.replace(/#|图鉴/g,"");
let msg = e.msg.replace(/#||信息|图鉴|命座|天赋|突破/g, "");
let id = YunzaiApps.mysInfo.roleIdToName(msg);
let id = gsCfg.roleNameToID(msg)
let name;
if (["10000005", "10000007", "20000000"].includes(id)) {
if (!["风主", "岩主", "雷主"].includes(msg)) {
@ -46,7 +47,7 @@ export async function roleInfo(e) {
}
name = msg;
} else {
name = YunzaiApps.mysInfo.roleIdToName(id, true);
name = gsCfg.roleIdToName(id, true);
if (!name) return false;
}
send_Msg(e, "juese_tujian", name)
@ -77,43 +78,43 @@ const send_Msg = function(e, type, name) {
e.reply(segment.image(`file:///${path}`));
return true;
}
let weapon = new Map();
let weaponFile = [];
await init();
export async function init(isUpdate = false) {
let weaponJson = JSON.parse(fs.readFileSync("./config/genshin/weapon.json", "utf8"));
for (let i in weaponJson) {
for (let val of weaponJson[i]) {
weapon.set(val, i);
}
}
let paths = "./plugins/xiaoyao-cvs-plugin/resources/xiaoyao-plus/wuqi_tujian";
if (!fs.existsSync(paths)) {
return true;
}
weaponFile = fs.readdirSync(paths);
for (let val of weaponFile) {
let name = val.replace(".png", "");
weapon.set(name, name);
}
}
// let weapon = new Map();
// let weaponFile = [];
// await init();
// export async function init(isUpdate = false) {
// let weaponJson = JSON.parse(fs.readFileSync("./config/genshin/weapon.json", "utf8"));
// for (let i in weaponJson) {
// for (let val of weaponJson[i]) {
// weapon.set(val, i);
// }
// }
// let paths = "./plugins/xiaoyao-cvs-plugin/resources/xiaoyao-plus/wuqi_tujian";
// if (!fs.existsSync(paths)) {
// return true;
// }
// weaponFile = fs.readdirSync(paths);
// for (let val of weaponFile) {
// let name = val.replace(".png", "");
// weapon.set(name, name);
// }
// }
export async function weaponInfo(e) {
let msg = e.msg || '';
if (e.atBot) {
msg = "#" + msg.replace("#", "");
}
if (!/(#*(.*)(信息|图鉴|突破|武器|材料)|#(.*))$/.test(msg)) return;
// export async function weaponInfo(e) {
// let msg = e.msg || '';
// if (e.atBot) {
// msg = "#" + msg.replace("#", "");
// }
// if (!/(#*(.*)(信息|图鉴|突破|武器|材料)|#(.*))$/.test(msg)) return;
let name = weapon.get(msg.replace(/#||信息|图鉴|突破|武器|材料/g, ""));
// let name = weapon.get(msg.replace(/#||信息|图鉴|突破|武器|材料/g, ""));
if (name) {
send_Msg(e, "wuqi_tujian", name)
return true;
}
// if (name) {
// send_Msg(e, "wuqi_tujian", name)
// return true;
// }
return false;
}
// return false;
// }
export async function Atlas_list(e) {
let list = Data.readJSON(`${_path}/plugins/xiaoyao-cvs-plugin/resources/Atlas_alias/`, "Atlas_list");
let name = e.msg.replace(/#|井/g, "")

View File

@ -1,46 +1,48 @@
import fs from "fs";
import lodash from "lodash";
import fs from 'fs'
import lodash from 'lodash'
const _path = process.cwd();
const _logPath = `${_path}/plugins/xiaoyao-cvs-plugin/CHANGELOG.md`;
const _path = process.cwd()
const _logPath = `${_path}/plugins/xiaoyao-cvs-plugin/CHANGELOG.md`
let logs = {};
let changelogs = [];
let currentVersion;
let versionCount = 4;
let logs = {}
let changelogs = []
let currentVersion
let versionCount = 4
let packageJson = JSON.parse(fs.readFileSync("package.json", "utf8"));
let packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'))
const getLine = function (line) {
line = line.replace(/(^\s*\*|\r)/g, '');
line = line.replace(/\s*`([^`]+`)/g, '<span class="cmd">$1');
line = line.replace(/`\s*/g, '</span>');
line = line.replace(/(^\s*\*|\r)/g, '')
line = line.replace(/\s*`([^`]+`)/g, '<span class="cmd">$1')
line = line.replace(/`\s*/g, '</span>')
line = line.replace(/\s*\*\*([^\*]+\*\*)/g, '<span class="strong">$1')
line = line.replace(/\*\*\s*/g, '</span>');
line = line.replace(/ⁿᵉʷ/g, '<span class="new"></span>');
return line;
line = line.replace(/\*\*\s*/g, '</span>')
line = line.replace(/ⁿᵉʷ/g, '<span class="new"></span>')
return line
}
try {
if (fs.existsSync(_logPath)) {
logs = fs.readFileSync(_logPath, "utf8") || "";
logs = logs.split("\n");
let temp = {}, lastLine = {};
logs = fs.readFileSync(_logPath, 'utf8') || ''
logs = logs.split('\n')
let temp = {};
let lastLine = {}
lodash.forEach(logs, (line) => {
if (versionCount <= -1) {
return false;
return false
}
let versionRet = /^#\s*([0-9\\.~\s]+?)\s*$/.exec(line);
let versionRet = /^#\s*([0-9\\.~\s]+?)\s*$/.exec(line)
if (versionRet && versionRet[1]) {
let v = versionRet[1].trim();
let v = versionRet[1].trim()
if (!currentVersion) {
currentVersion = v;
currentVersion = v
} else {
changelogs.push(temp);
changelogs.push(temp)
if (/0\s*$/.test(v) && versionCount > 0) {
versionCount = 0;
versionCount = 0
} else {
versionCount--;
versionCount--
}
}
@ -50,26 +52,25 @@ try {
}
} else {
if (!line.trim()) {
return;
return
}
if (/^\*/.test(line)) {
lastLine = {
title: getLine(line),
logs: []
}
temp.logs.push(lastLine);
} else if (/^\s{3,}\*/.test(line)) {
lastLine.logs.push(getLine(line));
temp.logs.push(lastLine)
} else if (/^\s{2,}\*/.test(line)) {
lastLine.logs.push(getLine(line))
}
}
});
})
}
} catch (e) {
// do nth
}
const yunzaiVersion = packageJson.version;
const yunzaiVersion = packageJson.version
const isV3 = yunzaiVersion[0] === '3'
export { currentVersion, yunzaiVersion, changelogs };
export { currentVersion, yunzaiVersion, isV3, changelogs }

View File

@ -29,7 +29,7 @@ export const render_path = async function (path, params, cfg,path_) {
let paths = path.split("/");
let { render, e } = cfg;
let _layout_path = process.cwd() + path_;
let base64 = await render(paths[0], paths[1], {
let base64 = await render1(paths[0], paths[1], {
...params,
_layout_path,
defaultLayout: _layout_path + "default.html",

View File

@ -1,6 +1,6 @@
import lodash from "lodash";
import fs from "fs";
import request from "request";
const _path = process.cwd()
let Data = {
@ -51,7 +51,18 @@ let Data = {
Data.createDir(path, true);
return fs.writeFileSync(`${path}/${file}`, JSON.stringify(data, null, space));
},
async importModule (path, file, rootPath = _path) {
if (!/\.js$/.test(file)) {
file = file + '.js'
}
// 检查并创建目录
Data.createDir(_path, path, true)
if (fs.existsSync(`${_path}/${path}/${file}`)) {
let data = await import(`file://${_path}/${path}/${file}`)
return data || {}
}
return {}
},
/*
* 返回一个从 target 中选中的属性的对象
*
@ -146,34 +157,11 @@ let Data = {
return Promise.all(ret);
},
async cacheFile(fileList, cacheRoot) {
let ret = {};
let cacheFn = async function (url) {
let path = Data.getUrlPath(url);
if (fs.existsSync(`${cacheRoot}/${path.path}/${path.filename}`)) {
console.log("已存在,跳过 " + path.path + "/" + path.filename);
ret[url] = `${path.path}/${path.filename}`;
return true;
}
Data.pathExists(cacheRoot, path.path);
await request(url).pipe(fs.createWriteStream(`${cacheRoot}/${path.path}/` + path.filename));
console.log("下载成功: " + path.path + "/" + path.filname);
ret[url] = `${path.path}/${path.filename}`;
return true;
};
await Data.asyncPool(10, fileList, cacheFn);
return ret;
},
sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}
export default Data;

2
config/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

3
defSet/mys/pubCk.yaml Normal file
View File

@ -0,0 +1,3 @@
# 米游社公共查询ck支持多个一行一个横杆空格开头
- ltoken=xxx; ltuid=xxx; cookie_token=xxx; account_id=xxx;
- ltoken=xxx; ltuid=xxx; cookie_token=xxx; account_id=xxx;

6
defSet/mys/set.yaml Normal file
View File

@ -0,0 +1,6 @@
# 公共查询是否使用用户ck 0-不使用 1-使用
allowUseCookie: 0
# 默认cookie帮助文档链接地址
cookieDoc: docs.qq.com/doc/DUWNVQVFTU3liTVlO
# 米游社原神签到定时任务Cron表达式默认00:02开始执行每10s签到一个
signTime: 0 2 0 * * ?

601
defSet/role/name.yaml Normal file
View File

@ -0,0 +1,601 @@
20000000:
- 主角
- 旅行者
- 卑鄙的外乡人
- 荣誉骑士
-
- 风主
- 岩主
- 雷主
- 草主
- 履刑者
- 抽卡不歪真君
10000002:
- 神里绫华
- Kamisato Ayaka
- Ayaka
- ayaka
- 神里
- 绫华
- 神里凌华
- 凌华
- 白鹭公主
- 神里大小姐
10000003:
-
- Jean
- jean
- 团长
- 代理团长
- 琴团长
- 蒲公英骑士
10000005:
-
- 男主
- 男主角
- 龙哥
- 空哥
10000006:
- 丽莎
- Lisa
- lisa
- 图书管理员
- 图书馆管理员
- 蔷薇魔女
10000007:
-
- 女主
- 女主角
-
-
- 黄毛阿姨
- 荧妹
10000014:
- 芭芭拉
- Barbara
- barbara
- 巴巴拉
- 拉粑粑
- 拉巴巴
- 内鬼
- 加湿器
- 闪耀偶像
- 偶像
10000015:
- 凯亚
- Kaeya
- kaeya
- 盖亚
- 凯子哥
- 凯鸭
- 矿工
- 矿工头子
- 骑兵队长
- 凯子
- 凝冰渡海真君
10000016:
- 迪卢克
- diluc
- Diluc
- 卢姥爷
- 姥爷
- 卢老爷
- 卢锅巴
- 正义人
- 正e人
- 正E人
- 卢本伟
- 暗夜英雄
- 卢卢伯爵
- 落魄了
- 落魄了家人们
10000020:
- 雷泽
- razor
- Razor
- 狼少年
- 狼崽子
- 狼崽
- 卢皮卡
- 小狼
- 小狼狗
10000021:
- 安柏
- Amber
- amber
- 安伯
- 兔兔伯爵
- 飞行冠军
- 侦查骑士
- 点火姬
- 点火机
- 打火机
- 打火姬
10000022:
- 温迪
- Venti
- venti
- 温蒂
- 风神
- 卖唱的
- 巴巴托斯
- 巴巴脱丝
- 芭芭托斯
- 芭芭脱丝
- 干点正事
- 不干正事
- 吟游诗人
- 诶嘿
- 唉嘿
- 摸鱼
10000023:
- 香菱
- Xiangling
- xiangling
- 香玲
- 锅巴
- 厨师
- 万民堂厨师
- 香师傅
10000024:
- 北斗
- Beidou
- beidou
- 大姐头
- 大姐
- 无冕的龙王
- 龙王
10000025:
- 行秋
- Xingqiu
- xingqiu
- 秋秋人
- 秋妹妹
- 书呆子
- 水神
- 飞云商会二少爷
10000026:
-
- Xiao
- xiao
- 杏仁豆腐
- 打桩机
- 插秧
- 三眼五显仙人
- 三眼五显真人
- 降魔大圣
- 护法夜叉
- 快乐风男
- 无聊
- 靖妖傩舞
- 矮子仙人
- 三点五尺仙人
- 跳跳虎
10000027:
- 凝光
- Ningguang
- ningguang
- 富婆
- 天权星
10000029:
- 可莉
- Klee
- klee
- 嘟嘟可
- 火花骑士
- 蹦蹦炸弹
- 炸鱼
- 放火烧山
- 放火烧山真君
- 蒙德最强战力
- 逃跑的太阳
- 啦啦啦
- 哒哒哒
- 炸弹人
- 禁闭室
10000030:
- 钟离
- Zhongli
- zhongli
- 摩拉克斯
- 岩王爷
- 岩神
- 钟师傅
- 天动万象
- 岩王帝君
- 未来可期
- 帝君
- 拒收病婿
10000031:
- 菲谢尔
- Fischl
- fischl
- 皇女
- 小艾米
- 小艾咪
- 奥兹
- 断罪皇女
- 中二病
- 中二少女
- 中二皇女
- 奥兹发射器
10000032:
- 班尼特
- Bennett
- bennett
- 点赞哥
- 点赞
- 倒霉少年
- 倒霉蛋
- 霹雳闪雷真君
- 班神
- 班爷
- 倒霉
- 火神
- 六星真神
10000033:
- 达达利亚
- Tartaglia
- tartaglia
- Childe
- childe
- Ajax
- ajax
- 达达鸭
- 达达利鸭
- 公子
- 玩具销售员
- 玩具推销员
- 钱包
- 鸭鸭
- 愚人众末席
10000034:
- 诺艾尔
- Noelle
- noelle
- 女仆
- 高达
- 岩王帝姬
10000035:
- 七七
- Qiqi
- qiqi
- 僵尸
- 肚饿真君
- 度厄真君
- 77
10000036:
- 重云
- Chongyun
- chongyun
- 纯阳之体
- 冰棍
10000037:
- 甘雨
- Ganyu
- ganyu
- 椰羊
- 椰奶
- 王小美
10000038:
- 阿贝多
- Albedo
- albedo
- 可莉哥哥
- 升降机
- 升降台
- 电梯
- 白垩之子
- 贝爷
- 白垩
- 阿贝少
- 花呗多
- 阿贝夕
- abd
- 阿师傅
10000039:
- 迪奥娜
- Diona
- diona
- 迪欧娜
- dio
- dio娜
- 冰猫
- 猫猫
- 猫娘
- 喵喵
- 调酒师
10000041:
- 莫娜
- Mona
- mona
- 穷鬼
- 穷光蛋
-
- 莫纳
- 占星术士
- 占星师
- 讨龙真君
- 半部讨龙真君
- 阿斯托洛吉斯·莫娜·梅姬斯图斯
10000042:
- 刻晴
- Keqing
- keqing
- 刻情
- 氪晴
- 刻师傅
- 刻师父
- 牛杂
- 牛杂师傅
- 斩尽牛杂
- 免疫
- 免疫免疫
- 屁斜剑法
- 玉衡星
- 阿晴
- 啊晴
10000043:
- 砂糖
- Sucrose
- sucrose
- 雷莹术士
- 雷萤术士
- 雷荧术士
10000044:
- 辛焱
- Xinyan
- xinyan
- 辛炎
- 黑妹
- 摇滚
10000045:
- 罗莎莉亚
- Rosaria
- rosaria
- 罗莎莉娅
- 白色史莱姆
- 白史莱姆
- 修女
- 罗莎利亚
- 罗莎利娅
- 罗沙莉亚
- 罗沙莉娅
- 罗沙利亚
- 罗沙利娅
- 萝莎莉亚
- 萝莎莉娅
- 萝莎利亚
- 萝莎利娅
- 萝沙莉亚
- 萝沙莉娅
- 萝沙利亚
- 萝沙利娅
10000046:
- 胡桃
- Hu Tao
- hu tao
- HuTao
- hutao
- Hutao
- 胡淘
- 往生堂堂主
- 火化
- 抬棺的
- 蝴蝶
- 核桃
- 堂主
- 胡堂主
- 雪霁梅香
10000047:
- 枫原万叶
- Kaedehara Kazuha
- Kazuha
- kazuha
- 万叶
- 叶天帝
- 天帝
- 叶师傅
10000048:
- 烟绯
- Yanfei
- yanfei
- 烟老师
- 律师
- 罗翔
10000049:
- 宵宫
- Yoimiya
- yoimiya
- 霄宫
- 烟花
- 肖宫
- 肖工
- 绷带女孩
10000050:
- 托马
- Thoma
- thoma
- 家政官
- 太郎丸
- 地头蛇
- 男仆
- 拖马
10000051:
- 优菈
- Eula
- eula
- 优拉
- 尤拉
- 尤菈
- 浪花骑士
- 记仇
- 劳伦斯
10000052:
- 雷电将军
- Raiden Shogun
- Raiden
- raiden
- 雷神
- 将军
- 雷军
- 巴尔
- 阿影
-
- 巴尔泽布
- 煮饭婆
- 奶香一刀
- 无想一刀
- 宅女
10000053:
- 早柚
- Sayu
- sayu
- 小狸猫
- 狸猫
- 忍者
10000054:
- 珊瑚宫心海
- Sangonomiya Kokomi
- Kokomi
- kokomi
- 心海
- 军师
- 珊瑚宫
- 书记
- 观赏鱼
- 水母
-
- 美人鱼
10000055:
- 五郎
- Gorou
- gorou
- 柴犬
- 土狗
- 希娜
- 希娜小姐
10000056:
- 九条裟罗
- Kujou Sara
- Sara
- sara
- 九条
- 九条沙罗
- 裟罗
- 沙罗
- 天狗
10000057:
- 荒泷一斗
- Arataki Itto
- Itto
- itto
- 荒龙一斗
- 荒泷天下第一斗
- 一斗
- 一抖
- 荒泷
- 1斗
- 牛牛
- 斗子哥
- 牛子哥
- 牛子
- 孩子王
- 斗虫
- 巧乐兹
- 放牛的
10000058:
- 八重神子
- Yae Miko
- Miko
- miko
- 八重
- 神子
- 狐狸
- 想得美哦
- 巫女
- 屑狐狸
- 骚狐狸
- 八重宫司
- 婶子
- 小八
10000059:
- 鹿野院平藏
- shikanoin heizou
- Heizou
- heizou
- heizo
- 鹿野苑
- 鹿野院
- 平藏
- 鹿野苑平藏
- 鹿野
- 小鹿
10000060:
- 夜兰
- Yelan
- yelan
- 夜阑
- 叶澜
- 腋兰
- 夜天后
10000062:
- 埃洛伊
- Aloy
- aloy
10000063:
- 申鹤
- Shenhe
- shenhe
- 神鹤
- 小姨
- 小姨子
- 审鹤
10000064:
- 云堇
- Yun Jin
- yunjin
- yun jin
- 云瑾
- 云先生
- 云锦
- 神女劈观
10000065:
- 久岐忍
- Kuki Shinobu
- Kuki
- kuki
- Shinobu
- shinobu
- 97忍
- 小忍
- 久歧忍
- 97
- 茄忍
- 阿忍
- 忍姐
10000066:
- 神里绫人
- Kamisato Ayato
- Ayato
- ayato
- 绫人
- 神里凌人
- 凌人
- 0人
- 神人
- 零人
- 大舅哥
10000067:
- 柯莱
- Collei
- collei
10000068:
- 多莉
- Dori
- dori
- 多利
10000069:
- 提纳里
- Tighnari
- tighnari

19
defSet/role/other.yaml Normal file
View File

@ -0,0 +1,19 @@
# 角色名称缩短
sortName:
达达利亚: 公子
神里绫华: 绫华
神里绫人: 绫人
枫原万叶: 万叶
雷电将军: 雷神
珊瑚宫心海: 心海
荒泷一斗: 一斗
八重神子: 八重
九条裟罗: 九条
罗莎莉亚: 罗莎
鹿野院平藏: 平藏
costumes:
- 海风之梦
- 闪耀协奏
- 纱中幽兰
- 霓裾翩跹

104
index.js
View File

@ -1,84 +1,26 @@
import lodash from "lodash";
import {
AtlasAlias
} from "./apps/xiaoyao_image.js";
import {
versionInfo,
help
} from "./apps/help.js";
// 适配V3 Yunzai将index.js移至app/index.js
import { currentVersion, isV3 } from './components/Changelog.js'
import Data from './components/Data.js'
import common from "../../lib/common.js";
import {
Note,DailyNoteTask,
Note_appoint,pokeNote
} from "./apps/Note.js";
import {
rule as adminRule,
updateRes,
sysCfg,
updateMiaoPlugin
} from "./apps/admin.js";
import {
currentVersion
} from "./components/Changelog.js";
export {
updateRes,
updateMiaoPlugin,
versionInfo,
Note_appoint,pokeNote,
sysCfg,
help,DailyNoteTask,
AtlasAlias,
Note
};
export * from './apps/index.js'
let index = { atlas: {} }
if (isV3) {
index = await Data.importModule('/plugins/xiaoyao-cvs-plugin/adapter', 'index.js')
console.log(index)
}
export const atlas = index.atlas || {}
let rule = {
versionInfo: {
reg: "^#图鉴版本$",
describe: "【#帮助】 图鉴版本介绍",
},
help: {
reg: "^#?(图鉴)?(命令|帮助|菜单|help|说明|功能|指令|使用说明)$",
describe: "查看插件的功能",
},
AtlasAlias: {
reg: "^(#(.*)|.*图鉴)$",
describe: "角色、食物、怪物、武器信息图鉴",
},
Note: {
reg: "^#*(体力|树脂|查询体力|便笺|便签)$",
describe: "体力",
},
Note_appoint: {
reg: "^#体力模板(设置(.*)|列表)$",
describe: "体力模板设置",
},
pokeNote: {
reg: "#poke#",
describe: "体力",
},
...adminRule
};
console.log(`图鉴插件${currentVersion}初始化~`)
lodash.forEach(rule, (r) => {
r.priority = r.priority || 50;
r.prehash = true;
r.hashMark = true;
});
export {
rule
};
console.log(`图鉴插件${currentVersion}初始化~`);
setTimeout(async function() {
let msgStr = await redis.get("xiaoyao:restart-msg");
if (msgStr) {
let msg = JSON.parse(msgStr);
await common.relpyPrivate(msg.qq, msg.msg);
await redis.del("xiaoyao:restart-msg");
let msgs = [`当前版本: ${currentVersion}`, `您可使用 #图鉴版本 命令查看更新信息`];
await common.relpyPrivate(msg.qq, msgs.join("\n"));
}
}, 1000);
setTimeout(async function () {
let msgStr = await redis.get('xiaoyao:restart-msg')
let relpyPrivate = async function () {
}
if (msgStr) {
let msg = JSON.parse(msgStr)
await relpyPrivate(msg.qq, msg.msg)
await redis.del('xiaoyao:restart-msg')
let msgs = [`当前图鉴版本: ${currentVersion}`, '您可使用 #图鉴版本 命令查看更新信息']
await relpyPrivate(msg.qq, msgs.join('\n'))
}
}, 1000)

233
model/gsCfg.js Normal file
View File

@ -0,0 +1,233 @@
import YAML from 'yaml'
import chokidar from 'chokidar'
import fs from 'node:fs'
import { promisify } from 'node:util'
import lodash from 'lodash'
const plugin="xiaoyao-cvs-plugin"
/** 配置文件 */
class GsCfg {
constructor () {
/** 默认设置 */
this.defSetPath = `./plugins/${plugin}/defSet/`
this.defSet = {}
/** 用户设置 */
this.configPath = `./plugins/${plugin}/config/`
this.config = {}
/** 监听文件 */
this.watcher = { config: {}, defSet: {} }
}
/**
* @param app 功能
* @param name 配置文件名称
*/
getdefSet (app, name) {
return this.getYaml(app, name, 'defSet')
}
/** 用户配置 */
getConfig (app, name) {
let ignore = ['mys.pubCk', 'gacha.set']
if (ignore.includes(`${app}.${name}`)) {
return this.getYaml(app, name, 'config')
}
return { ...this.getdefSet(app, name), ...this.getYaml(app, name, 'config') }
}
/**
* 获取配置yaml
* @param app 功能
* @param name 名称
* @param type 默认跑配置-defSet用户配置-config
*/
getYaml (app, name, type) {
let file = this.getFilePath(app, name, type)
let key = `${app}.${name}`
if (this[type][key]) return this[type][key]
this[type][key] = YAML.parse(
fs.readFileSync(file, 'utf8')
)
this.watch(file, app, name, type)
return this[type][key]
}
getFilePath (app, name, type) {
if (type == 'defSet') return `${this.defSetPath}${app}/${name}.yaml`
else return `${this.configPath}${app}.${name}.yaml`
}
/** 监听配置文件 */
watch (file, app, name, type = 'defSet') {
let key = `${app}.${name}`
if (this.watcher[type][key]) return
const watcher = chokidar.watch(file)
watcher.on('change', path => {
delete this[type][key]
logger.mark(`[修改配置文件][${type}][${app}][${name}]`)
if (this[`change_${app}${name}`]) {
this[`change_${app}${name}`]()
}
})
this.watcher[type][key] = watcher
}
get element () {
return { ...this.getdefSet('element', 'role'), ...this.getdefSet('element', 'weapon') }
}
/** 读取用户绑定的ck */
async getBingCk () {
let ck = {}
let ckQQ = {}
let dir = './data/MysCookie/'
let files = fs.readdirSync(dir).filter(file => file.endsWith('.yaml'))
const readFile = promisify(fs.readFile)
let promises = []
files.forEach((v) => promises.push(readFile(`${dir}${v}`, 'utf8')))
const res = await Promise.all(promises)
res.forEach((v) => {
let tmp = YAML.parse(v)
lodash.forEach(tmp, (v, i) => {
ck[String(i)] = v
if (v.isMain && !ckQQ[String(v.qq)]) {
ckQQ[String(v.qq)] = v
}
})
})
return { ck, ckQQ }
}
getBingCkSingle (userId) {
let file = `./data/MysCookie/${userId}.yaml`
try {
let ck = fs.readFileSync(file, 'utf-8')
ck = YAML.parse(ck)
return ck
} catch (error) {
return {}
}
}
saveBingCk (userId, data) {
let file = `./data/MysCookie/${userId}.yaml`
if (lodash.isEmpty(data)) {
fs.existsSync(file) && fs.unlinkSync(file)
} else {
let yaml = YAML.stringify(data)
fs.writeFileSync(file, yaml, 'utf8')
}
}
/**
* 原神角色id转换角色名字
*/
roleIdToName (id) {
let name = this.getdefSet('role', 'name')
if (name[id]) {
return name[id][0]
}
return ''
}
/** 原神角色别名转id */
roleNameToID (keyword) {
if (!this.nameID) {
this.nameID = new Map()
let nameArr = this.getdefSet('role', 'name')
for (let i in nameArr) {
for (let val of nameArr[i]) {
this.nameID.set(val, i)
}
}
}
if (!isNaN(keyword)) keyword = Number(keyword)
let roelId = this.nameID.get(keyword)
return roelId || false
}
/**
* 原神角色武器长名称缩写
* @param name 名称
* @param isWeapon 是否武器
*/
shortName (name, isWeapon = false) {
let other = {}
if (isWeapon) {
other = this.getdefSet('weapon', 'other')
} else {
other = this.getdefSet('role', 'other')
}
return other.sortName[name] ?? name
}
/** 公共配置ck文件修改hook */
async change_myspubCk () {
let MysInfo = await import('./mys/mysInfo.js').default
await new MysInfo().addPubCk()
}
getGachaSet (groupId = '') {
let config = this.getYaml('gacha', 'set', 'config')
let def = config.default
if (config[groupId]) {
return { ...def, ...config[groupId] }
}
return def
}
getMsgUid (msg) {
let ret = /[1|2|5][0-9]{8}/g.exec(msg)
if (!ret) return false
return ret[0]
}
/**
* 获取消息内原神角色名称uid
* @param msg 判断消息
* @param filterMsg 过滤消息
* @return roleId 角色id
* @return name 角色名称
* @return alias 当前别名
* @return uid 游戏uid
*/
getRole (msg, filterMsg = '') {
let alias = msg.replace(/#|老婆|老公|[1|2|5][0-9]{8}/g, '').trim()
if (filterMsg) {
alias = alias.replace(new RegExp(filterMsg, 'g'), '').trim()
}
/** 判断是否命中别名 */
let roleId = this.roleNameToID(alias)
if (!roleId) return false
/** 获取uid */
let uid = this.getMsgUid(msg) || ''
return {
roleId,
uid,
alias,
name: this.roleIdToName(roleId)
}
}
}
export default new GsCfg()

219
model/mys/mysApi.js Normal file
View File

@ -0,0 +1,219 @@
import md5 from 'md5'
import lodash from 'lodash'
import fetch from 'node-fetch'
export default class MysApi {
/**
* @param uid 游戏uid
* @param cookie 米游社cookie
* @param option 其他参数
* @param option.log 是否显示日志
*/
constructor (uid, cookie, option = {}) {
this.uid = uid
this.cookie = cookie
this.server = this.getServer()
let op = {
log: true,
...option
}
this.option = op
}
getUrl (type, data = {}) {
let host, hostRecord
if (['cn_gf01', 'cn_qd01'].includes(this.server)) {
host = 'https://api-takumi.mihoyo.com/'
hostRecord = 'https://api-takumi-record.mihoyo.com/'
}
let urlMap = {
/** 首页宝箱 */
index: {
url: `${hostRecord}game_record/app/genshin/api/index`,
query: `role_id=${this.uid}&server=${this.server}`
},
/** 深渊 */
spiralAbyss: {
url: `${hostRecord}game_record/app/genshin/api/spiralAbyss`,
query: `role_id=${this.uid}&schedule_type=${data.schedule_type || 1}&server=${this.server}`
},
/** 角色详情 */
character: {
url: `${hostRecord}game_record/app/genshin/api/character`,
body: { role_id: this.uid, server: this.server }
},
/** 树脂 */
dailyNote: {
url: `${hostRecord}game_record/app/genshin/api/dailyNote`,
query: `role_id=${this.uid}&server=${this.server}`
},
/** 签到信息 */
bbs_sign_info: {
url: `${host}event/bbs_sign_reward/info`,
query: `act_id=e202009291139501&region=${this.server}&uid=${this.uid}`,
sign: true
},
/** 签到奖励 */
bbs_sign_home: {
url: `${host}event/bbs_sign_reward/home`,
query: `act_id=e202009291139501&region=${this.server}&uid=${this.uid}`,
sign: true
},
/** 签到 */
bbs_sign: {
url: `${host}event/bbs_sign_reward/sign`,
body: { act_id: 'e202009291139501', region: this.server, uid: this.uid },
sign: true
},
/** 详情 */
detail: {
url: `${host}event/e20200928calculate/v1/sync/avatar/detail`,
query: `uid=${this.uid}&region=${this.server}&avatar_id=${data.avatar_id}`
},
/** 札记 */
ys_ledger: {
url: 'https://hk4e-api.mihoyo.com/event/ys_ledger/monthInfo',
query: `month=${data.month}&bind_uid=${this.uid}&bind_region=${this.server}`
},
/** 养成计算器 */
compute: {
url: `${host}event/e20200928calculate/v2/compute`,
body: data
},
/** 角色技能 */
avatarSkill: {
url: `${host}event/e20200928calculate/v1/avatarSkill/list`,
query: `avatar_id=${data.avatar_id}`
}
}
if (!urlMap[type]) return false
let { url, query = '', body = '', sign = '' } = urlMap[type]
if (query) url += `?${query}`
if (body) body = JSON.stringify(body)
let headers = this.getHeaders(query, body, sign)
return { url, headers, body }
}
getServer () {
let uid = this.uid
switch (String(uid)[0]) {
case '1':
case '2':
return 'cn_gf01' // 官服
case '5':
return 'cn_qd01' // B服
}
return 'cn_gf01'
}
async getData (type, data = {}, isForce = true) {
let { url, headers, body } = this.getUrl(type, data)
if (!url) return false
let cahce = await redis.get(`Yz:genshin:mys:cache:${type}:${this.uid}`)
if (cahce && !isForce) return JSON.parse(cahce)
headers.Cookie = this.cookie
let param = {
headers,
timeout: 10000
}
if (body) {
param.method = 'post'
param.body = body
} else {
param.method = 'get'
}
let response = {}
let start = Date.now()
try {
response = await fetch(url, param)
} catch (error) {
logger.error(error)
return false
}
if (!response.ok) {
logger.error(response)
return false
}
if (this.option.log) {
logger.mark(`[米游社接口][${type}][${this.uid}] ${Date.now() - start}ms`)
}
const res = await response.json()
if (!res) {
logger.mark('mys接口没有返回')
return false
}
if (res.retcode !== 0) {
logger.debug(`[米游社接口][请求参数] ${url} ${JSON.stringify(param)}`)
}
res.api = type
this.cache(res, type)
return res
}
getHeaders (query = '', body = '', sign = false) {
if (sign) {
return {
'x-rpc-app_version': '2.3.0',
'x-rpc-client_type': 5,
'x-rpc-device_id': this.getGuid(),
'User-Agent': ' miHoYoBBS/2.3.0',
DS: this.getDsSign()
}
}
return {
'x-rpc-app_version': '2.31.1',
'x-rpc-client_type': 5,
DS: this.getDs(query, body)
}
}
getDs (q = '', b = '') {
let n = ''
if (['cn_gf01', 'cn_qd01'].includes(this.server)) {
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 */
getDsSign () {
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}`
}
getGuid () {
function S4 () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
}
return (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4())
}
async cache (res, type) {
if (!res || res.retcode !== 0) return
redis.setEx(`Yz:genshin:mys:cache:${type}:${this.uid}`, 300, JSON.stringify(res))
}
}

650
model/mys/mysInfo.js Normal file
View File

@ -0,0 +1,650 @@
import MysApi from './mysApi.js'
import GsCfg from '../gsCfg.js'
import lodash from 'lodash'
import moment from 'moment'
/** 公共ck */
let pubCk = {}
/** 绑定ck */
let bingCkUid = {}
let bingCkQQ = {}
let bingCkLtuid = {}
export default class MysInfo {
/** redis key */
static keyPre = 'Yz:genshin:mys:'
static key = {
/** ck使用次数统计 */
count: `${MysInfo.keyPre}ck:count`,
/** ck使用详情 */
detail: `${MysInfo.keyPre}ck:detail`,
/** 单个ck使用次数 */
ckNum: `${MysInfo.keyPre}ckNum:`,
/** 已失效的ck使用详情 */
delDetail: `${MysInfo.keyPre}ck:delDetail`,
/** qq-uid */
qqUid: `${MysInfo.keyPre}qq-uid:`
}
static tips = '请先#绑定cookie\n发送【体力帮助】查看配置教程'
constructor (e) {
if (e) {
this.e = e
this.userId = String(e.user_id)
}
/** 当前查询原神uid */
this.uid = ''
/** 当前ck信息 */
this.ckInfo = {
ck: '',
uid: '',
qq: '',
ltuid: '',
type: ''
}
this.auth = ['dailyNote', 'bbs_sign_info', 'bbs_sign_home', 'bbs_sign']
}
static async init (e, api) {
let mysInfo = new MysInfo(e)
/** 检查时间 */
if (!mysInfo.checkTime()) return false
/** 初始化绑定ck */
await mysInfo.initBingCk()
/** 初始化公共ck */
await mysInfo.initPubCk()
if (mysInfo.checkAuth(api)) {
/** 获取ck绑定uid */
mysInfo.uid = (await MysInfo.getSelfUid(e)).uid
} else {
/** 获取uid */
mysInfo.uid = await MysInfo.getUid(e)
}
if (!mysInfo.uid) return false
mysInfo.e.uid = mysInfo.uid
/** 获取ck */
await mysInfo.getCookie()
/** 判断回复 */
await mysInfo.checkReply()
return mysInfo
}
/** 获取uid */
static async getUid (e) {
if (e.uid) return e.uid
let { msg = '', at = '' } = e
if (!msg) return false
let uid = false
/** at用户 */
if (at) {
uid = await redis.get(`${MysInfo.key.qqUid}${at}`)
if (uid) return String(uid)
e.reply('尚未绑定uid', false, { at })
return false
}
let matchUid = (msg = '') => {
let ret = /[1|2|5][0-9]{8}/g.exec(msg)
if (!ret) return false
return ret[0]
}
/** 命令消息携带 */
uid = matchUid(msg)
if (uid) return String(uid)
/** 绑定的uid */
uid = await redis.get(`${MysInfo.key.qqUid}${e.user_id}`)
if (uid) return String(uid)
/** 群名片 */
uid = matchUid(e.sender.card)
if (uid) return String(uid)
e.reply('请先#绑定uid', false, { at })
return false
}
/** 获取ck绑定uid */
static async getSelfUid (e) {
if (e.uid) return e.uid
let { msg = '', at = '' } = e
if (!msg) return false
/** at用户 */
if (at && (!bingCkQQ[at] || !bingCkQQ[at].uid)) {
e.reply('尚未绑定cookie', false, { at })
return false
}
if (!e.user_id || !bingCkQQ[e.user_id] || !bingCkQQ[e.user_id].uid) {
e.reply(MysInfo.tips, false, { at })
return false
}
return bingCkQQ[e.user_id]
}
/** 判断绑定ck才能查询 */
checkAuth (api) {
if (lodash.isObject(api)) {
for (let i in api) {
if (this.auth.includes(i)) {
return true
}
}
} else if (this.auth.includes(api)) {
return true
}
return false
}
/**
* @param api
* * `index` 米游社原神首页宝箱等数据
* * `spiralAbyss` 原神深渊
* * `character` 原神角色详情
* * `dailyNote` 原神树脂
* * `bbs_sign` 米游社原神签到
* * `detail` 详情
* * `ys_ledger` 札记
* * `compute` 养成计算器
* * `avatarSkill` 角色技能
*/
static async get (e, api, data = {}) {
let mysInfo = await MysInfo.init(e, api)
if (!mysInfo.uid || !mysInfo.ckInfo.ck) return false
e.uid = mysInfo.uid
let mysApi = new MysApi(mysInfo.uid, mysInfo.ckInfo.ck)
let res
if (lodash.isObject(api)) {
let all = []
lodash.forEach(api, (v, i) => {
all.push(mysApi.getData(i, v))
})
res = await Promise.all(all)
for (let i in res) {
res[i] = await mysInfo.checkCode(res[i], res[i].api)
if (res[i].retcode === 0) continue
break
}
} else {
res = await mysApi.getData(api, data)
if (!res) return false
res = await mysInfo.checkCode(res, api)
}
return res
}
async checkReply () {
if (!this.uid) {
this.e.reply('请先#绑定uid')
}
if (!this.ckInfo.ck) {
if (lodash.isEmpty(pubCk)) {
this.e.reply('请先配置公共查询ck')
} else {
this.e.reply('公共ck查询次数已用完暂无法查询新uid')
}
}
}
async getCookie () {
if (this.ckInfo.ck) return this.ckInfo.ck
// 使用用户uid绑定的ck
await this.getBingCK() ||
// 使用uid已查询的ck
await this.getCheckCK() ||
// 使用用户绑定的ck
await this.getBingCKqq() ||
// 使用公共ck
await this.getPublicCK()
return this.ckInfo.ck
}
async getBingCK () {
if (!bingCkUid[this.uid]) return false
this.isSelf = true
let ck = bingCkUid[this.uid]
this.ckInfo = ck
this.ckInfo.type = 'self'
logger.mark(`[米游社查询][uid${this.uid}]${logger.green(`[使用已绑定ck${ck.ltuid}]`)}`)
return ck.ck
}
async getCheckCK () {
let ltuid = await redis.zScore(MysInfo.key.detail, this.uid)
if (!ltuid) return false
this.ckInfo.ltuid = ltuid
this.ckInfo.type = 'public'
/** 使用用户绑定ck */
if (bingCkLtuid[ltuid]) {
logger.mark(`[米游社查询][uid${this.uid}]${logger.blue(`[已查询][使用用户ck${ltuid}]`)}`)
this.ckInfo = bingCkLtuid[ltuid]
this.ckInfo.type = 'self'
return this.ckInfo.ck
}
/** 公共ck */
if (pubCk[ltuid]) {
logger.mark(`[米游社查询][uid${this.uid}]${logger.cyan(`[已查询][使用公共ck${ltuid}]`)}`)
this.ckInfo.ck = pubCk[ltuid]
return this.ckInfo.ck
}
return false
}
/** 使用用户绑定的ck */
async getBingCKqq () {
/** 用户没有绑定ck */
if (!bingCkQQ[this.userId]) return false
let ck = bingCkQQ[this.userId]
/** 判断用户ck使用次数 */
let num = await redis.get(`${MysInfo.key.ckNum}${ck.ltuid}`)
if (num && num >= 27) {
logger.mark(`[米游社查询][uid${this.uid}] 绑定用户ck次数已用完`)
return
}
if (!num) num = 0
this.ckInfo = ck
this.ckInfo.type = 'bing'
/** 插入查询详情 */
await redis.zAdd(MysInfo.key.detail, { score: ck.ltuid, value: this.uid })
/** 获取ck查询详情 */
let count = await redis.zRangeByScore(MysInfo.key.detail, ck.ltuid, ck.ltuid)
/** 用户ck也配置公共ck */
if (pubCk[ck.ltuid]) {
/** 统计ck查询次数 */
redis.zAdd(MysInfo.key.count, { score: count.length || 1, value: String(ck.ltuid) })
}
this.expire(MysInfo.key.detail)
/** 插入单个查询次数 */
redis.setEx(`${MysInfo.key.ckNum}${ck.ltuid}`, this.getEnd(), String(count.length))
logger.mark(`[米游社查询][uid${this.uid}]${logger.blue(`[使用用户ck${ck.ltuid}]`)}[次数:${++num}次]`)
return ck.ck
}
async getPublicCK () {
if (lodash.isEmpty(pubCk)) {
logger.mark('请先配置公共查询ck')
return false
}
/** 获取使用次数最少的ck */
let list = await redis.zRangeByScore(MysInfo.key.count, 0, 27, true)
if (lodash.isEmpty(list)) {
logger.mark('公共查询ck已用完')
return false
}
let ltuid = list[0]
if (!pubCk[ltuid]) {
logger.mark(`公共查询ck错误[ltuid:${ltuid}]`)
await redis.zAdd(MysInfo.key.count, { score: 99, value: ltuid })
return false
}
this.ckInfo.ck = pubCk[ltuid]
this.ckInfo.ltuid = ltuid
this.ckInfo.type = 'public'
/** 非原子操作,可能存在误差 */
/** 插入查询详情 */
await redis.zAdd(MysInfo.key.detail, { score: ltuid, value: this.uid })
/** 获取ck查询详情 */
let count = await redis.zRangeByScore(MysInfo.key.detail, ltuid, ltuid)
/** 统计ck查询次数 */
redis.zAdd(MysInfo.key.count, { score: count.length, value: ltuid })
/** 插入单个查询次数 */
redis.setEx(`${MysInfo.key.ckNum}${ltuid}`, this.getEnd(), String(count.length))
this.expire(MysInfo.key.detail)
logger.mark(`[米游社查询][uid${this.uid}]${logger.yellow(`[使用公共ck${ltuid}][次数:${count.length}]`)}`)
return pubCk[ltuid]
}
/** 初始化公共查询ck */
async initPubCk () {
/** 没配置每次都会初始化 */
if (!lodash.isEmpty(pubCk)) return
let ckList = await redis.zRangeByScore(MysInfo.key.count, 0, 100)
await this.addPubCk(ckList)
/** 使用用户ck当公共查询 */
let set = GsCfg.getConfig('mys', 'set')
let userNum = 0
if (set.allowUseCookie == 1) {
lodash.forEach(bingCkUid, async v => {
if (pubCk[v.ltuid]) return
pubCk[v.ltuid] = v.ck
userNum++
/** 加入redis统计 */
if (!ckList.includes(v.ltuid)) {
await redis.zAdd(MysInfo.key.count, { score: 0, value: String(v.ltuid) })
}
})
}
this.expire(MysInfo.key.count)
if (userNum > 0) logger.mark(`加载用户ck${userNum}`)
}
/** 加入公共ck池 */
async addPubCk (ckList = '') {
let ckArr = GsCfg.getConfig('mys', 'pubCk')
if (!ckList) {
ckList = await redis.zRangeByScore(MysInfo.key.count, 0, 100)
}
let pubNum = 0
for (let v of ckArr) {
let [ltuid = ''] = v.match(/ltuid=(\w{0,9})/g)
if (!ltuid) return
ltuid = String(lodash.trim(ltuid, 'ltuid='))
if (isNaN(ltuid)) return
pubCk[ltuid] = v
pubNum++
/** 加入redis统计 */
if (!ckList.includes(ltuid)) {
await redis.zAdd(MysInfo.key.count, { score: 0, value: ltuid })
}
}
if (pubNum > 0) logger.mark(`加载公共ck${pubNum}`)
}
async initBingCk () {
if (!lodash.isEmpty(bingCkUid)) return
let res = await GsCfg.getBingCk()
bingCkUid = res.ck
bingCkQQ = res.ckQQ
bingCkLtuid = lodash.keyBy(bingCkUid, 'ltuid')
}
async checkCode (res, type) {
res.retcode = Number(res.retcode)
if (type == 'bbs_sign') {
if ([-5003].includes(res.retcode)) {
res.retcode = 0
}
}
switch (res.retcode) {
case 0:break
case -1:
case -100:
case 1001:
case 10001:
case 10103:
if (/(登录|login)/i.test(res.message)) {
if (this.ckInfo.uid) {
this.e.reply(`UID:${this.ckInfo.uid}米游社cookie已失效请重新绑定cookie`)
} else {
this.e.reply(`ltuid:${this.ckInfo.ltuid}米游社cookie已失效`)
}
await this.delCk()
} else {
this.e.reply(`米游社接口报错,暂时无法查询:${res.message}`)
}
break
case 1008:
this.e.reply('\n请先去米游社绑定角色', false, { at: this.userId })
break
case 10101:
this.disableToday()
this.e.reply('查询已达今日上限')
break
case 10102:
if (res.message == 'Data is not public for the user') {
this.e.reply(`\nUID:${this.ckInfo.uid},米游社数据未公开`, false, { at: this.userId })
} else {
this.e.reply(`uid:${this.uid},请先去米游社绑定角色`)
}
break
// 伙伴不存在~
case -1002:
if (res.api == 'detail') res.retcode = 0
break
default:
this.e.reply(`米游社接口报错,暂时无法查询:${res.message || 'error'}`)
break
}
if (res.retcode !== 0) {
logger.mark(`mys接口报错:${JSON.stringify(res)}uid${this.uid}`)
}
return res
}
/** 删除失效ck */
async delCk () {
let ltuid = this.ckInfo.ltuid
/** 记录公共ck失效 */
if (this.ckInfo.type == 'public') {
if (bingCkLtuid[ltuid]) {
this.ckInfo = bingCkLtuid[ltuid]
this.ckInfo.type = 'self'
} else {
logger.mark(`删除失效ck[ltuid:${ltuid}]`)
}
}
if (this.ckInfo.type == 'self' || this.ckInfo.type == 'bing') {
/** 获取用户绑定ck */
let ck = GsCfg.getBingCkSingle(this.userId)
let tmp = ck[this.ckInfo.uid]
if (tmp) {
ltuid = tmp.ltuid
logger.mark(`删除失效绑定ck[qq:${this.userId}]`)
/** 删除文件保存ck */
delete ck[this.ckInfo.uid]
GsCfg.saveBingCk(this.userId, ck)
this.redisDel(ltuid)
delete pubCk[ltuid]
delete bingCkUid[tmp.uid]
delete bingCkQQ[tmp.qq]
}
}
delete pubCk[ltuid]
await this.redisDel(ltuid)
}
async redisDel (ltuid) {
/** 统计次数设为超限 */
await redis.zRem(MysInfo.key.count, String(ltuid))
// await redis.setEx(`${MysInfo.key.ckNum}${ltuid}`, this.getEnd(), '99')
/** 将当前查询记录移入回收站 */
await this.detailDel(ltuid)
}
/** 将当前查询记录移入回收站 */
async detailDel (ltuid) {
let detail = await redis.zRangeByScore(MysInfo.key.detail, ltuid, ltuid)
if (!lodash.isEmpty(detail)) {
let delDetail = []
detail.forEach((v) => {
delDetail.push({ score: ltuid, value: String(v) })
})
await redis.zAdd(MysInfo.key.delDetail, delDetail)
this.expire(MysInfo.key.delDetail)
}
/** 删除当前ck查询记录 */
await redis.zRemRangeByScore(MysInfo.key.detail, ltuid, ltuid)
}
async disableToday () {
/** 统计次数设为超限 */
await redis.zAdd(MysInfo.key.count, { score: 99, value: String(this.ckInfo.ltuid) })
await redis.setEx(`${MysInfo.key.ckNum}${this.ckInfo.ltuid}`, this.getEnd(), '99')
}
async expire (key) {
return await redis.expire(key, this.getEnd())
}
getEnd () {
let end = moment().endOf('day').format('X')
return end - moment().format('X')
}
/** 处理用户绑定ck */
async addBingCk (ck) {
/** 加入缓存 */
bingCkUid[ck.uid] = ck
bingCkQQ[ck.qq] = ck
bingCkLtuid[ck.ltuid] = ck
let set = GsCfg.getConfig('mys', 'set')
/** qq-uid */
await redis.setEx(`${MysInfo.key.qqUid}${ck.qq}`, 3600 * 24 * 30, String(ck.uid))
/** 恢复回收站查询记录,会覆盖原来记录 */
let detail = await redis.zRangeByScore(MysInfo.key.delDetail, ck.ltuid, ck.ltuid)
if (!lodash.isEmpty(detail)) {
let delDetail = []
detail.forEach((v) => {
delDetail.push({ score: ck.ltuid, value: String(v) })
})
await redis.zAdd(MysInfo.key.detail, delDetail)
this.expire(MysInfo.key.detail)
}
/** 删除回收站记录 */
await redis.zRemRangeByScore(MysInfo.key.delDetail, ck.ltuid, ck.ltuid)
/** 获取ck查询详情 */
let count = await redis.zRangeByScore(MysInfo.key.detail, ck.ltuid, ck.ltuid)
/** 开启了用户ck查询 */
if (set.allowUseCookie == 1) {
pubCk[ck.ltuid] = ck
let ckList = await redis.zRangeByScore(MysInfo.key.count, 0, 100)
if (!ckList.includes(ck.ltuid)) {
await redis.zAdd(MysInfo.key.count, { score: count.length, value: String(ck.ltuid) })
}
}
}
async delBingCk (ck) {
delete bingCkUid[ck.uid]
delete bingCkQQ[ck.qq]
delete bingCkLtuid[ck.ltuid]
this.detailDel(ck.ltuid)
}
async resetCk () {
return await redis.del(MysInfo.key.count)
}
static async initCk () {
if (lodash.isEmpty(bingCkUid)) {
let mysInfo = new MysInfo()
await mysInfo.initBingCk()
}
}
static async getBingCkUid () {
await MysInfo.initCk()
return bingCkUid
}
/** 切换uid */
static toggleUid (qq, ck) {
bingCkQQ[qq] = ck
}
static async checkUidBing (uid) {
await MysInfo.initCk()
if (bingCkUid[uid]) return true
return false
}
/** 数据更新中,请稍后再试 */
checkTime () {
let hour = moment().hour()
let min = moment().minute()
let second = moment().second()
if (hour == 23 && min == 59 && second >= 58) {
this.e.reply('数据更新中,请稍后再试')
return false
}
if (hour == 0 && min == 0 && second <= 3) {
this.e.reply('数据更新中,请稍后再试')
return false
}
return true
}
}

View File

@ -0,0 +1,88 @@
{
"磐岩结绿": ["绿箭", "绿剑"],
"斫峰之刃": ["斫峰", "盾剑"],
"无工之剑": ["蜈蚣", "蜈蚣大剑", "无工大剑", "盾大剑", "无工"],
"贯虹之槊": ["贯虹", "岩枪", "盾枪"],
"赤角石溃杵": ["赤角", "石溃杵"],
"尘世之锁": ["尘世锁", "尘世", "盾书", "锁"],
"终末嗟叹之诗": ["终末", "终末弓", "叹气弓", "乐团弓"],
"松籁响起之时": ["松籁", "乐团大剑", "松剑"],
"苍古自由之誓": ["苍古", "乐团剑"],
"渔获": ["鱼叉"],
"衔珠海皇": ["海皇", "咸鱼剑", "咸鱼大剑"],
"匣里日月": ["日月"],
"匣里灭辰": ["灭辰"],
"匣里龙吟": ["龙吟"],
"天空之翼": ["天空弓"],
"天空之刃": ["天空剑"],
"天空之卷": ["天空书", "厕纸"],
"天空之脊": ["天空枪", "薄荷枪"],
"天空之傲": ["天空大剑"],
"四风原典": ["四风"],
"试作斩岩": ["斩岩"],
"试作星镰": ["星镰"],
"试作金珀": ["金珀"],
"试作古华": ["古华"],
"试作澹月": ["澹月"],
"千岩长枪": ["千岩枪"],
"千岩古剑": ["千岩剑", "千岩大剑"],
"暗巷闪光": ["暗巷剑"],
"暗巷猎手": ["暗巷弓"],
"阿莫斯之弓": ["阿莫斯", "ams", "痛苦弓"],
"雾切之回光": ["雾切"],
"飞雷之弦振": ["飞雷", "飞雷弓"],
"薙草之稻光": ["薙草", "稻光", "薙草稻光", "马尾枪", "马尾", "薙刀"],
"神乐之真意": ["神乐", "真意"],
"狼的末路": ["狼末"],
"护摩之杖": ["护摩", "护摩枪","护膜"],
"和璞鸢": ["鸟枪", "绿枪"],
"风鹰剑": ["风鹰"],
"冬极白星": ["冬极"],
"不灭月华": ["月华"],
"波乱月白经津": ["波乱", "月白", "波乱月白", "经津", "波波津"],
"若水": ["麒麟弓","Aqua","aqua"],
"笼钓瓶一心":["妖刀", "红刀", "笼钓瓶", "一心传名刀"],
"昭心": ["糟心"],
"幽夜华尔兹": ["幽夜", "幽夜弓", "华尔兹", "皇女弓"],
"雪葬的星银": ["雪葬", "星银", "雪葬星银", "雪山大剑"],
"喜多院十文字": ["喜多院", "十文字"],
"万国诸海图谱": ["万国", "万国诸海"],
"天目影打刀": ["天目刀", "天目"],
"破魔之弓": ["破魔弓"],
"曚云之月": ["曚云弓"],
"流月针": ["针"],
"流浪乐章": ["赌狗书", "赌狗乐章", "赌狗"],
"桂木斩长正": ["桂木", "斩长正"],
"腐殖之剑": ["腐殖", "腐殖剑"],
"风花之颂": ["风花弓"],
"证誓之明瞳": ["证誓", "明瞳", "证誓明瞳"],
"嘟嘟可故事集": ["嘟嘟可"],
"辰砂之纺锤": ["辰砂", "辰砂纺锤", "纺锤"],
"白辰之环": ["白辰", "白辰环"],
"决斗之枪": ["决斗枪", "决斗", "月卡枪"],
"螭骨剑": ["螭骨", "丈育剑", "离骨剑", "月卡大剑"],
"黑剑": ["月卡剑"],
"苍翠猎弓": ["绿弓", "月卡弓"],
"讨龙英杰谭": ["讨龙"],
"神射手之誓": ["脚气弓", "神射手"],
"黑缨枪": ["史莱姆枪"],
"贯月矢":["贯月矢","月矢"],
"竭泽":["竭泽"],
"猎人之径":["猎人之径"],
"森林王器":["森林王器","王器"],
"王下近侍":["王下近侍","近侍"],
"盈满之实":["盈满之实","盈满"],
"原木刀":["原木刀","木刀"]
}