From 74a20a54db9a2f6708f3dc3567a95186fa29c4ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wuyi=E6=97=A0=E7=96=91?= <444835641@qq.com> Date: Fri, 10 Mar 2023 23:40:41 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E6=96=B0=E5=A2=9E`gsrc`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GenshinUID/genshinuid_topup/__init__.py | 14 ++++ GenshinUID/genshinuid_topup/gs_topup.py | 93 +++++++++++++++++++++++ GenshinUID/gsuid_utils/api/mys/api.py | 6 ++ GenshinUID/gsuid_utils/api/mys/models.py | 55 ++++++++++++++ GenshinUID/gsuid_utils/api/mys/request.py | 89 +++++++++++++++++++++- GenshinUID/gsuid_utils/api/mys/tools.py | 15 ++++ poetry.lock | 6 +- 7 files changed, 274 insertions(+), 4 deletions(-) create mode 100644 GenshinUID/genshinuid_topup/__init__.py create mode 100644 GenshinUID/genshinuid_topup/gs_topup.py diff --git a/GenshinUID/genshinuid_topup/__init__.py b/GenshinUID/genshinuid_topup/__init__.py new file mode 100644 index 00000000..3b1ec1ee --- /dev/null +++ b/GenshinUID/genshinuid_topup/__init__.py @@ -0,0 +1,14 @@ +from gsuid_core.sv import SV +from gsuid_core.bot import Bot +from gsuid_core.models import Event + +from .gs_topup import topup_ + + +@SV('原神充值').on_fullmatch(('gsrc', '原神充值')) +async def send_qrcode_login(bot: Bot, ev: Event): + await bot.logger.info('开始执行[原神充值]') + goods_id = int(ev.text) if ev.text.isdigit() else None + if goods_id is None: + return await bot.send('请输入正确的商品编号(1~6), 例如原神充值6!') + await topup_(bot, ev.bot_id, ev.user_id, ev.group_id, ev.text) diff --git a/GenshinUID/genshinuid_topup/gs_topup.py b/GenshinUID/genshinuid_topup/gs_topup.py new file mode 100644 index 00000000..63e71c49 --- /dev/null +++ b/GenshinUID/genshinuid_topup/gs_topup.py @@ -0,0 +1,93 @@ +import io +import base64 +import asyncio +import traceback + +import qrcode +from gsuid_core.bot import Bot +from nonebot.log import logger +from qrcode import ERROR_CORRECT_L +from gsuid_core.segment import MessageSegment + +from ..utils.mys_api import mys_api +from ..utils.database import get_sqla +from ..utils.error_reply import get_error +from ..gsuid_utils.api.mys.models import MysOrder + +disnote = '''免责声明: +该充值接口由米游社提供,不对充值结果负责。 +请在充值前仔细阅读米哈游的充值条款。 +''' + + +def get_qrcode_base64(url: str): + qr = qrcode.QRCode( + version=1, + error_correction=ERROR_CORRECT_L, + box_size=10, + border=4, + ) + qr.add_data(url) + qr.make(fit=True) + img = qr.make_image(fill_color='black', back_color='white') + img_byte = io.BytesIO() + img.save(img_byte, format='PNG') # type:ignore + img_byte = img_byte.getvalue() + return base64.b64encode(img_byte).decode() + + +async def refresh(order: MysOrder, uid: str) -> str: + times = 0 + while True: + await asyncio.sleep(5) + order_status = await mys_api.check_order(order, uid) + if isinstance(order_status, int): + return get_error(order_status) + if order_status['status'] != 900: + pass + else: + return '支付成功' + times += 1 + if times > 60: + return '支付超时' + + +async def topup_( + bot: Bot, bot_id: str, user_id: str, group_id: str, goods_id: int +): + sqla = get_sqla(bot_id) + uid = await sqla.get_bind_uid(user_id) + if uid is None: + return await bot.send('未绑定米游社账号') + fetchgoods_data = await mys_api.get_fetchgoods() + if isinstance(fetchgoods_data, int): + return await bot.send(get_error(fetchgoods_data)) + if goods_id < len(fetchgoods_data): + goods_data = fetchgoods_data[goods_id] + else: + return await bot.send('商品不存在,最大为' + str(len(fetchgoods_data) - 1)) + order = await mys_api.topup(uid, goods_data) + if isinstance(order, int): + logger.warning(f'[充值] {group_id} {user_id} 出错!') + return await bot.send(get_error(order)) + try: + im = [] + b64_data = get_qrcode_base64(order['encode_order']) + qrc = MessageSegment.image(b64_data) + im.append(MessageSegment.text(f'充值uid:{uid}')) + im.append( + MessageSegment.text( + f'商品名称:{goods_data["goods_name"]}×{goods_data["goods_unit"]}' + if int(goods_data['goods_unit']) > 0 + else goods_data['goods_name'] + ), + ) + im.append(MessageSegment.text(f'商品价格:{int(order["amount"])/100}')) + im.append(MessageSegment.text(f'订单号:{order["order_no"]}')) + im.append(MessageSegment.text(disnote)) + im.append(MessageSegment.text(qrc)) + return await bot.send(MessageSegment.node(im)) + except Exception: + traceback.print_exc() + logger.warning(f'[充值] {group_id} 图片发送失败') + return await bot.send(await refresh(order, uid)) diff --git a/GenshinUID/gsuid_utils/api/mys/api.py b/GenshinUID/gsuid_utils/api/mys/api.py index 5552ce41..a36f2f6b 100644 --- a/GenshinUID/gsuid_utils/api/mys/api.py +++ b/GenshinUID/gsuid_utils/api/mys/api.py @@ -116,4 +116,10 @@ bbs_Detailurl = BBS_URL + '/post/api/getPostFull?post_id={}' bbs_Shareurl = BBS_URL + '/apihub/api/getShareConf?entity_id={}&entity_type=1' bbs_Likeurl = f'{BBS_URL}/apihub/sapi/upvotePost' +# 原神充值中心 +fetchGoodsurl = f"{HK4_SDK_URL}/hk4e_cn/mdk/shopwindow/shopwindow/fetchGoods" +CreateOrderurl = f"{HK4_SDK_URL}/hk4e_cn/mdk/atropos/api/createOrder" +CheckOrderurl = f"{HK4_SDK_URL}/hk4e_cn/mdk/atropos/api/checkOrder" +PriceTierurl = f"{HK4_SDK_URL}/hk4e_cn/mdk/shopwindow/shopwindow/listPriceTier" + _API = locals() diff --git a/GenshinUID/gsuid_utils/api/mys/models.py b/GenshinUID/gsuid_utils/api/mys/models.py index dff192ed..801e9da7 100644 --- a/GenshinUID/gsuid_utils/api/mys/models.py +++ b/GenshinUID/gsuid_utils/api/mys/models.py @@ -591,3 +591,58 @@ class MysGameSwitch(TypedDict): switch_id: int is_public: bool switch_name: str + + +class MysGoods(TypedDict): + goods_id: str + goods_name: str + goods_name_i18n_key: str + goods_desc: str + goods_desc_i18n_key: str + goods_type: Literal['Normal', 'Special'] + goods_unit: str + goods_icon: str + currency: Literal['CNY'] + price: str + symbol: Literal['¥'] + tier_id: Literal['Tier_1'] + bonus_desc: MysGoodsBonus + once_bonus_desc: MysGoodsBonus + available: bool + tips_desc: str + tips_i18n_key: str + battle_pass_limit: str + + +class MysGoodsBonus(TypedDict): + bonus_desc: str + bonus_desc_i18n_key: str + bonus_unit: int + bonus_goods_id: str + bonus_icon: str + + +class MysOrderCheck(TypedDict): + status: int # 900为成功 + amount: str + goods_title: str + goods_num: str + order_no: str + pay_plat: Literal['alipay'] + + +class MysOrder(TypedDict): + goods_id: str + order_no: str + currency: Literal['CNY'] + amount: str + redirect_url: str + foreign_serial: str + encode_order: str + account: str # mysid + create_time: str + ext_info: str + balance: str + method: str + action: str + session_cookie: str diff --git a/GenshinUID/gsuid_utils/api/mys/request.py b/GenshinUID/gsuid_utils/api/mys/request.py index a13d4953..39d1915d 100644 --- a/GenshinUID/gsuid_utils/api/mys/request.py +++ b/GenshinUID/gsuid_utils/api/mys/request.py @@ -19,6 +19,7 @@ from .tools import ( random_text, get_ds_token, generate_os_ds, + gen_payment_sign, get_web_ds_token, generate_passport_ds, ) @@ -28,6 +29,8 @@ from .models import ( MysSign, RegTime, GachaLog, + MysGoods, + MysOrder, SignInfo, SignList, AbyssData, @@ -38,6 +41,7 @@ from .models import ( CalculateInfo, DailyNoteData, GameTokenInfo, + MysOrderCheck, CharDetailData, CookieTokenInfo, LoginTicketInfo, @@ -642,6 +646,90 @@ class MysApi: else: return data + async def get_fetchgoods(self) -> Union[int, List[MysGoods]]: + data = { + 'released_flag': True, + 'game': 'hk4e_cn', + 'region': 'cn_gf01', + 'uid': '1', + 'account': '1', + } + resp = await self._mys_request( + url=_API['fetchGoodsurl'], + method='POST', + data=data, + ) + if isinstance(resp, int): + return resp + return cast(List[MysGoods], resp['data']['goods_list']) + + async def topup(self, uid: str, goods: MysGoods) -> Union[int, MysOrder]: + device_id = str(uuid.uuid4()) + HEADER = copy.deepcopy(_HEADER) + ck = await self.get_ck(uid, 'OWNER') + if ck is None: + return -51 + HEADER['Cookie'] = ck + account = HEADER['Cookie'].split('account_id=')[1].split(';')[0] + order = { + 'account': str(account), + 'region': 'cn_gf01', + 'uid': uid, + 'delivery_url': '', + 'device': device_id, + 'channel_id': 1, + 'client_ip': '', + 'client_type': 4, + 'game': 'hk4e_cn', + 'amount': goods['price'], + # 'amount': 600, + 'goods_num': 1, + 'goods_id': goods['goods_id'], + 'goods_title': f'{goods["goods_name"]}×{str(goods["goods_unit"])}' + if int(goods['goods_unit']) > 0 + else goods['goods_name'], + 'price_tier': goods['tier_id'], + # 'price_tier': 'Tier_1', + 'currency': 'CNY', + 'pay_plat': 'alipay', + } + data = {'order': order, 'sign': gen_payment_sign(order)} + HEADER['x-rpc-device_id'] = device_id + HEADER['x-rpc-client_type'] = '4' + resp = await self._mys_request( + url=_API['CreateOrderurl'], + method='POST', + header=HEADER, + data=data, + ) + if isinstance(resp, int): + return resp + return cast(MysOrder, resp['data']) + + async def check_order( + self, order: MysOrder, uid: str + ) -> Union[int, MysOrderCheck]: + HEADER = copy.deepcopy(_HEADER) + ck = await self.get_ck(uid, 'OWNER') + if ck is None: + return -51 + HEADER['Cookie'] = ck + data = { + 'order_no': order['order_no'], + 'game': 'hk4e_cn', + 'region': 'cn_gf01', + 'uid': uid, + } + resp = await self._mys_request( + url=_API['CheckOrderurl'], + method='GET', + header=HEADER, + params=data, + ) + if isinstance(resp, int): + return resp + return cast(MysOrderCheck, resp['data']) + async def simple_mys_req( self, URL: str, @@ -657,7 +745,6 @@ class MysApi: server_id = RECOGNIZE_SERVER.get(uid[0]) is_os = False if int(uid[0]) < 6 else True ex_params = '&'.join([f'{k}={v}' for k, v in params.items()]) - print(ex_params) if is_os: _URL = _API[f'{URL}_OS'] HEADER = copy.deepcopy(_HEADER_OS) diff --git a/GenshinUID/gsuid_utils/api/mys/tools.py b/GenshinUID/gsuid_utils/api/mys/tools.py index 40ac5c15..97d3154e 100644 --- a/GenshinUID/gsuid_utils/api/mys/tools.py +++ b/GenshinUID/gsuid_utils/api/mys/tools.py @@ -1,3 +1,4 @@ +import hmac import json import time import random @@ -76,3 +77,17 @@ def generate_os_ds(salt: str = '') -> str: def generate_passport_ds(q: str = '', b: Optional[Dict[str, Any]] = None): return _random_str_ds(_S['PD'], string.ascii_letters, True, q, b) + + +def HMCASHA256(data: str, key: str): + key_bytes = key.encode('utf-8') + message = data.encode('utf-8') + sign = hmac.new(key_bytes, message, digestmod=hashlib.sha256).digest() + return sign.hex() + + +def gen_payment_sign(data): + data = dict(sorted(data.items(), key=lambda x: x[0])) + value = ''.join([str(i) for i in data.values()]) + sign = HMCASHA256(value, '6bdc3982c25f3f3c38668a32d287d16b') + return sign diff --git a/poetry.lock b/poetry.lock index 1ef5c79a..aac2e91b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1544,14 +1544,14 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "3.1.0" +version = "3.1.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.1.0-py3-none-any.whl", hash = "sha256:13b08a53ed71021350c9e300d4ea8668438fb0046ab3937ac9a29913a1a1350a"}, - {file = "platformdirs-3.1.0.tar.gz", hash = "sha256:accc3665857288317f32c7bebb5a8e482ba717b474f3fc1d18ca7f9214be0cef"}, + {file = "platformdirs-3.1.1-py3-none-any.whl", hash = "sha256:e5986afb596e4bb5bde29a79ac9061aa955b94fca2399b7aaac4090860920dd8"}, + {file = "platformdirs-3.1.1.tar.gz", hash = "sha256:024996549ee88ec1a9aa99ff7f8fc819bb59e2c3477b410d90a16d32d6e707aa"}, ] [package.extras]