diff --git a/CHANGELOG.md b/CHANGELOG.md index 83fdbf9..b0be465 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 1.1.2 +* 初步实现米游社签到米游币以及社区签到 +* 需要引入包文件`cnpm i promise-retry `跟`cnpm i superagent` +* V3版本引入文件`pnpm i promise-retry `跟`pnpm i superagent` +* 如果出现了安装其中一个包导致另外的包出问题的请群里询问 +* 具体获取cookie教程后续会完善 敬情期待~ +* 需要完整的cookie内容要包含`login_ticket`跟`login_uid`不然是无法实现米游社签到的 +* 自动签到逻辑目前还在梳理中..... + # 1.1.1 * 初步兼容V3版本 * 版本兼容问题V2报错的话请导入`yaml`包 指令 `cnpm i yaml` diff --git a/README.md b/README.md index 7358bb0..9c65481 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ #### 介绍 # xiaoyao-cvs-plugin -yunzai-bot扩展图鉴以及体力优化 +yunzai-bot扩展图鉴以及体力优化; + +项目仅供学习交流使用,严禁用于任何商业用途和非法行为 #### 介绍 -# xiaoyao-cvs-plugin - 介绍 原神图鉴插件。 @@ -31,13 +31,24 @@ git clone https://github.com/Ctrlcvs/xiaoyao-cvs-plugin.git ./plugins/xiaoyao-cv 1. 配合云崽使用, https://gitee.com/Le-niao/Yunzai-Bot 2. V3版本安装报错的话请用指令引入包 ``` -pnpm i date-format +pnpm i superagent +``` + +``` +pnpm i promise-retry ``` 3. V2版本安装报错指令引入包 ``` cnpm i yaml ``` +``` +cnpm i superagent +``` + +``` +cnpm i promise-retry +``` #### 命令说明 1. 发送 #图鉴更新 获取最新的图鉴记录。(必须) 2. 发送 #**图鉴 进行触发,例如发送 #刻晴图鉴,即可返回对应的图片信息。 diff --git a/apps/Note.js b/apps/Note.js index 745e24f..46b2292 100644 --- a/apps/Note.js +++ b/apps/Note.js @@ -26,7 +26,6 @@ let path_img = ["background_image", "/icon/bg"]; let tempDataUrl = `${_path}/plugins/xiaoyao-cvs-plugin/data/NoteTemp` let tempData = {}; init() - function init() { Data.createDir("", tempDataUrl, false); tempData = Data.readJSON(tempDataUrl, "tempData") diff --git a/apps/index.js b/apps/index.js index 80a8894..f5ba14c 100644 --- a/apps/index.js +++ b/apps/index.js @@ -19,15 +19,19 @@ import { import { currentVersion } from "../components/Changelog.js"; +import { + rule as signRule, + sign,mysSign +} from "./sign.js" export { updateRes, - updateMiaoPlugin, + updateMiaoPlugin,sign, versionInfo, Note_appoint,pokeNote, sysCfg, help,DailyNoteTask, AtlasAlias, - Note + Note,mysSign }; let rule = { @@ -55,7 +59,7 @@ let rule = { reg: "#poke#", describe: "体力", }, - + ...signRule, ...adminRule }; diff --git a/apps/sign.js b/apps/sign.js new file mode 100644 index 0000000..17dd056 --- /dev/null +++ b/apps/sign.js @@ -0,0 +1,181 @@ +import MihoYoApi from "../model/mys/mihoyo-api.js" +import utils from '../model/mys/utils.js'; +import promiseRetry from 'promise-retry'; +import { + Cfg, + Data +} from "../components/index.js"; +import moment from 'moment'; +import MysInfo from '../model/mys/mysInfo.js' +import { + isV3 +} from '../components/Changelog.js'; +import gsCfg from '../model/gsCfg.js' +import { fileFrom } from "node-fetch"; +export const rule = { + mysSign: { + reg: "^#*(米游社|mys|社区)(原神|崩坏3|崩坏2|未定事件簿|大别野|崩坏星穹铁道|绝区零|全部)签到$", + describe: "米游社米游币签到(理论上会签到全部所以区分开了)" + }, + sign: { + reg: "^#*(原神|崩坏3|崩坏2|未定事件簿|大别野|崩坏星穹铁道|绝区零)签到$", + describe: "米社规则签到" + } +}; + +const _path = process.cwd(); +let START = moment().unix(); +const TODAY_DATE = moment().format('YYYY-MM-DD'); +const RETRY_OPTIONS = { + retries: 3, + minTimeout: 5000, + maxTimeout: 10000 +}; +export async function sign(e) { + if (!(await cookie(e))) { + return true; + } + START = moment().unix(); + let miHoYoApi = new MihoYoApi(e); + let resultMessage=""; + let msg = e.msg.replace(/#|签到|井|米游社|mys|社区/g, ""); + let ForumData = await getDataList(msg); + for (let forum of ForumData) { + resultMessage += `**${forum.name}**\n` + try { + // 1 BBS Sign + let resObj = await promiseRetry((retry, number) => { + Bot.logger.info(`开始签到: [${forum.name}] 尝试次数: ${number}`); + return miHoYoApi.forumSign(forum.forumId).catch((e) => { + Bot.logger.error(`${forum.name} 签到失败: [${e.message}] 尝试次数: ${number}`); + return retry(e); + }); + }, RETRY_OPTIONS); + Bot.logger.info(`${forum.name} 签到结果: [${resObj.message}]`); + resultMessage += `签到: [${resObj.message}]\n`; + } catch (e) { + Bot.logger.error(`${forum.name} 签到失败 [${e.message}]`); + resultMessage += `签到失败: [${e.message}]\n`; + } + + await utils.randomSleepAsync(); + } + await replyMsg(e,resultMessage); + return true +} +export async function mysSign(e) { + if (!(await cookie(e))) { + return true; + } + START = moment().unix(); + let miHoYoApi = new MihoYoApi(e); + let resultMessage=""; + // Execute task + let msg = e.msg.replace(/#|签到|井|米游社|mys|社区/g, ""); + let ForumData = await getDataList(msg); + for (let forum of ForumData) { + resultMessage += `\n**${forum.name}**\n` + try { + // 2 BBS list post + let resObj = await promiseRetry((retry, number) => { + Bot.logger.info(`读取帖子列表: [${forum.name}] 尝试次数: ${number}`); + return miHoYoApi.forumPostList(forum.forumId).catch((e) => { + Bot.logger.error(`${forum.name} 读取帖子列表失败: [${e.message}] 尝试次数: ${number}`); + return retry(e); + }); + }, RETRY_OPTIONS); + Bot.logger.info(`${forum.name} 读取列表成功 [${resObj.message}],读取到 [${resObj.data.list.length}] 条记录`); + + let postList = resObj.data.list; + for (let post of postList) { + post = post.post; + // 2.1 BBS read post + let resObj = await promiseRetry((retry, number) => { + Bot.logger.info(`读取帖子: [${post.subject}] 尝试次数: ${number}`); + return miHoYoApi.forumPostDetail(post['post_id']).catch((e) => { + Bot.logger.error(`${forum.name} 读取帖子失败: [${e.message}] 尝试次数: ${number}`); + return retry(e); + }); + }, RETRY_OPTIONS); + Bot.logger.info(`${forum.name} [${post.subject}] 读取成功 [${resObj.message}]`); + await utils.randomSleepAsync(); + // 2.2 BBS vote post + resObj = await promiseRetry((retry, number) => { + Bot.logger.info(`点赞帖子: [${post.subject}] 尝试次数: ${number}`); + return miHoYoApi.forumPostVote(post['post_id']).catch((e) => { + Bot.logger.error(`${forum.name} 点赞帖子失败: [${e.message}] 尝试次数: ${number}`); + return retry(e); + }); + }, RETRY_OPTIONS); + + Bot.logger.info(`${forum.name} [${post.subject}] 点赞成功 [${resObj.message}]`); + await utils.randomSleepAsync(); + } + + // 2.3 BBS share post + let sharePost = postList[0].post; + resObj = await promiseRetry((retry, number) => { + Bot.logger.info(`分享帖子: [${sharePost.subject}] 尝试次数: ${number}`); + return miHoYoApi.forumPostShare(sharePost['post_id']).catch((e) => { + Bot.logger.error(`${forum.name} 分享帖子失败: [${e.message}] 尝试次数: ${number}`); + return retry(e); + }); + }, RETRY_OPTIONS); + } catch (e) { + Bot.logger.error(`${forum.name} 读帖点赞分享失败 [${e.message}]`); + resultMessage += `读帖点赞分享: 失败 [${e.message}]\n`; + } + resultMessage += `读帖点赞分享: 成功\n`; + await utils.randomSleepAsync(); + } + await replyMsg(e,resultMessage); + return true +} + +async function replyMsg(e,resultMessage){ + const END = moment().unix(); + Bot.logger.info(`运行结束, 用时 ${END - START} 秒`); + resultMessage += `\n用时 ${END - START} 秒`; + e.reply(resultMessage); +} + +async function getDataList(name){ + let ForumData = Data.readJSON(`${_path}/plugins/xiaoyao-cvs-plugin/defSet/json`, "mys") + + for(let item of ForumData){ + if(item.name=name){ //循环结束未找到的时候返回原数组签到全部 + return [item] + } + } + return ForumData; +} + +async function cookie(e) { + let cookie, uid; + let miHoYoApi = new MihoYoApi(e); + if (isV3) { + // console.log(e) + let skuid = await gsCfg.getBingCookie(e.user_id); + cookie = skuid.ck; + uid = skuid.item; + } 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; + } + } + e.uid = uid; + if (!cookie.includes("login_ticket")) { + e.reply("米游社登录cookie不完整,请前往米游社通行证处重新获取cookie~\ncookies必须包含login_ticket") + return false; + } + let flot = (await miHoYoApi.stoken(cookie, e)); + if (!flot) { + e.reply("登录失效请重新登录获取cookie发送机器人~") + return false; + } + return true; +} diff --git a/defSet/json/mys.json b/defSet/json/mys.json new file mode 100644 index 0000000..0981448 --- /dev/null +++ b/defSet/json/mys.json @@ -0,0 +1,44 @@ +[ + { + "id": "1", + "forumId": "1", + "name": "崩坏3", + "url": "https://bbs.mihoyo.com/bh3/" + }, + { + "id": "2", + "forumId": "26", + "name": "原神", + "url": "https://bbs.mihoyo.com/ys/" + }, + { + "id": "3", + "forumId": "30", + "name": "崩坏2", + "url": "https://bbs.mihoyo.com/bh2/" + }, + { + "id": "4", + "forumId": "37", + "name": "未定事件簿", + "url": "https://bbs.mihoyo.com/wd/" + }, + { + "id": "5", + "forumId": "34", + "name": "大别野", + "url": "https://bbs.mihoyo.com/dby/" + }, + { + "id": "6", + "forumId": "52", + "name": "崩坏星穹铁道", + "url": "https://bbs.mihoyo.com/sr/" + }, + { + "id": "7", + "forumId": "57", + "name": "绝区零", + "url": "https://bbs.mihoyo.com/zzz/" + } +] \ No newline at end of file diff --git a/model/gsCfg.js b/model/gsCfg.js index bd4aa20..cd6fe17 100644 --- a/model/gsCfg.js +++ b/model/gsCfg.js @@ -1,233 +1,262 @@ import YAML from 'yaml' import chokidar from 'chokidar' import fs from 'node:fs' -import { promisify } from 'node:util' +import { + promisify +} from 'node:util' import lodash from 'lodash' -const plugin="xiaoyao-cvs-plugin" +const plugin = "xiaoyao-cvs-plugin" /** 配置文件 */ class GsCfg { - constructor () { - /** 默认设置 */ - this.defSetPath = `./plugins/${plugin}/defSet/` - this.defSet = {} + constructor() { + /** 默认设置 */ + this.defSetPath = `./plugins/${plugin}/defSet/` + this.defSet = {} - /** 用户设置 */ - this.configPath = `./plugins/${plugin}/config/` - this.config = {} + /** 用户设置 */ + this.configPath = `./plugins/${plugin}/config/` + this.config = {} - /** 监听文件 */ - this.watcher = { config: {}, defSet: {} } - } + /** 监听文件 */ + this.watcher = { + config: {}, + defSet: {} + } + } - /** - * @param app 功能 - * @param name 配置文件名称 - */ - getdefSet (app, name) { - return this.getYaml(app, name, 'defSet') - } + /** + * @param app 功能 + * @param name 配置文件名称 + */ + getdefSet(app, name) { + return this.getYaml(app, name, 'defSet') + } - /** 用户配置 */ - getConfig (app, name) { - let ignore = ['mys.pubCk', 'gacha.set'] + /** 用户配置 */ + getConfig(app, name) { + let ignore = ['mys.pubCk', 'gacha.set'] - if (ignore.includes(`${app}.${name}`)) { - return this.getYaml(app, name, 'config') - } + if (ignore.includes(`${app}.${name}`)) { + return this.getYaml(app, name, 'config') + } - return { ...this.getdefSet(app, name), ...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}` + /** + * 获取配置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] + if (this[type][key]) return this[type][key] - this[type][key] = YAML.parse( - fs.readFileSync(file, 'utf8') - ) + this[type][key] = YAML.parse( + fs.readFileSync(file, 'utf8') + ) - this.watch(file, app, name, type) + this.watch(file, app, name, type) - return this[type][key] - } + return this[type][key] + } - getFilePath (app, name, type) { - if (type == 'defSet') return `${this.defSetPath}${app}/${name}.yaml` - else return `${this.configPath}${app}.${name}.yaml` - } + 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}` + /** 监听配置文件 */ + watch(file, app, name, type = 'defSet') { + let key = `${app}.${name}` - if (this.watcher[type][key]) return + 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}`]() - } - }) + 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 - } + this.watcher[type][key] = watcher + } - get element () { - return { ...this.getdefSet('element', 'role'), ...this.getdefSet('element', 'weapon') } - } + 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')) + /** 读取用户绑定的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) + const readFile = promisify(fs.readFile) - let promises = [] + let promises = [] - files.forEach((v) => promises.push(readFile(`${dir}${v}`, 'utf8'))) + files.forEach((v) => promises.push(readFile(`${dir}${v}`, 'utf8'))) - const res = await Promise.all(promises) + 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 - } - }) - }) + 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 } - } + 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 {} - } - } + 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 {} + } + } + getBingCookie(userId) { + let file = `./data/MysCookie/${userId}.yaml` + try { + let ck = fs.readFileSync(file, 'utf-8') + ck = YAML.parse(ck) + for(let item in ck){ + ck=ck[item].ck + return {ck,item}; + } + } 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') + } + } - 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] + } - /** - * 原神角色id转换角色名字 - */ - roleIdToName (id) { - let name = this.getdefSet('role', 'name') - if (name[id]) { - return name[id][0] - } + return '' + } - 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 + } - /** 原神角色别名转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 + } - /** - * 原神角色武器长名称缩写 - * @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() + } - /** 公共配置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 + } - 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] + } - 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() + } - /** - * 获取消息内原神角色名称,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) || '' - /** 判断是否命中别名 */ - let roleId = this.roleNameToID(alias) - if (!roleId) return false - /** 获取uid */ - let uid = this.getMsgUid(msg) || '' - - return { - roleId, - uid, - alias, - name: this.roleIdToName(roleId) - } - } + return { + roleId, + uid, + alias, + name: this.roleIdToName(roleId) + } + } } export default new GsCfg() diff --git a/model/mys/mihoyo-api.js b/model/mys/mihoyo-api.js new file mode 100644 index 0000000..517e40c --- /dev/null +++ b/model/mys/mihoyo-api.js @@ -0,0 +1,158 @@ +import utils from './utils.js'; +import md5 from 'md5'; +import _ from 'lodash'; +import superagent from 'superagent'; +import fs from "fs"; +import YAML from 'yaml' +import {Data} from "../../components/index.js"; +import fetch from "node-fetch" +const APP_VERSION = "2.2.0"; +const DEVICE_ID = utils.randomString(32).toUpperCase(); +const DEVICE_NAME = utils.randomString(_.random(1, 10)); +const _path = process.cwd(); +let YamlDataUrl = `${_path}/plugins/xiaoyao-cvs-plugin/data/yaml` +export default class MihoYoApi { + constructor(e) { + if (e) { + this.e = e + this.userId = String(e.user_id) + } + Data.createDir("", YamlDataUrl, false); + } + + async forumSign(forumId) { + const url = `https://api-takumi.mihoyo.com/apihub/sapi/signIn?gids=${forumId}`; + let res = await superagent.post(url).set(this._getHeader()).timeout(10000); + let resObj = JSON.parse(res.text); + // Bot.logger.mark(`ForumSign: ${res.text}`); + return resObj; + } + + async forumPostList(forumId) { + const url = + `https://api-takumi.mihoyo.com/post/api/getForumPostList?forum_id=${forumId}&is_good=false&is_hot=false&page_size=20&sort_type=1`; + + let res = await superagent.get(url).set(this._getHeader()).timeout(10000); + let resObj = JSON.parse(res.text); + // logger.mark(`ForumList: ${res.text}`) + return resObj; + } + + async forumPostDetail(postId) { + const url = `https://api-takumi.mihoyo.com/post/api/getPostFull?post_id=${postId}`; + + let res = await superagent.get(url).set(this._getHeader()).timeout(10000); + let resObj = JSON.parse(res.text); + // logger.mark(`ForumDetail: ${res.text}`) + return resObj; + } + + async forumPostShare(postId) { + const url = `https://api-takumi.mihoyo.com/apihub/api/getShareConf?entity_id=${postId}&entity_type=1`; + let res = await superagent.get(url).set(this._getHeader()).timeout(10000); + let resObj = JSON.parse(res.text); + // Bot.logger.mark(`ForumShare: ${res.text}`) + return resObj; + } + + async forumPostVote(postId) { + const url = `https://api-takumi.mihoyo.com/apihub/sapi/upvotePost`; + const upvotePostData = { + "post_id": postId, + "is_cancel": false + } + let res = await superagent.post(url).set(this._getHeader()).send(JSON.stringify(upvotePostData)); + let resObj = JSON.parse(res.text); + // Bot.logger.mark(`ForumVote: ${res.text}`) + return resObj; + } + async stoken(cookie, e) { + this.e=e; + if(this.getStoken(e.user_id)){ + return true; + } + const map = this.getCookieMap(cookie); + const loginTicket = map.get("login_ticket"); + const loginUid = map.get("login_uid"); + const url = "https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?login_ticket=" + + loginTicket + "&token_types=3&uid=" + loginUid; + fetch(url, { + "headers": { + "x-rpc-device_id": "zxcvbnmasadfghjk123456", + "Content-Type": "application/json;charset=UTF-8", + "x-rpc-client_type": "", + "x-rpc-app_version": "", + "DS": "", + "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/%s", + "Referer": "cors", + "Accept-Encoding": "gzip, deflate, br", + "x-rpc-channel": "appstore", + }, + "method": "GET" + }).then( + function(response) { + if (response.status !== 200) { + return false; + } + response.json().then(function(data) { + // console.log(data); + let datalist = { + stuid: map.get("account_id"), + stoken: data.data.list[0].token, + ltoken: data.data.list[1].token, + uid: e.uid + } + let yamlStr = YAML.stringify(datalist); + fs.writeFileSync(`${YamlDataUrl}/${e.user_id}.yaml`, yamlStr, 'utf8'); + return true; + }); + } + ).catch(function(err) { + return false; + }); + return true; + } + _getHeader() { + const randomStr = utils.randomString(6); + const timestamp = Math.floor(Date.now() / 1000) + let data=this.getStoken(this.e.user_id); + // console.log(data) + // iOS sign + let sign = md5(`salt=b253c83ab2609b1b600eddfe974df47b&t=${timestamp}&r=${randomStr}`); + let cookie =`stuid=${data.stuid};stoken=${data.stoken};ltoken=${data.ltoken};`; + return { + 'Cookie': cookie, + 'Content-Type': 'application/json', + 'User-Agent': 'Hyperion/67 CFNetwork/1128.0.1 Darwin/19.6.0', + 'Referer': 'https://app.mihoyo.com', + 'x-rpc-channel': 'appstore', + 'x-rpc-device_id': DEVICE_ID, + 'x-rpc-app_version': APP_VERSION, + 'x-rpc-device_model': 'iPhone11,8', + 'x-rpc-device_name': DEVICE_NAME, + 'x-rpc-client_type': '1', // 1 - iOS, 2 - Android, 4 - Web + 'DS': `${timestamp},${randomStr},${sign}` + // 'DS': `1602569298,k0xfEh,07f4545f5d88eac59cb1257aef74a570` + } + } + getCookieMap(cookie) { + let cookiePattern = /^(\S+)=(\S+)$/; + let cookieArray = cookie.split("; "); + let cookieMap = new Map(); + for (let item of cookieArray) { + let entry = cookiePattern.exec(item); + cookieMap.set(entry[1], entry[2]); + } + return cookieMap; + } + getStoken(userId) { + let file = `${YamlDataUrl}/${userId}.yaml` + try { + let ck = fs.readFileSync(file, 'utf-8') + ck = YAML.parse(ck) + return ck + } catch (error) { + return {} + } + } +} diff --git a/model/mys/utils.js b/model/mys/utils.js new file mode 100644 index 0000000..07eb7a1 --- /dev/null +++ b/model/mys/utils.js @@ -0,0 +1,29 @@ +import _ from 'lodash'; + +export async function sleepAsync(sleepms) { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, sleepms) + }); +} + + +export async function randomSleepAsync(){ + let sleep = 2 * 1000 + _.random(3 * 1000); + await sleepAsync(sleep); +} + +export function randomString(length){ + let randomStr = ''; + for (let i = 0; i < length; i++) { + randomStr += _.sample('abcdefghijklmnopqrstuvwxyz0123456789'); + } + return randomStr; +} + +export default { + sleepAsync, + randomSleepAsync, + randomString +}