Use SIMNet and Remove genshin.py

Co-authored-by: 洛水居室 <luoshuijs@outlook.com>
Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
This commit is contained in:
omg-xtao 2023-07-19 13:52:30 +08:00 committed by GitHub
parent e0b0156f27
commit fcaf0d398e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1572 additions and 2434 deletions

View File

@ -1,7 +1,7 @@
from typing import List, Optional
import genshin
from genshin import GenshinException, InvalidCookies, TooManyRequests, types, Game
from simnet import StarRailClient, Region, Game
from simnet.errors import InvalidCookies, BadRequest as SimnetBadRequest, TooManyRequests
from core.base_service import BaseService
from core.basemodel import RegionEnum
@ -86,31 +86,29 @@ class PublicCookiesService(BaseService):
await self._cache.delete_public_cookies(public_id, region)
continue
if region == RegionEnum.HYPERION:
client = genshin.Client(cookies=cookies.data, game=types.Game.STARRAIL, region=types.Region.CHINESE)
client = StarRailClient(cookies=cookies.data, region=Region.CHINESE)
elif region == RegionEnum.HOYOLAB:
client = genshin.Client(
cookies=cookies.data, game=types.Game.STARRAIL, region=types.Region.OVERSEAS, lang="zh-cn"
)
client = StarRailClient(cookies=cookies.data, region=Region.OVERSEAS, lang="zh-cn")
else:
raise CookieServiceError
try:
if client.cookie_manager.user_id is None:
if client.account_id is None:
raise RuntimeError("account_id not found")
record_cards = await client.get_record_cards()
for record_card in record_cards:
if record_card.game == Game.STARRAIL:
if record_card.game == Game.GENSHIN:
await client.get_starrail_user(record_card.uid)
break
else:
accounts = await client.get_game_accounts()
for account in accounts:
if account.game == Game.STARRAIL:
if account.game == Game.GENSHIN:
await client.get_starrail_user(account.uid)
break
except InvalidCookies as exc:
if exc.retcode in (10001, -100):
if exc.ret_code in (10001, -100):
logger.warning("用户 [%s] Cookies无效", public_id)
elif exc.retcode == 10103:
elif exc.ret_code == 10103:
logger.warning("用户 [%s] Cookies有效但没有绑定到游戏帐户", public_id)
else:
logger.warning("Cookies无效 ")
@ -125,10 +123,10 @@ class PublicCookiesService(BaseService):
await self._repository.update(cookies)
await self._cache.delete_public_cookies(cookies.user_id, region)
continue
except GenshinException as exc:
if "invalid content type" in exc.msg:
except SimnetBadRequest as exc:
if "invalid content type" in exc.message:
raise exc
if exc.retcode == 1034:
if exc.ret_code == 1034:
logger.warning("用户 [%s] 触发验证", public_id)
else:
logger.warning("用户 [%s] 获取账号信息发生错误,错误信息为", public_id)
@ -145,6 +143,8 @@ class PublicCookiesService(BaseService):
except Exception as exc:
await self._cache.delete_public_cookies(cookies.user_id, region)
raise exc
finally:
await client.shutdown()
logger.info("用户 user_id[%s] 请求用户 user_id[%s] 的公共Cookies 该Cookies使用次数为%s", user_id, public_id, count)
return cookies

View File

@ -34,9 +34,6 @@ class AuthClient:
QRCODE_GET_API = f"https://{HK4E_SDK_HOST}/hk4e_cn/combo/panda/qrcode/query"
GET_COOKIE_ACCOUNT_BY_GAME_TOKEN_API = f"https://{TAKUMI_HOST}/auth/api/getCookieAccountInfoByGameToken"
GET_TOKEN_BY_GAME_LTOKEN_API = f"https://{PASSPORT_HOST}/account/ma-cn-session/app/getTokenByGameToken"
GET_COOKIES_TOKEN_BY_STOKEN_API = f"https://{PASSPORT_HOST}/account/auth/api/getCookieAccountInfoBySToken"
GET_LTOKEN_BY_STOKEN_API = f"https://{PASSPORT_HOST}/account/auth/api/getLTokenBySToken"
get_STOKEN_URL = f"https://{TAKUMI_HOST}/auth/api/getMultiTokenByLoginTicket"
def __init__(
self,
@ -60,24 +57,6 @@ class AuthClient:
else:
self.user_id = self.cookies.user_id
async def get_stoken_by_login_ticket(self) -> bool:
if self.cookies.login_ticket is None and self.user_id is None:
return False
params = {"login_ticket": self.cookies.login_ticket, "uid": self.user_id, "token_types": 3}
data = await self.client.get(self.get_STOKEN_URL, params=params, headers={"User-Agent": self.USER_AGENT})
res_json = data.json()
res_data = res_json.get("data", {}).get("list", [])
for i in res_data:
name = i.get("name")
token = i.get("token")
if name and token and hasattr(self.cookies, name):
setattr(self.cookies, name, token)
if self.cookies.stoken:
if self.cookies.stuid:
self.cookies.stuid = self.user_id
return True
return False
async def get_ltoken_by_game_token(self, game_token: str) -> bool:
if self.user_id is None:
return False
@ -154,68 +133,6 @@ class AuthClient:
if res_data.get("stat", "") == "Confirmed":
return await self._set_cookie_by_game_token(res_json.get("data", {}))
async def get_cookie_token_by_stoken(self) -> bool:
if self.cookies.stoken is None:
return False
user_id = self.cookies.user_id
headers = {
"x-rpc-app_version": "2.11.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.11.1"
),
"x-rpc-client_type": "5",
"Referer": "https://webstatic.mihoyo.com/",
"Origin": "https://webstatic.mihoyo.com",
}
params = {
"stoken": self.cookies.stoken,
"uid": user_id,
}
res = await self.client.get(
self.GET_COOKIES_TOKEN_BY_STOKEN_API,
headers=headers,
params=params,
)
res_json = res.json()
cookie_token = res_json.get("data", {}).get("cookie_token", "")
if cookie_token:
self.cookies.cookie_token = cookie_token
self.cookies.account_id = user_id
return True
return False
async def get_ltoken_by_stoken(self) -> bool:
if self.cookies.stoken is None:
return False
user_id = self.cookies.user_id
headers = {
"x-rpc-app_version": "2.11.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.11.1"
),
"x-rpc-client_type": "5",
"Referer": "https://webstatic.mihoyo.com/",
"Origin": "https://webstatic.mihoyo.com",
}
params = {
"stoken": self.cookies.stoken,
"uid": user_id,
}
res = await self.client.get(
self.GET_LTOKEN_BY_STOKEN_API,
headers=headers,
params=params,
)
res_json = res.json()
ltoken = res_json.get("data", {}).get("ltoken", "")
if ltoken:
self.cookies.ltoken = ltoken
self.cookies.ltuid = user_id
return True
return False
@staticmethod
def generate_qrcode(url: str) -> bytes:
qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4)

View File

@ -1,7 +1,9 @@
import json
import re
import time
from typing import Dict, Optional
from typing import Dict, Optional, Union
from httpx import Cookies
from ..base.hyperionrequest import HyperionRequest
from ...utility.devices import devices_methods
@ -37,7 +39,7 @@ class Verify:
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
}
def __init__(self, account_id: int = None, cookies: Dict = None):
def __init__(self, account_id: int = None, cookies: Union[Dict, Cookies] = None):
self.account_id = account_id
self.client = HyperionRequest(headers=self.BBS_HEADERS, cookies=cookies)

View File

@ -1,4 +1,4 @@
from genshin.models.genshin.gacha import StarRailBannerType
from simnet.models.starrail.wish import StarRailBannerType
SRGF_VERSION = "v1.0"

View File

