mirror of
https://github.com/ZZZure/ZZZ-Plugin.git
synced 2025-12-14 12:17:48 +00:00
gacha
This commit is contained in:
parent
0c72964b12
commit
51fb65cdb4
11 changed files with 313 additions and 56 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -4,3 +4,5 @@ config/**/*.*
|
|||
|
||||
.DS_Store
|
||||
|
||||
data/**/*.*
|
||||
!data/.gitkeep
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import _ from 'lodash';
|
|||
import render from '../lib/render.js';
|
||||
import { ZZZNoteResp } from '../model/note.js';
|
||||
import { rulePrefix } from '../lib/common.js';
|
||||
import { getAuthKey, getStoken } from '../lib/authkey.js';
|
||||
import { updateGachaLog } from '../lib/gacha.js';
|
||||
|
||||
export class GachaLog extends ZZZPlugin {
|
||||
constructor() {
|
||||
|
|
@ -13,41 +15,69 @@ export class GachaLog extends ZZZPlugin {
|
|||
priority: 100,
|
||||
rule: [
|
||||
{
|
||||
reg: `${rulePrefix}抽卡记录$`,
|
||||
fnc: 'gachaLog',
|
||||
reg: `${rulePrefix}抽卡链接$`,
|
||||
fnc: 'startGachaLog',
|
||||
},
|
||||
{
|
||||
reg: `${rulePrefix}刷新抽卡链接$`,
|
||||
fnc: 'refreshGachaLog',
|
||||
},
|
||||
{
|
||||
reg: `^${rulePrefix}抽卡帮助$`,
|
||||
fnc: 'gachaHelp',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
async gachaLog(e) {
|
||||
const { api, deviceFp } = await this.getAPI();
|
||||
if (!api) return false;
|
||||
let userData = await api.getData('zzzUser');
|
||||
if (!userData?.data || _.isEmpty(userData.data.list)) {
|
||||
await e.reply('[zzznote]玩家信息获取失败');
|
||||
async gachaHelp() {
|
||||
const reply_msg = [
|
||||
'StarRail-Plugin 抽卡链接绑定方法:',
|
||||
'1. 输入【#zzz抽卡链接】,等待 bot 回复【请发送抽卡链接】',
|
||||
'2. 获取抽卡链接',
|
||||
'3. 将获取到的抽卡链接发送给 bot',
|
||||
].join('\n');
|
||||
await this.reply(reply_msg);
|
||||
}
|
||||
async startGachaLog() {
|
||||
if (!this.e.isPrivate) {
|
||||
await this.reply('请私聊发送抽卡链接', false, { at: true });
|
||||
return false;
|
||||
}
|
||||
userData = userData?.data?.list[0];
|
||||
let noteData = await api.getData('zzzNote', { deviceFp });
|
||||
noteData = await api.checkCode(e, noteData, 'zzzNote', {});
|
||||
if (!noteData || noteData.retcode !== 0) {
|
||||
await e.reply('[zzznote]每日数据获取失败');
|
||||
this.setContext('gachaLog');
|
||||
await this.reply('请发送抽卡链接', false, { at: true });
|
||||
}
|
||||
async gachaLog() {
|
||||
if (!this.e.isPrivate) {
|
||||
await this.reply('请私聊发送抽卡链接', false, { at: true });
|
||||
return false;
|
||||
}
|
||||
noteData = noteData.data;
|
||||
noteData = new ZZZNoteResp(noteData);
|
||||
let avatar = this.e.bot.avatar;
|
||||
// 头像
|
||||
if (this.e.member?.getAvatarUrl) {
|
||||
avatar = await this.e.member.getAvatarUrl();
|
||||
} else if (this.e.friend?.getAvatarUrl) {
|
||||
avatar = await this.e.friend.getAvatarUrl();
|
||||
let key = this.e.msg.trim();
|
||||
key = key?.split?.('authkey=')?.[1]?.split('&')?.[0];
|
||||
if (!key) {
|
||||
await this.reply('抽卡链接格式错误,请重新发送');
|
||||
this.finish('gachaLog');
|
||||
return false;
|
||||
}
|
||||
const finalData = {
|
||||
avatar,
|
||||
player: userData,
|
||||
note: noteData,
|
||||
};
|
||||
await render(e, 'note/index.html', finalData);
|
||||
this.finish('gachaLog');
|
||||
this.getLog(key);
|
||||
}
|
||||
async refreshGachaLog() {
|
||||
const uid = await this.getUID();
|
||||
const key = await getAuthKey(this.e, uid);
|
||||
if (!key) {
|
||||
await this.reply('authKey获取失败,请检查cookie是否过期');
|
||||
return false;
|
||||
}
|
||||
this.getLog(key);
|
||||
}
|
||||
async getLog(key) {
|
||||
const uid = await this.getUID();
|
||||
const data = await updateGachaLog(key, uid);
|
||||
let msg = `抽卡记录更新成功,共${Object.keys(data).length}个卡池`;
|
||||
for (const name in data) {
|
||||
msg += `\n${name}一共${data[name].length}条记录`;
|
||||
}
|
||||
await this.reply(msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
0
data/.gitkeep
Normal file
0
data/.gitkeep
Normal file
|
|
@ -15,16 +15,15 @@ export async function getAuthKey(e, zzzUid, authAppid = 'csc') {
|
|||
if (!User) {
|
||||
throw new Error('未安装逍遥插件,无法自动刷新抽卡链接');
|
||||
}
|
||||
let user = new User(e);
|
||||
// set genshin uid
|
||||
const user = new User(e);
|
||||
await user.getCookie(e);
|
||||
let ck = await user.getStoken(e.user_id);
|
||||
ck = `stuid=${ck.stuid};stoken=${ck.stoken};mid=${ck.mid};`;
|
||||
let api = new MysZZZApi(zzzUid, ck);
|
||||
let type = 'zzzPayAuthKey';
|
||||
const api = new MysZZZApi(zzzUid, ck);
|
||||
let type = 'zzzAuthKey';
|
||||
switch (authAppid) {
|
||||
case 'csc': {
|
||||
type = 'zzzPayAuthKey';
|
||||
type = 'zzzAuthKey';
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
|
|||
59
lib/db.js
Normal file
59
lib/db.js
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { checkFolderExistAndCreate } from '../utils/file.js';
|
||||
import { dataPath } from './path.js';
|
||||
const dbPath = {
|
||||
gacha: 'gacha',
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} dbName
|
||||
* @param {string} dbFile
|
||||
* @returns {object}
|
||||
*/
|
||||
export function getDB(dbName, dbFile) {
|
||||
const db = dbPath[dbName];
|
||||
const dbFolder = path.join(dataPath, db);
|
||||
try {
|
||||
const dbPath = path.join(dbFolder, `${dbFile}.json`);
|
||||
return JSON.parse(readFileSync(dbPath, 'utf-8'));
|
||||
} catch (error) {
|
||||
logger.mark(`读取数据库失败: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} dbName
|
||||
* @param {string} dbFile
|
||||
* @param {object} data
|
||||
*/
|
||||
export function setDB(dbName, dbFile, data) {
|
||||
const db = dbPath[dbName];
|
||||
const dbFolder = path.join(dataPath, db);
|
||||
try {
|
||||
checkFolderExistAndCreate(dbFolder);
|
||||
const dbPath = path.join(dbFolder, `${dbFile}.json`);
|
||||
writeFileSync(dbPath, JSON.stringify(data, null, 2));
|
||||
} catch (error) {
|
||||
logger.mark(`读取数据库失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} uid
|
||||
* @returns {object}
|
||||
*/
|
||||
export function getGachaLog(uid) {
|
||||
return getDB('gacha', uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} uid
|
||||
* @param {object} data
|
||||
*/
|
||||
export function saveGachaLog(uid, data) {
|
||||
setDB('gacha', uid, data);
|
||||
}
|
||||
129
lib/gacha.js
Normal file
129
lib/gacha.js
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
import { SingleGachaLog, ZZZGachaLogResp } from '../model/gacha.js';
|
||||
import { sleep } from '../utils/time.js';
|
||||
import { getGachaLog, saveGachaLog } from './db.js';
|
||||
import { ZZZ_GET_GACHA_LOG_API } from './mysapi/api.js';
|
||||
|
||||
export const gacha_type_meta_data = {
|
||||
音擎频段: ['3001'],
|
||||
独家频段: ['2001'],
|
||||
常驻频段: ['1001'],
|
||||
邦布频段: ['5001'],
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param {string} authKey
|
||||
* @param {*} gachaType
|
||||
* @param {*} initLogGachaBaseType
|
||||
* @param {number} page
|
||||
* @param {string} endId
|
||||
* @returns {Promise<ZZZGachaLogResp>}
|
||||
*/
|
||||
export async function getZZZGachaLogByAuthkey(
|
||||
authKey,
|
||||
gachaType = '2001',
|
||||
initLogGachaBaseType = '2',
|
||||
page = 1,
|
||||
endId = '0'
|
||||
) {
|
||||
const serverId = 'prod_gf_cn';
|
||||
const url = ZZZ_GET_GACHA_LOG_API;
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
|
||||
const params = new URLSearchParams({
|
||||
authkey_ver: '1',
|
||||
sign_type: '2',
|
||||
auth_appid: 'webview_gacha',
|
||||
init_log_gacha_type: gachaType,
|
||||
init_log_gacha_base_type: initLogGachaBaseType,
|
||||
gacha_id: '2c1f5692fdfbb733a08733f9eb69d32aed1d37',
|
||||
timestamp: timestamp.toString(),
|
||||
lang: 'zh-cn',
|
||||
device_type: 'mobile',
|
||||
plat_type: 'ios',
|
||||
region: serverId,
|
||||
authkey: authKey,
|
||||
game_biz: 'nap_cn',
|
||||
gacha_type: gachaType,
|
||||
real_gacha_type: initLogGachaBaseType,
|
||||
page: page,
|
||||
size: '20',
|
||||
end_id: endId,
|
||||
});
|
||||
|
||||
const response = await fetch(`${url}?${params}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data || !data?.data) return null;
|
||||
|
||||
return new ZZZGachaLogResp(data.data);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} authKey
|
||||
* @param {string} uid
|
||||
* @returns {Promise<{
|
||||
* [x: string]: SingleGachaLog[];
|
||||
* }>}
|
||||
*/
|
||||
export async function updateGachaLog(authKey, uid) {
|
||||
let previousLog = getGachaLog(uid);
|
||||
if (!previousLog) {
|
||||
previousLog = {};
|
||||
}
|
||||
for (const name in gacha_type_meta_data) {
|
||||
if (!previousLog[name]) {
|
||||
previousLog[name] = [];
|
||||
}
|
||||
previousLog[name] = previousLog[name].map(
|
||||
i =>
|
||||
new SingleGachaLog(
|
||||
i.uid,
|
||||
i.gacha_id,
|
||||
i.gacha_type,
|
||||
i.item_id,
|
||||
i.count,
|
||||
i.time,
|
||||
i.name,
|
||||
i.lang,
|
||||
i.item_type,
|
||||
i.rank_type,
|
||||
i.id
|
||||
)
|
||||
);
|
||||
const lastSaved = previousLog[name]?.[0];
|
||||
let page = 1;
|
||||
let endId = '0';
|
||||
for (const type of gacha_type_meta_data[name]) {
|
||||
queryLabel: while (true) {
|
||||
const log = await getZZZGachaLogByAuthkey(
|
||||
authKey,
|
||||
type,
|
||||
type[0],
|
||||
page,
|
||||
endId
|
||||
);
|
||||
if (!log || !log?.list || log?.list?.length === 0) {
|
||||
break;
|
||||
}
|
||||
for (const item of log.list) {
|
||||
if (lastSaved && lastSaved.equals(item)) {
|
||||
break queryLabel;
|
||||
}
|
||||
previousLog[name].push(item);
|
||||
}
|
||||
endId = log.list[log.list.length - 1]?.id || endId;
|
||||
page++;
|
||||
await sleep(400);
|
||||
}
|
||||
}
|
||||
}
|
||||
saveGachaLog(uid, previousLog);
|
||||
return previousLog;
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import MysApi from '../../genshin/model/mys/mysApi.js';
|
||||
import md5 from 'md5';
|
||||
import _ from 'lodash';
|
||||
import crypto from 'crypto';
|
||||
import ZZZApiTool from './mysapi/tool.js';
|
||||
import MysApi from '../../genshin/model/mys/mysApi.js';
|
||||
|
||||
// const DEVICE_ID = randomString(32).toUpperCase()
|
||||
const DEVICE_NAME = randomString(_.random(1, 10));
|
||||
const game_region = [
|
||||
|
|
@ -79,20 +80,9 @@ export default class MysZZZApi extends MysApi {
|
|||
}
|
||||
default:
|
||||
}
|
||||
if (type === 'zzzPayAuthKey') {
|
||||
if (type === 'zzzAuthKey') {
|
||||
let extra = {
|
||||
'x-rpc-app_version': '2.40.1',
|
||||
'User-Agent': 'okhttp/4.8.0',
|
||||
'x-rpc-client_type': '5',
|
||||
Referer: 'https://app.mihoyo.com',
|
||||
Origin: 'https://webstatic.mihoyo.com',
|
||||
// Cookie: this.cookies,
|
||||
// DS: this.getDS2(),
|
||||
'x-rpc-sys_version': '12',
|
||||
'x-rpc-channel': 'mihoyo',
|
||||
'x-rpc-device_id': this._device,
|
||||
'x-rpc-device_name': DEVICE_NAME,
|
||||
'x-rpc-device_model': 'Mi 10',
|
||||
DS: this.getDS2(),
|
||||
Host: 'api-takumi.mihoyo.com',
|
||||
};
|
||||
headers = Object.assign(headers, extra);
|
||||
|
|
@ -114,7 +104,7 @@ export default class MysZZZApi extends MysApi {
|
|||
let n = '';
|
||||
if (['prod_gf_cn', 'prod_qd_cn'].includes(this.server)) {
|
||||
n = 'xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs';
|
||||
} else if (/official/.test(this.server)) {
|
||||
} else if (/prod_gf_/.test(this.server)) {
|
||||
n = 'okr4obncj8bw5a65hbnn5oo6ixjc3l9w';
|
||||
}
|
||||
let t = Math.round(new Date().getTime() / 1000);
|
||||
|
|
@ -126,15 +116,15 @@ export default class MysZZZApi extends MysApi {
|
|||
getDS2() {
|
||||
let t = Math.round(new Date().getTime() / 1000);
|
||||
let r = randomString(6);
|
||||
let sign = md5(`salt=jEpJb9rRARU2rXDA9qYbZ3selxkuct9a&t=${t}&r=${r}`);
|
||||
let sign = md5(`salt=BIPaooxbWZW02fGHZL1If26mYCljPgst&t=${t}&r=${r}`);
|
||||
return `${t},${r},${sign}`;
|
||||
}
|
||||
|
||||
getHeaders(query = '', body = '') {
|
||||
const cn = {
|
||||
app_version: '2.44.1',
|
||||
app_version: '2.63.1',
|
||||
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.44.1',
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.63.1',
|
||||
client_type: '5',
|
||||
Origin: 'https://webstatic.mihoyo.com',
|
||||
X_Requested_With: 'com.mihoyo.hyperion',
|
||||
|
|
@ -158,7 +148,10 @@ export default class MysZZZApi extends MysApi {
|
|||
return {
|
||||
'x-rpc-app_version': client.app_version,
|
||||
'x-rpc-client_type': client.client_type,
|
||||
// 'x-rpc-page': '3.1.3_#/rpg',
|
||||
'User-Agent': 'okhttp/4.8.0',
|
||||
'x-rpc-sys_version': '12',
|
||||
'x-rpc-client_type': '2',
|
||||
'x-rpc-channel': 'mihoyo',
|
||||
'User-Agent': client.User_Agent,
|
||||
Referer: client.Referer,
|
||||
DS: this.getDs(query, body),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default class ZZZApiTool {
|
|||
*/
|
||||
constructor(uid, server) {
|
||||
this.uid = uid;
|
||||
this.isSr = true;
|
||||
this.isZZZ = true;
|
||||
this.server = server;
|
||||
this.game = 'zzz';
|
||||
this.uuid = crypto.randomUUID();
|
||||
|
|
@ -19,18 +19,18 @@ export default class ZZZApiTool {
|
|||
|
||||
getUrlMap = (data = {}) => {
|
||||
let host, hostRecord, hostPublicData;
|
||||
if (['prod_gf_cn', 'prod_qd_cn'].includes(this.server)) {
|
||||
if (['prod_gf_cn'].includes(this.server)) {
|
||||
host = 'https://api-takumi.mihoyo.com/';
|
||||
hostRecord = 'https://api-takumi-record.mihoyo.com/';
|
||||
hostPublicData = 'https://public-data-api.mihoyo.com/';
|
||||
} else if (/official/.test(this.server)) {
|
||||
} else if (/prod_gf_/.test(this.server)) {
|
||||
host = 'https://sg-public-api.hoyolab.com/';
|
||||
hostRecord = 'https://bbs-api-os.hoyolab.com/';
|
||||
hostPublicData = 'https://sg-public-data-api.hoyoverse.com/';
|
||||
}
|
||||
let urlMap = {
|
||||
zzz: {
|
||||
...(['prod_gf_cn', 'prod_qd_cn'].includes(this.server)
|
||||
...(['prod_gf_cn'].includes(this.server)
|
||||
? {
|
||||
zzzUser: {
|
||||
url: `${host}binding/api/getUserGameRolesByCookie`,
|
||||
|
|
@ -78,6 +78,16 @@ export default class ZZZApiTool {
|
|||
url: `${hostRecord}event/game_record_zzz/api/zzz/index`,
|
||||
query: `role_id=${this.uid}&server=${this.server}`,
|
||||
},
|
||||
zzzAuthKey: {
|
||||
url: `${host}binding/api/genAuthKey`,
|
||||
body: {
|
||||
auth_appid: 'webview_gacha',
|
||||
game_biz: 'nap_cn',
|
||||
game_uid: this.uid * 1,
|
||||
region: this.server,
|
||||
},
|
||||
dsSalt: 'web',
|
||||
},
|
||||
},
|
||||
};
|
||||
return urlMap[this.game];
|
||||
|
|
|
|||
|
|
@ -40,6 +40,18 @@ export class SingleGachaLog {
|
|||
this.rank_type = rank_type;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {SingleGachaLog} item
|
||||
*/
|
||||
equals(item) {
|
||||
return (
|
||||
this.uid === item.uid &&
|
||||
this.gacha_id === item.gacha_id &&
|
||||
this.gacha_type === this.gacha_type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -59,7 +71,22 @@ export class ZZZGachaLogResp {
|
|||
const { page, size, list, region, region_time_zone } = data;
|
||||
this.page = page;
|
||||
this.size = size;
|
||||
this.list = list;
|
||||
this.list = list.map(
|
||||
item =>
|
||||
new SingleGachaLog(
|
||||
item.uid,
|
||||
item.gacha_id,
|
||||
item.gacha_type,
|
||||
item.item_id,
|
||||
item.count,
|
||||
item.time,
|
||||
item.name,
|
||||
item.lang,
|
||||
item.item_type,
|
||||
item.rank_type,
|
||||
item.id
|
||||
)
|
||||
);
|
||||
this.region = region;
|
||||
this.region_time_zone = region_time_zone;
|
||||
}
|
||||
|
|
|
|||
6
utils/file.js
Normal file
6
utils/file.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import fs from 'fs';
|
||||
export function checkFolderExistAndCreate(folderPath) {
|
||||
if (!fs.existsSync(folderPath)) {
|
||||
fs.mkdirSync(folderPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
|
@ -4,3 +4,5 @@ export const converSecondsToHM = seconds => {
|
|||
const mm = d.getUTCMinutes();
|
||||
return [hh, mm];
|
||||
};
|
||||
|
||||
export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue