diff --git a/.gitignore b/.gitignore index 56de180..9fd6b84 100644 --- a/.gitignore +++ b/.gitignore @@ -146,6 +146,5 @@ nohup.out # 忽略文件夹 .history -_Paimon_exchange/* user_data/gacha_log_data/* user_data/player_info/* diff --git a/Paimon_Exchange/__init__.py b/Paimon_Exchange/__init__.py new file mode 100644 index 0000000..df2b67a --- /dev/null +++ b/Paimon_Exchange/__init__.py @@ -0,0 +1,158 @@ +import re +from nonebot import on_command +from nonebot.params import CommandArg, T_State, Arg +from nonebot.adapters.onebot.v11 import PrivateMessageEvent, Message +from .data_source import get_address, get_goods, save_exchange_info, get_exchange_info, delete_exchange_info + +__paimon_help__ = { + 'type': '工具', + 'range': ['private'] +} + +myb_exchange = on_command('myb', aliases={'米游币兑换', '米游币商品兑换', '米游社商品兑换'}, priority=4, block=True) +myb_exchange.__paimon_help__ = { + "usage": "myb", + "introduce": "让派蒙帮你兑换米游币商品哦", + "priority": 7 +} +myb_info = on_command('myb_info', aliases={'米游币兑换信息', '米游币兑换计划'}, priority=4, block=True) +myb_info.__paimon_help__ = { + "usage": "myb_info", + "introduce": "查看你的米游币兑换计划", + "priority": 8 +} +myb_delete = on_command('myb_delete', aliases={'米游币兑换删除', '米游币兑换取消'}, priority=4, block=True) +myb_delete.__paimon_help__ = { + "usage": "myb_delete", + "introduce": "取消你的米游币兑换计划", + "priority": 9 +} + + +@myb_exchange.handle() +async def _(event: PrivateMessageEvent, state: T_State, msg: Message = CommandArg()): + if msg: + msg = msg.extract_plain_text().strip() + if '虚拟' in msg: + state['商品类型'] = '虚拟' + elif '实体' in msg: + state['商品类型'] = '实体' + state['uid'] = None + + +@myb_exchange.got('商品类型', prompt='请给出要抢的商品类型(虚拟|实体),例如原石属于虚拟') +async def _(event: PrivateMessageEvent, state: T_State, type: Message = Arg('商品类型')): + type = type.extract_plain_text().strip() + if '虚拟' in type: + state['商品类型'] = '虚拟' + print(state) + elif '实体' in type: + state['商品类型'] = '实体' + state['uid'] = None + else: + await myb_exchange.reject('请给出要抢的商品类型(虚拟|实体),例如原石属于虚拟') + + +@myb_exchange.got('uid', prompt='请把虚拟商品要兑换到的游戏uid告诉我') +async def _(event: PrivateMessageEvent, state: T_State, uid: Message = Arg('uid')): + uid = uid.extract_plain_text().strip() + find_uid = re.search(r'(?P(1|2|5)\d{8})', uid) + if find_uid: + state['uid'] = find_uid.group('uid') + else: + await myb_exchange.reject('这不是有效的uid') + + +@myb_exchange.got('cookie', prompt='请把米游币cookie给我,cookie获取方式详见:\ndocs.qq.com/doc/DQ3JLWk1vQVllZ2Z1') +async def _(event: PrivateMessageEvent, state: T_State, cookie: Message = Arg('cookie')): + cookie = cookie.extract_plain_text().strip() + address = await get_address(cookie) + if address is None: + await myb_exchange.reject('这个cookie无效,请检查是否以按照正常方法获取') + elif len(address) == 0: + await myb_exchange.finish('你的账号还没有填写收货地址哦,请先去填写收货地址重新再来') + else: + state['cookie'] = cookie + if len(address) == 1: + state['address_id'] = address[0] + else: + state['address_list'] = address + if state['商品类型'] == '虚拟': + if 'login_ticket' not in cookie and 'stoken' not in cookie: + await myb_exchange.reject('你的cookie中没有login_ticket字段哦,请尝试退出后重新登录再获取cookie') + + +@myb_exchange.got('address_id', prompt='回复任意文字继续,接下来回复选择你的收货地址的ID') +async def _(event: PrivateMessageEvent, state: T_State, address_id: Message = Arg('address_id')): + address_id = address_id.extract_plain_text().strip() + flag = False + for add in state['address_list']: + if address_id == add['id']: + state['address_id'] = add + flag = True + break + if not flag: + address_list = '' + for add in state['address_list']: + address_list += f'ID:{add["id"]},{add["地址"]}\n' + await myb_exchange.reject(f'请选择收货地址ID:\n{address_list}') + + +@myb_exchange.got('game', prompt='请给出要抢的商品所属游戏名称,有崩坏3|原神|崩坏学园2|未定事件簿|米游社') +async def _(event: PrivateMessageEvent, state: T_State, game: Message = Arg('game')): + game = game.extract_plain_text().strip() + if game in ['崩坏3', 'bh3', '崩崩崩', '三崩子']: + state['goods_list'] = await get_goods('崩坏3') + elif game in ['原神', 'ys']: + state['goods_list'] = await get_goods('原神') + elif game in ['崩坏学园2', 'bh2', '二崩子', '崩坏学院2', '崩崩']: + state['goods_list'] = await get_goods('崩坏学园2') + elif game in ['未定事件簿', 'wdsjb', '未定']: + state['goods_list'] = await get_goods('未定事件簿') + elif game in ['米游社', 'mys']: + state['goods_list'] = await get_goods('米游社') + else: + await myb_exchange.reject('请给出要抢的商品所属游戏名称,有崩坏3|原神|崩坏学园2|未定事件簿|米游社') + + +@myb_exchange.got('goods_search', prompt='请给出要兑换的商品名,或者其含有的关键词') +async def _(event: PrivateMessageEvent, state: T_State, goods_search: Message = Arg('goods_search')): + goods_search = goods_search.extract_plain_text().strip() + match_goods = [] + for good in state['goods_list']: + if goods_search in good['name']: + match_goods.append(good) + if len(match_goods) == 1: + state['goods'] = match_goods[0] + save_exchange_info(event.user_id, state) + await myb_exchange.finish('完成') + elif len(match_goods) > 1: + state['goods_search_result'] = match_goods + else: + await myb_exchange.reject('没有相关可兑换的商品,请重新输入') + + +@myb_exchange.got('goods', prompt='回复任意文字继续,接下来回复选择你想要兑换的商品的ID') +async def _(event: PrivateMessageEvent, state: T_State, msg: Message = Arg('goods')): + msg = msg.extract_plain_text().strip() + for good in state['goods_search_result']: + if msg == good['id']: + state['goods'] = good + save_exchange_info(event.user_id, state) + await myb_exchange.finish('派蒙记住啦!到时候会帮你兑换,发送 myb_info 可以再次确认兑换信息,发送 myb_delete 可以取消兑换计划') + good_str = '' + for good in state['goods_search_result']: + good_str += f'ID:{good["id"]}, 商品名:{good["name"]}\n' + await myb_exchange.reject('请选择商品ID:\n'+good_str) + + +@myb_info.handle() +async def _(event: PrivateMessageEvent): + info = get_exchange_info(str(event.user_id)) + await myb_info.finish(info) + + +@myb_delete.handle() +async def _(event: PrivateMessageEvent): + delete_exchange_info(str(event.user_id)) + await myb_delete.finish('米游币兑换计划已全部取消') diff --git a/Paimon_Exchange/data_source.py b/Paimon_Exchange/data_source.py new file mode 100644 index 0000000..b825e6c --- /dev/null +++ b/Paimon_Exchange/data_source.py @@ -0,0 +1,226 @@ +import datetime +import random +import time +import json +import string +import re +from pathlib import Path +from asyncio import sleep +from nonebot import require, get_bot, get_driver +from utils import aiorequests +from utils.file_handler import load_json, save_json + +require('nonebot_plugin_apscheduler') +from nonebot_plugin_apscheduler import scheduler + +driver = get_driver() + + +async def get_address(cookie): + address_url = 'https://api-takumi.mihoyo.com/account/address/list' + header = { + 'Accept': 'application/json, text/plain, */*', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'zh-CN,zh-Hans;q=0.9', + 'Connection': 'keep-alive', + 'Cookie': cookie, + 'Host': 'api-takumi.mihoyo.com', + 'Origin': 'https://user.mihoyo.com', + 'Referer': 'https://user.mihoyo.com/', + 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.25.1' + } + res = await aiorequests.get(url=address_url, headers=header) + res = res.json() + if res['message'] == 'OK': + address = res['data']['list'] + add_list = [] + for add in address: + add_list.append({'id': add['id'], + '地址': f'姓名:{add["connect_name"]} 电话:{add["connect_mobile"]} 地址:{add["province_name"] + add["city_name"] + add["county_name"] + add["addr_ext"]}'}) + return add_list + else: + return None + + +async def get_goods(game): + game_type = {'崩坏3': 'bh3', '原神': 'hk4e', '崩坏学园2': 'bh2', '未定事件簿': 'nxx', '米游社': 'bbs'} + url = 'https://api-takumi.mihoyo.com/mall/v1/web/goods/list?app_id=1&point_sn=myb&page_size=20&page={page}&game=' + \ + game_type[game] + goods_list = [] + goods_list_new = [] + page = 1 + while True: + res = await aiorequests.get(url=url.format(page=page)) + res = res.json() + if not res['data']['list']: + break + else: + goods_list += res['data']['list'] + page += 1 + + for good in goods_list: + if good['next_time'] == 0 and good['type'] == 1: + continue + good_info = {} + good_info['id'] = good['goods_id'] + good_info['name'] = good['goods_name'] + good_info['price'] = good['price'] + good_info['time'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(good['next_time'])) + goods_list_new.append(good_info) + + return goods_list_new + + +def save_exchange_info(user_id, state): + info = {} + info['user_id'] = user_id + info['类型'] = state['商品类型'] + info['cookie'] = state['cookie'] + info['商品'] = state['goods'] + info['地址'] = state['address_id'] + if info['类型'] == '虚拟': + info['uid'] = state['uid'] + + t = f"{user_id}-{info['商品']['id']}" + + path = Path() / 'data' / 'LittlePaimon' / 'myb_exchange' / f'{t}.json' + path.parent.mkdir(parents=True, exist_ok=True) + + save_json(data=info, path=path) + + scheduler.add_job( + id=t, + replace_existing=True, + misfire_grace_time=5, + func=exchange_action, + trigger='date', + args=(info,), + next_run_time=datetime.datetime.strptime(info['商品']['time'], '%Y-%m-%d %H:%M:%S') + ) + + +async def get_bbs_info(info, headers): + url = 'https://api-takumi.mihoyo.com/binding/api/getUserGameRolesByCookie' + res = (await aiorequests.get(url=url, headers=headers)).json() + if res['retcode'] == 0: + data = res['data']['list'] + for d in data: + if d['game_uid'] == info['uid']: + return d['game_biz'], d['region'] + return None, None + + +async def get_stoken(cookie): + bbs_cookie_url = 'https://webapi.account.mihoyo.com/Api/cookie_accountinfo_by_loginticket?login_ticket={}' + bbs_cookie_url2 = 'https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?login_ticket={}&token_types=3&uid={}' + + login_ticket = re.search('login_ticket=[a-zA-Z0-9]{0,100}', cookie) + data = (await aiorequests.get(url=bbs_cookie_url.format(login_ticket.group().split('=')[1]))).json() + if '成功' in data['data']['msg']: + stuid = data['data']['cookie_info']['account_id'] + data2 = (await aiorequests.get(url=bbs_cookie_url2.format(login_ticket.group().split('=')[1], stuid))).json() + return data2['data']['list'][0]['token'] + else: + return None + + +async def exchange_action(info): + url = 'https://api-takumi.mihoyo.com/mall/v1/web/goods/exchange' + headers = { + 'Accept': 'application/json, text/plain, */*', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'zh-CN,zh-Hans;q=0.9', + 'Connection': 'keep-alive', + # 'Content-Length': '88', + 'Content-Type': 'application/json;charset=utf-8', + 'Cookie': info['cookie'], + 'Host': 'api-takumi.mihoyo.com', + 'Origin': 'https://webstatic.mihoyo.com', + 'Referer': 'https://webstatic.mihoyo.com/', + 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHtimeL, like Gecko) miHoYoBBS/2.14.1', + 'x-rpc-app_version': '2.14.1', + 'x-rpc-channel': 'appstore', + 'x-rpc-client_type': '1', + 'x-rpc-device_id': ''.join(random.sample(string.ascii_letters + string.digits, 32)).upper(), + 'x-rpc-device_model': 'iPhone10,2', + 'x-rpc-device_name': ''.join(random.sample(string.ascii_letters + string.digits, random.randrange(5))).upper(), + 'x-rpc-sys_version': '15.1' + } + data = { + "app_id": 1, + "point_sn": "myb", + "goods_id": info['商品']['id'], + "exchange_num": 1, + "address_id": info['地址']['id'] + } + if info['类型'] == '虚拟': + game_biz, region = await get_bbs_info(info, headers) + data['uid'] = info['uid'] + data['game_biz'] = game_biz + data['region'] = region + if 'stoken' not in info['cookie']: + stoken = await get_stoken(info['cookie']) + info['cookie'] += f'stoken={stoken};' + + exchange_url = 'https://api-takumi.mihoyo.com/mall/v1/web/goods/exchange' + flag = False + for _ in range(3): + exchange_res = (await aiorequests.post(url=exchange_url, headers=headers, json=data)).json() + if exchange_res['retcode'] == 0: + await get_bot().send_private_msg(user_id=info['user_id'], + message=f'你的米游币商品{info["商品"]["name"]}的兑换成功,结果为:\n{exchange_res["message"]}') + flag = True + break + await sleep(0.2) + try: + if not flag: + await get_bot().send_private_msg(user_id=info['user_id'], + message=f'你的米游币商品{info["商品"]["name"]}兑换失败:\n{exchange_res["message"]}') + except: + pass + + path = Path() / 'data' / 'LittlePaimon' / 'myb_exchange' / f"{info['user_id']}-{info['商品']['id']}.json" + path.unlink() + + +@driver.on_startup +async def _(): + path = Path() / 'data' / 'LittlePaimon' / 'myb_exchange' + for exchange_data in path.iterdir(): + info = load_json(path=exchange_data) + t = str(exchange_data).replace('data\\LittlePaimon\\myb_exchange\\', '').replace('data/LittlePaimon/myb_exchange/', '').replace( + '.json', '') + scheduler.add_job( + id=t, + replace_existing=True, + misfire_grace_time=5, + func=exchange_action, + trigger='date', + args=(info,), + next_run_time=datetime.datetime.strptime(info['商品']['time'], '%Y-%m-%d %H:%M:%S') + ) + + +def get_exchange_info(user_id): + result = '' + path = Path() / 'data' / 'LittlePaimon' / 'myb_exchange' + i = 1 + for exchange_data in path.iterdir(): + file_name = str(exchange_data).replace('data\\LittlePaimon\\myb_exchange\\', '').replace('data/LittlePaimon/myb_exchange/', '') + if file_name.startswith(user_id): + info = load_json(path=exchange_data) + result += f"{i}.{info['商品']['name']} {info['商品']['time']}\n" + if info['类型'] == '虚拟': + result += f"兑换至uid{info['uid']}\n" + else: + result += f"兑换至{info['地址']['地址']}\n" + i += 1 + return result or '你还没有米游币兑换计划哦' + + +def delete_exchange_info(user_id): + path = Path() / 'data' / 'LittlePaimon' / 'myb_exchange' + for exchange_data in path.iterdir(): + file_name = str(exchange_data).replace('data\\LittlePaimon\\myb_exchange\\', '').replace('data/LittlePaimon/myb_exchange/', '') + if file_name.startswith(user_id): + exchange_data.unlink() diff --git a/README.md b/README.md index 059d6ed..b3463e0 100644 --- a/README.md +++ b/README.md @@ -151,13 +151,15 @@ javascript:(function(){prompt(document.domain,document.cookie)})(); - [NoneBot2](https://github.com/nonebot/nonebot2) - 跨平台异步机器人框架 - [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) - Onebot标准的框架实现 - [西北一枝花](https://github.com/Nwflower) - 美工大大和武器攻略图提供 -- [nicklly](https://github.com/nicklly) - 原神日历、云原神等功能贡献者 +- [nicklly](https://github.com/nicklly) 、[SCU_OP](https://github.com/SCUOP) - PR贡献者们 - [egenshin](https://github.com/pcrbot/erinilis-modules/tree/master/egenshin) - 抽卡和猜语音代码、资源参考 - [bluemushoom](https://bbs.nga.cn/nuke.php?func=ucp&uid=62861898) - 全角色收益曲线和参考面板攻略图来源 - [genshin-gacha-export](https://github.com/sunfkny/genshin-gacha-export) - 抽卡记录导出代码参考 +- [GenshinUID](https://github.com/KimigaiiWuyi/GenshinUID) - 部分map资源来源 - [Pallas-Bot](https://github.com/InvoluteHell/Pallas-Bot/tree/master/src/plugins/repeater) - 群聊记录发言学习代码参考 - [西风驿站](https://bbs.mihoyo.com/ys/collection/307224) - 角色攻略一图流来源 - [游创工坊](https://space.bilibili.com/176858937) - 深渊排行榜数据来源 +- [Enka Network](https://enka.shinshin.moe/) - 角色面板查询数据来源 ## 丨赞助 - 如果本项目对你有帮助,给个star~~求求啦 @@ -173,7 +175,7 @@ javascript:(function(){prompt(document.domain,document.cookie)})(); | 永远的皇珈骑士 | 30 | | 小兔和鹿 | 30 | | el psy congroo | 20 | -| Why U Bully Me | 30 | +| SCU_OP | 30 | | 南絮ヽ | 20 | | 夜空koi我老婆 | 30 | ## 丨其他