@ -4,13 +4,14 @@ import datetime
import json
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from typing import Dict, List, Optional, Tuple, TYPE_CHECKING
import aiofiles
from genshin import AuthkeyTimeout, Client, InvalidAuthkey
from genshin.models.genshin.gacha import StarRailBannerType
from simnet import StarRailClient, Region
from simnet.errors import AuthkeyTimeout, InvalidAuthkey
from simnet.models.starrail.wish import StarRailBannerType
from simnet.utils.player import recognize_starrail_server
from core.dependence.assets import AssetsService
from metadata.pool.pool import get_pool_by_id
from modules.gacha_log.const import GACHA_TYPE_LIST
from modules.gacha_log.error import (
@ -35,6 +36,10 @@ from modules.gacha_log.models import (
)
from utils.const import PROJECT_ROOT
if TYPE_CHECKING:
from core.dependence.assets import AssetsService
GACHA_LOG_PATH = PROJECT_ROOT.joinpath("data", "apihelper", "warp_log")
GACHA_LOG_PATH.mkdir(parents=True, exist_ok=True)
@ -98,7 +103,7 @@ class GachaLog:
async def save_gacha_log_info(self, user_id: str, uid: str, info: GachaLogInfo):
"""保存跃迁记录数据
:param user_id: 用户id
:param uid: 原神uid
:param uid: 玩家uid
:param info: 跃迁记录数据
"""
save_path = self.gacha_log_path / f"{user_id}-{uid}.json"
@ -166,13 +171,13 @@ class GachaLog:
new_num += 1
return new_num
async def import_gacha_log_data(self, user_id: int, client: Client, data: dict, verify_uid: bool = True) -> int:
async def import_gacha_log_data(self, user_id: int, player_id: int, data: dict, verify_uid: bool = True) -> int:
new_num = 0
try:
uid = data["info"]["uid"]
if not verify_uid:
uid = client.uid
elif int(uid) != client.uid:
uid = player_id
elif int(uid) != player_id:
raise GachaLogAccountNotFound
try:
import_type = ImportType(data["info"]["export_app"])
@ -208,20 +213,29 @@ class GachaLog:
except Exception as exc:
raise GachaLogException from exc
async def get_gacha_log_data(self, user_id: int, client: Client, authkey: str) -> int:
@staticmethod
def get_game_client(player_id: int) -> StarRailClient:
if recognize_starrail_server(player_id) in ["prod_gf_cn", "prod_qd_cn"]:
return StarRailClient(player_id=player_id, region=Region.CHINESE, lang="zh-cn")
else:
return StarRailClient(player_id=player_id, region=Region.OVERSEAS, lang="zh-cn")
async def get_gacha_log_data(self, user_id: int, player_id: int, authkey: str) -> int:
"""使用authkey获取跃迁记录数据并合并旧数据
:param user_id: 用户id
:param client: genshin client
:param player_id: 玩家id
:param authkey: authkey
:return: 更新结果
"""
new_num = 0
gacha_log, _ = await self.load_history_info(str(user_id), str(client.uid))
gacha_log, _ = await self.load_history_info(str(user_id), str(player_id))
# 将唯一 id 放入临时数据中,加快查找速度
temp_id_data = {pool_name: {i.id: i for i in pool_data} for pool_name, pool_data in gacha_log.item_list.items()}
client = self.get_game_client(player_id)
try:
for pool_id, pool_name in GACHA_TYPE_LIST.items():
async for data in client.warp_history(pool_id, authkey=authkey):
wish_history = await client.wish_history(pool_id.value, authkey=authkey)
for data in wish_history:
item = GachaItem(
id=str(data.id),
name=data.name,
@ -252,11 +266,13 @@ class GachaLog:
raise GachaLogAuthkeyTimeout from exc
except InvalidAuthkey as exc:
raise GachaLogInvalidAuthkey from exc
finally:
await client.shutdown()
for i in gacha_log.item_list.values():
i.sort(key=lambda x: (x.time, x.id))
gacha_log.update_time = datetime.datetime.now()
gacha_log.import_type = ImportType.PaiGram.value
await self.save_gacha_log_info(str(user_id), str(client.uid), gacha_log)
await self.save_gacha_log_info(str(user_id), str(player_id), gacha_log)
return new_num
@staticmethod
@ -265,7 +281,7 @@ class GachaLog:
return False
return True
async def get_all_5_star_items(self, data: List[GachaItem], assets: AssetsService, pool_name: str = "角色跃迁"):
async def get_all_5_star_items(self, data: List[GachaItem], assets: "AssetsService", pool_name: str = "角色跃迁"):
"""
获取所有5星角色
:param data: 跃迁记录
@ -314,7 +330,7 @@ class GachaLog:
return result, count
@staticmethod
async def get_all_4_star_items(data: List[GachaItem], assets: AssetsService):
async def get_all_4_star_items(data: List[GachaItem], assets: "AssetsService"):
"""
获取 no_fout_star
:param data: 跃迁记录
@ -475,16 +491,16 @@ class GachaLog:
return f"{pool_name} · 非"
return pool_name
async def get_analysis(self, user_id: int, client: Client, pool: StarRailBannerType, assets: AssetsService):
async def get_analysis(self, user_id: int, player_id: int, pool: StarRailBannerType, assets: "AssetsService"):
"""
获取跃迁记录分析数据
:param user_id: 用户id
:param client: genshin client
:param player_id: 玩家id
:param pool: 池子类型
:param assets: 资源服务
:return: 分析数据
"""
gacha_log, status = await self.load_history_info(str(user_id), str(client.uid))
gacha_log, status = await self.load_history_info(str(user_id), str(player_id))
if not status:
raise GachaLogNotFound
pool_name = GACHA_TYPE_LIST[pool]
@ -507,7 +523,7 @@ class GachaLog:
last_time = data[0].time.strftime("%Y-%m-%d %H:%M")
first_time = data[-1].time.strftime("%Y-%m-%d %H:%M")
return {
"uid": client.uid,
"uid": player_id,
"allNum": total,
"type": pool.value,
"typeName": pool_name,
@ -519,17 +535,17 @@ class GachaLog:
}
async def get_pool_analysis(
self, user_id: int, client: Client, pool: StarRailBannerType, assets: AssetsService, group: bool
self, user_id: int, player_id: int, pool: StarRailBannerType, assets: "AssetsService", group: bool
) -> dict:
"""获取跃迁记录分析数据
:param user_id: 用户id
:param client: genshin client
:param player_id: 玩家id
:param pool: 池子类型
:param assets: 资源服务
:param group: 是否群组
:return: 分析数据
"""
gacha_log, status = await self.load_history_info(str(user_id), str(client.uid))
gacha_log, status = await self.load_history_info(str(user_id), str(player_id))
if not status:
raise GachaLogNotFound
pool_name = GACHA_TYPE_LIST[pool]
@ -559,20 +575,20 @@ class GachaLog:
)
pool_data = [i for i in pool_data if i["count"] > 0]
return {
"uid": client.uid,
"uid": player_id,
"typeName": pool_name,
"pool": pool_data[:6] if group else pool_data,
"hasMore": len(pool_data) > 6,
}
async def get_all_five_analysis(self, user_id: int, client: Client, assets: AssetsService) -> dict:
async def get_all_five_analysis(self, user_id: int, player_id: int, assets: "AssetsService") -> dict:
"""获取五星跃迁记录分析数据
:param user_id: 用户id
:param client: genshin client
:param player_id: 玩家id
:param assets: 资源服务
:return: 分析数据
"""
gacha_log, status = await self.load_history_info(str(user_id), str(client.uid))
gacha_log, status = await self.load_history_info(str(user_id), str(player_id))
if not status:
raise GachaLogNotFound
pools = []
@ -600,7 +616,7 @@ class GachaLog:
for up_pool in pools
]
return {
"uid": client.uid,
"uid": player_id,
"typeName": "五星列表",
"pool": pool_data,
"hasMore": False,

View File

@ -47,24 +47,24 @@ class GachaItem(BaseModel):
if item_id := (roleToId(v) or lightConeToId(v)):
if item_id not in not_real_roles:
return v
raise ValueError("Invalid name")
raise ValueError(f"Invalid name {v}")
@validator("gacha_type")
def check_gacha_type(cls, v):
if v not in {"1", "2", "11", "12"}:
raise ValueError("gacha_type must be 1, 2, 11 or 12")
raise ValueError(f"gacha_type must be 1, 2, 11 or 12, invalid value: {v}")
return v
@validator("item_type")
def check_item_type(cls, item):
if item not in {"角色", "光锥"}:
raise ValueError("error item type")
raise ValueError(f"error item type {item}")
return item
@validator("rank_type")
def check_rank_type(cls, rank):
if rank not in {"5", "4", "3"}:
raise ValueError("error rank type")
raise ValueError(f"error rank type {rank}")
return rank

View File

@ -1,26 +0,0 @@
class PayLogException(Exception):
pass
class PayLogFileError(PayLogException):
pass
class PayLogNotFound(PayLogFileError):
pass
class PayLogAccountNotFound(PayLogException):
pass
class PayLogAuthkeyException(PayLogException):
pass
class PayLogAuthkeyTimeout(PayLogAuthkeyException):
pass
class PayLogInvalidAuthkey(PayLogAuthkeyException):
pass

View File

@ -1,234 +0,0 @@
import contextlib
from pathlib import Path
from typing import Tuple, Optional, List, Dict
import aiofiles
from genshin import Client, AuthkeyTimeout, InvalidAuthkey
from genshin.models import TransactionKind, BaseTransaction
from modules.pay_log.error import PayLogAuthkeyTimeout, PayLogInvalidAuthkey, PayLogNotFound
from modules.pay_log.models import PayLog as PayLogModel, BaseInfo
from utils.const import PROJECT_ROOT
try:
import ujson as jsonlib
except ImportError:
import json as jsonlib
PAY_LOG_PATH = PROJECT_ROOT.joinpath("data", "apihelper", "pay_log")
PAY_LOG_PATH.mkdir(parents=True, exist_ok=True)
class PayLog:
def __init__(self, pay_log_path: Path = PAY_LOG_PATH):
self.pay_log_path = pay_log_path
@staticmethod
async def load_json(path):
async with aiofiles.open(path, "r", encoding="utf-8") as f:
return jsonlib.loads(await f.read())
@staticmethod
async def save_json(path, data: PayLogModel):
async with aiofiles.open(path, "w", encoding="utf-8") as f:
return await f.write(data.json(ensure_ascii=False, indent=4))
def get_file_path(
self,
user_id: str,
uid: str,
bak: bool = False,
) -> Path:
"""获取文件路径
:param user_id: 用户 ID
:param uid: UID
:param bak: 是否为备份文件
:return: 文件路径
"""
return self.pay_log_path / f"{user_id}-{uid}.json{'.bak' if bak else ''}"
async def load_history_info(
self,
user_id: str,
uid: str,
only_status: bool = False,
) -> Tuple[Optional[PayLogModel], bool]:
"""读取历史记录数据
:param user_id: 用户id
:param uid: 原神uid
:param only_status: 是否只读取状态
:return: 抽卡记录数据
"""
file_path = self.get_file_path(user_id, uid)
if only_status:
return None, file_path.exists()
if not file_path.exists():
return PayLogModel(info=BaseInfo(uid=uid), list=[]), False
try:
return PayLogModel.parse_obj(await self.load_json(file_path)), True
except jsonlib.JSONDecodeError:
return PayLogModel(info=BaseInfo(uid=uid), list=[]), False
async def remove_history_info(
self,
user_id: str,
uid: str,
) -> bool:
"""删除历史记录数据
:param user_id: 用户id
:param uid: 原神uid
:return: 是否删除成功
"""
file_path = self.get_file_path(user_id, uid)
file_bak_path = self.get_file_path(user_id, uid, bak=True)
with contextlib.suppress(Exception):
file_bak_path.unlink(missing_ok=True)
if file_path.exists():
try:
file_path.unlink()
except PermissionError:
return False
return True
return False
async def save_pay_log_info(self, user_id: str, uid: str, info: PayLogModel) -> None:
"""保存日志记录数据
:param user_id: 用户id
:param uid: 原神uid
:param info: 记录数据
"""
save_path = self.pay_log_path / f"{user_id}-{uid}.json"
save_path_bak = self.pay_log_path / f"{user_id}-{uid}.json.bak"
# 将旧数据备份一次
with contextlib.suppress(PermissionError):
if save_path.exists():
if save_path_bak.exists():
save_path_bak.unlink()
save_path.rename(save_path.parent / f"{save_path.name}.bak")
# 写入数据
await self.save_json(save_path, info)
async def get_log_data(
self,
user_id: int,
client: Client,
authkey: str,
) -> int:
"""使用 authkey 获取历史记录数据,并合并旧数据
:param user_id: 用户id
:param client: genshin client
:param authkey: authkey
:return: 更新结果
"""
new_num = 0
pay_log, have_old = await self.load_history_info(str(user_id), str(client.uid))
history_ids = [i.id for i in pay_log.list]
try:
async for data in client.transaction_log(TransactionKind.CRYSTAL, authkey=authkey):
if data.id not in history_ids:
pay_log.list.append(data)
new_num += 1
except AuthkeyTimeout as exc:
raise PayLogAuthkeyTimeout from exc
except InvalidAuthkey as exc:
raise PayLogInvalidAuthkey from exc
if new_num > 0 or have_old:
pay_log.list.sort(key=lambda x: (x.time, x.id), reverse=True)
pay_log.info.update_now()
await self.save_pay_log_info(str(user_id), str(client.uid), pay_log)
return new_num
@staticmethod
async def get_month_data(pay_log: PayLogModel, price_data: List[Dict]) -> Tuple[int, List[Dict]]:
"""获取月份数据
:param pay_log: 日志数据
:param price_data: 商品数据
:return: 月份数据
"""
all_amount: int = 0
months: List[int] = []
month_datas: List[Dict] = []
last_month: Optional[Dict] = None
month_data: List[Optional[BaseTransaction]] = []
for i in pay_log.list:
if i.amount <= 0:
continue
all_amount += i.amount
if i.time.month not in months:
months.append(i.time.month)
if last_month:
last_month["amount"] = sum(i.amount for i in month_data)
month_data.clear()
if len(months) <= 6:
last_month = {
"month": f"{i.time.month}",
"amount": 0,
}
month_datas.append(last_month)
else:
last_month = None
for j in price_data:
if i.amount in j["price"]:
j["count"] += 1
break
month_data.append(i)
if last_month:
last_month["amount"] = sum(i.amount for i in month_data)
month_data.clear()
if not month_datas:
raise PayLogNotFound
return all_amount, month_datas
async def get_analysis(self, user_id: int, client: Client):
"""获取分析数据
:param user_id: 用户id
:param client: genshin client
:return: 分析数据
"""
pay_log, status = await self.load_history_info(str(user_id), str(client.uid))
if not status:
raise PayLogNotFound
# 单双倍结晶数
price_data = [
{
"price": price,
"count": 0,
}
for price in [[680], [300], [8080, 12960], [3880, 6560], [2240, 3960], [1090, 1960], [330, 600], [60, 120]]
]
price_data_name = ["大月卡", "小月卡", "648", "328", "198", "98", "30", "6"]
real_price = [68, 30, 648, 328, 198, 98, 30, 6]
all_amount, month_datas = await PayLog.get_month_data(pay_log, price_data)
month_data = sorted(month_datas, key=lambda k: k["amount"], reverse=True)
all_pay = sum((price_data[i]["count"] * real_price[i]) for i in range(len(price_data)))
datas = [
{"value": f"{all_pay:.0f}", "name": "总消费"},
{"value": all_amount, "name": "总结晶"},
{"value": f"{month_data[0]['month']}", "name": "消费最多"},
{
"value": f"{month_data[0]['amount'] / 10:.0f}",
"name": f"{month_data[0]['month']}消费",
},
*[
{
"value": price_data[i]["count"] if i != 0 else "*",
"name": f"{price_data_name[i]}",
}
for i in range(len(price_data))
],
]
pie_datas = [
{
"value": f"{price_data[i]['count'] * real_price[i]:.0f}",
"name": f"{price_data_name[i]}",
}
for i in range(len(price_data))
if price_data[i]["count"] > 0
]
return {
"uid": client.uid,
"datas": datas,
"bar_data": month_datas,
"pie_data": pie_datas,
}

View File

@ -1,41 +0,0 @@
import datetime
from typing import Any, List
from genshin.models import BaseTransaction
from pydantic import BaseModel, BaseConfig
try:
import ujson as jsonlib
except ImportError:
import json as jsonlib
class _ModelConfig(BaseConfig):
json_dumps = jsonlib.dumps
json_loads = jsonlib.loads
class BaseInfo(BaseModel):
Config = _ModelConfig
uid: str = "0"
lang: str = "zh-cn"
export_time: str = ""
export_timestamp: int = 0
export_app: str = "PaimonBot"
def __init__(self, **data: Any):
super().__init__(**data)
if not self.export_time:
self.update_now()
def update_now(self):
self.export_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.export_timestamp = int(datetime.datetime.now().timestamp())
class PayLog(BaseModel):
Config = _ModelConfig
info: BaseInfo
list: List[BaseTransaction]

View File

@ -1,8 +1,13 @@
from datetime import datetime
from typing import Optional, TYPE_CHECKING
import genshin
from genshin import DataNotPublic, GenshinException, types, AccountNotFound, InvalidCookies
from simnet import StarRailClient, Region
from simnet.errors import (
InvalidCookies,
BadRequest as SimnetBadRequest,
DataNotPublic,
AccountNotFound,
)
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, TelegramObject
from telegram.ext import ConversationHandler, filters
from telegram.helpers import escape_markdown
@ -132,32 +137,25 @@ class BindAccountPlugin(Plugin.Conversation):
await message.reply_text("用户查询次数过多,请稍后重试", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
if region == RegionEnum.HYPERION:
client = genshin.Client(cookies=cookies.data, game=types.Game.STARRAIL, region=types.Region.CHINESE)
client = StarRailClient(cookies=cookies.data, region=Region.CHINESE, lang="zh-cn")
elif region == RegionEnum.HOYOLAB:
client = genshin.Client(
cookies=cookies.data, game=types.Game.STARRAIL, region=types.Region.OVERSEAS, lang="zh-cn"
)
client = StarRailClient(cookies=cookies.data, region=Region.OVERSEAS, lang="zh-cn")
else:
return ConversationHandler.END
try:
record_cards = await client.get_record_cards(account_id)
record_card = record_cards[0]
for card in record_cards:
if card.game == types.Game.STARRAIL:
record_card = card
break
record_card = await client.get_record_card(account_id)
if record_card is None:
await message.reply_text("请在设置展示主界面添加崩坏:星穹铁道", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
except DataNotPublic:
await message.reply_text("角色未公开", reply_markup=ReplyKeyboardRemove())
logger.warning("获取账号信息发生错误 %s 账户信息未公开", account_id)
return ConversationHandler.END
except GenshinException as exc:
except SimnetBadRequest as exc:
await message.reply_text("获取账号信息发生错误", reply_markup=ReplyKeyboardRemove())
logger.error("获取账号信息发生错误")
logger.exception(exc)
return ConversationHandler.END
if record_card.game != types.Game.STARRAIL:
await message.reply_text("角色信息查询返回无星穹铁道游戏信息,请确定你有星穹铁道账号", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
player_info = await self.players_service.get(
user.id, player_id=record_card.uid, region=bind_account_plugin_data.region
)
@ -201,11 +199,9 @@ class BindAccountPlugin(Plugin.Conversation):
await message.reply_text("用户查询次数过多,请稍后重试", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
if region == RegionEnum.HYPERION:
client = genshin.Client(cookies=cookies.data, game=types.Game.STARRAIL, region=types.Region.CHINESE)
client = StarRailClient(cookies=cookies.data, region=Region.CHINESE, lang="zh-cn")
elif region == RegionEnum.HOYOLAB:
client = genshin.Client(
cookies=cookies.data, game=types.Game.STARRAIL, region=types.Region.OVERSEAS, lang="zh-cn"
)
client = StarRailClient(cookies=cookies.data, region=Region.OVERSEAS, lang="zh-cn")
else:
return ConversationHandler.END
try:
@ -222,7 +218,7 @@ class BindAccountPlugin(Plugin.Conversation):
await self.public_cookies_service.undo(user.id, cookies, CookiesStatusEnum.INVALID_COOKIES)
await message.reply_text("出错了呜呜呜 ~ 请稍后重试")
return ConversationHandler.END
except GenshinException as exc:
except SimnetBadRequest as exc:
if exc.retcode == 1034:
await self.public_cookies_service.undo(user.id)
await message.reply_text("出错了呜呜呜 ~ 请稍后重试")
@ -234,6 +230,8 @@ class BindAccountPlugin(Plugin.Conversation):
except ValueError:
await message.reply_text("ID 格式有误,请检查", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
finally:
await client.shutdown()
player_info = await self.players_service.get(
user.id, player_id=player_id, region=bind_account_plugin_data.region
)

View File

@ -1,10 +1,10 @@
from datetime import datetime
from typing import Dict, Optional
import genshin
from arkowrapper import ArkoWrapper
from genshin import DataNotPublic, GenshinException, InvalidCookies, types
from genshin.models import GenshinAccount
from simnet import StarRailClient, Region
from simnet.errors import DataNotPublic, InvalidCookies, BadRequest as SimnetBadRequest
from simnet.models.lab.record import Account
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, TelegramObject, Update
from telegram.constants import ParseMode
from telegram.ext import CallbackContext, ConversationHandler, filters
@ -34,7 +34,7 @@ class AccountCookiesPluginData(TelegramObject):
cookies: dict = {}
account_id: int = 0
# player_id: int = 0
starrail_account: Optional[GenshinAccount] = None
starrail_account: Optional[Account] = None
def reset(self):
self.player = None
@ -136,44 +136,32 @@ class AccountCookiesPlugin(Plugin.Conversation):
return CHECK_SERVER
account_cookies_plugin_data.region = region
await message.reply_text(f"请输入{bbs_name}的Cookies或回复退出取消操作", reply_markup=ReplyKeyboardRemove())
if bbs_name == "米游社":
help_message = (
"<b>关于如何获取Cookies</b>\n"
"<b>现在因为网站HttpOnly策略无法通过脚本获取因此操作只能在PC上运行。</b>\n\n"
"PC\n"
"1、打开<a href='https://user.mihoyo.com/'>通行证</a>或<a href='https://www.miyoushe.com/ys/'>社区</a>并登录\n"
"2、进入通行证按F12打开开发者工具\n"
"3、将开发者工具切换至网络(Network)并点击过滤栏中的文档(Document)并刷新页面\n"
"4、在请求列表中选择第一个并点击\n"
"5、找到并复制请求标头(Request Headers)中的<b>Cookie</b>\n"
"<u>如发现没有请求标头(Request Headers)大概因为缓存的存在需要你点击禁用缓存(Disable Cache)再次刷新页面</u>"
)
else:
javascript = (
"javascript:(()=>{_=(n)=>{for(i in(r=document.cookie.split(';'))){var a=r[i].split('=');if(a["
"0].trim()==n)return a[1]}};c=_('account_id')||alert('无效的Cookie,请重新登录!');c&&confirm("
"'将Cookie复制到剪贴板?')&&copy(document.cookie)})(); "
)
javascript_android = "javascript:(()=>{prompt('',document.cookie)})();"
help_message = (
f"<b>关于如何获取Cookies</b>\n\n"
f"PC\n"
f"1、<a href='https://www.hoyolab.com/home'>打开社区并登录</a>\n"
"2、按F12打开开发者工具\n"
"3、将开发者工具切换至控制台(Console)\n"
"4、复制下方的代码并将其粘贴在控制台中按下回车\n"
f"<pre><code class='javascript'>{javascript}</code></pre>\n"
"Android\n"
f"1、<a href='https://www.hoyolab.com/home'>通过 Via 打开 {bbs_name} 并登录</a>\n"
"2、复制下方的代码并将其粘贴在地址栏中点击右侧箭头\n"
f"<code>{javascript_android}</code>\n"
"iOS\n"
"1、在App Store上安装Web Inspector并在iOS设置- Safari浏览器-扩展-允许这些扩展下找到Web Inspector-打开,允许所有网站\n"
f"2、<a href='https://www.hoyolab.com/home'>通过 Safari 打开 {bbs_name} 并登录</a>\n"
"3、点击地址栏左侧的大小按钮 - Web Inspector扩展 - Console - 点击下方文本框复制下方代码粘贴:\n"
f"<pre><code class='javascript'>{javascript}</code></pre>\n"
"4、点击Console下的Execute"
)
javascript = (
"javascript:(()=>{_=(n)=>{for(i in(r=document.cookie.split(';'))){var a=r[i].split('=');if(a["
"0].trim()==n)return a[1]}};c=_('login_ticket')||alert('无效的Cookie,请重新登录!');c&&confirm("
"'将Cookie复制到剪贴板?')&&copy(document.cookie)})(); "
)
javascript_android = "javascript:(()=>{prompt('',document.cookie)})();"
account_host = "https://user.mihoyo.com" if bbs_name == "米游社" else "https://account.hoyoverse.com"
help_message = (
"<b>关于如何获取Cookies</b>\n\n"
"PC\n"
f"1、打开<a href='{account_host}'>通行证</a>并登录\n"
"2、按F12打开开发者工具\n"
"3、将开发者工具切换至控制台(Console)\n"
"4、复制下方的代码并将其粘贴在控制台中按下回车\n"
f"<pre><code class='javascript'>{javascript}</code></pre>\n"
"Android\n"
f"1、通过 Via 打开<a href='{account_host}'>通行证</a>并登录\n"
"2、复制下方的代码并将其粘贴在地址栏中点击右侧箭头\n"
f"<code>{javascript_android}</code>\n"
"iOS\n"
"1、在App Store上安装Web Inspector并在iOS设置- Safari浏览器-扩展-允许这些扩展下找到Web Inspector-打开,允许所有网站\n"
f"2、通过 Safari 打开<a href='{account_host}'>通行证</a>并登录\n"
"3、点击地址栏左侧的大小按钮 - Web Inspector扩展 - Console - 点击下方文本框复制下方代码粘贴:\n"
f"<pre><code class='javascript'>{javascript}</code></pre>\n"
"4、点击Console下的Execute"
)
await message.reply_html(help_message, disable_web_page_preview=True)
return INPUT_COOKIES
@ -214,77 +202,75 @@ class AccountCookiesPlugin(Plugin.Conversation):
account_cookies_plugin_data: AccountCookiesPluginData = context.chat_data.get("account_cookies_plugin_data")
cookies = CookiesModel(**account_cookies_plugin_data.cookies)
if account_cookies_plugin_data.region == RegionEnum.HYPERION:
client = genshin.Client(cookies=cookies.to_dict(), region=types.Region.CHINESE, game=types.Game.STARRAIL)
region = Region.CHINESE
elif account_cookies_plugin_data.region == RegionEnum.HOYOLAB:
client = genshin.Client(cookies=cookies.to_dict(), region=types.Region.OVERSEAS, game=types.Game.STARRAIL)
region = Region.OVERSEAS
else:
logger.error("用户 %s[%s] region 异常", user.full_name, user.id)
await message.reply_text("数据错误", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
if not cookies.check():
await message.reply_text("检测到Cookie不完整可能会出现问题。", reply_markup=ReplyKeyboardRemove())
try:
if client.cookie_manager.user_id is None and cookies.is_v2:
logger.info("检测到用户 %s[%s] 使用 V2 Cookie 正在尝试获取 account_id", user.full_name, user.id)
if client.region == types.Region.CHINESE:
account_info = await client.get_hoyolab_user()
account_id = account_info.hoyolab_id
async with StarRailClient(cookies=cookies.to_dict(), region=region, lang="zh-cn") as client:
check_cookie = cookies.check()
if cookies.login_ticket is not None:
try:
cookies.cookie_token = await client.get_cookie_token_by_login_ticket()
cookies.account_id = client.account_id
cookies.ltuid = client.account_id
logger.success("用户 %s[%s] 绑定时获取 cookie_token 成功", user.full_name, user.id)
cookies.stoken = await client.get_stoken_by_login_ticket()
logger.success("用户 %s[%s] 绑定时获取 stoken 成功", user.full_name, user.id)
cookies.ltoken = await client.get_ltoken_by_stoken()
logger.success("用户 %s[%s] 绑定时获取 ltoken 成功", user.full_name, user.id)
check_cookie = True
except SimnetBadRequest as exc:
logger.warning("用户 %s[%s] 获取账号信息发生错误 [%s]%s", user.full_name, user.id, exc.ret_code, exc.original)
except Exception as exc:
logger.error("绑定时获取新Cookie失败 [%s]", (str(exc)))
finally:
cookies.login_ticket = None
cookies.login_uid = None
if not check_cookie:
await message.reply_text("检测到Cookie不完整可能会出现问题。", reply_markup=ReplyKeyboardRemove())
try:
if client.account_id is None and cookies.is_v2:
logger.info("检测到用户 %s[%s] 使用 V2 Cookie 正在尝试获取 account_id", user.full_name, user.id)
account_info = await client.get_user_info()
account_id = account_info.accident_id
account_cookies_plugin_data.account_id = account_id
cookies.set_v2_uid(account_id)
logger.success("获取用户 %s[%s] account_id[%s] 成功", user.full_name, user.id, account_id)
else:
logger.warning("用户 %s[%s] region[%s] 也许是不正确的", user.full_name, user.id, client.region.name)
else:
account_cookies_plugin_data.account_id = client.cookie_manager.user_id
accounts = await client.get_game_accounts()
starrail_accounts = [account for account in accounts if account.game == types.Game.STARRAIL]
except DataNotPublic:
logger.info("用户 %s[%s] 账号疑似被注销", user.full_name, user.id)
await message.reply_text("账号疑似被注销,请检查账号状态", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
except InvalidCookies:
logger.info("用户 %s[%s] Cookies已经过期", user.full_name, user.id)
await message.reply_text(
"获取账号信息失败返回Cookies已经过期请尝试在无痕浏览器中登录获取Cookies。", reply_markup=ReplyKeyboardRemove()
)
return ConversationHandler.END
except GenshinException as exc:
logger.info("用户 %s[%s] 获取账号信息发生错误 [%s]%s", user.full_name, user.id, exc.retcode, exc.original)
await message.reply_text(
f"获取账号信息发生错误,错误信息为 {exc.original}请检查Cookie或者账号是否正常", reply_markup=ReplyKeyboardRemove()
)
return ConversationHandler.END
except AccountIdNotFound:
logger.info("用户 %s[%s] 无法获取账号ID", user.full_name, user.id)
await message.reply_text("无法获取账号ID请检查Cookie是否正常", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
except (AttributeError, ValueError) as exc:
logger.warning("用户 %s[%s] Cookies错误", user.full_name, user.id)
logger.debug("用户 %s[%s] Cookies错误", user.full_name, user.id, exc_info=exc)
await message.reply_text("Cookies错误请检查是否正确", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
if cookies.login_ticket is not None:
try:
if cookies.login_ticket is not None:
auth_client = AuthClient(cookies=cookies)
if await auth_client.get_stoken_by_login_ticket():
logger.success("用户 %s[%s] 绑定时获取 stoken 成功", user.full_name, user.id)
if await auth_client.get_cookie_token_by_stoken():
logger.success("用户 %s[%s] 绑定时获取 cookie_token 成功", user.full_name, user.id)
if await auth_client.get_ltoken_by_stoken():
logger.success("用户 %s[%s] 绑定时获取 ltoken 成功", user.full_name, user.id)
auth_client.cookies.remove_v2()
except Exception as exc: # pylint: disable=W0703
logger.error("绑定时获取新Cookie失败 [%s]", (str(exc)))
finally:
if cookies.user_id is not None:
account_cookies_plugin_data.account_id = cookies.user_id
cookies.login_ticket = None
cookies.login_uid = None
account_cookies_plugin_data.account_id = client.account_id
starrail_accounts = await client.get_starrail_accounts()
except DataNotPublic:
logger.info("用户 %s[%s] 账号疑似被注销", user.full_name, user.id)
await message.reply_text("账号疑似被注销,请检查账号状态", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
except InvalidCookies:
logger.info("用户 %s[%s] Cookies已经过期", user.full_name, user.id)
await message.reply_text(
"获取账号信息失败返回Cookies已经过期请尝试在无痕浏览器中登录获取Cookies。", reply_markup=ReplyKeyboardRemove()
)
return ConversationHandler.END
except SimnetBadRequest as exc:
logger.info("用户 %s[%s] 获取账号信息发生错误 [%s]%s", user.full_name, user.id, exc.ret_code, exc.original)
await message.reply_text(
f"获取账号信息发生错误,错误信息为 {exc.original}请检查Cookie或者账号是否正常", reply_markup=ReplyKeyboardRemove()
)
return ConversationHandler.END
except AccountIdNotFound:
logger.info("用户 %s[%s] 无法获取账号ID", user.full_name, user.id)
await message.reply_text("无法获取账号ID请检查Cookie是否正常", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
except (AttributeError, ValueError) as exc:
logger.warning("用户 %s[%s] Cookies错误", user.full_name, user.id)
logger.debug("用户 %s[%s] Cookies错误", user.full_name, user.id, exc_info=exc)
await message.reply_text("Cookies错误请检查是否正确", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
if account_cookies_plugin_data.account_id is None:
await message.reply_text("无法获取账号ID请检查Cookie是否正确或请稍后重试")
return ConversationHandler.END
starrail_account: Optional[GenshinAccount] = None
starrail_account: Optional[Account] = None
level: int = 0
# todo : 多账号绑定
for temp in starrail_accounts:

View File

@ -2,6 +2,7 @@ import html
from http.cookies import SimpleCookie
from typing import Tuple, TYPE_CHECKING
from simnet import Region, StarRailClient
from telegram import InlineKeyboardMarkup, InlineKeyboardButton
from telegram.ext import filters
@ -10,7 +11,6 @@ from core.plugin import Plugin, handler
from core.services.cookies import CookiesService
from core.services.players import PlayersService
from core.services.players.services import PlayerInfoService
from modules.apihelper.client.components.authclient import AuthClient
from modules.apihelper.models.genshin.cookies import CookiesModel
from utils.log import logger
@ -211,12 +211,13 @@ class PlayersManagesPlugin(Plugin):
if cookies.stoken is not None:
try:
auth_client = AuthClient(cookies=cookies)
if await auth_client.get_cookie_token_by_stoken():
region = Region.CHINESE if player.region.value == 1 else Region.OVERSEAS
async with StarRailClient(cookies=cookies.to_dict(), region=region) as client:
cookies.cookie_token = await client.get_cookie_token_by_stoken()
logger.success("用户 %s[%s] 刷新 cookie_token 成功", user.full_name, user.id)
if await auth_client.get_ltoken_by_stoken():
logger.success("用户 %s[%s] 刷新 ltoken 成功", user.full_name, user.id)
auth_client.cookies.remove_v2()
cookies.ltoken = await client.get_ltoken_by_stoken()
logger.success("用户 %s[%s] 刷新 ltoken 成功", user.full_name, user.id)
cookies.remove_v2()
except Exception as exc: # pylint: disable=W0703
logger.error("刷新 cookie_token 失败 [%s]", (str(exc)))
await callback_query.edit_message_text(

View File

@ -7,6 +7,7 @@ from telegram.helpers import escape_markdown
from core.config import config
from core.plugin import handler, Plugin
from core.services.players import PlayersService
from plugins.tools.challenge import ChallengeSystem, ChallengeSystemException
from plugins.tools.genshin import PlayerNotFoundError, CookiesNotFoundError, GenshinHelper
from plugins.tools.sign import SignSystem, NeedChallenge
@ -14,10 +15,17 @@ from utils.log import logger
class StartPlugin(Plugin):
def __init__(self, sign_system: SignSystem, challenge_system: ChallengeSystem, genshin_helper: GenshinHelper):
def __init__(
self,
player: PlayersService,
sign_system: SignSystem,
challenge_system: ChallengeSystem,
genshin_helper: GenshinHelper,
):
self.challenge_system = challenge_system
self.sign_system = sign_system
self.genshin_helper = genshin_helper
self.players_service = player
@handler.command("start", block=False)
async def start(self, update: Update, context: CallbackContext) -> None:
@ -73,14 +81,14 @@ class StartPlugin(Plugin):
async def process_sign_validate(self, message: Message, user: User, validate: str):
try:
client = await self.genshin_helper.get_genshin_client(user.id)
await message.reply_chat_action(ChatAction.TYPING)
_, challenge = await self.sign_system.get_challenge(client.uid)
if not challenge:
await message.reply_text("验证请求已过期。", allow_sending_without_reply=True)
return
sign_text = await self.sign_system.start_sign(client, challenge=challenge, validate=validate)
await message.reply_text(sign_text, allow_sending_without_reply=True)
async with self.genshin_helper.genshin(user.id) as client:
await message.reply_chat_action(ChatAction.TYPING)
_, challenge = await self.sign_system.get_challenge(client.player_id)
if not challenge:
await message.reply_text("验证请求已过期。", allow_sending_without_reply=True)
return
sign_text = await self.sign_system.start_sign(client, challenge=challenge, validate=validate)
await message.reply_text(sign_text, allow_sending_without_reply=True)
except (PlayerNotFoundError, CookiesNotFoundError):
logger.warning("用户 %s[%s] 账号信息未找到", user.full_name, user.id)
except NeedChallenge:
@ -115,13 +123,13 @@ class StartPlugin(Plugin):
)
async def get_sign_button(self, message: Message, user: User, bot_username: str):
try:
client = await self.genshin_helper.get_genshin_client(user.id)
await message.reply_chat_action(ChatAction.TYPING)
button = await self.sign_system.get_challenge_button(bot_username, client.uid, user.id, callback=False)
if not button:
await message.reply_text("验证请求已过期。", allow_sending_without_reply=True)
return
await message.reply_text("请尽快点击下方按钮进行验证。", allow_sending_without_reply=True, reply_markup=button)
except (PlayerNotFoundError, CookiesNotFoundError):
player = await self.players_service.get_player(user.id)
if player is None:
logger.warning("用户 %s[%s] 账号信息未找到", user.full_name, user.id)
return
await message.reply_chat_action(ChatAction.TYPING)
button = await self.sign_system.get_challenge_button(bot_username, player.player_id, user.id, callback=False)
if not button:
await message.reply_text("验证请求已过期。", allow_sending_without_reply=True)
return
await message.reply_text("请尽快点击下方按钮进行验证。", allow_sending_without_reply=True, reply_markup=button)

View File

@ -1,7 +1,7 @@
from typing import Optional, List, Dict
from typing import Optional, List, Dict, TYPE_CHECKING
from genshin import Client, GenshinException
from genshin.models import StarRailStarFight
from simnet.errors import BadRequest as SimnetBadRequest
from simnet.models.starrail.chronicle.activity import StarRailStarFight
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, Message
from telegram.constants import ChatAction
from telegram.ext import CallbackContext, filters
@ -15,6 +15,10 @@ from core.services.template.services import TemplateService
from plugins.tools.genshin import GenshinHelper, PlayerNotFoundError, CookiesNotFoundError
from utils.log import logger
if TYPE_CHECKING:
from simnet import StarRailClient
__all__ = ("PlayerActivityPlugins",)
@ -71,12 +75,13 @@ class PlayerActivityPlugins(Plugin):
try:
uid = await self.get_uid(user.id, context.args, message.reply_to_message)
try:
client = await self.helper.get_genshin_client(user.id)
if client.uid != uid:
raise CookiesNotFoundError(uid)
async with self.helper.genshin(user.id) as client:
if client.player_id != uid:
raise CookiesNotFoundError(uid)
render_result = await self.star_fight_render(client, uid)
except CookiesNotFoundError:
client, _ = await self.helper.get_public_genshin_client(user.id)
render_result = await self.star_fight_render(client, uid)
async with self.helper.public_genshin(user.id) as client:
render_result = await self.star_fight_render(client, uid)
except PlayerNotFoundError:
buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_cookie"))]]
if filters.ChatType.GROUPS.filter(message):
@ -88,7 +93,7 @@ class PlayerActivityPlugins(Plugin):
else:
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
return
except GenshinException as exc:
except SimnetBadRequest as exc:
if exc.retcode == 1034:
await message.reply_text("出错了呜呜呜 ~ 请稍后重试")
return
@ -114,7 +119,7 @@ class PlayerActivityPlugins(Plugin):
self.add_delete_message_job(reply_message)
return
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
await render_result.reply_photo(message, filename=f"{client.uid}.png", allow_sending_without_reply=True)
await render_result.reply_photo(message, filename=f"{user.id}.png", allow_sending_without_reply=True)
async def get_star_fight_rander_data(self, uid: int, data: StarRailStarFight) -> Dict:
if not data.exists_data:
@ -129,9 +134,9 @@ class PlayerActivityPlugins(Plugin):
"avatar_icons": avatar_icons,
}
async def star_fight_render(self, client: Client, uid: Optional[int] = None) -> RenderResult:
async def star_fight_render(self, client: "StarRailClient", uid: Optional[int] = None) -> RenderResult:
if uid is None:
uid = client.uid
uid = client.player_id
act_data = await client.get_starrail_activity(uid)
try:

View File

@ -1,8 +1,8 @@
from typing import List, Optional, TYPE_CHECKING
from genshin import Client, InvalidCookies, Game
from genshin.models.starrail.chronicle import StarRailDetailCharacter
from pydantic import BaseModel
from simnet.errors import InvalidCookies
from simnet.models.starrail.chronicle.characters import StarRailDetailCharacter
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.constants import ChatAction, ParseMode
from telegram.ext import filters
@ -18,6 +18,7 @@ from plugins.tools.genshin import CookiesNotFoundError, GenshinHelper, PlayerNot
from utils.log import logger
if TYPE_CHECKING:
from simnet import StarRailClient
from telegram.ext import ContextTypes
from telegram import Update
@ -58,7 +59,9 @@ class AvatarListPlugin(Plugin):
self.wiki_service = wiki_service
self.helper = helper
async def get_user_client(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> Optional[Client]:
async def get_user_client(
self, update: "Update", context: "ContextTypes.DEFAULT_TYPE"
) -> Optional["StarRailClient"]:
message = update.effective_message
user = update.effective_user
try:
@ -91,7 +94,7 @@ class AvatarListPlugin(Plugin):
)
@staticmethod
async def get_avatars_data(client: Client) -> List[StarRailDetailCharacter]:
async def get_avatars_data(client: "StarRailClient") -> List[StarRailDetailCharacter]:
task_results = (await client.get_starrail_characters()).avatar_list
return sorted(
list(filter(lambda x: x, task_results)),
@ -150,21 +153,18 @@ class AvatarListPlugin(Plugin):
await message.reply_chat_action(ChatAction.TYPING)
try:
characters: List[StarRailDetailCharacter] = await self.get_avatars_data(client)
record_cards = await client.get_record_cards()
record_card = record_cards[0]
for card in record_cards:
if card.game == Game.STARRAIL:
record_card = card
break
record_card = await client.get_record_card()
nickname = record_card.nickname
except InvalidCookies as exc:
await client.get_starrail_user(client.uid)
await client.get_starrail_user(client.player_id)
logger.warning("用户 %s[%s] 无法请求角色数数据 API返回信息为 [%s]%s", user.full_name, user.id, exc.retcode, exc.original)
reply_message = await message.reply_text("出错了呜呜呜 ~ 当前访问令牌无法请求角色数数据请尝试重新获取Cookie。")
if filters.ChatType.GROUPS.filter(message):
self.add_delete_message_job(reply_message, delay=30)
self.add_delete_message_job(message, delay=30)
return
finally:
await client.shutdown()
has_more = (not all_avatars) and len(characters) > 20
if has_more:
@ -172,7 +172,7 @@ class AvatarListPlugin(Plugin):
avatar_datas = await self.get_final_data(characters)
render_data = {
"uid": client.uid, # 玩家uid
"uid": client.player_id, # 玩家uid
"nickname": nickname, # 玩家昵称
"avatar_datas": avatar_datas, # 角色数据
"has_more": has_more, # 是否显示了全部角色

View File

@ -2,11 +2,11 @@
import asyncio
import re
from functools import lru_cache
from typing import Any, List, Optional, Tuple, Union
from typing import Any, List, Optional, Tuple, Union, TYPE_CHECKING
from arkowrapper import ArkoWrapper
from genshin import Client, GenshinException
from pytz import timezone
from simnet.errors import BadRequest as SimnetBadRequest
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Message, Update
from telegram.constants import ChatAction, ParseMode
from telegram.ext import CallbackContext, filters
@ -26,6 +26,9 @@ try:
except ImportError:
import json as jsonlib
if TYPE_CHECKING:
from simnet import StarRailClient
TZ = timezone("Asia/Shanghai")
cmd_pattern = r"(?i)^/challenge\s*((?:\d+)|(?:all))?\s*(pre)?"
@ -126,13 +129,26 @@ class ChallengePlugin(Plugin):
extra={"markup": True},
)
async def reply_message_func(content: str) -> None:
_reply_msg = await message.reply_text(f"开拓者 (<code>{uid}</code>) {content}", parse_mode=ParseMode.HTML)
reply_text: Optional[Message] = None
try:
try:
client = await self.helper.get_genshin_client(user.id)
if client.uid != uid:
raise CookiesNotFoundError(uid)
async with self.helper.genshin(user.id) as client:
if client.player_id != uid:
raise CookiesNotFoundError(uid)
if total:
reply_text = await message.reply_text("彦卿需要时间整理混沌回忆数据,还请耐心等待哦~")
await message.reply_chat_action(ChatAction.TYPING)
images = await self.get_rendered_pic(client, uid, floor, total, previous)
except CookiesNotFoundError:
client, _ = await self.helper.get_public_genshin_client(user.id)
async with self.helper.public_genshin(user.id) as client:
if total:
reply_text = await message.reply_text("彦卿需要时间整理混沌回忆数据,还请耐心等待哦~")
await message.reply_chat_action(ChatAction.TYPING)
images = await self.get_rendered_pic(client, uid, floor, total, previous)
except PlayerNotFoundError: # 若未找到账号
buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_uid"))]]
if filters.ChatType.GROUPS.filter(message):
@ -150,27 +166,14 @@ class ChallengePlugin(Plugin):
self.add_delete_message_job(reply_message)
self.add_delete_message_job(message)
return
async def reply_message_func(content: str) -> None:
_reply_msg = await message.reply_text(f"开拓者 (<code>{uid}</code>) {content}", parse_mode=ParseMode.HTML)
reply_text: Optional[Message] = None
if total:
reply_text = await message.reply_text("彦卿需要时间整理混沌回忆数据,还请耐心等待哦~")
await message.reply_chat_action(ChatAction.TYPING)
try:
images = await self.get_rendered_pic(client, uid, floor, total, previous)
except AbyssUnlocked: # 若混沌回忆未解锁
await reply_message_func("还未解锁混沌回忆哦~")
return
except IndexError: # 若混沌回忆为挑战此层
await reply_message_func("还没有挑战本层呢,咕咕咕~")
return
except GenshinException as exc:
if exc.retcode == 1034 and client.uid != uid:
except SimnetBadRequest as exc:
if exc.retcode == 1034 and client.player_id != uid:
await message.reply_text("出错了呜呜呜 ~ 请稍后重试 ~ 米游社风控太严力")
return
raise exc
@ -216,7 +219,7 @@ class ChallengePlugin(Plugin):
return avatar_data
async def get_rendered_pic(
self, client: Client, uid: int, floor: int, total: bool, previous: bool
self, client: "StarRailClient", uid: int, floor: int, total: bool, previous: bool
) -> Union[
Tuple[
Union[BaseException, Any],

View File

@ -1,9 +1,8 @@
import datetime
from datetime import datetime
from typing import Optional
from typing import Optional, TYPE_CHECKING
import genshin
from genshin import DataNotPublic
from simnet.errors import DataNotPublic
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.constants import ChatAction
from telegram.ext import ConversationHandler, filters, CallbackContext
@ -15,6 +14,10 @@ from core.services.template.services import TemplateService
from plugins.tools.genshin import GenshinHelper, CookiesNotFoundError, PlayerNotFoundError
from utils.log import logger
if TYPE_CHECKING:
from simnet import StarRailClient
__all__ = ("DailyNotePlugin",)
@ -29,8 +32,8 @@ class DailyNotePlugin(Plugin):
self.template_service = template
self.helper = helper
async def _get_daily_note(self, client: genshin.Client) -> RenderResult:
daily_info = await client.get_starrail_notes(client.uid)
async def _get_daily_note(self, client: "StarRailClient") -> RenderResult:
daily_info = await client.get_starrail_notes(client.player_id)
day = datetime.now().strftime("%m-%d %H:%M") + " 星期" + "一二三四五六日"[datetime.now().weekday()]
resin_recovery_time = (
@ -50,7 +53,7 @@ class DailyNotePlugin(Plugin):
remained_time = (datetime.now().astimezone() + remained_time).strftime("%m-%d %H:%M")
render_data = {
"uid": client.uid,
"uid": client.player_id,
"day": day,
"resin_recovery_time": resin_recovery_time,
"current_resin": daily_info.current_stamina,
@ -77,10 +80,8 @@ class DailyNotePlugin(Plugin):
logger.info("用户 %s[%s] 每日便签命令请求", user.full_name, user.id)
try:
# 获取当前用户的 genshin.Client
client = await self.helper.get_genshin_client(user.id)
# 渲染
render_result = await self._get_daily_note(client)
async with self.helper.genshin(user.id) as client:
render_result = await self._get_daily_note(client)
except (CookiesNotFoundError, PlayerNotFoundError):
buttons = [
[
@ -106,4 +107,4 @@ class DailyNotePlugin(Plugin):
return ConversationHandler.END
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
await render_result.reply_photo(message, filename=f"{client.uid}.png", allow_sending_without_reply=True)
await render_result.reply_photo(message, filename=f"{client.player_id}.png", allow_sending_without_reply=True)

View File

@ -1,9 +1,10 @@
import os
import re
from datetime import datetime, timedelta
from typing import TYPE_CHECKING
from genshin import DataNotPublic, InvalidCookies, GenshinException
from genshin.models.genshin.diary import StarRailDiary
from simnet.errors import BadRequest as SimnetBadRequest, DataNotPublic, InvalidCookies
from simnet.models.starrail.diary import StarRailDiary
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.constants import ChatAction
from telegram.ext import filters, CallbackContext
@ -16,6 +17,10 @@ from core.services.template.services import TemplateService
from plugins.tools.genshin import CookiesNotFoundError, GenshinHelper, PlayerNotFoundError
from utils.log import logger
if TYPE_CHECKING:
from simnet import StarRailClient
__all__ = ("LedgerPlugin",)
@ -33,9 +38,9 @@ class LedgerPlugin(Plugin):
self.current_dir = os.getcwd()
self.helper = helper
async def _start_get_ledger(self, client, year, month) -> RenderResult:
async def _start_get_ledger(self, client: "StarRailClient", year, month) -> RenderResult:
req_month = f"{year}0{month}" if month < 10 else f"{year}{month}"
diary_info: StarRailDiary = await client.get_starrail_diary(client.uid, month=req_month)
diary_info: StarRailDiary = await client.get_starrail_diary(client.player_id, month=req_month)
color = ["#73a9c6", "#d56565", "#70b2b4", "#bd9a5a", "#739970", "#7a6da7", "#597ea0"]
categories = [
{
@ -53,7 +58,7 @@ class LedgerPlugin(Plugin):
return f"{round(amount / 10000, 2)}w" if amount >= 10000 else amount
ledger_data = {
"uid": client.uid,
"uid": client.player_id,
"day": month,
"current_hcoin": format_amount(diary_info.month_data.current_hcoin),
"gacha": int(diary_info.month_data.current_hcoin / 160),
@ -108,19 +113,19 @@ class LedgerPlugin(Plugin):
logger.info("用户 %s[%s] 查询开拓月历", user.full_name, user.id)
await message.reply_chat_action(ChatAction.TYPING)
try:
client = await self.helper.get_genshin_client(user.id)
try:
render_result = await self._start_get_ledger(client, year, month)
except InvalidCookies as exc: # 如果抛出InvalidCookies 判断是否真的玄学过期(或权限不足?)
await client.get_starrail_user(client.uid)
logger.warning(
"用户 %s[%s] 无法请求开拓月历数据 API返回信息为 [%s]%s", user.full_name, user.id, exc.retcode, exc.original
)
reply_message = await message.reply_text("出错了呜呜呜 ~ 当前访问令牌无法请求角色数数据请尝试重新获取Cookie。")
if filters.ChatType.GROUPS.filter(message):
self.add_delete_message_job(reply_message, delay=30)
self.add_delete_message_job(message, delay=30)
return
async with self.helper.genshin(user.id) as client:
try:
render_result = await self._start_get_ledger(client, year, month)
except InvalidCookies as exc: # 如果抛出InvalidCookies 判断是否真的玄学过期(或权限不足?)
await client.get_starrail_user(client.player_id)
logger.warning(
"用户 %s[%s] 无法请求开拓月历数据 API返回信息为 [%s]%s", user.full_name, user.id, exc.retcode, exc.original
)
reply_message = await message.reply_text("出错了呜呜呜 ~ 当前访问令牌无法请求角色数数据请尝试重新获取Cookie。")
if filters.ChatType.GROUPS.filter(message):
self.add_delete_message_job(reply_message, delay=30)
self.add_delete_message_job(message, delay=30)
return
except (PlayerNotFoundError, CookiesNotFoundError):
buttons = [
[
@ -144,10 +149,10 @@ class LedgerPlugin(Plugin):
self.add_delete_message_job(reply_message, delay=30)
self.add_delete_message_job(message, delay=30)
return
except GenshinException as exc:
except SimnetBadRequest as exc:
if exc.retcode == -120:
await message.reply_text("当前角色开拓等级不足,暂时无法获取信息")
return
raise exc
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
await render_result.reply_photo(message, filename=f"{client.uid}.png", allow_sending_without_reply=True)
await render_result.reply_photo(message, filename=f"{client.player_id}.png", allow_sending_without_reply=True)

View File

@ -1,7 +1,7 @@
from typing import Optional, List, Dict
from typing import Optional, List, Dict, TYPE_CHECKING
from genshin import Client, GenshinException
from genshin.models import StarRailMuseumBasic, StarRailMuseumDetail
from simnet.errors import BadRequest as SimnetBadRequest
from simnet.models.starrail.chronicle.museum import StarRailMuseumBasic, StarRailMuseumDetail
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, Message
from telegram.constants import ChatAction
from telegram.ext import CallbackContext, filters
@ -15,13 +15,13 @@ from core.services.template.services import TemplateService
from plugins.tools.genshin import GenshinHelper, PlayerNotFoundError, CookiesNotFoundError
from utils.log import logger
if TYPE_CHECKING:
from simnet import StarRailClient
__all__ = ("PlayerMuseumPlugins",)
class NotSupport(Exception):
"""不支持的服务器"""
class NotHaveData(Exception):
"""没有数据"""
@ -70,16 +70,14 @@ class PlayerMuseumPlugins(Plugin):
logger.info("用户 %s[%s] 查询博物馆信息命令请求", user.full_name, user.id)
try:
uid = await self.get_uid(user.id, context.args, message.reply_to_message)
if uid and str(uid)[0] not in ["1", "2", "5"]:
# todo: 支持国际服
raise NotSupport
try:
client = await self.helper.get_genshin_client(user.id)
if client.uid != uid:
raise CookiesNotFoundError(uid)
async with self.helper.genshin(user.id) as client:
if client.player_id != uid:
raise CookiesNotFoundError(uid)
render_result = await self.render(client, uid)
except CookiesNotFoundError:
client, _ = await self.helper.get_public_genshin_client(user.id)
render_result = await self.render(client, uid)
async with self.helper.public_genshin(user.id) as client:
render_result = await self.render(client, uid)
except PlayerNotFoundError:
buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_cookie"))]]
if filters.ChatType.GROUPS.filter(message):
@ -91,7 +89,7 @@ class PlayerMuseumPlugins(Plugin):
else:
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
return
except GenshinException as exc:
except SimnetBadRequest as exc:
if exc.retcode == 1034:
await message.reply_text("出错了呜呜呜 ~ 请稍后重试")
return
@ -104,12 +102,6 @@ class PlayerMuseumPlugins(Plugin):
logger.exception(exc)
await message.reply_text("冬城博物珍奇簿数据有误 估计是彦卿晕了")
return
except NotSupport:
reply_message = await message.reply_text("暂不支持该服务器查询冬城博物珍奇簿数据")
if filters.ChatType.GROUPS.filter(reply_message):
self.add_delete_message_job(message)
self.add_delete_message_job(reply_message)
return
except NotHaveData:
reply_message = await message.reply_text("没有查找到冬城博物珍奇簿数据")
if filters.ChatType.GROUPS.filter(reply_message):
@ -117,9 +109,10 @@ class PlayerMuseumPlugins(Plugin):
self.add_delete_message_job(reply_message)
return
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
await render_result.reply_photo(message, filename=f"{client.uid}.png", allow_sending_without_reply=True)
await render_result.reply_photo(message, filename=f"{user.id}.png", allow_sending_without_reply=True)
async def get_rander_data(self, uid: int, basic: StarRailMuseumBasic, detail: StarRailMuseumDetail) -> Dict:
@staticmethod
async def get_rander_data(uid: int, basic: StarRailMuseumBasic, detail: StarRailMuseumDetail) -> Dict:
exhibitions = []
for region in detail.regions:
for exhibition in region.exhibitions:
@ -132,14 +125,14 @@ class PlayerMuseumPlugins(Plugin):
"directors": detail.director,
}
async def render(self, client: Client, uid: Optional[int] = None) -> RenderResult:
async def render(self, client: "StarRailClient", uid: Optional[int] = None) -> RenderResult:
if uid is None:
uid = client.uid
uid = client.player_id
basic = await client.get_starrail_museum_info(uid)
try:
basic = await client.get_starrail_museum_info(uid)
detail = await client.get_starrail_museum_detail(uid)
except GenshinException as e:
except SimnetBadRequest as e:
if e.retcode == 10301:
raise NotHaveData from e
raise e

View File

@ -1,7 +1,8 @@
from typing import Optional, List, Dict, Tuple
from typing import Optional, List, Dict, Tuple, TYPE_CHECKING
from genshin import Client, GenshinException
from genshin.models import StarRailRogue, RogueCharacter
from simnet.errors import BadRequest as SimnetBadRequest
from simnet.models.starrail.character import RogueCharacter
from simnet.models.starrail.chronicle.rogue import StarRailRogue
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, Message
from telegram.constants import ChatAction
from telegram.ext import CallbackContext, filters
@ -15,6 +16,10 @@ from core.services.template.services import TemplateService
from plugins.tools.genshin import GenshinHelper, PlayerNotFoundError, CookiesNotFoundError
from utils.log import logger
if TYPE_CHECKING:
from simnet import StarRailClient
__all__ = ("PlayerRoguePlugins",)
@ -73,12 +78,13 @@ class PlayerRoguePlugins(Plugin):
try:
uid, pre = await self.get_uid(user.id, context.args, message.reply_to_message)
try:
client = await self.helper.get_genshin_client(user.id)
if client.uid != uid:
raise CookiesNotFoundError(uid)
async with self.helper.genshin(user.id) as client:
if client.player_id != uid:
raise CookiesNotFoundError(uid)
render_result = await self.render(client, pre, uid)
except CookiesNotFoundError:
client, _ = await self.helper.get_public_genshin_client(user.id)
render_result = await self.render(client, pre, uid)
async with self.helper.public_genshin(user.id) as client:
render_result = await self.render(client, pre, uid)
except PlayerNotFoundError:
buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_cookie"))]]
if filters.ChatType.GROUPS.filter(message):
@ -90,7 +96,7 @@ class PlayerRoguePlugins(Plugin):
else:
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
return
except GenshinException as exc:
except SimnetBadRequest as exc:
if exc.retcode == 1034:
await message.reply_text("出错了呜呜呜 ~ 请稍后重试")
return
@ -116,7 +122,7 @@ class PlayerRoguePlugins(Plugin):
self.add_delete_message_job(reply_message)
return
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
await render_result.reply_photo(message, filename=f"{client.uid}.png", allow_sending_without_reply=True)
await render_result.reply_photo(message, filename=f"{user.id}.png", allow_sending_without_reply=True)
async def get_rander_data(self, uid: int, data: StarRailRogue, pre: bool) -> Dict:
luo_ma_bum = ["", "", "", "", "", "", ""]
@ -155,9 +161,9 @@ class PlayerRoguePlugins(Plugin):
"miracles": record.miracles,
}
async def render(self, client: Client, pre: bool, uid: Optional[int] = None) -> RenderResult:
async def render(self, client: "StarRailClient", pre: bool, uid: Optional[int] = None) -> RenderResult:
if uid is None:
uid = client.uid
uid = client.player_id
rogue = await client.get_starrail_rogue(uid)
data = await self.get_rander_data(uid, rogue, pre)

View File

@ -95,9 +95,9 @@ class Sign(Plugin):
try:
client = await self.genshin_helper.get_genshin_client(user.id)
await message.reply_chat_action(ChatAction.TYPING)
_, challenge = await self.sign_system.get_challenge(client.uid)
_, challenge = await self.sign_system.get_challenge(client.player_id)
if validate:
_, challenge = await self.sign_system.get_challenge(client.uid)
_, challenge = await self.sign_system.get_challenge(client.player_id)
if challenge:
sign_text = await self.sign_system.start_sign(client, challenge=challenge, validate=validate)
else:

View File

@ -1,6 +1,6 @@
from typing import Optional, List
from typing import Optional, List, TYPE_CHECKING
from genshin import Client, GenshinException
from simnet.errors import BadRequest as SimnetBadRequest
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, Message
from telegram.constants import ChatAction
from telegram.ext import CallbackContext, filters
@ -13,6 +13,10 @@ from core.services.template.services import TemplateService
from plugins.tools.genshin import GenshinHelper, PlayerNotFoundError, CookiesNotFoundError
from utils.log import logger
if TYPE_CHECKING:
from simnet import StarRailClient
__all__ = ("PlayerStatsPlugins",)
@ -59,12 +63,13 @@ class PlayerStatsPlugins(Plugin):
try:
uid: int = await self.get_uid(user.id, context.args, message.reply_to_message)
try:
client = await self.helper.get_genshin_client(user.id)
if client.uid != uid:
raise CookiesNotFoundError(uid)
async with self.helper.genshin(user.id) as client:
if client.player_id != uid:
raise CookiesNotFoundError(uid)
render_result = await self.render(client, uid)
except CookiesNotFoundError:
client, _ = await self.helper.get_public_genshin_client(user.id)
render_result = await self.render(client, uid)
async with self.helper.public_genshin(user.id) as client:
render_result = await self.render(client, uid)
except PlayerNotFoundError:
buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_cookie"))]]
if filters.ChatType.GROUPS.filter(message):
@ -76,7 +81,7 @@ class PlayerStatsPlugins(Plugin):
else:
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
return
except GenshinException as exc:
except SimnetBadRequest as exc:
if exc.retcode == 1034:
await message.reply_text("出错了呜呜呜 ~ 请稍后重试")
return
@ -90,16 +95,16 @@ class PlayerStatsPlugins(Plugin):
await message.reply_text("角色数据有误 估计是彦卿晕了")
return
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
await render_result.reply_photo(message, filename=f"{client.uid}.png", allow_sending_without_reply=True)
await render_result.reply_photo(message, filename=f"{user.id}.png", allow_sending_without_reply=True)
async def render(self, client: Client, uid: Optional[int] = None) -> RenderResult:
async def render(self, client: "StarRailClient", uid: Optional[int] = None) -> RenderResult:
if uid is None:
uid = client.uid
uid = client.player_id
user_info = await client.get_starrail_user(uid)
try:
rogue = await client.get_starrail_rogue(uid)
except GenshinException:
except SimnetBadRequest:
rogue = None
logger.debug(user_info)

View File

@ -1,6 +1,7 @@
from io import BytesIO
from typing import Optional
from genshin.models.genshin.gacha import StarRailBannerType
from simnet.models.starrail.wish import StarRailBannerType
from telegram import Document, InlineKeyboardButton, InlineKeyboardMarkup, Message, Update, User
from telegram.constants import ChatAction
from telegram.ext import CallbackContext, CommandHandler, ConversationHandler, MessageHandler, filters
@ -22,7 +23,7 @@ from modules.gacha_log.error import (
)
from modules.gacha_log.helpers import from_url_get_authkey
from modules.gacha_log.log import GachaLog
from plugins.tools.genshin import PlayerNotFoundError, GenshinHelper
from plugins.tools.genshin import PlayerNotFoundError
from utils.log import logger
try:
@ -43,14 +44,20 @@ class WishLogPlugin(Plugin.Conversation):
players_service: PlayersService,
assets: AssetsService,
cookie_service: CookiesService,
helper: GenshinHelper,
):
self.template_service = template_service
self.players_service = players_service
self.assets_service = assets
self.cookie_service = cookie_service
self.gacha_log = GachaLog()
self.helper = helper
async def get_player_id(self, uid: int) -> Optional[int]:
"""获取绑定的游戏ID"""
logger.debug("尝试获取已绑定的星穹铁道账号")
player = await self.players_service.get_player(uid)
if player is None:
raise PlayerNotFoundError(uid)
return player.player_id
async def _refresh_user_data(
self, user: User, data: dict = None, authkey: str = None, verify_uid: bool = True
@ -63,12 +70,12 @@ class WishLogPlugin(Plugin.Conversation):
"""
try:
logger.debug("尝试获取已绑定的星穹铁道账号")
client = await self.helper.get_genshin_client(user.id, need_cookie=False)
player_id = await self.get_player_id(user.id)
if authkey:
new_num = await self.gacha_log.get_gacha_log_data(user.id, client, authkey)
new_num = await self.gacha_log.get_gacha_log_data(user.id, player_id, authkey)
return "更新完成,本次没有新增数据" if new_num == 0 else f"更新完成,本次共新增{new_num}条跃迁记录"
if data:
new_num = await self.gacha_log.import_gacha_log_data(user.id, client, data, verify_uid)
new_num = await self.gacha_log.import_gacha_log_data(user.id, player_id, data, verify_uid)
return "更新完成,本次没有新增数据" if new_num == 0 else f"更新完成,本次共新增{new_num}条跃迁记录"
except GachaLogNotFound:
return "彦卿没有找到你的跃迁记录,快来私聊彦卿导入吧~"
@ -136,23 +143,6 @@ class WishLogPlugin(Plugin.Conversation):
args = self.get_args(context)
logger.info("用户 %s[%s] 导入跃迁记录命令请求", user.full_name, user.id)
authkey = from_url_get_authkey(args[0] if args else "")
# if not args:
# player_info = await self.players_service.get_player(user.id, region=RegionEnum.HYPERION)
# if player_info is not None:
# cookies = await self.cookie_service.get(user.id, account_id=player_info.account_id)
# if cookies is not None and cookies.data and "stoken" in cookies.data:
# if stuid := next(
# (value for key, value in cookies.data.items() if key in ["ltuid", "login_uid"]), None
# ):
# cookies.data["stuid"] = stuid
# client = genshin.Client(
# cookies=cookies.data,
# game=genshin.types.Game.STARRAIL,
# region=genshin.Region.CHINESE,
# lang="zh-cn",
# uid=player_info.player_id,
# )
# authkey = await get_authkey_by_stoken(client)
if not authkey:
await message.reply_text(
"<b>开始导入跃迁历史记录:请通过 https://starrailstation.com/cn/warp#import 获取跃迁记录链接后发送给我"
@ -198,13 +188,13 @@ class WishLogPlugin(Plugin.Conversation):
user = update.effective_user
logger.info("用户 %s[%s] 删除跃迁记录命令请求", user.full_name, user.id)
try:
client = await self.helper.get_genshin_client(user.id, need_cookie=False)
context.chat_data["uid"] = client.uid
player_id = await self.get_player_id(user.id)
context.chat_data["uid"] = player_id
except PlayerNotFoundError:
logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id)
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号")
return ConversationHandler.END
_, status = await self.gacha_log.load_history_info(str(user.id), str(client.uid), only_status=True)
_, status = await self.gacha_log.load_history_info(str(user.id), str(player_id), only_status=True)
if not status:
await message.reply_text("你还没有导入跃迁记录哦~")
return ConversationHandler.END
@ -234,12 +224,12 @@ class WishLogPlugin(Plugin.Conversation):
cid = int(args[0])
if cid < 0:
raise ValueError("Invalid cid")
client = await self.helper.get_genshin_client(cid, need_cookie=False)
_, status = await self.gacha_log.load_history_info(str(cid), str(client.uid), only_status=True)
player_id = await self.get_player_id(cid)
_, status = await self.gacha_log.load_history_info(str(cid), str(player_id), only_status=True)
if not status:
await message.reply_text("该用户还没有导入跃迁记录")
return
status = await self.gacha_log.remove_history_info(str(cid), str(client.uid))
status = await self.gacha_log.remove_history_info(str(cid), str(player_id))
await message.reply_text("跃迁记录已强制删除" if status else "跃迁记录删除失败")
except GachaLogNotFound:
await message.reply_text("该用户还没有导入跃迁记录")
@ -255,9 +245,9 @@ class WishLogPlugin(Plugin.Conversation):
user = update.effective_user
logger.info("用户 %s[%s] 导出跃迁记录命令请求", user.full_name, user.id)
try:
client = await self.helper.get_genshin_client(user.id, need_cookie=False)
await message.reply_chat_action(ChatAction.TYPING)
path = await self.gacha_log.gacha_log_to_srgf(str(user.id), str(client.uid))
player_id = await self.get_player_id(user.id)
path = await self.gacha_log.gacha_log_to_srgf(str(user.id), str(player_id))
await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT)
await message.reply_document(document=open(path, "rb+"), caption="跃迁记录导出文件 - SRGF V1.0")
except GachaLogNotFound:
@ -289,9 +279,9 @@ class WishLogPlugin(Plugin.Conversation):
pool_type = StarRailBannerType.NOVICE
logger.info("用户 %s[%s] 跃迁记录命令请求 || 参数 %s", user.full_name, user.id, pool_type.name)
try:
client = await self.helper.get_genshin_client(user.id, need_cookie=False)
await message.reply_chat_action(ChatAction.TYPING)
data = await self.gacha_log.get_analysis(user.id, client, pool_type, self.assets_service)
player_id = await self.get_player_id(user.id)
data = await self.gacha_log.get_analysis(user.id, player_id, pool_type, self.assets_service)
if isinstance(data, str):
reply_message = await message.reply_text(data)
if filters.ChatType.GROUPS.filter(message):
@ -346,13 +336,13 @@ class WishLogPlugin(Plugin.Conversation):
all_five = True
logger.info("用户 %s[%s] 跃迁统计命令请求 || 参数 %s || 仅五星 %s", user.full_name, user.id, pool_type.name, all_five)
try:
client = await self.helper.get_genshin_client(user.id, need_cookie=False)
group = filters.ChatType.GROUPS.filter(message)
await message.reply_chat_action(ChatAction.TYPING)
player_id = await self.get_player_id(user.id)
if all_five:
data = await self.gacha_log.get_all_five_analysis(user.id, client, self.assets_service)
data = await self.gacha_log.get_all_five_analysis(user.id, player_id, self.assets_service)
else:
data = await self.gacha_log.get_pool_analysis(user.id, client, pool_type, self.assets_service, group)
data = await self.gacha_log.get_pool_analysis(user.id, player_id, pool_type, self.assets_service, group)
if isinstance(data, str):
reply_message = await message.reply_text(data)
if filters.ChatType.GROUPS.filter(message):

View File

@ -5,8 +5,14 @@ from typing import Optional
import aiofiles
from aiohttp import ClientError, ClientConnectorError
from genshin import DataNotPublic, GenshinException, InvalidCookies, TooManyRequests
from httpx import HTTPError, TimeoutException
from simnet.errors import (
DataNotPublic,
BadRequest as SIMNetBadRequest,
InvalidCookies,
TooManyRequests,
CookieException,
)
from telegram import ReplyKeyboardRemove, Update, InlineKeyboardMarkup, InlineKeyboardButton
from telegram.constants import ParseMode
from telegram.error import BadRequest, Forbidden, TelegramError, TimedOut, NetworkError
@ -100,7 +106,7 @@ class ErrorHandler(Plugin):
@error_handler()
async def process_genshin_exception(self, update: object, context: CallbackContext):
if not isinstance(context.error, GenshinException) or not isinstance(update, Update):
if not isinstance(context.error, SIMNetBadRequest) or not isinstance(update, Update):
return
exc = context.error
notice: Optional[str] = None
@ -114,6 +120,12 @@ class ErrorHandler(Plugin):
else:
logger.error("未知Cookie错误", exc_info=exc)
notice = self.ERROR_MSG_PREFIX + f"Cookie 无效 错误信息为 {exc.original} 请尝试重新绑定"
elif isinstance(exc, CookieException):
if exc.retcode == 0:
notice = self.ERROR_MSG_PREFIX + "Cookie 已经被刷新,请尝试重试操作~"
else:
logger.error("未知Cookie错误", exc_info=exc)
notice = self.ERROR_MSG_PREFIX + f"Cookie 无效 错误信息为 {exc.original} 请尝试重新绑定"
elif isinstance(exc, DataNotPublic):
notice = self.ERROR_MSG_PREFIX + "查询的用户数据未公开"
else:
@ -124,14 +136,17 @@ class ErrorHandler(Plugin):
elif exc.retcode == -500001:
notice = self.ERROR_MSG_PREFIX + "网络出小差了,请稍后重试~"
elif exc.retcode == -1:
notice = self.ERROR_MSG_PREFIX + "系统发生错误,请稍后重试~"
logger.warning("内部数据库错误 [%s]%s", exc.ret_code, exc.original)
notice = self.ERROR_MSG_PREFIX + "系统内部数据库错误,请稍后重试~"
elif exc.retcode == -10001: # 参数异常 不应该抛出异常 进入下一步处理
pass
else:
logger.error("GenshinException", exc_info=exc)
notice = (
self.ERROR_MSG_PREFIX + f"获取账号信息发生错误 错误信息为 {exc.original if exc.original else exc.retcode} ~ 请稍后再试"
)
message = exc.original if exc.original else exc.message
if message:
notice = self.ERROR_MSG_PREFIX + f"获取信息发生错误 错误信息为 {message} ~ 请稍后再试"
else:
notice = self.ERROR_MSG_PREFIX + "获取信息发生错误 请稍后再试"
if notice:
self.create_notice_task(update, context, notice)
raise ApplicationHandlerStop

View File

@ -1,13 +1,15 @@
from typing import Tuple, Optional
from genshin import Region, GenshinException
from simnet import Region
from simnet.errors import BadRequest as SIMNetBadRequest
from core.dependence.redisdb import RedisDB
from core.plugin import Plugin
from core.services.cookies import CookiesService
from core.services.players import PlayersService
from modules.apihelper.client.components.verify import Verify
from modules.apihelper.error import ResponseException, APIHelperException
from plugins.tools.genshin import GenshinHelper, PlayerNotFoundError, CookiesNotFoundError
from plugins.tools.genshin import PlayerNotFoundError, CookiesNotFoundError, GenshinHelper
from utils.log import logger
__all__ = ("ChallengeSystemException", "ChallengeSystem")
@ -25,11 +27,13 @@ class ChallengeSystem(Plugin):
cookies_service: CookiesService,
redis: RedisDB,
genshin_helper: GenshinHelper,
player: PlayersService,
) -> None:
self.cookies_service = cookies_service
self.genshin_helper = genshin_helper
self.cache = redis.client
self.qname = "plugin:challenge:"
self.players_service = player
async def get_challenge(self, uid: int) -> Tuple[Optional[str], Optional[str]]:
data = await self.cache.get(f"{self.qname}{uid}")
@ -56,12 +60,16 @@ class ChallengeSystem(Plugin):
if need_verify:
try:
await client.get_starrail_notes()
except GenshinException as exc:
except SIMNetBadRequest as exc:
if exc.retcode != 1034:
raise exc
else:
raise ChallengeSystemException("账户正常,无需验证")
verify = Verify(account_id=client.hoyolab_id, cookies=client.cookie_manager.cookies)
finally:
await client.shutdown()
else:
await client.shutdown()
verify = Verify(cookies=client.cookies)
try:
data = await verify.create()
challenge = data["challenge"]
@ -74,26 +82,28 @@ class ChallengeSystem(Plugin):
validate = await verify.ajax(referer="https://webstatic.mihoyo.com/", gt=gt, challenge=challenge)
if validate:
await verify.verify(challenge, validate)
return client.uid, "ajax", "ajax"
return client.player_id, "ajax", "ajax"
except APIHelperException as exc:
logger.warning("用户 %s ajax 验证失效 错误信息为 %s", user_id, str(exc))
logger.warning("用户 %s ajax 验证失败 重新申请验证", user_id)
return await self.create_challenge(user_id, need_verify, False)
await self.set_challenge(client.uid, gt, challenge)
return client.uid, gt, challenge
await self.set_challenge(client.player_id, gt, challenge)
return client.player_id, gt, challenge
async def pass_challenge(self, user_id: int, validate: str, challenge: Optional[str] = None) -> bool:
try:
client = await self.genshin_helper.get_genshin_client(user_id)
except PlayerNotFoundError:
player = await self.players_service.get_player(user_id)
if player is None:
raise ChallengeSystemException("用户未找到")
if client.region != Region.CHINESE:
if player.region != Region.CHINESE:
raise ChallengeSystemException("非法用户")
cookie_model = await self.cookies_service.get(player.user_id, player.account_id, player.region)
if cookie_model is None:
raise ChallengeSystemException("无需验证")
if challenge is None:
_, challenge = await self.get_challenge(client.uid)
_, challenge = await self.get_challenge(player.player_id)
if challenge is None:
raise ChallengeSystemException("验证失效 请求已经过期")
verify = Verify(account_id=client.hoyolab_id, cookies=client.cookie_manager.cookies)
verify = Verify(cookies=cookie_model.data)
try:
await verify.verify(challenge=challenge, validate=validate)
except ResponseException as exc:

View File

@ -1,46 +1,37 @@
import asyncio
import random
from contextlib import asynccontextmanager
from datetime import datetime, time, timedelta
from typing import Optional, TYPE_CHECKING, Tuple, Union
from typing import Optional
from typing import TYPE_CHECKING, Union
import genshin
from genshin.errors import GenshinException
from genshin.models import BaseCharacter, CalculatorCharacterDetails
from pydantic import ValidationError
from simnet import StarRailClient, Region
from simnet.errors import BadRequest as SimnetBadRequest, InvalidCookies, NetworkError, CookieException
from simnet.models.genshin.calculator import CalculatorCharacterDetails
from simnet.models.genshin.chronicle.characters import Character
from sqlalchemy.exc import SQLAlchemyError
from sqlmodel import BigInteger, Column, DateTime, Field, Index, Integer, SQLModel, String, delete, func, select
from telegram.ext import ContextTypes
from core.basemodel import RegionEnum
from core.config import config
from core.dependence.database import Database
from core.dependence.redisdb import RedisDB
from core.error import ServiceNotFoundError
from core.plugin import Plugin
from core.services.cookies.services import CookiesService, PublicCookiesService
from core.services.devices import DevicesService
from core.services.players.services import PlayersService
from core.services.users.services import UserService
from core.sqlmodel.session import AsyncSession
from utils.const import REGION_MAP
from utils.log import logger
if TYPE_CHECKING:
from sqlalchemy import Table
from genshin import Client as GenshinClient
__all__ = ("GenshinHelper", "PlayerNotFoundError", "CookiesNotFoundError", "CharacterDetails")
class PlayerNotFoundError(Exception):
def __init__(self, user_id):
super().__init__(f"User not found, user_id: {user_id}")
class CookiesNotFoundError(Exception):
def __init__(self, user_id):
super().__init__(f"{user_id} cookies not found")
class CharacterDetailsSQLModel(SQLModel, table=True):
__tablename__ = "character_details"
__table_args__ = (
@ -164,132 +155,191 @@ class CharacterDetails(Plugin):
return None
async def get_character_details(
self, client: "GenshinClient", character: "Union[int,BaseCharacter]"
self, client: "StarRailClient", character: "Union[int,Character]"
) -> Optional["CalculatorCharacterDetails"]:
"""缓存 character_details 并定时对其进行数据存储 当遇到 Too Many Requests 可以获取以前的数据
:param client: genshin.py
:param character:
:return:
"""
uid = client.uid
"""缓存 character_details 并定时对其进行数据存储 当遇到 Too Many Requests 可以获取以前的数据"""
uid = client.player_id
if isinstance(character, Character):
character_id = character.id
else:
character_id = character
if uid is not None:
if isinstance(character, BaseCharacter):
character_id = character.id
else:
character_id = character
detail = await self.get_character_details_for_redis(uid, character_id)
if detail is not None:
return detail
try:
detail = await client.get_character_details(character)
except GenshinException as exc:
if "Too Many Requests" in exc.msg:
detail = await client.get_character_details(character_id)
except SimnetBadRequest as exc:
if "Too Many Requests" in exc.message:
return await self.get_character_details_for_mysql(uid, character_id)
raise exc
asyncio.create_task(self.set_character_details(uid, character_id, detail.json()))
asyncio.create_task(self.set_character_details(uid, character_id, detail.json(by_alias=True)))
return detail
try:
return await client.get_character_details(character)
except GenshinException as exc:
if "Too Many Requests" in exc.msg:
return await client.get_character_details(character_id)
except SimnetBadRequest as exc:
if "Too Many Requests" in exc.message:
logger.warning("Too Many Requests")
else:
raise exc
return None
class PlayerNotFoundError(Exception):
def __init__(self, user_id):
super().__init__(f"User not found, user_id: {user_id}")
class CookiesNotFoundError(Exception):
def __init__(self, user_id):
super().__init__(f"{user_id} cookies not found")
class GenshinHelper(Plugin):
def __init__(
self,
cookies: CookiesService,
public_cookies: PublicCookiesService,
user: UserService,
redis: RedisDB,
player: PlayersService,
devices: DevicesService,
) -> None:
self.cookies_service = cookies
self.public_cookies_service = public_cookies
self.user_service = user
self.redis_db = redis
self.players_service = player
if self.redis_db and config.genshin_ttl:
self.genshin_cache = genshin.RedisCache(self.redis_db.client, ttl=config.genshin_ttl)
else:
self.genshin_cache = None
self.devices_service = devices
if None in (temp := [self.user_service, self.cookies_service, self.players_service]):
raise ServiceNotFoundError(*filter(lambda x: x is None, temp))
@staticmethod
def region_server(uid: Union[int, str]) -> RegionEnum:
if isinstance(uid, (int, str)):
region = REGION_MAP.get(str(uid)[0])
else:
raise TypeError("UID variable type error")
if region:
return region
raise ValueError(f"UID {uid} isn't associated with any region.")
async def get_genshin_client(
self, user_id: int, region: Optional[RegionEnum] = None, need_cookie: bool = True
) -> Optional[genshin.Client]:
"""通过 user_id 和 region 获取私有的 `genshin.Client`"""
@asynccontextmanager
async def genshin(self, user_id: int, region: Optional[RegionEnum] = None) -> StarRailClient:
player = await self.players_service.get_player(user_id, region)
if player is None:
raise PlayerNotFoundError(user_id)
cookies = None
if need_cookie:
if player.account_id is None:
raise CookiesNotFoundError(user_id)
cookie_model = await self.cookies_service.get(player.user_id, player.account_id, player.region)
if cookie_model is None:
raise CookiesNotFoundError(user_id)
cookies = cookie_model.data
uid = player.player_id
region = player.region
if region == RegionEnum.HYPERION: # 国服
game_region = genshin.types.Region.CHINESE
elif region == RegionEnum.HOYOLAB: # 国际服
game_region = genshin.types.Region.OVERSEAS
if player.account_id is None:
raise CookiesNotFoundError(user_id)
cookie_model = await self.cookies_service.get(player.user_id, player.account_id, player.region)
if cookie_model is None:
raise CookiesNotFoundError(user_id)
cookies = cookie_model.data
if player.region == RegionEnum.HYPERION: # 国服
region = Region.CHINESE
elif player.region == RegionEnum.HOYOLAB: # 国际服
region = Region.OVERSEAS
else:
raise TypeError("Region is not None")
client = genshin.Client(
device_id: Optional[str] = None
device_fp: Optional[str] = None
devices = await self.devices_service.get(player.account_id)
if devices:
device_id = devices.device_id
device_fp = devices.device_fp
async with StarRailClient(
cookies,
region=region,
account_id=player.account_id,
player_id=player.player_id,
lang="zh-cn",
game=genshin.types.Game.STARRAIL,
region=game_region,
uid=uid,
hoyolab_id=player.account_id,
device_id=device_id,
device_fp=device_fp,
) as client:
try:
yield client
except InvalidCookies as exc:
stoken = client.cookies.get("stoken")
if stoken is not None:
try:
await client.get_cookie_token_by_stoken()
logger.success("用户 %s 刷新 cookie_token 成功", user_id)
await client.get_ltoken_by_stoken()
logger.success("用户 %s 刷新 ltoken 成功", user_id)
except SimnetBadRequest as _exc:
logger.warning(
"用户 %s 刷新 token 失败 [%s]%s", user_id, _exc.ret_code, _exc.original or _exc.message
)
except NetworkError:
logger.warning("用户 %s 刷新 Cookies 失败 网络错误", user_id)
except Exception as _exc:
logger.error("用户 %s 刷新 Cookies 失败", user_id, exc_info=_exc)
else:
raise CookieException(message="The cookie has been refreshed.") from exc
raise exc
async def get_genshin_client(self, user_id: int, region: Optional[RegionEnum] = None) -> StarRailClient:
player = await self.players_service.get_player(user_id, region)
if player is None:
raise PlayerNotFoundError(user_id)
if player.account_id is None:
raise CookiesNotFoundError(user_id)
cookie_model = await self.cookies_service.get(player.user_id, player.account_id, player.region)
if cookie_model is None:
raise CookiesNotFoundError(user_id)
cookies = cookie_model.data
if player.region == RegionEnum.HYPERION:
region = Region.CHINESE
elif player.region == RegionEnum.HOYOLAB:
region = Region.OVERSEAS
else:
raise TypeError("Region is not None")
device_id: Optional[str] = None
device_fp: Optional[str] = None
devices = await self.devices_service.get(player.account_id)
if devices:
device_id = devices.device_id
device_fp = devices.device_fp
return StarRailClient(
cookies,
region=region,
account_id=player.account_id,
player_id=player.player_id,
lang="zh-cn",
device_id=device_id,
device_fp=device_fp,
)
if self.genshin_cache is not None:
client.cache = self.genshin_cache
return client
async def get_public_genshin_client(self, user_id: int) -> Tuple[genshin.Client, int]:
"""通过 user_id 获取公共的 `genshin.Client`"""
player = await self.players_service.get_player(user_id)
@asynccontextmanager
async def public_genshin(self, user_id: int, region: Optional[RegionEnum] = None) -> StarRailClient:
player = await self.players_service.get_player(user_id, region)
region = player.region
cookies = await self.public_cookies_service.get_cookies(user_id, region)
uid = player.player_id
if region is RegionEnum.HYPERION:
game_region = genshin.types.Region.CHINESE
elif region is RegionEnum.HOYOLAB:
game_region = genshin.types.Region.OVERSEAS
if player.region == RegionEnum.HYPERION:
region = Region.CHINESE
elif player.region == RegionEnum.HOYOLAB:
region = Region.OVERSEAS
else:
raise TypeError("Region is not `RegionEnum.NULL`")
client = genshin.Client(
cookies.data, region=game_region, uid=uid, game=genshin.types.Game.STARRAIL, lang="zh-cn"
)
device_id: Optional[str] = None
device_fp: Optional[str] = None
devices = await self.devices_service.get(cookies.account_id)
if devices:
device_id = devices.device_id
device_fp = devices.device_fp
if self.genshin_cache is not None:
client.cache = self.genshin_cache
return client, uid
async with StarRailClient(
cookies.data,
region=region,
account_id=player.account_id,
player_id=uid,
lang="zh-cn",
device_id=device_id,
device_fp=device_fp,
) as client:
try:
yield client
except SimnetBadRequest as exc:
if exc.ret_code == 1034:
await self.public_cookies_service.undo(user_id)
raise exc

View File

@ -6,9 +6,10 @@ from enum import Enum
from typing import Optional, Tuple, List, TYPE_CHECKING
from aiohttp import ClientConnectorError
from genshin import Game, GenshinException, AlreadyClaimed, Client, InvalidCookies
from genshin.utility import recognize_genshin_server
from httpx import TimeoutException
from simnet import Game
from simnet.errors import BadRequest as SimnetBadRequest, AlreadyClaimed, InvalidCookies
from simnet.utils.player import recognize_starrail_server
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.constants import ParseMode
from telegram.error import Forbidden, BadRequest
@ -21,11 +22,12 @@ from core.services.sign.models import SignStatusEnum
from core.services.sign.services import SignServices
from core.services.users.services import UserService
from modules.apihelper.client.components.verify import Verify
from plugins.tools.genshin import GenshinHelper, CookiesNotFoundError, PlayerNotFoundError
from plugins.tools.genshin import PlayerNotFoundError, CookiesNotFoundError, GenshinHelper
from plugins.tools.recognize import RecognizeSystem
from utils.log import logger
if TYPE_CHECKING:
from simnet import StarRailClient
from telegram.ext import ContextTypes
@ -104,7 +106,7 @@ class SignSystem(Plugin):
async def start_sign(
self,
client: Client,
client: "StarRailClient",
challenge: Optional[str] = None,
validate: Optional[str] = None,
is_sleep: bool = False,
@ -112,28 +114,28 @@ class SignSystem(Plugin):
title: Optional[str] = "签到结果",
) -> str:
if is_sleep:
if recognize_genshin_server(client.uid) in ("cn_gf01", "cn_qd01"):
if recognize_starrail_server(client.player_id) in ("prod_gf_cn", "prod_qd_cn"):
await asyncio.sleep(random.randint(10, 300)) # nosec
else:
await asyncio.sleep(random.randint(0, 3)) # nosec
try:
rewards = await client.get_monthly_rewards(game=Game.STARRAIL, lang="zh-cn")
except GenshinException as error:
logger.warning("UID[%s] 获取签到信息失败API返回信息为 %s", client.uid, str(error))
except SimnetBadRequest as error:
logger.warning("UID[%s] 获取签到信息失败API返回信息为 %s", client.player_id, str(error))
if is_raise:
raise error
return f"获取签到信息失败API返回信息为 {str(error)}"
try:
daily_reward_info = await client.get_reward_info(game=Game.STARRAIL, lang="zh-cn") # 获取签到信息失败
except GenshinException as error:
logger.warning("UID[%s] 获取签到状态失败API返回信息为 %s", client.uid, str(error))
except SimnetBadRequest as error:
logger.warning("UID[%s] 获取签到状态失败API返回信息为 %s", client.player_id, str(error))
if is_raise:
raise error
return f"获取签到状态失败API返回信息为 {str(error)}"
if not daily_reward_info.signed_in:
try:
if validate:
logger.info("UID[%s] 正在尝试通过验证码\nchallenge[%s]\nvalidate[%s]", client.uid, challenge, validate)
logger.info("UID[%s] 正在尝试通过验证码\nchallenge[%s]\nvalidate[%s]", client.player_id, challenge, validate)
request_daily_reward = await client.request_daily_reward(
"sign",
method="POST",
@ -147,8 +149,8 @@ class SignSystem(Plugin):
# 尝试通过 ajax 请求绕过签到
gt = request_daily_reward.get("gt", "")
challenge = request_daily_reward.get("challenge", "")
logger.warning("UID[%s] 触发验证码\ngt[%s]\nchallenge[%s]", client.uid, gt, challenge)
self.verify.account_id = client.hoyolab_id
logger.warning("UID[%s] 触发验证码\ngt[%s]\nchallenge[%s]", client.player_id, gt, challenge)
self.verify.account_id = client.account_id
validate = await self.verify.ajax(
referer=RecognizeSystem.REFERER,
gt=gt,
@ -166,16 +168,16 @@ class SignSystem(Plugin):
)
logger.debug("request_daily_reward 返回 %s", request_daily_reward)
if request_daily_reward and request_daily_reward.get("success", 0) == 1:
logger.warning("UID[%s] 触发验证码\nchallenge[%s]", client.uid, challenge)
logger.warning("UID[%s] 触发验证码\nchallenge[%s]", client.player_id, challenge)
raise NeedChallenge(
uid=client.uid,
uid=client.player_id,
gt=request_daily_reward.get("gt", ""),
challenge=request_daily_reward.get("challenge", ""),
)
elif config.pass_challenge_app_key:
# 如果无法绕过 检查配置文件是否配置识别 API 尝试请求绕过
# 注意 需要重新获取没有进行任何请求的 Challenge
logger.info("UID[%s] 正在使用 recognize 重新请求签到", client.uid)
logger.info("UID[%s] 正在使用 recognize 重新请求签到", client.player_id)
_request_daily_reward = await client.request_daily_reward(
"sign",
method="POST",
@ -186,8 +188,8 @@ class SignSystem(Plugin):
if _request_daily_reward and _request_daily_reward.get("success", 0) == 1:
_gt = _request_daily_reward.get("gt", "")
_challenge = _request_daily_reward.get("challenge", "")
logger.info("UID[%s] 创建验证码\ngt[%s]\nchallenge[%s]", client.uid, _gt, _challenge)
_validate = await RecognizeSystem.recognize(_gt, _challenge, uid=client.uid)
logger.info("UID[%s] 创建验证码\ngt[%s]\nchallenge[%s]", client.player_id, _gt, _challenge)
_validate = await RecognizeSystem.recognize(_gt, _challenge, uid=client.player_id)
if _validate:
logger.success("recognize 通过验证成功\nchallenge[%s]\nvalidate[%s]", _challenge, _validate)
request_daily_reward = await client.request_daily_reward(
@ -199,55 +201,57 @@ class SignSystem(Plugin):
validate=_validate,
)
if request_daily_reward and request_daily_reward.get("success", 0) == 1:
logger.warning("UID[%s] 触发验证码\nchallenge[%s]", client.uid, _challenge)
logger.warning("UID[%s] 触发验证码\nchallenge[%s]", client.player_id, _challenge)
gt = request_daily_reward.get("gt", "")
challenge = request_daily_reward.get("challenge", "")
logger.success("UID[%s] 创建验证成功\ngt[%s]\nchallenge[%s]", client.uid, gt, challenge)
logger.success(
"UID[%s] 创建验证成功\ngt[%s]\nchallenge[%s]", client.player_id, gt, challenge
)
raise NeedChallenge(
uid=client.uid,
uid=client.player_id,
gt=gt,
challenge=challenge,
)
logger.success("UID[%s] 通过 recognize 签到成功", client.uid)
logger.success("UID[%s] 通过 recognize 签到成功", client.player_id)
else:
request_daily_reward = await client.request_daily_reward(
"sign", method="POST", game=Game.STARRAIL, lang="zh-cn"
)
gt = request_daily_reward.get("gt", "")
challenge = request_daily_reward.get("challenge", "")
logger.success("UID[%s] 创建验证成功\ngt[%s]\nchallenge[%s]", client.uid, gt, challenge)
raise NeedChallenge(uid=client.uid, gt=gt, challenge=challenge)
logger.success("UID[%s] 创建验证成功\ngt[%s]\nchallenge[%s]", client.player_id, gt, challenge)
raise NeedChallenge(uid=client.player_id, gt=gt, challenge=challenge)
else:
request_daily_reward = await client.request_daily_reward(
"sign", method="POST", game=Game.STARRAIL, lang="zh-cn"
)
gt = request_daily_reward.get("gt", "")
challenge = request_daily_reward.get("challenge", "")
logger.success("UID[%s] 创建验证成功\ngt[%s]\nchallenge[%s]", client.uid, gt, challenge)
raise NeedChallenge(uid=client.uid, gt=gt, challenge=challenge)
logger.success("UID[%s] 创建验证成功\ngt[%s]\nchallenge[%s]", client.player_id, gt, challenge)
raise NeedChallenge(uid=client.player_id, gt=gt, challenge=challenge)
else:
logger.success("UID[%s] 签到成功", client.uid)
logger.success("UID[%s] 签到成功", client.player_id)
except TimeoutException as error:
logger.warning("UID[%s] 签到请求超时", client.uid)
logger.warning("UID[%s] 签到请求超时", client.player_id)
if is_raise:
raise error
return "签到失败了呜呜呜 ~ 服务器连接超时 服务器熟啦 ~ "
except AlreadyClaimed as error:
logger.warning("UID[%s] 已经签到", client.uid)
logger.warning("UID[%s] 已经签到", client.player_id)
if is_raise:
raise error
result = "今天开拓者已经签到过了~"
except GenshinException as error:
logger.warning("UID %s 签到失败API返回信息为 %s", client.uid, str(error))
except SimnetBadRequest as error:
logger.warning("UID %s 签到失败API返回信息为 %s", client.player_id, str(error))
if is_raise:
raise error
return f"获取签到状态失败API返回信息为 {str(error)}"
else:
result = "OK"
else:
logger.info("UID[%s] 已经签到", client.uid)
logger.info("UID[%s] 已经签到", client.player_id)
result = "今天开拓者已经签到过了~"
logger.info("UID[%s] 签到结果 %s", client.uid, result)
logger.info("UID[%s] 签到结果 %s", client.player_id, result)
reward = rewards[daily_reward_info.claimed_rewards - (1 if daily_reward_info.signed_in else 0)]
today = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
cn_timezone = datetime.timezone(datetime.timedelta(hours=8))
@ -258,7 +262,7 @@ class SignSystem(Plugin):
message = (
f"#### {title} ####\n"
f"时间:{today} (UTC+8)\n"
f"UID: {client.uid}\n"
f"UID: {client.player_id}\n"
f"今日奖励: {reward.name} × {reward.amount}\n"
f"本月漏签次数:{missed_days}\n"
f"签到结果: {result}"
@ -284,15 +288,15 @@ class SignSystem(Plugin):
continue
user_id = sign_db.user_id
try:
client = await self.genshin_helper.get_genshin_client(user_id)
text = await self.start_sign(client, is_sleep=True, is_raise=True, title=title)
async with self.genshin_helper.genshin(user_id) as client:
text = await self.start_sign(client, is_sleep=True, is_raise=True, title=title)
except InvalidCookies:
text = "自动签到执行失败Cookie无效"
sign_db.status = SignStatusEnum.INVALID_COOKIES
except AlreadyClaimed:
text = "今天开拓者已经签到过了~"
sign_db.status = SignStatusEnum.ALREADY_CLAIMED
except GenshinException as exc:
except SimnetBadRequest as exc:
text = f"自动签到执行失败API返回信息为 {str(exc)}"
sign_db.status = SignStatusEnum.GENSHIN_EXCEPTION
except ClientConnectorError:

1942
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,29 @@
[tool.poetry]
name = "PaiGram"
name = "PamGram"
version = "0.4.0"
description = "Telegarm robot, query the official genshin information."
description = "Telegarm robot, query the official starrail information."
authors = ["洛水居室", "zhxy-CN", "Chuangbo Li", "kotoriのねこ", "omg-xtao", "艾迪", "Karako", "SiHuaN"]
license = "AGPL-3.0"
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.8"
httpx = "^0.23.3"
ujson = "^5.7.0"
genshin = { git = "https://github.com/thesadru/genshin.py" }
httpx = "^0.24.0"
ujson = "^5.8.0"
Jinja2 = "^3.1.2"
python-telegram-bot = { version = "^20.1", extras = ["ext", "rate-limiter"] }
sqlmodel = "^0.0.8"
colorlog = "^6.7.0"
fakeredis = "^2.10.3"
redis = "^4.5.4"
fakeredis = "^2.16.0"
redis = "^4.6.0"
beautifulsoup4 = "^4.12.1"
asyncmy = "^0.2.7"
pyppeteer = "^1.0.2"
aiofiles = "^23.1.0"
python-dotenv = "^1.0.0"
alembic = "^1.10.3"
alembic = "^1.11.1"
black = "^23.3.0"
rich = "^13.3.1"
rich = "^13.4.1"
enkanetwork-py = { git = "https://github.com/mrwan200/EnkaNetwork.py" }
TgCrypto = { version = "^1.2.5", optional = true }
Pyrogram = { version = "^2.0.102", optional = true }
@ -33,18 +32,19 @@ pytest-asyncio = { version = "^0.21.0", optional = true }
flaky = { version = "^3.7.0", optional = true }
lxml = "^4.9.2"
arko-wrapper = "^0.2.8"
fastapi = "^0.95.0"
uvicorn = { extras = ["standard"], version = "^0.21.1" }
sentry-sdk = "^1.19.1"
fastapi = "<0.100.0"
uvicorn = { extras = ["standard"], version = "^0.23.1" }
sentry-sdk = "^1.28.1"
GitPython = "^3.1.30"
openpyxl = "^3.1.1"
async-lru = "^2.0.2"
thefuzz = "^0.19.0"
qrcode = "^7.4.2"
cryptography = "^40.0.1"
pillow = "^9.4.0"
cryptography = "^41.0.2"
pillow = "^10.0.0"
playwright = "^1.27.1"
aiosqlite = { extras = ["sqlite"], version = "^0.19.0" }
simnet = { git = "https://github.com/PaiGramTeam/SIMNet" }
[tool.poetry.extras]
pyro = ["Pyrogram", "TgCrypto"]

View File

@ -1,106 +1,104 @@
aiofiles==23.1.0 ; python_version >= "3.8" and python_version < "4.0"
aiohttp==3.8.4 ; python_version >= "3.8" and python_version < "4.0"
aiolimiter==1.0.0 ; python_version >= "3.8" and python_version < "4.0"
aiolimiter==1.1.0 ; python_version >= "3.8" and python_version < "4.0"
aiosignal==1.3.1 ; python_version >= "3.8" and python_version < "4.0"
aiosqlite[sqlite]==0.19.0 ; python_version >= "3.8" and python_version < "4.0"
alembic==1.10.3 ; python_version >= "3.8" and python_version < "4.0"
anyio==3.6.2 ; python_version >= "3.8" and python_version < "4.0"
alembic==1.11.1 ; python_version >= "3.8" and python_version < "4.0"
anyio==3.7.1 ; python_version >= "3.8" and python_version < "4.0"
appdirs==1.4.4 ; python_version >= "3.8" and python_version < "4.0"
apscheduler==3.10.1 ; python_version >= "3.8" and python_version < "4.0"
arko-wrapper==0.2.8 ; python_version >= "3.8" and python_version < "4.0"
async-lru==2.0.2 ; python_version >= "3.8" and python_version < "4.0"
async-lru==2.0.3 ; python_version >= "3.8" and python_version < "4.0"
async-timeout==4.0.2 ; python_version >= "3.8" and python_version < "4.0"
asyncmy==0.2.7 ; python_version >= "3.8" and python_version < "4.0"
asyncmy==0.2.8 ; python_version >= "3.8" and python_version < "4.0"
attrs==23.1.0 ; python_version >= "3.8" and python_version < "4.0"
backports-zoneinfo==0.2.1 ; python_version >= "3.8" and python_version < "3.9"
beautifulsoup4==4.12.2 ; python_version >= "3.8" and python_version < "4.0"
black==23.3.0 ; python_version >= "3.8" and python_version < "4.0"
cachetools==5.3.0 ; python_version >= "3.8" and python_version < "4.0"
certifi==2022.12.7 ; python_version >= "3.8" and python_version < "4.0"
black==23.7.0 ; python_version >= "3.8" and python_version < "4.0"
cachetools==5.3.1 ; python_version >= "3.8" and python_version < "4.0"
certifi==2023.5.7 ; python_version >= "3.8" and python_version < "4.0"
cffi==1.15.1 ; python_version >= "3.8" and python_version < "4.0"
charset-normalizer==3.1.0 ; python_version >= "3.8" and python_version < "4.0"
click==8.1.3 ; python_version >= "3.8" and python_version < "4.0"
colorama==0.4.6 ; python_version >= "3.8" and python_version < "4.0" and sys_platform == "win32" or python_version >= "3.8" and python_version < "4.0" and platform_system == "Windows"
charset-normalizer==3.2.0 ; python_version >= "3.8" and python_version < "4.0"
click==8.1.5 ; python_version >= "3.8" and python_version < "4.0"
colorama==0.4.6 ; python_version >= "3.8" and python_version < "4.0" and (sys_platform == "win32" or platform_system == "Windows")
colorlog==6.7.0 ; python_version >= "3.8" and python_version < "4.0"
cryptography==40.0.2 ; python_version >= "3.8" and python_version < "4.0"
cryptography==41.0.2 ; python_version >= "3.8" and python_version < "4.0"
enkanetwork-py @ git+https://github.com/mrwan200/EnkaNetwork.py@master ; python_version >= "3.8" and python_version < "4.0"
et-xmlfile==1.1.0 ; python_version >= "3.8" and python_version < "4.0"
exceptiongroup==1.1.1 ; python_version >= "3.8" and python_version < "3.11"
fakeredis==2.10.3 ; python_version >= "3.8" and python_version < "4.0"
fastapi==0.95.1 ; python_version >= "3.8" and python_version < "4.0"
exceptiongroup==1.1.2 ; python_version >= "3.8" and python_version < "3.11"
fakeredis==2.16.0 ; python_version >= "3.8" and python_version < "4.0"
fastapi==0.99.1 ; python_version >= "3.8" and python_version < "4.0"
flaky==3.7.0 ; python_version >= "3.8" and python_version < "4.0"
frozenlist==1.3.3 ; python_version >= "3.8" and python_version < "4.0"
genshin @ git+https://github.com/thesadru/genshin.py@master ; python_version >= "3.8" and python_version < "4.0"
frozenlist==1.4.0 ; python_version >= "3.8" and python_version < "4.0"
gitdb==4.0.10 ; python_version >= "3.8" and python_version < "4.0"
gitpython==3.1.31 ; python_version >= "3.8" and python_version < "4.0"
gitpython==3.1.32 ; python_version >= "3.8" and python_version < "4.0"
greenlet==1.1.3 ; python_version >= "3.8" and python_version < "4.0"
h11==0.14.0 ; python_version >= "3.8" and python_version < "4.0"
httpcore==0.16.3 ; python_version >= "3.8" and python_version < "4.0"
httptools==0.5.0 ; python_version >= "3.8" and python_version < "4.0"
httpx==0.23.3 ; python_version >= "3.8" and python_version < "4.0"
httpcore==0.17.3 ; python_version >= "3.8" and python_version < "4.0"
httptools==0.6.0 ; python_version >= "3.8" and python_version < "4.0"
httpx==0.24.1 ; python_version >= "3.8" and python_version < "4.0"
idna==3.4 ; python_version >= "3.8" and python_version < "4.0"
importlib-metadata==6.5.0 ; python_version >= "3.8" and python_version < "4.0"
importlib-resources==5.12.0 ; python_version >= "3.8" and python_version < "3.9"
importlib-metadata==6.8.0 ; python_version >= "3.8" and python_version < "4.0"
importlib-resources==6.0.0 ; python_version >= "3.8" and python_version < "3.9"
iniconfig==2.0.0 ; python_version >= "3.8" and python_version < "4.0"
jinja2==3.1.2 ; python_version >= "3.8" and python_version < "4.0"
lxml==4.9.2 ; python_version >= "3.8" and python_version < "4.0"
lxml==4.9.3 ; python_version >= "3.8" and python_version < "4.0"
mako==1.2.4 ; python_version >= "3.8" and python_version < "4.0"
markdown-it-py==2.2.0 ; python_version >= "3.8" and python_version < "4.0"
markupsafe==2.1.2 ; python_version >= "3.8" and python_version < "4.0"
markdown-it-py==3.0.0 ; python_version >= "3.8" and python_version < "4.0"
markupsafe==2.1.3 ; python_version >= "3.8" and python_version < "4.0"
mdurl==0.1.2 ; python_version >= "3.8" and python_version < "4.0"
multidict==6.0.4 ; python_version >= "3.8" and python_version < "4.0"
mypy-extensions==1.0.0 ; python_version >= "3.8" and python_version < "4.0"
openpyxl==3.1.2 ; python_version >= "3.8" and python_version < "4.0"
packaging==23.1 ; python_version >= "3.8" and python_version < "4.0"
pathspec==0.11.1 ; python_version >= "3.8" and python_version < "4.0"
pillow==9.5.0 ; python_version >= "3.8" and python_version < "4.0"
platformdirs==3.2.0 ; python_version >= "3.8" and python_version < "4.0"
pillow==10.0.0 ; python_version >= "3.8" and python_version < "4.0"
platformdirs==3.9.1 ; python_version >= "3.8" and python_version < "4.0"
playwright==1.27.1 ; python_version >= "3.8" and python_version < "4.0"
pluggy==1.0.0 ; python_version >= "3.8" and python_version < "4.0"
pluggy==1.2.0 ; python_version >= "3.8" and python_version < "4.0"
pyaes==1.6.1 ; python_version >= "3.8" and python_version < "4.0"
pycparser==2.21 ; python_version >= "3.8" and python_version < "4.0"
pydantic==1.10.7 ; python_version >= "3.8" and python_version < "4.0"
pydantic==1.10.11 ; python_version >= "3.8" and python_version < "4.0"
pyee==8.1.0 ; python_version >= "3.8" and python_version < "4.0"
pygments==2.15.1 ; python_version >= "3.8" and python_version < "4.0"
pypng==0.20220715.0 ; python_version >= "3.8" and python_version < "4.0"
pyppeteer==1.0.2 ; python_version >= "3.8" and python_version < "4.0"
pyrogram==2.0.104 ; python_version >= "3.8" and python_version < "4.0"
pyrogram==2.0.106 ; python_version >= "3.8" and python_version < "4.0"
pysocks==1.7.1 ; python_version >= "3.8" and python_version < "4.0"
pytest-asyncio==0.21.0 ; python_version >= "3.8" and python_version < "4.0"
pytest==7.3.1 ; python_version >= "3.8" and python_version < "4.0"
pytest-asyncio==0.21.1 ; python_version >= "3.8" and python_version < "4.0"
pytest==7.4.0 ; python_version >= "3.8" and python_version < "4.0"
python-dotenv==1.0.0 ; python_version >= "3.8" and python_version < "4.0"
python-telegram-bot[ext,rate-limiter]==20.2 ; python_version >= "3.8" and python_version < "4.0"
pytz-deprecation-shim==0.1.0.post0 ; python_version >= "3.8" and python_version < "4.0"
python-telegram-bot[ext,rate-limiter]==20.4 ; python_version >= "3.8" and python_version < "4.0"
pytz==2023.3 ; python_version >= "3.8" and python_version < "4.0"
pyyaml==6.0 ; python_version >= "3.8" and python_version < "4.0"
pyyaml==6.0.1 ; python_version >= "3.8" and python_version < "4.0"
qrcode==7.4.2 ; python_version >= "3.8" and python_version < "4.0"
redis==4.5.4 ; python_version >= "3.8" and python_version < "4.0"
rfc3986[idna2008]==1.5.0 ; python_version >= "3.8" and python_version < "4.0"
rich==13.3.4 ; python_version >= "3.8" and python_version < "4.0"
sentry-sdk==1.20.0 ; python_version >= "3.8" and python_version < "4.0"
setuptools==67.7.1 ; python_version >= "3.8" and python_version < "4.0"
redis==4.6.0 ; python_version >= "3.8" and python_version < "4.0"
rich==13.4.2 ; python_version >= "3.8" and python_version < "4.0"
sentry-sdk==1.28.1 ; python_version >= "3.8" and python_version < "4.0"
setuptools==68.0.0 ; python_version >= "3.8" and python_version < "4.0"
simnet @ git+https://github.com/PaiGramTeam/SIMNet@main ; python_version >= "3.8" and python_version < "4.0"
six==1.16.0 ; python_version >= "3.8" and python_version < "4.0"
smmap==5.0.0 ; python_version >= "3.8" and python_version < "4.0"
sniffio==1.3.0 ; python_version >= "3.8" and python_version < "4.0"
sortedcontainers==2.4.0 ; python_version >= "3.8" and python_version < "4.0"
soupsieve==2.4.1 ; python_version >= "3.8" and python_version < "4.0"
sqlalchemy2-stubs==0.0.2a34 ; python_version >= "3.8" and python_version < "4.0"
sqlalchemy2-stubs==0.0.2a35 ; python_version >= "3.8" and python_version < "4.0"
sqlalchemy==1.4.41 ; python_version >= "3.8" and python_version < "4.0"
sqlmodel==0.0.8 ; python_version >= "3.8" and python_version < "4.0"
starlette==0.26.1 ; python_version >= "3.8" and python_version < "4.0"
starlette==0.27.0 ; python_version >= "3.8" and python_version < "4.0"
tgcrypto==1.2.5 ; python_version >= "3.8" and python_version < "4.0"
thefuzz==0.19.0 ; python_version >= "3.8" and python_version < "4.0"
tomli==2.0.1 ; python_version >= "3.8" and python_version < "3.11"
tornado==6.3 ; python_version >= "3.8" and python_version < "4.0"
tornado==6.3.2 ; python_version >= "3.8" and python_version < "4.0"
tqdm==4.65.0 ; python_version >= "3.8" and python_version < "4.0"
typing-extensions==4.5.0 ; python_version >= "3.8" and python_version < "4.0"
tzdata==2023.3 ; python_version >= "3.8" and python_version < "4.0"
tzlocal==4.3 ; python_version >= "3.8" and python_version < "4.0"
ujson==5.7.0 ; python_version >= "3.8" and python_version < "4.0"
urllib3==1.26.15 ; python_version >= "3.8" and python_version < "4.0"
uvicorn[standard]==0.21.1 ; python_version >= "3.8" and python_version < "4.0"
uvloop==0.17.0 ; sys_platform != "win32" and sys_platform != "cygwin" and platform_python_implementation != "PyPy" and python_version >= "3.8" and python_version < "4.0"
typing-extensions==4.7.1 ; python_version >= "3.8" and python_version < "4.0"
tzdata==2023.3 ; python_version >= "3.8" and python_version < "4.0" and platform_system == "Windows"
tzlocal==5.0.1 ; python_version >= "3.8" and python_version < "4.0"
ujson==5.8.0 ; python_version >= "3.8" and python_version < "4.0"
urllib3==1.26.16 ; python_version >= "3.8" and python_version < "4.0"
uvicorn[standard]==0.22.0 ; python_version >= "3.8" and python_version < "4.0"
uvloop==0.17.0 ; (sys_platform != "win32" and sys_platform != "cygwin") and platform_python_implementation != "PyPy" and python_version >= "3.8" and python_version < "4.0"
watchfiles==0.19.0 ; python_version >= "3.8" and python_version < "4.0"
websockets==10.4 ; python_version >= "3.8" and python_version < "4.0"
yarl==1.8.2 ; python_version >= "3.8" and python_version < "4.0"
zipp==3.15.0 ; python_version >= "3.8" and python_version < "4.0"
yarl==1.9.2 ; python_version >= "3.8" and python_version < "4.0"
zipp==3.16.2 ; python_version >= "3.8" and python_version < "4.0"

View File

@ -1,64 +0,0 @@
from typing import Optional
from genshin import Client
from genshin.client.routes import InternationalRoute # noqa F401
from genshin.utility import recognize_starrail_server
from modules.apihelper.utility.devices import devices_methods
from modules.apihelper.utility.helpers import hex_digest, get_ds
AUTHKEY_API = "https://api-takumi.mihoyo.com/binding/api/genAuthKey"
HK4E_LOGIN_URL = InternationalRoute(
overseas="https://sg-public-api.hoyoverse.com/common/badge/v1/login/account",
chinese="https://api-takumi.mihoyo.com/common/badge/v1/login/account",
)
GACHA_HEADERS = {
"User-Agent": "okhttp/4.8.0",
"x-rpc-sys_version": "12",
"x-rpc-channel": "mihoyo",
"x-rpc-device_name": "",
"x-rpc-device_model": "",
"Referer": "https://app.mihoyo.com",
"Host": "api-takumi.mihoyo.com",
}
def recognize_starrail_game_biz(game_uid: int) -> str:
return "hkrpg_cn" if game_uid < 600000000 else "hkrpg_global"
async def get_authkey_by_stoken(client: Client) -> Optional[str]:
"""通过 stoken 获取 authkey"""
headers = GACHA_HEADERS.copy()
json = {
"auth_appid": "webview_gacha",
"game_biz": recognize_starrail_game_biz(client.uid),
"game_uid": client.uid,
"region": recognize_starrail_server(client.uid),
}
device_id = hex_digest(str(client.uid))
device = f"Paimon Build {device_id[:5]}"
await devices_methods.update_device_headers(client.hoyolab_id, headers)
headers["x-rpc-device_name"] = device
headers["x-rpc-device_model"] = device
app_version, client_type, ds_sign = get_ds()
headers["x-rpc-app_version"] = app_version
headers["x-rpc-client_type"] = client_type
headers["ds"] = ds_sign
data = await client.cookie_manager.request(AUTHKEY_API, method="POST", json=json, headers=headers)
return data.get("authkey")
async def fetch_hk4e_token_by_cookie(client: Client) -> None:
"""通过 cookie_token 获取 hk4e_token 保存到 client"""
url = HK4E_LOGIN_URL.get_url(client.region)
headers = {
"Content-Type": "application/json;charset=UTF-8",
}
json = {
"game_biz": recognize_starrail_game_biz(client.uid),
"lang": "zh-cn",
"uid": str(client.uid),
"region": recognize_starrail_server(client.uid),
}
await client.cookie_manager.request(url, method="POST", json=json, headers=headers)

View File

@ -1,3 +1,3 @@
from utils.patch import aiohttp, genshin
from utils.patch import aiohttp
__all__ = ["aiohttp", "genshin"]
__all__ = ["aiohttp"]

View File

@ -1,358 +0,0 @@
import asyncio
import typing
import warnings
import aiohttp.typedefs
import genshin # pylint: disable=W0406
import yarl
from genshin import constants, types, utility
from genshin.client import routes
from genshin.utility import generate_dynamic_secret, ds
from modules.apihelper.utility.devices import devices_methods
from modules.apihelper.utility.helpers import get_ds, get_ua, get_device_id, hex_digest
from utils.patch.methods import patch, patchable
DEVICE_ID = get_device_id()
UPDATE_CHARACTERS = False
def get_account_mid_v2(cookies: typing.Dict[str, str]) -> typing.Optional[str]:
return next(
(value for name, value in cookies.items() if name == "account_mid_v2"),
None,
)
@patch(genshin.client.components.calculator.CalculatorClient) # noqa
class CalculatorClient:
@patchable
async def request_calculator(
self,
endpoint: str,
*,
method: str = "POST",
lang: typing.Optional[str] = None,
params: typing.Optional[typing.Mapping[str, typing.Any]] = None,
data: typing.Optional[typing.Mapping[str, typing.Any]] = None,
headers: typing.Optional[aiohttp.typedefs.LooseHeaders] = None,
**kwargs: typing.Any,
) -> typing.Mapping[str, typing.Any]:
global UPDATE_CHARACTERS # pylint: disable=W0603
params = dict(params or {})
headers = dict(headers or {})
base_url = routes.CALCULATOR_URL.get_url(self.region)
url = base_url / endpoint
if method == "GET":
params["lang"] = lang or self.lang
data = None
else:
data = dict(data or {})
data["lang"] = lang or self.lang
if self.region == types.Region.CHINESE:
headers["referer"] = str(routes.CALCULATOR_REFERER_URL.get_url())
update_task = (
None
if UPDATE_CHARACTERS
else asyncio.create_task(utility.update_characters_any(lang or self.lang, lenient=True))
)
data = await self.request(url, method=method, params=params, data=data, headers=headers, **kwargs)
if update_task:
try:
await update_task
UPDATE_CHARACTERS = True
except Exception as e: # pylint: disable=W0703
warnings.warn(f"Failed to update characters: {e!r}")
return data
@patchable
async def get_character_details(
self,
character: genshin.types.IDOr[genshin.models.genshin.Character],
*,
uid: typing.Optional[int] = None,
lang: typing.Optional[str] = None,
):
uid = uid or await self._get_uid(genshin.types.Game.GENSHIN)
data = await self.request_calculator(
"sync/avatar/detail",
method="GET",
lang=lang,
params=dict(
avatar_id=int(character),
uid=uid,
region=genshin.utility.recognize_genshin_server(uid),
),
)
if data.get("weapon") is None:
weapon = {
"id": character.weapon.id,
"name": character.weapon.name,
"icon": character.weapon.icon,
"weapon_cat_id": character.weapon.type,
"weapon_level": character.weapon.rarity,
"max_level": 90,
"level_current": character.weapon.level,
}
data["weapon"] = weapon
return genshin.models.genshin.CalculatorCharacterDetails(**data)
@patch(genshin.client.components.base.BaseClient) # noqa
class BaseClient:
@patchable
async def request_hoyolab(
self: "genshin.Client",
url: aiohttp.typedefs.StrOrURL,
*,
lang: typing.Optional[str] = None,
region: typing.Optional[types.Region] = None,
method: typing.Optional[str] = None,
params: typing.Optional[typing.Mapping[str, typing.Any]] = None,
data: typing.Any = None,
headers: typing.Optional[aiohttp.typedefs.LooseHeaders] = None,
**kwargs: typing.Any,
) -> typing.Mapping[str, typing.Any]:
"""Make a request any hoyolab endpoint."""
if lang is not None and lang not in constants.LANGS:
raise ValueError(f"{lang} is not a valid language, must be one of: " + ", ".join(constants.LANGS))
lang = lang or self.lang
region = region or self.region
url = routes.TAKUMI_URL.get_url(region).join(yarl.URL(url))
if region == types.Region.OVERSEAS:
headers = {
"x-rpc-app_version": "1.5.0",
"x-rpc-client_type": "5",
"x-rpc-language": lang,
"ds": generate_dynamic_secret(),
}
elif region == types.Region.CHINESE:
account_id = self.cookie_manager.user_id
if account_id:
device_id = hex_digest(str(account_id))
else:
account_mid_v2 = get_account_mid_v2(self.cookie_manager.cookies)
if account_mid_v2:
device_id = hex_digest(account_mid_v2)
else:
device_id = DEVICE_ID
app_version, client_type, ds_sign = get_ds(new_ds=True, data=data, params=params)
ua = get_ua(device="Paimon Build " + device_id[0:5], version=app_version)
headers = {
"User-Agent": ua,
"X_Requested_With": "com.mihoyo.hoyolab",
"Referer": "https://webstatic.mihoyo.com",
"x-rpc-app_version": app_version,
"x-rpc-client_type": client_type,
"ds": ds_sign,
}
await devices_methods.update_device_headers(self.hoyolab_id, headers)
else:
raise TypeError(f"{region!r} is not a valid region.")
data = await self.request(url, method=method, params=params, data=data, headers=headers, **kwargs)
return data
@patchable
async def request(
self: "genshin.Client",
url: aiohttp.typedefs.StrOrURL,
*,
method: typing.Optional[str] = None,
params: typing.Optional[typing.Mapping[str, typing.Any]] = None,
data: typing.Any = None,
headers: typing.Optional[aiohttp.typedefs.LooseHeaders] = None,
cache: typing.Any = None,
static_cache: typing.Any = None,
**kwargs: typing.Any,
) -> typing.Mapping[str, typing.Any]:
"""Make a request and return a parsed json response."""
if cache is not None:
value = await self.cache.get(cache)
if value is not None:
return value
elif static_cache is not None:
value = await self.cache.get_static(static_cache)
if value is not None:
return value
# actual request
headers = dict(headers or {})
headers.setdefault("User-Agent", self.USER_AGENT)
await devices_methods.update_device_headers(self.hoyolab_id, headers)
if method is None:
method = "POST" if data else "GET"
if "json" in kwargs:
raise TypeError("Use data instead of json in request.")
await self._request_hook(method, url, params=params, data=data, headers=headers, **kwargs)
response = await self.cookie_manager.request(
url,
method=method,
params=params,
json=data,
headers=headers,
**kwargs,
)
# cache
if cache is not None:
await self.cache.set(cache, response)
elif static_cache is not None:
await self.cache.set_static(static_cache, response)
return response
@patchable
async def request_bbs(
self: "genshin.Client",
url: aiohttp.typedefs.StrOrURL,
*,
lang: typing.Optional[str] = None,
region: typing.Optional[types.Region] = None,
method: typing.Optional[str] = None,
params: typing.Optional[typing.Mapping[str, typing.Any]] = None,
data: typing.Any = None,
headers: typing.Optional[aiohttp.typedefs.LooseHeaders] = None,
**kwargs: typing.Any,
) -> typing.Mapping[str, typing.Any]:
"""Make a request any bbs endpoint."""
if lang is not None and lang not in constants.LANGS:
raise ValueError(f"{lang} is not a valid language, must be one of: " + ", ".join(constants.LANGS))
lang = lang or self.lang
region = region or self.region
url = routes.BBS_URL.get_url(region).join(yarl.URL(url))
headers = dict(headers or {})
if self.region == types.Region.CHINESE:
if self.region == types.Region.CHINESE:
account_id = self.cookie_manager.user_id
if account_id:
device_id = hex_digest(str(account_id))
else:
account_mid_v2 = get_account_mid_v2(self.cookie_manager.cookies)
if account_mid_v2:
device_id = hex_digest(account_mid_v2)
else:
device_id = DEVICE_ID
app_version, _, ds_sign = get_ds()
ua = get_ua(device="Paimon Build " + device_id[0:5], version=app_version)
add_headers = {
"User-Agent": ua,
"Referer": "https://www.miyoushe.com/ys/",
"x-rpc-app_version": app_version,
"x-rpc-client_type": "4",
"ds": ds_sign,
}
headers.update(add_headers)
await devices_methods.update_device_headers(self.hoyolab_id, headers)
elif self.region == types.Region.OVERSEAS:
headers.update(ds.get_ds_headers(data=data, params=params, region=region, lang=lang or self.lang))
headers["Referer"] = str(routes.BBS_REFERER_URL.get_url(self.region))
else:
raise TypeError(f"{region!r} is not a valid region.")
data = await self.request(url, method=method, params=params, data=data, headers=headers, **kwargs)
return data
@patch(genshin.client.components.daily.DailyRewardClient) # noqa
class DailyRewardClient:
@patchable
async def request_daily_reward(
self: "genshin.Client",
endpoint: str,
*,
game: typing.Optional[types.Game] = None,
method: str = "GET",
lang: typing.Optional[str] = None,
params: typing.Optional[typing.Mapping[str, typing.Any]] = None,
headers: typing.Optional[aiohttp.typedefs.LooseHeaders] = None,
**kwargs: typing.Any,
) -> typing.Mapping[str, typing.Any]:
"""Make a request towards the daily reward endpoint."""
params = dict(params or {})
headers = dict(headers or {})
if game is None:
if self.default_game is None:
raise RuntimeError("No default game set.")
game = self.default_game
base_url = routes.REWARD_URL.get_url(self.region, game)
url = (base_url / endpoint).update_query(**base_url.query)
if self.region == types.Region.OVERSEAS:
params["lang"] = lang or self.lang
elif self.region == types.Region.CHINESE:
# TODO: Support cn honkai
player_id = await self._get_uid(types.Game.STARRAIL)
params["uid"] = player_id
params["region"] = utility.recognize_server(player_id, types.Game.STARRAIL)
account_id = self.cookie_manager.user_id
if account_id:
device_id = hex_digest(str(account_id))
else:
account_mid_v2 = get_account_mid_v2(self.cookie_manager.cookies)
if account_mid_v2:
device_id = hex_digest(account_mid_v2)
else:
device_id = DEVICE_ID
if endpoint == "sign":
app_version, client_type, ds_sign = get_ds()
else:
app_version, client_type, ds_sign = get_ds(new_ds=True, params=params)
device = "Paimon Build " + device_id[0:5]
ua = get_ua(device=device)
headers["User-Agent"] = ua
headers["X_Requested_With"] = "com.mihoyo.hoyolab"
headers["Referer"] = (
"https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?"
"bbs_auth_required=true&act_id=e202009291139501&utm_source=bbs&utm_medium=mys&utm_campaign=icon"
)
headers["x-rpc-device_name"] = device
headers["x-rpc-app_version"] = app_version
headers["x-rpc-client_type"] = client_type
headers["x-rpc-sys_version"] = "12"
headers["x-rpc-platform"] = "android"
headers["x-rpc-channel"] = "miyousheluodi"
headers["x-rpc-device_model"] = device
headers["ds"] = ds_sign
validate = kwargs.get("validate")
challenge = kwargs.get("challenge")
if validate and challenge:
headers["x-rpc-challenge"] = challenge
headers["x-rpc-validate"] = validate
headers["x-rpc-seccode"] = f"{validate}|jordan"
await devices_methods.update_device_headers(self.hoyolab_id, headers)
else:
raise TypeError(f"{self.region!r} is not a valid region.")
kwargs.pop("challenge", None)
kwargs.pop("validate", None)
return await self.request(url, method=method, params=params, headers=headers, **kwargs)