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 gsCfg from '../gsCfg.js' import { isV3 } from '../../components/Changelog.js'; import fetch from "node-fetch" const APP_VERSION = "2.38.1"; const mhyVersion = "2.11.1"; const salt = "PVeGWIZACpxXZ1ibMVJPi9inCY4Nd4y2"; const salt2 = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v"; const saltWeb = "yUZ3s0Sna1IrSNfk29Vo6vRapdOyqyhB"; const oldsalt = "z8DRIUjNDT7IT5IZXvrUAxyupA1peND9"; const osSaltWeb=''; //os 浏览帖子需要用到的salt const DEVICE_ID = utils.randomString(32).toUpperCase(); const DEVICE_NAME = utils.randomString(_.random(1, 10)); const _path = process.cwd(); const YamlDataUrl = `${_path}/plugins/xiaoyao-cvs-plugin/data/yaml`; const web_api = `https://api-takumi.mihoyo.com` const os_web_api = `https://api-os-takumi.mihoyo.com` const os_hk4_api=`https://hk4e-api-os.hoyoverse.com`; const hk4_api = `https://hk4e-api.mihoyo.com`; const bbs_api = `https://bbs-api.mihoyo.com`; let HttpsProxyAgent = '' // 米游社的版块 const boards = { honkai3rd: { forumid: 1, key: 'honkai3rd', biz: 'bh3_cn', actid: 'e202207181446311', name: '崩坏3', url: "https://bbs.mihoyo.com/bh3/", getReferer() { return `https://webstatic.mihoyo.com/bh3/event/euthenia/index.html?bbs_presentation_style=fullscreen&bbs_game_role_required=${this.biz}&bbs_auth_required=true&act_id=${this.actid}&utm_source=bbs&utm_medium=mys&utm_campaign=icon` } }, genshin: { forumid: 26, key: 'genshin', biz: 'hk4e_cn', actid: 'e202009291139501', name: '原神', url: "https://bbs.mihoyo.com/ys/", getReferer() { return `https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?bbs_auth_required=true&act_id=${this.actid}&utm_source=bbs&utm_medium=mys&utm_campaign=icon` } }, honkai2: { forumid: 30, biz: 'bh2_cn', actid: 'e202203291431091', name: '崩坏2', url: "https://bbs.mihoyo.com/bh2/", getReferer() { return `https://webstatic.mihoyo.com/bbs/event/signin/bh2/index.html?bbs_auth_required=true&act_id=${this.actid}&bbs_presentation_style=fullscreen&utm_source=bbs&utm_medium=mys&utm_campaign=icon` } }, tears: { forumid: 37, biz: 'nxx_cn', name: '未定事件簿', actid: 'e202202251749321', url: "https://bbs.mihoyo.com/wd/", getReferer() { return `https://webstatic.mihoyo.com/bbs/event/signin/nxx/index.html?bbs_auth_required=true&bbs_presentation_style=fullscreen&act_id=${this.actid}` } }, /** 以下数据待定 由于并未有存在签到入口可能后续会开放*/ house: { forumid: 34, name: '大别野', url: "https://bbs.mihoyo.com/dby/" }, honkaisr: { forumid: 52, name: '崩坏星穹铁道', url: "https://bbs.mihoyo.com/sr/" }, jql: { forumid: 57, name: "绝区零", url: "https://bbs.mihoyo.com/zzz/" } } export default class MihoYoApi { constructor(e) { if (e) { this.e = e this.cookie = e.cookie this.userId = String(e.user_id) this.yuntoken = e.yuntoken this.devId = e.devId this.e.region=utils.getServer(e.uid) this.isOs= this.e.region>5 // //初始化配置文件 let data = this.getStoken(this.e.user_id); if (data) { this.cookies = `stuid=${data.stuid};stoken=${data.stoken};ltoken=${data.ltoken};`; this.e.cookies = this.cookies } } Data.createDir("", YamlDataUrl, false); } getbody(name) { for (let item in boards) { if (boards[item]?.name === name) { return boards[item] } } } async honkai3rdSignTask(name) { let kkbody = this.getbody(name); try { // 获取账号信息 const objData = await this.getUserInfo(kkbody) let data = objData.data if (data?.list?.length == 0 || !data?.list) { return { message: `未绑定${name}信息` } } let message = `\n${name}共计${data.list.length}个账号\n`; let upData = []; for (let item of data.list) { item.upName = name let objshuj = await this.isPostSign(kkbody, item.game_uid, item.region) item.total_sign_day = objshuj?.data?.total_sign_day if (objshuj?.data?.is_sign) { item.is_sign = true; // console.log(objshuj) message += `游戏id:${item.nickname}-${item.game_uid}:今日已签到~\n`; } else { objshuj = (await this.postSign(kkbody, item.game_uid, item.region)) if (objshuj?.data?.gt) { item.is_sign = false; message += `游戏id:${item.nickname}-${item.game_uid}:签到出现验证码~\n请晚点后重试,或者手动上米游社签到`; } else { item.total_sign_day++; item.is_sign = true; message += `游戏id:${item.nickname}-${item.game_uid}:${objshuj.message=="OK"?"签到成功":objshuj.message}\n` } } //获取签到信息和奖励信息 const SignInfo = await this.getSignInfo(kkbody) if (SignInfo) { let awards = SignInfo.data.awards[item.total_sign_day - 1]; item.awards = awards?.name + "*" + awards?.cnt } upData.push(item) await utils.randomSleepAsync(); } // 签到操作 return { message, upData } } catch (error) { Bot.logger.mark(`error.message`, error.message) } } async forumSign(forumId) { const url = `https://bbs-api.mihoyo.com/apihub/app/api/signIn`; this.forumId = forumId; let res = await superagent.post(url).set(this._getHeader()).send(JSON.stringify({ gids: forumId * 1 })).timeout(10000); let resObj = JSON.parse(res.text); // Bot.logger.mark(`ForumSign: ${res.text}`); return resObj; } async getTasksList() { let res = await superagent.get(`https://bbs-api.mihoyo.com/apihub/sapi/getUserMissionsState`).set(this ._getHeader()).timeout(10000); let resObj = JSON.parse(res.text); return resObj } // 获取签到状态和奖励信息 async getSignInfo(board) { let url = `${web_api}/event/luna/home?lang=zh-cn&` if (board?.name == "原神") { url = `${web_api}/event/bbs_sign_reward/home?` } // if (board.name == "崩坏2" || board.name == "未定事件簿") { // url = `${web_api}/event/luna/home?lang=zh-cn` // } let res = await superagent.get( `${url}act_id=${board.actid}` ).set(this .getpubHeaders(board)).timeout(10000); let resObj = JSON.parse(res.text); // logger.mark(`getSignInfo: ${res.text}`); return resObj; } async forumPostList(forumId) { const url = `${web_api}/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); return resObj; } async forumPostDetail(postId) { const url = `${bbs_api}/post/api/getPostFull?post_id=${postId}`; let res = await superagent.get(url).set(this._getHeader()).timeout(10000); let resObj = JSON.parse(res.text); return resObj; } async forumPostShare(postId) { const url = `${bbs_api}/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); return resObj; } async forumPostVote(postId) { const url = `${bbs_api}/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); return resObj; } async yunGenshen() { let url = `https://api-cloudgame.mihoyo.com/hk4e_cg_cn/gamer/api/login`; let res = await superagent.post(url).set(this.getyunHeader()).timeout(10000); let sendMSg = ""; let log_msg = (await this.logyunGenshen()).log_msg sendMSg += log_msg Bot.logger.info(log_msg) url = `https://api-cloudgame.mihoyo.com/hk4e_cg_cn/gamer/api/listNotifications?status=NotificationStatusUnread&type=NotificationTypePopup&is_sort=true`; res = await superagent.get(url).set(this.getyunHeader()).timeout(10000); let resObj = JSON.parse(res.text); let list = resObj.data.list; if (Object.keys(list).length == 0) { resObj.sendMSg = "您今天的奖励已经领取了啦~"; return resObj; } for (let item of list) { let reward_id = item.id; let reward_msg = item.msg; url = `https://api-cloudgame.mihoyo.com/hk4e_cg_cn/gamer/api/ackNotification?id=${reward_id}`; res = await superagent.post(url).set(this.getyunHeader()).timeout(10000); let log_msg = `\n领取奖励,ID:${reward_id},Msg:${reward_msg}`; Bot.logger.info(log_msg) sendMSg += log_msg } resObj.sendMSg = sendMSg; return resObj; } async logyunGenshen() { let url = `https://api-cloudgame.mihoyo.com/hk4e_cg_cn/wallet/wallet/get`; let res = await superagent.get(url).set(this.getyunHeader()).timeout(10000); let resObj = JSON.parse(res.text); let data = resObj.data let log_msg = `米云币:${data?.coin?.coin_num},免费时长:${data?.free_time?.free_time}分钟,总时长:${data.total_time}分钟`; resObj.log_msg = log_msg return resObj } async updCookie() { let url = `${web_api}/auth/api/getCookieAccountInfoBySToken?game_biz=hk4e_cn`; if (this.e.region.includes("os")) { url = `${os_web_api}/auth/api/getCookieAccountInfoBySToken?game_biz=hk4e_global`; } let map = this.getCookieMap(this.cookies) url += `&stoken=${map.get("stoken")}&uid=${map.get("stuid")}`; let param = { agent: await this.getAgent(), timeout: 10000, method:'get' } let res = await fetch(url, param); let resObj = await res.json() return resObj; } async stoken(cookie, e) { this.e = e; let datalist = this.getStoken(e.user_id) || {} if (Object.keys(datalist).length > 0) { return true; } const map = this.getCookieMap(cookie); let loginTicket = map.get("login_ticket"); const loginUid = map.get("login_uid") ? map.get("login_uid") : map.get("ltuid"); if (isV3) { loginTicket = gsCfg.getBingCookie(e.user_id).login_ticket } 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) { if (!data.data) { return false; } datalist[e.uid] = { stuid: map.get("account_id"), stoken: data.data.list[0].token, ltoken: data.data.list[1].token, uid: e.uid, userId: e.user_id, is_sign: true } gsCfg.saveBingStoken(e.user_id, datalist) return true; }); } ).catch(function(err) { return false; }); return true; } /** 米游社 api headers */ // 签到的 headers getpubHeaders(board) { const randomStr = utils.randomString(6); const timestamp = Math.floor(Date.now() / 1000) let sign = md5(`salt=${saltWeb}&t=${timestamp}&r=${randomStr}`); return { 'accept-language': 'zh-CN,zh;q=0.9,ja-JP;q=0.8,ja;q=0.7,en-US;q=0.6,en;q=0.5', 'x-rpc-device_id': DEVICE_ID, 'User-Agent': `Mozilla/5.0 (iPhone; CPU iPhone OS 14_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/${APP_VERSION}`, Referer: board.getReferer(), Host: 'api-takumi.mihoyo.com', 'x-rpc-channel': 'appstore', 'x-rpc-app_version': APP_VERSION, 'x-requested-with': 'com.mihoyo.hyperion', 'x-rpc-client_type': '5', 'Content-Type': 'application/json;charset=UTF-8', DS: `${timestamp},${randomStr},${sign}`, 'Cookie': this.cookie } } //社区签到ds get_ds2(q = "", b) { let n = salt2 // n ="6cqshh5dhw73bzxn20oexa9k516chk7s" let i = Math.floor(Date.now() / 1000) let r = _.random(100001, 200000) let add = `&b=${b}&q=${q}` let c = md5("salt=" + n + "&t=" + i + "&r=" + r + add) // this.e.reply("md5"+c) return `${i},${r},${c}` } // 米游币任务的 headers _getHeader() { const randomStr = utils.randomString(6); const timestamp = Math.floor(Date.now() / 1000) let sign = md5(`salt=${salt}&t=${timestamp}&r=${randomStr}`); let ds = `${timestamp},${randomStr},${sign}` if (this.forumId) { ds = this.get_ds2("", JSON.stringify({ gids: this.forumId * 1 })); this.forumId = ""; } return { 'Cookie': this.cookies, "x-rpc-channel": "miyousheluodi", 'x-rpc-device_id': DEVICE_ID, 'x-rpc-app_version': APP_VERSION, "x-rpc-device_model": "Mi 10", 'x-rpc-device_name': DEVICE_NAME, 'x-rpc-client_type': '2', // 1 - iOS, 2 - Android, 4 - Web 'DS': ds, "Referer": "https://app.mihoyo.com", "x-rpc-sys_version": "12", "Host": "bbs-api.mihoyo.com", "User-Agent": "okhttp/4.8.0", // 'DS': `1602569298,k0xfEh,07f4545f5d88eac59cb1257aef74a570` } } //云原神签到头 getyunHeader() { return { "x-rpc-combo_token": this.yuntoken, //这里填你的ck "x-rpc-client_type": "2", "x-rpc-app_version": "1.3.0", "x-rpc-sys_version": "11", "x-rpc-channel": "mihoyo", "x-rpc-device_id": this.devId, //这里填获取到的设备Id "x-rpc-device_name": "Xiaomi Mi 10 Pro", "x-rpc-device_model": "Mi 10 Pro", "x-rpc-app_id": "1953439974", "Referer": "https://app.mihoyo.com", "Content-Length": "0", "Host": "api-cloudgame.mihoyo.com", "Connection": "Keep-Alive", "Accept-Encoding": "gzip", "User-Agent": "okhttp/3.14.9" } } //一个奇怪的请求头 getHeader() { return { 'x-rpc-app_version': mhyVersion, 'User-Agent': `Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1`, 'x-rpc-client_type': '5', 'Referer': 'https://webstatic.mihoyo.com/', 'Origin': 'https://webstatic.mihoyo.com', } } old_version_get_ds_token() { // 1 测试过的salt很显然都不是 也可能是header参数错误导致无法使用的 另外一个salt加密方法 get_ds // xV8v4Qu44lUKrEYFZkJhB8cuOh9Asafs // 2 // t0qEgfub6cvueAPgR5m9aQWWVciEer7v // 3 // 599uqkwc0dlqu3h6epzjzfhgyyrd44PR // 4 // rk4xg2hakoi26nljpr099fv9fck1ah10 // 5 6cqshh5dhw73bzxn20oexa9k516chk7s 签到salt?? 6s25p5ox5y14umn1p61aqyyvbvvl3lrt let n = this.e.region.startsWith('os')?osSaltWeb:'N50pqm7FSy2AkFz2B3TqtuZMJ5TOl3Ep' let i = Math.floor(Date.now() / 1000) let r =utils.randomString(6) let c = md5('salt=' + n + '&t=' + i + '&r=' + r ) // this.e.reply("md5"+c) return i + ',' + r + ',' + c } async authkey(e) { let isos=this.e.region.startsWith('os')?true:false; let url = `${(isos?os_web_api:web_api)}/binding/api/genAuthKey`; let HEADER = this.getHeader(); HEADER['Cookie'] = this.cookies HEADER['DS'] =this.old_version_get_ds_token() HEADER['User-Agent'] = 'okhttp/4.8.0' HEADER['x-rpc-app_version'] =isos? '2.18.1':'2.35.2' HEADER['x-rpc-sys_version'] = '12' HEADER['x-rpc-client_type'] = '5' HEADER['x-rpc-channel'] =isos?'hoyolab':'mihoyo' HEADER['x-rpc-device_id'] = utils.randomString(32).toUpperCase(); HEADER['x-rpc-device_name'] = utils.randomString(_.random(1, 10)); HEADER['x-rpc-device_model'] = 'Mi 10' // HEADER['Referer'] = 'https://app.mihoyo.com' // HEADER['Host'] = 'api-takumi.mihoyo.com' HEADER['Referer'] = isos?'https://app.hoyolab.com':'https://app.mihoyo.com' HEADER['Host'] = (isos?os_web_api:web_api).replace(/https:\/\//,"") HEADER['Origin']=(isos?'https://webstatic-sea.hoyolab.com':'https://webstatic.mihoyo.com') let data = { 'auth_appid': 'webview_gacha', 'game_biz': isos?'hk4e_global':'hk4e_cn', 'game_uid': this.e.uid * 1, 'region': this.e.region, } let param = { headers:HEADER, agent: await this.getAgent(), timeout: 10000, body:JSON.stringify(data), method:'post' } let res = await fetch(url, param); // console.log(res) let resObj = await res.json() // headers // let res = await superagent.post(url).set(HEADER).send(JSON.stringify(data)); // let resObj = JSON.parse(res.text); return resObj } getCookieMap(cookie) { let cookieArray = cookie.replace(/\s*/g, "").split(";"); let cookieMap = new Map(); for (let item of cookieArray) { let entry = item.split("="); if (!entry[0]) continue; cookieMap.set(entry[0], entry[1]); } return cookieMap; } getStoken(userId) { let file = `${YamlDataUrl}/${userId}.yaml` try { let ck = fs.readFileSync(file, 'utf-8') ck = YAML.parse(ck) if (ck?.uid) { let datalist = {}; ck.userId = this.e.user_id datalist[ck.uid] = ck; ck = datalist gsCfg.saveBingStoken(this.e.user_id, datalist) } return ck[this.e.uid] || {} } catch (error) { return {} } } //==== 签到任务 ==== // @todo 签到任务大概率是接口通用的, 只是部分参数不一样, 可以构造通用方法, 方便后续整合崩2, 事件簿, 铁道等 // 获取账号信息 通用 async getUserInfo(board) { let url = `${web_api}/binding/api/getUserGameRolesByCookie?game_biz=${board.biz}` // if(this.e.region.includes("os")){ // 暂时先不接入 // url=`${os_web_api}/binding/api/getUserGameRolesByCookie?game_biz=hk4e_global`; // } let res = await superagent.get(url) .set(this .getpubHeaders(board)).timeout(10000); let resObj = JSON.parse(res.text); // console.log(resObj) if (resObj.retcode != 0) { return resObj } // const game_uid = data?.list?. [0]?.game_uid // const region = data?.list?. [0]?.region // const nickname = data?.list?. [0]?.nickname return resObj } // 游戏签到操作查询 async isPostSign(board, game_uid, region) { let url = `${web_api}/event/luna/info?lang=zh-cn` if (board?.name == "原神") { url = `${web_api}/event/bbs_sign_reward/info` } if (board?.name == "崩坏2" || board.name == "未定事件簿") { url = `${web_api}/event/luna/info?lang=zh-cn` } url += `${board?.name == "原神"?"?":"&"}region=${region}&act_id=${board.actid}&uid=${game_uid}` let res = await superagent.get(url).set(this.getpubHeaders(board)).timeout(10000); let resObj = JSON.parse(res.text); return resObj } // 游戏签到操作 async postSign(board, game_uid, region) { let url = `${web_api}/event/luna/sign` if (board?.name == "原神") { url = `${web_api}/event/bbs_sign_reward/sign` } if (board?.name == "崩坏2" || board?.name == "未定事件簿") { url = `${web_api}/event/luna/sign` } url += `?region=${region}&act_id=${board.actid}&uid=${game_uid}` let res = await superagent.post(url).set(this.getpubHeaders(board)).timeout(10000); let resObj = JSON.parse(res.text); return resObj } async getAgent() { if (isV3) { let cfg = await import(`file://${_path}/lib/config/config.js`); let proxyAddress = cfg.default.bot.proxyAddress if (!proxyAddress) return null if (proxyAddress === 'http://0.0.0.0:0') return null if (!this.e.region.startsWith('os')) return null if (HttpsProxyAgent === '') { HttpsProxyAgent = await import('https-proxy-agent').catch((err) => { logger.error(err) }) HttpsProxyAgent = HttpsProxyAgent ? HttpsProxyAgent.default : undefined } if (HttpsProxyAgent) { return new HttpsProxyAgent(proxyAddress) } } return null } }