mirror of
https://github.com/PaiGramTeam/MibooGram.git
synced 2024-11-24 17:19:31 +00:00
♻️ 重构 gacha_log
Co-authored-by: xtaodada <xtao@xtaolink.cn>
This commit is contained in:
parent
24c63002cd
commit
ab1c490a13
14
metadata/scripts/paimon_moe.py
Normal file
14
metadata/scripts/paimon_moe.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from utils.const import PROJECT_ROOT
|
||||||
|
from aiofiles import open as async_open
|
||||||
|
from httpx import AsyncClient, URL
|
||||||
|
|
||||||
|
|
||||||
|
async def update_paimon_moe_zh(overwrite: bool = True):
|
||||||
|
path = PROJECT_ROOT.joinpath("metadata/data/paimon_moe_zh.json")
|
||||||
|
if not overwrite and path.exists():
|
||||||
|
return
|
||||||
|
host = URL("https://raw.fastgit.org/MadeBaruna/paimon-moe/main/src/locales/items/zh.json")
|
||||||
|
client = AsyncClient()
|
||||||
|
text = (await client.get(host)).text
|
||||||
|
async with async_open(path, mode="w", encoding="utf-8") as file:
|
||||||
|
await file.write(text)
|
@ -4,7 +4,7 @@ import functools
|
|||||||
|
|
||||||
from metadata.genshin import WEAPON_DATA
|
from metadata.genshin import WEAPON_DATA
|
||||||
|
|
||||||
__all__ = ["roles", "weapons", "roleToId", "roleToName", "weaponToName", "weaponToId"]
|
__all__ = ["roles", "weapons", "roleToId", "roleToName", "weaponToName", "weaponToId", "not_real_roles"]
|
||||||
|
|
||||||
# noinspection SpellCheckingInspection
|
# noinspection SpellCheckingInspection
|
||||||
roles = {
|
roles = {
|
||||||
|
@ -5,14 +5,6 @@ class APIHelperException(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class GachaLogException(APIHelperException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class GachaLogAccountNotFound(GachaLogException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkException(APIHelperException):
|
class NetworkException(APIHelperException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
13
modules/gacha_log/const.py
Normal file
13
modules/gacha_log/const.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from genshin.models import BannerType
|
||||||
|
|
||||||
|
PAIMONMOE_VERSION = 3
|
||||||
|
UIGF_VERSION = "v2.2"
|
||||||
|
|
||||||
|
|
||||||
|
GACHA_TYPE_LIST = {
|
||||||
|
BannerType.NOVICE: "新手祈愿",
|
||||||
|
BannerType.PERMANENT: "常驻祈愿",
|
||||||
|
BannerType.WEAPON: "武器祈愿",
|
||||||
|
BannerType.CHARACTER1: "角色祈愿",
|
||||||
|
BannerType.CHARACTER2: "角色祈愿",
|
||||||
|
}
|
29
modules/gacha_log/error.py
Normal file
29
modules/gacha_log/error.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
class GachaLogException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GachaLogFileError(GachaLogException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GachaLogNotFound(GachaLogException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GachaLogAccountNotFound(GachaLogException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GachaLogInvalidAuthkey(GachaLogException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GachaLogMixedProvider(GachaLogException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PaimonMoeGachaLogFileError(GachaLogFileError):
|
||||||
|
def __init__(self, file_version: int, support_version: int):
|
||||||
|
super().__init__("Paimon.Moe version not supported")
|
||||||
|
self.support_version = support_version
|
||||||
|
self.file_version = file_version
|
9
modules/gacha_log/helpers.py
Normal file
9
modules/gacha_log/helpers.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
def from_url_get_authkey(url: str) -> str:
|
||||||
|
"""从 UEL 解析 authkey
|
||||||
|
:param url: URL
|
||||||
|
:return: authkey
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return url.split("authkey=")[1].split("&")[0]
|
||||||
|
except IndexError:
|
||||||
|
return url
|
@ -1,138 +1,47 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import time
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Dict, Tuple, Optional, Union
|
from typing import List, Tuple, Optional
|
||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
from genshin import Client, InvalidAuthkey
|
from genshin import Client, InvalidAuthkey
|
||||||
from genshin.models import BannerType
|
from genshin.models import BannerType
|
||||||
from pydantic import BaseModel, validator
|
from openpyxl import load_workbook
|
||||||
|
|
||||||
from core.base.assets import AssetsService
|
from core.base.assets import AssetsService
|
||||||
from metadata.pool.pool import get_pool_by_id
|
from metadata.pool.pool import get_pool_by_id
|
||||||
from metadata.shortname import roleToId, weaponToId, not_real_roles
|
from metadata.shortname import roleToId, weaponToId
|
||||||
from modules.apihelper.error import GachaLogAccountNotFound
|
from modules.gacha_log.const import GACHA_TYPE_LIST, PAIMONMOE_VERSION
|
||||||
from utils.const import PROJECT_ROOT
|
from modules.gacha_log.error import (
|
||||||
from utils.log import logger
|
GachaLogAccountNotFound,
|
||||||
|
GachaLogInvalidAuthkey,
|
||||||
GACHA_LOG_PATH = PROJECT_ROOT.joinpath("data", "apihelper", "gacha_log")
|
GachaLogException,
|
||||||
GACHA_LOG_PATH.mkdir(parents=True, exist_ok=True)
|
GachaLogFileError,
|
||||||
GACHA_TYPE_LIST = {
|
GachaLogNotFound,
|
||||||
BannerType.NOVICE: "新手祈愿",
|
PaimonMoeGachaLogFileError,
|
||||||
BannerType.PERMANENT: "常驻祈愿",
|
GachaLogMixedProvider,
|
||||||
BannerType.WEAPON: "武器祈愿",
|
)
|
||||||
BannerType.CHARACTER1: "角色祈愿",
|
from modules.gacha_log.models import (
|
||||||
BannerType.CHARACTER2: "角色祈愿",
|
GachaItem,
|
||||||
}
|
FiveStarItem,
|
||||||
|
FourStarItem,
|
||||||
|
Pool,
|
||||||
class FiveStarItem(BaseModel):
|
GachaLogInfo,
|
||||||
name: str
|
UIGFGachaType,
|
||||||
icon: str
|
ItemType,
|
||||||
count: int
|
ImportType,
|
||||||
type: str
|
UIGFModel,
|
||||||
isUp: bool
|
UIGFInfo,
|
||||||
isBig: bool
|
UIGFItem,
|
||||||
time: datetime.datetime
|
)
|
||||||
|
|
||||||
|
|
||||||
class FourStarItem(BaseModel):
|
|
||||||
name: str
|
|
||||||
icon: str
|
|
||||||
count: int
|
|
||||||
type: str
|
|
||||||
time: datetime.datetime
|
|
||||||
|
|
||||||
|
|
||||||
class GachaItem(BaseModel):
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
gacha_type: str
|
|
||||||
item_type: str
|
|
||||||
rank_type: str
|
|
||||||
time: datetime.datetime
|
|
||||||
|
|
||||||
@validator("name")
|
|
||||||
def name_validator(cls, v):
|
|
||||||
if item_id := (roleToId(v) or weaponToId(v)):
|
|
||||||
if item_id not in not_real_roles:
|
|
||||||
return v
|
|
||||||
raise ValueError("Invalid name")
|
|
||||||
|
|
||||||
@validator("gacha_type")
|
|
||||||
def check_gacha_type(cls, v):
|
|
||||||
if v not in {"100", "200", "301", "302", "400"}:
|
|
||||||
raise ValueError("gacha_type must be 200, 301, 302 or 400")
|
|
||||||
return v
|
|
||||||
|
|
||||||
@validator("item_type")
|
|
||||||
def check_item_type(cls, item):
|
|
||||||
if item not in {"角色", "武器"}:
|
|
||||||
raise ValueError("error item type")
|
|
||||||
return item
|
|
||||||
|
|
||||||
@validator("rank_type")
|
|
||||||
def check_rank_type(cls, rank):
|
|
||||||
if rank not in {"5", "4", "3"}:
|
|
||||||
raise ValueError("error rank type")
|
|
||||||
return rank
|
|
||||||
|
|
||||||
|
|
||||||
class GachaLogInfo(BaseModel):
|
|
||||||
user_id: str
|
|
||||||
uid: str
|
|
||||||
update_time: datetime.datetime
|
|
||||||
item_list: Dict[str, List[GachaItem]] = {
|
|
||||||
"角色祈愿": [],
|
|
||||||
"武器祈愿": [],
|
|
||||||
"常驻祈愿": [],
|
|
||||||
"新手祈愿": [],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Pool:
|
|
||||||
def __init__(self, five: List[str], four: List[str], name: str, to: str, **kwargs):
|
|
||||||
self.five = five
|
|
||||||
self.real_name = name
|
|
||||||
self.name = "、".join(self.five)
|
|
||||||
self.four = four
|
|
||||||
self.from_ = kwargs.get("from")
|
|
||||||
self.to = to
|
|
||||||
self.from_time = datetime.datetime.strptime(self.from_, "%Y-%m-%d %H:%M:%S")
|
|
||||||
self.to_time = datetime.datetime.strptime(self.to, "%Y-%m-%d %H:%M:%S")
|
|
||||||
self.start = self.from_time
|
|
||||||
self.start_init = False
|
|
||||||
self.end = self.to_time
|
|
||||||
self.dict = {}
|
|
||||||
self.count = 0
|
|
||||||
|
|
||||||
def parse(self, item: Union[FiveStarItem, FourStarItem]):
|
|
||||||
if self.from_time <= item.time <= self.to_time:
|
|
||||||
if self.dict.get(item.name):
|
|
||||||
self.dict[item.name]["count"] += 1
|
|
||||||
else:
|
|
||||||
self.dict[item.name] = {
|
|
||||||
"name": item.name,
|
|
||||||
"icon": item.icon,
|
|
||||||
"count": 1,
|
|
||||||
"rank_type": 5 if isinstance(item, FiveStarItem) else 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
def count_item(self, item: List[GachaItem]):
|
|
||||||
for i in item:
|
|
||||||
if self.from_time <= i.time <= self.to_time:
|
|
||||||
self.count += 1
|
|
||||||
if not self.start_init:
|
|
||||||
self.start = i.time
|
|
||||||
self.end = i.time
|
|
||||||
|
|
||||||
def to_list(self):
|
|
||||||
return list(self.dict.values())
|
|
||||||
|
|
||||||
|
|
||||||
class GachaLog:
|
class GachaLog:
|
||||||
|
def __init__(self, gacha_log_path: Path):
|
||||||
|
self.gacha_log_path = gacha_log_path
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def load_json(path):
|
async def load_json(path):
|
||||||
async with aiofiles.open(path, "r", encoding="utf-8") as f:
|
async with aiofiles.open(path, "r", encoding="utf-8") as f:
|
||||||
@ -145,9 +54,8 @@ class GachaLog:
|
|||||||
return await f.write(json.dumps(data, ensure_ascii=False, indent=4))
|
return await f.write(json.dumps(data, ensure_ascii=False, indent=4))
|
||||||
await f.write(data)
|
await f.write(data)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def load_history_info(
|
async def load_history_info(
|
||||||
user_id: str, uid: str, only_status: bool = False
|
self, user_id: str, uid: str, only_status: bool = False
|
||||||
) -> Tuple[Optional[GachaLogInfo], bool]:
|
) -> Tuple[Optional[GachaLogInfo], bool]:
|
||||||
"""读取历史抽卡记录数据
|
"""读取历史抽卡记录数据
|
||||||
:param user_id: 用户id
|
:param user_id: 用户id
|
||||||
@ -155,26 +63,25 @@ class GachaLog:
|
|||||||
:param only_status: 是否只读取状态
|
:param only_status: 是否只读取状态
|
||||||
:return: 抽卡记录数据
|
:return: 抽卡记录数据
|
||||||
"""
|
"""
|
||||||
file_path = GACHA_LOG_PATH / f"{user_id}-{uid}.json"
|
file_path = self.gacha_log_path / f"{user_id}-{uid}.json"
|
||||||
if only_status:
|
if only_status:
|
||||||
return None, file_path.exists()
|
return None, file_path.exists()
|
||||||
if not file_path.exists():
|
if not file_path.exists():
|
||||||
return GachaLogInfo(user_id=user_id, uid=uid, update_time=datetime.datetime.now()), False
|
return GachaLogInfo(user_id=user_id, uid=uid, update_time=datetime.datetime.now()), False
|
||||||
try:
|
try:
|
||||||
return GachaLogInfo.parse_obj(await GachaLog.load_json(file_path)), True
|
return GachaLogInfo.parse_obj(await self.load_json(file_path)), True
|
||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
return GachaLogInfo(user_id=user_id, uid=uid, update_time=datetime.datetime.now()), False
|
return GachaLogInfo(user_id=user_id, uid=uid, update_time=datetime.datetime.now()), False
|
||||||
|
|
||||||
@staticmethod
|
async def remove_history_info(self, user_id: str, uid: str) -> bool:
|
||||||
async def remove_history_info(user_id: str, uid: str) -> bool:
|
|
||||||
"""删除历史抽卡记录数据
|
"""删除历史抽卡记录数据
|
||||||
:param user_id: 用户id
|
:param user_id: 用户id
|
||||||
:param uid: 原神uid
|
:param uid: 原神uid
|
||||||
:return: 是否删除成功
|
:return: 是否删除成功
|
||||||
"""
|
"""
|
||||||
file_path = GACHA_LOG_PATH / f"{user_id}-{uid}.json"
|
file_path = self.gacha_log_path / f"{user_id}-{uid}.json"
|
||||||
file_bak_path = GACHA_LOG_PATH / f"{user_id}-{uid}.json.bak"
|
file_bak_path = self.gacha_log_path / f"{user_id}-{uid}.json.bak"
|
||||||
file_export_path = GACHA_LOG_PATH / f"{user_id}-{uid}-uigf.json"
|
file_export_path = self.gacha_log_path / f"{user_id}-{uid}-uigf.json"
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
file_bak_path.unlink(missing_ok=True)
|
file_bak_path.unlink(missing_ok=True)
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
@ -187,15 +94,14 @@ class GachaLog:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
async def save_gacha_log_info(self, user_id: str, uid: str, info: GachaLogInfo):
|
||||||
async def save_gacha_log_info(user_id: str, uid: str, info: GachaLogInfo):
|
|
||||||
"""保存抽卡记录数据
|
"""保存抽卡记录数据
|
||||||
:param user_id: 用户id
|
:param user_id: 用户id
|
||||||
:param uid: 原神uid
|
:param uid: 原神uid
|
||||||
:param info: 抽卡记录数据
|
:param info: 抽卡记录数据
|
||||||
"""
|
"""
|
||||||
save_path = GACHA_LOG_PATH / f"{user_id}-{uid}.json"
|
save_path = self.gacha_log_path / f"{user_id}-{uid}.json"
|
||||||
save_path_bak = GACHA_LOG_PATH / f"{user_id}-{uid}.json.bak"
|
save_path_bak = self.gacha_log_path / f"{user_id}-{uid}.json.bak"
|
||||||
# 将旧数据备份一次
|
# 将旧数据备份一次
|
||||||
with contextlib.suppress(PermissionError):
|
with contextlib.suppress(PermissionError):
|
||||||
if save_path.exists():
|
if save_path.exists():
|
||||||
@ -203,82 +109,73 @@ class GachaLog:
|
|||||||
save_path_bak.unlink()
|
save_path_bak.unlink()
|
||||||
save_path.rename(save_path.parent / f"{save_path.name}.bak")
|
save_path.rename(save_path.parent / f"{save_path.name}.bak")
|
||||||
# 写入数据
|
# 写入数据
|
||||||
await GachaLog.save_json(save_path, info.json())
|
await self.save_json(save_path, info.json())
|
||||||
|
|
||||||
@staticmethod
|
async def gacha_log_to_uigf(self, user_id: str, uid: str) -> Optional[Path]:
|
||||||
async def gacha_log_to_uigf(user_id: str, uid: str) -> Tuple[bool, str, Optional[Path]]:
|
|
||||||
"""抽卡日记转换为 UIGF 格式
|
"""抽卡日记转换为 UIGF 格式
|
||||||
:param user_id: 用户ID
|
:param user_id: 用户ID
|
||||||
:param uid: 游戏UID
|
:param uid: 游戏UID
|
||||||
:return: 转换是否成功、转换信息、UIGF文件目录
|
:return: 转换是否成功、转换信息、UIGF文件目录
|
||||||
"""
|
"""
|
||||||
data, state = await GachaLog.load_history_info(user_id, uid)
|
data, state = await self.load_history_info(user_id, uid)
|
||||||
if not state:
|
if not state:
|
||||||
return False, "派蒙还没有找到你导入的任何抽卡记录哦,快试试导入吧~", None
|
raise GachaLogNotFound
|
||||||
save_path = GACHA_LOG_PATH / f"{user_id}-{uid}-uigf.json"
|
save_path = self.gacha_log_path / f"{user_id}-{uid}-uigf.json"
|
||||||
uigf_dict = {
|
info = UIGFModel(
|
||||||
"info": {
|
info=UIGFInfo(uid=uid, export_app=ImportType.TGPaimonBot.value, export_app_version="v3"), list=[]
|
||||||
"uid": uid,
|
)
|
||||||
"lang": "zh-cn",
|
|
||||||
"export_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
||||||
"export_timestamp": int(time.time()),
|
|
||||||
"export_app": "TGPaimonBot",
|
|
||||||
"export_app_version": "v3",
|
|
||||||
"uigf_version": "v2.2",
|
|
||||||
},
|
|
||||||
"list": [],
|
|
||||||
}
|
|
||||||
for items in data.item_list.values():
|
for items in data.item_list.values():
|
||||||
for item in items:
|
for item in items:
|
||||||
uigf_dict["list"].append(
|
info.list.append(
|
||||||
{
|
UIGFItem(
|
||||||
"gacha_type": item.gacha_type,
|
id=item.id,
|
||||||
"item_id": "",
|
name=item.name,
|
||||||
"count": "1",
|
gacha_type=item.gacha_type,
|
||||||
"time": item.time.strftime("%Y-%m-%d %H:%M:%S"),
|
item_type=item.item_type,
|
||||||
"name": item.name,
|
rank_type=item.rank_type,
|
||||||
"item_type": item.item_type,
|
time=item.time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
"rank_type": item.rank_type,
|
uigf_gacha_type=item.gacha_type,
|
||||||
"id": item.id,
|
|
||||||
"uigf_gacha_type": item.gacha_type,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
await GachaLog.save_json(save_path, uigf_dict)
|
)
|
||||||
return True, "", save_path
|
await self.save_json(save_path, info.dict())
|
||||||
|
return save_path
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def verify_data(data: List[GachaItem]):
|
async def verify_data(data: List[GachaItem]) -> bool:
|
||||||
try:
|
try:
|
||||||
total = len(data)
|
total = len(data)
|
||||||
five_star = len([i for i in data if i.rank_type == "5"])
|
five_star = len([i for i in data if i.rank_type == "5"])
|
||||||
four_star = len([i for i in data if i.rank_type == "4"])
|
four_star = len([i for i in data if i.rank_type == "4"])
|
||||||
if total > 50:
|
if total > 50:
|
||||||
if total <= five_star * 15:
|
if total <= five_star * 15:
|
||||||
return False, "检测到您将要导入的抽卡记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。"
|
raise GachaLogFileError("检测到您将要导入的抽卡记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。")
|
||||||
if four_star < five_star:
|
if four_star < five_star:
|
||||||
return False, "检测到您将要导入的抽卡记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。"
|
raise GachaLogFileError("检测到您将要导入的抽卡记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。")
|
||||||
return True, ""
|
return True
|
||||||
except Exception as exc: # pylint: disable=W0703
|
except Exception as exc: # pylint: disable=W0703
|
||||||
logger.warning(f"抽卡记录数据验证失败 {repr(exc)}")
|
raise GachaLogFileError from exc
|
||||||
return False, "导入失败,数据格式错误"
|
|
||||||
|
|
||||||
@staticmethod
|
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(
|
|
||||||
user_id: int, client: Client, data: dict, verify_uid: bool = True
|
|
||||||
) -> Tuple[bool, str]:
|
|
||||||
new_num = 0
|
new_num = 0
|
||||||
try:
|
try:
|
||||||
if not verify_uid:
|
|
||||||
data["info"]["uid"] = str(client.uid)
|
|
||||||
uid = data["info"]["uid"]
|
uid = data["info"]["uid"]
|
||||||
if int(uid) != client.uid:
|
if not verify_uid:
|
||||||
|
uid = client.uid
|
||||||
|
elif int(uid) != client.uid:
|
||||||
raise GachaLogAccountNotFound
|
raise GachaLogAccountNotFound
|
||||||
|
try:
|
||||||
|
import_type = ImportType(data["info"]["export_app"])
|
||||||
|
except ValueError:
|
||||||
|
import_type = ImportType.UNKNOWN
|
||||||
# 检查导入数据是否合法
|
# 检查导入数据是否合法
|
||||||
all_items = [GachaItem(**i) for i in data["list"]]
|
all_items = [GachaItem(**i) for i in data["list"]]
|
||||||
status, text = await GachaLog.verify_data(all_items)
|
await self.verify_data(all_items)
|
||||||
if not status:
|
gacha_log, status = await self.load_history_info(str(user_id), uid)
|
||||||
return text
|
if import_type == ImportType.PAIMONMOE:
|
||||||
gacha_log, _ = await GachaLog.load_history_info(str(user_id), uid)
|
if status and gacha_log.get_import_type != ImportType.PAIMONMOE:
|
||||||
|
raise GachaLogMixedProvider
|
||||||
|
elif status and gacha_log.get_import_type == ImportType.PAIMONMOE:
|
||||||
|
raise GachaLogMixedProvider
|
||||||
# 将唯一 id 放入临时数据中,加快查找速度
|
# 将唯一 id 放入临时数据中,加快查找速度
|
||||||
temp_id_data = {
|
temp_id_data = {
|
||||||
pool_name: [i.id for i in pool_data] for pool_name, pool_data in gacha_log.item_list.items()
|
pool_name: [i.id for i in pool_data] for pool_name, pool_data in gacha_log.item_list.items()
|
||||||
@ -291,30 +188,30 @@ class GachaLog:
|
|||||||
new_num += 1
|
new_num += 1
|
||||||
for i in gacha_log.item_list.values():
|
for i in gacha_log.item_list.values():
|
||||||
# 检查导入后的数据是否合法
|
# 检查导入后的数据是否合法
|
||||||
status, text = await GachaLog.verify_data(i)
|
await self.verify_data(i)
|
||||||
if not status:
|
|
||||||
return text
|
|
||||||
i.sort(key=lambda x: (x.time, x.id))
|
i.sort(key=lambda x: (x.time, x.id))
|
||||||
gacha_log.update_time = datetime.datetime.now()
|
gacha_log.update_time = datetime.datetime.now()
|
||||||
await GachaLog.save_gacha_log_info(str(user_id), uid, gacha_log)
|
gacha_log.import_type = import_type.value
|
||||||
return "导入完成,本次没有新增数据" if new_num == 0 else f"导入完成,本次共新增{new_num}条抽卡记录"
|
await self.save_gacha_log_info(str(user_id), uid, gacha_log)
|
||||||
except GachaLogAccountNotFound:
|
return new_num
|
||||||
return "导入失败,文件包含的祈愿记录所属 uid 与你当前绑定的 uid 不同"
|
except GachaLogAccountNotFound as e:
|
||||||
|
raise GachaLogAccountNotFound("导入失败,文件包含的祈愿记录所属 uid 与你当前绑定的 uid 不同") from e
|
||||||
|
except GachaLogMixedProvider as e:
|
||||||
|
raise GachaLogMixedProvider from e
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning(f"导入失败,数据格式错误 {repr(exc)}")
|
raise GachaLogException from exc
|
||||||
return "导入失败,数据格式错误"
|
|
||||||
|
|
||||||
@staticmethod
|
async def get_gacha_log_data(self, user_id: int, client: Client, authkey: str) -> int:
|
||||||
async def get_gacha_log_data(user_id: int, client: Client, authkey: str) -> str:
|
"""使用authkey获取抽卡记录数据,并合并旧数据
|
||||||
"""
|
|
||||||
使用authkey获取抽卡记录数据,并合并旧数据
|
|
||||||
:param user_id: 用户id
|
:param user_id: 用户id
|
||||||
:param client: genshin client
|
:param client: genshin client
|
||||||
:param authkey: authkey
|
:param authkey: authkey
|
||||||
:return: 更新结果
|
:return: 更新结果
|
||||||
"""
|
"""
|
||||||
new_num = 0
|
new_num = 0
|
||||||
gacha_log, _ = await GachaLog.load_history_info(str(user_id), str(client.uid))
|
gacha_log, _ = await self.load_history_info(str(user_id), str(client.uid))
|
||||||
|
if gacha_log.get_import_type == ImportType.PAIMONMOE:
|
||||||
|
raise GachaLogMixedProvider
|
||||||
# 将唯一 id 放入临时数据中,加快查找速度
|
# 将唯一 id 放入临时数据中,加快查找速度
|
||||||
temp_id_data = {pool_name: [i.id for i in pool_data] for pool_name, pool_data in gacha_log.item_list.items()}
|
temp_id_data = {pool_name: [i.id for i in pool_data] for pool_name, pool_data in gacha_log.item_list.items()}
|
||||||
try:
|
try:
|
||||||
@ -340,13 +237,14 @@ class GachaLog:
|
|||||||
gacha_log.item_list[pool_name].append(item)
|
gacha_log.item_list[pool_name].append(item)
|
||||||
temp_id_data[pool_name].append(item.id)
|
temp_id_data[pool_name].append(item.id)
|
||||||
new_num += 1
|
new_num += 1
|
||||||
except InvalidAuthkey:
|
except InvalidAuthkey as exc:
|
||||||
return "更新数据失败,authkey 无效"
|
raise GachaLogInvalidAuthkey from exc
|
||||||
for i in gacha_log.item_list.values():
|
for i in gacha_log.item_list.values():
|
||||||
i.sort(key=lambda x: (x.time, x.id))
|
i.sort(key=lambda x: (x.time, x.id))
|
||||||
gacha_log.update_time = datetime.datetime.now()
|
gacha_log.update_time = datetime.datetime.now()
|
||||||
await GachaLog.save_gacha_log_info(str(user_id), str(client.uid), gacha_log)
|
gacha_log.import_type = ImportType.UIGF.value
|
||||||
return "更新完成,本次没有新增数据" if new_num == 0 else f"更新完成,本次共新增{new_num}条抽卡记录"
|
await self.save_gacha_log_info(str(user_id), str(client.uid), gacha_log)
|
||||||
|
return new_num
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_avatar_up(name: str, gacha_time: datetime.datetime) -> bool:
|
def check_avatar_up(name: str, gacha_time: datetime.datetime) -> bool:
|
||||||
@ -364,8 +262,7 @@ class GachaLog:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
async def get_all_5_star_items(self, data: List[GachaItem], assets: AssetsService, pool_name: str = "角色祈愿"):
|
||||||
async def get_all_5_star_items(data: List[GachaItem], assets: AssetsService, pool_name: str = "角色祈愿"):
|
|
||||||
"""
|
"""
|
||||||
获取所有5星角色
|
获取所有5星角色
|
||||||
:param data: 抽卡记录
|
:param data: 抽卡记录
|
||||||
@ -384,7 +281,7 @@ class GachaLog:
|
|||||||
"icon": (await assets.avatar(roleToId(item.name)).icon()).as_uri(),
|
"icon": (await assets.avatar(roleToId(item.name)).icon()).as_uri(),
|
||||||
"count": count,
|
"count": count,
|
||||||
"type": "角色",
|
"type": "角色",
|
||||||
"isUp": GachaLog.check_avatar_up(item.name, item.time) if pool_name == "角色祈愿" else False,
|
"isUp": self.check_avatar_up(item.name, item.time) if pool_name == "角色祈愿" else False,
|
||||||
"isBig": (not result[-1].isUp) if result and pool_name == "角色祈愿" else False,
|
"isBig": (not result[-1].isUp) if result and pool_name == "角色祈愿" else False,
|
||||||
"time": item.time,
|
"time": item.time,
|
||||||
}
|
}
|
||||||
@ -567,8 +464,7 @@ class GachaLog:
|
|||||||
return f"{pool_name} · 非"
|
return f"{pool_name} · 非"
|
||||||
return pool_name
|
return pool_name
|
||||||
|
|
||||||
@staticmethod
|
async def get_analysis(self, user_id: int, client: Client, pool: BannerType, assets: AssetsService):
|
||||||
async def get_analysis(user_id: int, client: Client, pool: BannerType, assets: AssetsService):
|
|
||||||
"""
|
"""
|
||||||
获取抽卡记录分析数据
|
获取抽卡记录分析数据
|
||||||
:param user_id: 用户id
|
:param user_id: 用户id
|
||||||
@ -577,26 +473,26 @@ class GachaLog:
|
|||||||
:param assets: 资源服务
|
:param assets: 资源服务
|
||||||
:return: 分析数据
|
:return: 分析数据
|
||||||
"""
|
"""
|
||||||
gacha_log, status = await GachaLog.load_history_info(str(user_id), str(client.uid))
|
gacha_log, status = await self.load_history_info(str(user_id), str(client.uid))
|
||||||
if not status:
|
if not status:
|
||||||
return "派蒙没有找到你的抽卡记录,快来私聊派蒙导入吧~"
|
raise GachaLogNotFound
|
||||||
pool_name = GACHA_TYPE_LIST[pool]
|
pool_name = GACHA_TYPE_LIST[pool]
|
||||||
data = gacha_log.item_list[pool_name]
|
data = gacha_log.item_list[pool_name]
|
||||||
total = len(data)
|
total = len(data)
|
||||||
if total == 0:
|
if total == 0:
|
||||||
return "派蒙没有找到这个卡池的抽卡记录,快来私聊派蒙导入吧~"
|
raise GachaLogNotFound
|
||||||
all_five, no_five_star = await GachaLog.get_all_5_star_items(data, assets, pool_name)
|
all_five, no_five_star = await self.get_all_5_star_items(data, assets, pool_name)
|
||||||
all_four, no_four_star = await GachaLog.get_all_4_star_items(data, assets)
|
all_four, no_four_star = await self.get_all_4_star_items(data, assets)
|
||||||
summon_data = None
|
summon_data = None
|
||||||
if pool == BannerType.CHARACTER1:
|
if pool == BannerType.CHARACTER1:
|
||||||
summon_data = GachaLog.get_301_pool_data(total, all_five, no_five_star, no_four_star)
|
summon_data = self.get_301_pool_data(total, all_five, no_five_star, no_four_star)
|
||||||
pool_name = GachaLog.count_fortune(pool_name, summon_data)
|
pool_name = self.count_fortune(pool_name, summon_data)
|
||||||
elif pool == BannerType.WEAPON:
|
elif pool == BannerType.WEAPON:
|
||||||
summon_data = GachaLog.get_302_pool_data(total, all_five, all_four, no_five_star, no_four_star)
|
summon_data = self.get_302_pool_data(total, all_five, all_four, no_five_star, no_four_star)
|
||||||
pool_name = GachaLog.count_fortune(pool_name, summon_data, True)
|
pool_name = self.count_fortune(pool_name, summon_data, True)
|
||||||
elif pool == BannerType.PERMANENT:
|
elif pool == BannerType.PERMANENT:
|
||||||
summon_data = GachaLog.get_200_pool_data(total, all_five, all_four, no_five_star, no_four_star)
|
summon_data = self.get_200_pool_data(total, all_five, all_four, no_five_star, no_four_star)
|
||||||
pool_name = GachaLog.count_fortune(pool_name, summon_data)
|
pool_name = self.count_fortune(pool_name, summon_data)
|
||||||
last_time = data[0].time.strftime("%Y-%m-%d %H:%M")
|
last_time = data[0].time.strftime("%Y-%m-%d %H:%M")
|
||||||
first_time = data[-1].time.strftime("%Y-%m-%d %H:%M")
|
first_time = data[-1].time.strftime("%Y-%m-%d %H:%M")
|
||||||
return {
|
return {
|
||||||
@ -611,10 +507,10 @@ class GachaLog:
|
|||||||
"fourLog": all_four[:18],
|
"fourLog": all_four[:18],
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
async def get_pool_analysis(
|
||||||
async def get_pool_analysis(user_id: int, client: Client, pool: BannerType, assets: AssetsService, group: bool):
|
self, user_id: int, client: Client, pool: BannerType, assets: AssetsService, group: bool
|
||||||
"""
|
) -> dict:
|
||||||
获取抽卡记录分析数据
|
"""获取抽卡记录分析数据
|
||||||
:param user_id: 用户id
|
:param user_id: 用户id
|
||||||
:param client: genshin client
|
:param client: genshin client
|
||||||
:param pool: 池子类型
|
:param pool: 池子类型
|
||||||
@ -622,16 +518,16 @@ class GachaLog:
|
|||||||
:param group: 是否群组
|
:param group: 是否群组
|
||||||
:return: 分析数据
|
:return: 分析数据
|
||||||
"""
|
"""
|
||||||
gacha_log, status = await GachaLog.load_history_info(str(user_id), str(client.uid))
|
gacha_log, status = await self.load_history_info(str(user_id), str(client.uid))
|
||||||
if not status:
|
if not status:
|
||||||
return "派蒙没有找到你的抽卡记录,快来私聊派蒙导入吧~"
|
raise GachaLogNotFound
|
||||||
pool_name = GACHA_TYPE_LIST[pool]
|
pool_name = GACHA_TYPE_LIST[pool]
|
||||||
data = gacha_log.item_list[pool_name]
|
data = gacha_log.item_list[pool_name]
|
||||||
total = len(data)
|
total = len(data)
|
||||||
if total == 0:
|
if total == 0:
|
||||||
return "派蒙没有找到这个卡池的抽卡记录,快来私聊派蒙导入吧~"
|
raise GachaLogNotFound
|
||||||
all_five, _ = await GachaLog.get_all_5_star_items(data, assets, pool_name)
|
all_five, _ = await self.get_all_5_star_items(data, assets, pool_name)
|
||||||
all_four, _ = await GachaLog.get_all_4_star_items(data, assets)
|
all_four, _ = await self.get_all_4_star_items(data, assets)
|
||||||
pool_data = []
|
pool_data = []
|
||||||
up_pool_data = [Pool(**i) for i in get_pool_by_id(pool.value)]
|
up_pool_data = [Pool(**i) for i in get_pool_by_id(pool.value)]
|
||||||
for up_pool in up_pool_data:
|
for up_pool in up_pool_data:
|
||||||
@ -657,3 +553,128 @@ class GachaLog:
|
|||||||
"pool": pool_data[:6] if group else pool_data,
|
"pool": pool_data[:6] if group else pool_data,
|
||||||
"hasMore": len(pool_data) > 6,
|
"hasMore": len(pool_data) > 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_xlsx_to_uigf(data: BytesIO, zh_dict: dict) -> dict:
|
||||||
|
"""转换 paimone.moe 或 非小酋 导出 xlsx 数据为 UIGF 格式
|
||||||
|
:param zh_dict:
|
||||||
|
:param data: paimon.moe 导出的 xlsx 数据
|
||||||
|
:return: UIGF 格式数据
|
||||||
|
"""
|
||||||
|
|
||||||
|
def from_paimon_moe(
|
||||||
|
uigf_gacha_type: UIGFGachaType, item_type: str, name: str, date_string: str, rank_type: int, _id: int
|
||||||
|
) -> UIGFItem:
|
||||||
|
item_type = ItemType.CHARACTER if item_type == "Character" else ItemType.WEAPON
|
||||||
|
return UIGFItem(
|
||||||
|
id=str(_id),
|
||||||
|
name=zh_dict[name],
|
||||||
|
gacha_type=uigf_gacha_type,
|
||||||
|
item_type=item_type,
|
||||||
|
rank_type=str(rank_type),
|
||||||
|
time=date_string,
|
||||||
|
uigf_gacha_type=uigf_gacha_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
def from_uigf(
|
||||||
|
uigf_gacha_type: str,
|
||||||
|
gacha__type: str,
|
||||||
|
item_type: str,
|
||||||
|
name: str,
|
||||||
|
date_string: str,
|
||||||
|
rank_type: str,
|
||||||
|
_id: str,
|
||||||
|
) -> UIGFItem:
|
||||||
|
return UIGFItem(
|
||||||
|
id=_id,
|
||||||
|
name=name,
|
||||||
|
gacha_type=gacha__type,
|
||||||
|
item_type=item_type,
|
||||||
|
rank_type=rank_type,
|
||||||
|
time=date_string,
|
||||||
|
uigf_gacha_type=uigf_gacha_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
def from_fxq(
|
||||||
|
uigf_gacha_type: UIGFGachaType, item_type: str, name: str, date_string: str, rank_type: int, _id: int
|
||||||
|
) -> UIGFItem:
|
||||||
|
item_type = ItemType.CHARACTER if item_type == "角色" else ItemType.WEAPON
|
||||||
|
return UIGFItem(
|
||||||
|
id=str(_id),
|
||||||
|
name=name,
|
||||||
|
gacha_type=uigf_gacha_type,
|
||||||
|
item_type=item_type,
|
||||||
|
rank_type=str(rank_type),
|
||||||
|
time=date_string,
|
||||||
|
uigf_gacha_type=uigf_gacha_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
wb = load_workbook(data)
|
||||||
|
wb_len = len(wb.worksheets)
|
||||||
|
|
||||||
|
if wb_len == 6:
|
||||||
|
import_type = ImportType.PAIMONMOE
|
||||||
|
elif wb_len == 5:
|
||||||
|
import_type = ImportType.UIGF
|
||||||
|
elif wb_len == 4:
|
||||||
|
import_type = ImportType.FXQ
|
||||||
|
else:
|
||||||
|
raise GachaLogFileError("xlsx 格式错误")
|
||||||
|
|
||||||
|
paimonmoe_sheets = {
|
||||||
|
UIGFGachaType.BEGINNER: "Beginners' Wish",
|
||||||
|
UIGFGachaType.STANDARD: "Standard",
|
||||||
|
UIGFGachaType.CHARACTER: "Character Event",
|
||||||
|
UIGFGachaType.WEAPON: "Weapon Event",
|
||||||
|
}
|
||||||
|
fxq_sheets = {
|
||||||
|
UIGFGachaType.BEGINNER: "新手祈愿",
|
||||||
|
UIGFGachaType.STANDARD: "常驻祈愿",
|
||||||
|
UIGFGachaType.CHARACTER: "角色活动祈愿",
|
||||||
|
UIGFGachaType.WEAPON: "武器活动祈愿",
|
||||||
|
}
|
||||||
|
data = UIGFModel(info=UIGFInfo(export_app=import_type.value), list=[])
|
||||||
|
if import_type == ImportType.PAIMONMOE:
|
||||||
|
ws = wb["Information"]
|
||||||
|
if ws["B2"].value != PAIMONMOE_VERSION:
|
||||||
|
raise PaimonMoeGachaLogFileError(file_version=ws["B2"].value, support_version=PAIMONMOE_VERSION)
|
||||||
|
count = 1
|
||||||
|
for gacha_type in paimonmoe_sheets:
|
||||||
|
ws = wb[paimonmoe_sheets[gacha_type]]
|
||||||
|
for row in ws.iter_rows(min_row=2, values_only=True):
|
||||||
|
if row[0] is None:
|
||||||
|
break
|
||||||
|
data.list.append(from_paimon_moe(gacha_type, row[0], row[1], row[2], row[3], count))
|
||||||
|
count += 1
|
||||||
|
elif import_type == ImportType.UIGF:
|
||||||
|
ws = wb["原始数据"]
|
||||||
|
type_map = {}
|
||||||
|
count = 0
|
||||||
|
for row in ws["1"]:
|
||||||
|
if row.value is None:
|
||||||
|
break
|
||||||
|
type_map[row.value] = count
|
||||||
|
count += 1
|
||||||
|
for row in ws.iter_rows(min_row=2, values_only=True):
|
||||||
|
if row[0] is None:
|
||||||
|
break
|
||||||
|
data.list.append(
|
||||||
|
from_uigf(
|
||||||
|
row[type_map["uigf_gacha_type"]],
|
||||||
|
row[type_map["gacha_type"]],
|
||||||
|
row[type_map["item_type"]],
|
||||||
|
row[type_map["name"]],
|
||||||
|
row[type_map["time"]],
|
||||||
|
row[type_map["rank_type"]],
|
||||||
|
row[type_map["id"]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
for gacha_type in fxq_sheets:
|
||||||
|
ws = wb[fxq_sheets[gacha_type]]
|
||||||
|
for row in ws.iter_rows(min_row=2, values_only=True):
|
||||||
|
if row[0] is None:
|
||||||
|
break
|
||||||
|
data.list.append(from_fxq(gacha_type, row[2], row[1], row[0], row[3], row[6]))
|
||||||
|
|
||||||
|
return json.loads(data.json())
|
174
modules/gacha_log/models.py
Normal file
174
modules/gacha_log/models.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import datetime
|
||||||
|
from enum import Enum
|
||||||
|
from typing import List, Dict, Union, Any
|
||||||
|
|
||||||
|
from pydantic import BaseModel, validator
|
||||||
|
|
||||||
|
from metadata.shortname import roleToId, weaponToId, not_real_roles
|
||||||
|
from modules.gacha_log.const import UIGF_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
class ImportType(Enum):
|
||||||
|
TGPaimonBot = "TGPaimonBot"
|
||||||
|
PAIMONMOE = "PAIMONMOE"
|
||||||
|
FXQ = "FXQ"
|
||||||
|
UIGF = "UIGF"
|
||||||
|
UNKNOWN = "UNKNOWN"
|
||||||
|
|
||||||
|
|
||||||
|
class FiveStarItem(BaseModel):
|
||||||
|
name: str
|
||||||
|
icon: str
|
||||||
|
count: int
|
||||||
|
type: str
|
||||||
|
isUp: bool
|
||||||
|
isBig: bool
|
||||||
|
time: datetime.datetime
|
||||||
|
|
||||||
|
|
||||||
|
class FourStarItem(BaseModel):
|
||||||
|
name: str
|
||||||
|
icon: str
|
||||||
|
count: int
|
||||||
|
type: str
|
||||||
|
time: datetime.datetime
|
||||||
|
|
||||||
|
|
||||||
|
class GachaItem(BaseModel):
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
gacha_type: str
|
||||||
|
item_type: str
|
||||||
|
rank_type: str
|
||||||
|
time: datetime.datetime
|
||||||
|
|
||||||
|
@validator("name")
|
||||||
|
def name_validator(cls, v):
|
||||||
|
if item_id := (roleToId(v) or weaponToId(v)):
|
||||||
|
if item_id not in not_real_roles:
|
||||||
|
return v
|
||||||
|
raise ValueError("Invalid name")
|
||||||
|
|
||||||
|
@validator("gacha_type")
|
||||||
|
def check_gacha_type(cls, v):
|
||||||
|
if v not in {"100", "200", "301", "302", "400"}:
|
||||||
|
raise ValueError("gacha_type must be 200, 301, 302 or 400")
|
||||||
|
return v
|
||||||
|
|
||||||
|
@validator("item_type")
|
||||||
|
def check_item_type(cls, item):
|
||||||
|
if item not in {"角色", "武器"}:
|
||||||
|
raise ValueError("error item type")
|
||||||
|
return item
|
||||||
|
|
||||||
|
@validator("rank_type")
|
||||||
|
def check_rank_type(cls, rank):
|
||||||
|
if rank not in {"5", "4", "3"}:
|
||||||
|
raise ValueError("error rank type")
|
||||||
|
return rank
|
||||||
|
|
||||||
|
|
||||||
|
class GachaLogInfo(BaseModel):
|
||||||
|
user_id: str
|
||||||
|
uid: str
|
||||||
|
update_time: datetime.datetime
|
||||||
|
import_type: str = ""
|
||||||
|
item_list: Dict[str, List[GachaItem]] = {
|
||||||
|
"角色祈愿": [],
|
||||||
|
"武器祈愿": [],
|
||||||
|
"常驻祈愿": [],
|
||||||
|
"新手祈愿": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_import_type(self) -> ImportType:
|
||||||
|
try:
|
||||||
|
return ImportType(self.import_type)
|
||||||
|
except ValueError:
|
||||||
|
return ImportType.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
class Pool:
|
||||||
|
def __init__(self, five: List[str], four: List[str], name: str, to: str, **kwargs):
|
||||||
|
self.five = five
|
||||||
|
self.real_name = name
|
||||||
|
self.name = "、".join(self.five)
|
||||||
|
self.four = four
|
||||||
|
self.from_ = kwargs.get("from")
|
||||||
|
self.to = to
|
||||||
|
self.from_time = datetime.datetime.strptime(self.from_, "%Y-%m-%d %H:%M:%S")
|
||||||
|
self.to_time = datetime.datetime.strptime(self.to, "%Y-%m-%d %H:%M:%S")
|
||||||
|
self.start = self.from_time
|
||||||
|
self.start_init = False
|
||||||
|
self.end = self.to_time
|
||||||
|
self.dict = {}
|
||||||
|
self.count = 0
|
||||||
|
|
||||||
|
def parse(self, item: Union[FiveStarItem, FourStarItem]):
|
||||||
|
if self.from_time <= item.time <= self.to_time:
|
||||||
|
if self.dict.get(item.name):
|
||||||
|
self.dict[item.name]["count"] += 1
|
||||||
|
else:
|
||||||
|
self.dict[item.name] = {
|
||||||
|
"name": item.name,
|
||||||
|
"icon": item.icon,
|
||||||
|
"count": 1,
|
||||||
|
"rank_type": 5 if isinstance(item, FiveStarItem) else 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
def count_item(self, item: List[GachaItem]):
|
||||||
|
for i in item:
|
||||||
|
if self.from_time <= i.time <= self.to_time:
|
||||||
|
self.count += 1
|
||||||
|
if not self.start_init:
|
||||||
|
self.start = i.time
|
||||||
|
self.end = i.time
|
||||||
|
|
||||||
|
def to_list(self):
|
||||||
|
return list(self.dict.values())
|
||||||
|
|
||||||
|
|
||||||
|
class ItemType(Enum):
|
||||||
|
CHARACTER = "角色"
|
||||||
|
WEAPON = "武器"
|
||||||
|
|
||||||
|
|
||||||
|
class UIGFGachaType(Enum):
|
||||||
|
BEGINNER = "100"
|
||||||
|
STANDARD = "200"
|
||||||
|
CHARACTER = "301"
|
||||||
|
WEAPON = "302"
|
||||||
|
CHARACTER2 = "400"
|
||||||
|
|
||||||
|
|
||||||
|
class UIGFItem(BaseModel):
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
count: str = "1"
|
||||||
|
gacha_type: UIGFGachaType
|
||||||
|
item_id: str = ""
|
||||||
|
item_type: ItemType
|
||||||
|
rank_type: str
|
||||||
|
time: str
|
||||||
|
uigf_gacha_type: UIGFGachaType
|
||||||
|
|
||||||
|
|
||||||
|
class UIGFInfo(BaseModel):
|
||||||
|
uid: str = "0"
|
||||||
|
lang: str = "zh-cn"
|
||||||
|
export_time: str = ""
|
||||||
|
export_timestamp: int = 0
|
||||||
|
export_app: str = ""
|
||||||
|
export_app_version: str = ""
|
||||||
|
uigf_version: str = UIGF_VERSION
|
||||||
|
|
||||||
|
def __init__(self, **data: Any):
|
||||||
|
super().__init__(**data)
|
||||||
|
if not self.export_time:
|
||||||
|
self.export_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
self.export_timestamp = int(datetime.datetime.now().timestamp())
|
||||||
|
|
||||||
|
|
||||||
|
class UIGFModel(BaseModel):
|
||||||
|
info: UIGFInfo
|
||||||
|
list: List[UIGFItem]
|
@ -1,13 +1,10 @@
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime
|
|
||||||
from enum import Enum
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from os import sep
|
from os import sep
|
||||||
|
|
||||||
import genshin
|
import genshin
|
||||||
|
from aiofiles import open as async_open
|
||||||
from genshin.models import BannerType
|
from genshin.models import BannerType
|
||||||
from modules.apihelper.gacha_log import GachaLog as GachaLogService
|
|
||||||
from openpyxl import load_workbook
|
|
||||||
from telegram import Update, User, Message, Document, InlineKeyboardButton, InlineKeyboardMarkup
|
from telegram import Update, User, Message, Document, InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
from telegram.constants import ChatAction
|
from telegram.constants import ChatAction
|
||||||
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters, ConversationHandler
|
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters, ConversationHandler
|
||||||
@ -20,8 +17,20 @@ from core.plugin import Plugin, handler, conversation
|
|||||||
from core.template import TemplateService
|
from core.template import TemplateService
|
||||||
from core.user import UserService
|
from core.user import UserService
|
||||||
from core.user.error import UserNotFoundError
|
from core.user.error import UserNotFoundError
|
||||||
|
from metadata.scripts.paimon_moe import update_paimon_moe_zh
|
||||||
from modules.apihelper.hyperion import SignIn
|
from modules.apihelper.hyperion import SignIn
|
||||||
|
from modules.gacha_log.error import (
|
||||||
|
GachaLogInvalidAuthkey,
|
||||||
|
PaimonMoeGachaLogFileError,
|
||||||
|
GachaLogFileError,
|
||||||
|
GachaLogNotFound,
|
||||||
|
GachaLogAccountNotFound,
|
||||||
|
GachaLogMixedProvider,
|
||||||
|
)
|
||||||
|
from modules.gacha_log.helpers import from_url_get_authkey
|
||||||
|
from modules.gacha_log.log import GachaLog
|
||||||
from utils.bot import get_all_args
|
from utils.bot import get_all_args
|
||||||
|
from utils.const import PROJECT_ROOT
|
||||||
from utils.decorators.admins import bot_admins_rights_check
|
from utils.decorators.admins import bot_admins_rights_check
|
||||||
from utils.decorators.error import error_callable
|
from utils.decorators.error import error_callable
|
||||||
from utils.decorators.restricts import restricts
|
from utils.decorators.restricts import restricts
|
||||||
@ -29,10 +38,13 @@ from utils.helpers import get_genshin_client
|
|||||||
from utils.log import logger
|
from utils.log import logger
|
||||||
from utils.models.base import RegionEnum
|
from utils.models.base import RegionEnum
|
||||||
|
|
||||||
|
GACHA_LOG_PATH = PROJECT_ROOT.joinpath("data", "apihelper", "gacha_log")
|
||||||
|
GACHA_LOG_PAIMON_MOE_PATH = PROJECT_ROOT.joinpath("metadata/data/paimon_moe_zh.json")
|
||||||
|
GACHA_LOG_PATH.mkdir(parents=True, exist_ok=True)
|
||||||
INPUT_URL, INPUT_FILE, CONFIRM_DELETE = range(10100, 10103)
|
INPUT_URL, INPUT_FILE, CONFIRM_DELETE = range(10100, 10103)
|
||||||
|
|
||||||
|
|
||||||
class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
|
||||||
"""抽卡记录导入/导出/分析"""
|
"""抽卡记录导入/导出/分析"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -46,20 +58,17 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
self.user_service = user_service
|
self.user_service = user_service
|
||||||
self.assets_service = assets
|
self.assets_service = assets
|
||||||
self.cookie_service = cookie_service
|
self.cookie_service = cookie_service
|
||||||
|
self.zh_dict = None
|
||||||
|
self.gacha_log = GachaLog(GACHA_LOG_PATH)
|
||||||
|
|
||||||
@staticmethod
|
async def __async_init__(self):
|
||||||
def from_url_get_authkey(url: str) -> str:
|
await update_paimon_moe_zh(False)
|
||||||
"""从 UEL 解析 authkey
|
async with async_open(GACHA_LOG_PAIMON_MOE_PATH, "r", encoding="utf-8") as load_f:
|
||||||
:param url: URL
|
self.zh_dict = json.loads(await load_f.read())
|
||||||
:return: authkey
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return url.split("authkey=")[1].split("&")[0]
|
|
||||||
except IndexError:
|
|
||||||
return url
|
|
||||||
|
|
||||||
@staticmethod
|
async def _refresh_user_data(
|
||||||
async def _refresh_user_data(user: User, data: dict = None, authkey: str = None, verify_uid: bool = True) -> str:
|
self, user: User, data: dict = None, authkey: str = None, verify_uid: bool = True
|
||||||
|
) -> str:
|
||||||
"""刷新用户数据
|
"""刷新用户数据
|
||||||
:param user: 用户
|
:param user: 用户
|
||||||
:param data: 数据
|
:param data: 数据
|
||||||
@ -70,153 +79,25 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
logger.debug("尝试获取已绑定的原神账号")
|
logger.debug("尝试获取已绑定的原神账号")
|
||||||
client = await get_genshin_client(user.id, need_cookie=False)
|
client = await get_genshin_client(user.id, need_cookie=False)
|
||||||
if authkey:
|
if authkey:
|
||||||
return await GachaLogService.get_gacha_log_data(user.id, client, authkey)
|
new_num = await self.gacha_log.get_gacha_log_data(user.id, client, authkey)
|
||||||
|
return "更新完成,本次没有新增数据" if new_num == 0 else f"更新完成,本次共新增{new_num}条抽卡记录"
|
||||||
if data:
|
if data:
|
||||||
return await GachaLogService.import_gacha_log_data(user.id, client, data, verify_uid)
|
new_num = await self.gacha_log.import_gacha_log_data(user.id, client, data, verify_uid)
|
||||||
|
return "更新完成,本次没有新增数据" if new_num == 0 else f"更新完成,本次共新增{new_num}条抽卡记录"
|
||||||
|
except GachaLogNotFound:
|
||||||
|
return "派蒙没有找到你的抽卡记录,快来私聊派蒙导入吧~"
|
||||||
|
except GachaLogAccountNotFound:
|
||||||
|
return "导入失败,可能文件包含的祈愿记录所属 uid 与你当前绑定的 uid 不同"
|
||||||
|
except GachaLogFileError:
|
||||||
|
return "导入失败,数据格式错误"
|
||||||
|
except GachaLogInvalidAuthkey:
|
||||||
|
return "更新数据失败,authkey 无效"
|
||||||
|
except GachaLogMixedProvider:
|
||||||
|
return "导入失败,你已经通过其他方式导入过抽卡记录了,本次无法导入"
|
||||||
except UserNotFoundError:
|
except UserNotFoundError:
|
||||||
logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息")
|
logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息")
|
||||||
return "派蒙没有找到您所绑定的账号信息,请先私聊派蒙绑定账号"
|
return "派蒙没有找到您所绑定的账号信息,请先私聊派蒙绑定账号"
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def convert_paimonmoe_to_uigf(data: BytesIO) -> dict:
|
|
||||||
"""转换 paimone.moe 或 非小酋 导出 xlsx 数据为 UIGF 格式
|
|
||||||
:param data: paimon.moe 导出的 xlsx 数据
|
|
||||||
:return: UIGF 格式数据
|
|
||||||
"""
|
|
||||||
PAIMONMOE_VERSION = 3
|
|
||||||
PM2UIGF_VERSION = 1
|
|
||||||
PM2UIGF_NAME = "paimon_moe_to_uigf"
|
|
||||||
UIGF_VERSION = "v2.2"
|
|
||||||
|
|
||||||
with open(f"resources{sep}json{sep}zh.json", "r") as load_f:
|
|
||||||
zh_dict = json.load(load_f)
|
|
||||||
|
|
||||||
class XlsxType(Enum):
|
|
||||||
PAIMONMOE = 1
|
|
||||||
FXQ = 2
|
|
||||||
|
|
||||||
class ItemType(Enum):
|
|
||||||
CHARACTER = "角色"
|
|
||||||
WEAPON = "武器"
|
|
||||||
|
|
||||||
class UIGFGachaType(Enum):
|
|
||||||
BEGINNER = 100
|
|
||||||
STANDARD = 200
|
|
||||||
CHARACTER = 301
|
|
||||||
WEAPON = 302
|
|
||||||
|
|
||||||
class Qiyr:
|
|
||||||
def __init__(
|
|
||||||
self, uigf_gacha_type: UIGFGachaType, item_type: ItemType, name: str, time: datetime, p: int, _id: int
|
|
||||||
) -> None:
|
|
||||||
self.uigf_gacha_type = uigf_gacha_type
|
|
||||||
self.item_type = item_type
|
|
||||||
self.name = name
|
|
||||||
self.time = time
|
|
||||||
self.rank_type = p
|
|
||||||
self.id = _id
|
|
||||||
|
|
||||||
def qy2_json(self):
|
|
||||||
return {
|
|
||||||
"gacha_type": self.uigf_gacha_type.value, # 注意!
|
|
||||||
"item_id": "",
|
|
||||||
"count": -1,
|
|
||||||
"time": self.time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
||||||
"name": self.name,
|
|
||||||
"item_type": self.item_type.value,
|
|
||||||
"rank_type": self.rank_type,
|
|
||||||
"id": self.id,
|
|
||||||
"uigf_gacha_type": self.uigf_gacha_type.value,
|
|
||||||
}
|
|
||||||
|
|
||||||
def from_paimon_moe(uigf_gacha_type: UIGFGachaType, item_type: str, name: str, time: str, p: int) -> Qiyr:
|
|
||||||
item_type = ItemType.CHARACTER if item_type == "Character" else ItemType.WEAPON
|
|
||||||
name = zh_dict[name]
|
|
||||||
|
|
||||||
time = datetime.strptime(time, "%Y-%m-%d %H:%M:%S")
|
|
||||||
return Qiyr(uigf_gacha_type, item_type, name, time, p, 0)
|
|
||||||
|
|
||||||
def from_fxq(uigf_gacha_type: UIGFGachaType, item_type: str, name: str, time: str, p: int, _id: int) -> Qiyr:
|
|
||||||
item_type = ItemType.CHARACTER if item_type == "角色" else ItemType.WEAPON
|
|
||||||
time = datetime.strptime(time, "%Y-%m-%d %H:%M:%S")
|
|
||||||
return Qiyr(uigf_gacha_type, item_type, name, time, p, _id)
|
|
||||||
|
|
||||||
class uigf:
|
|
||||||
qiyes: list[Qiyr]
|
|
||||||
uid: int
|
|
||||||
export_time: datetime
|
|
||||||
export_app: str = PM2UIGF_NAME
|
|
||||||
export_app_version: str = PM2UIGF_VERSION
|
|
||||||
uigf_version = UIGF_VERSION
|
|
||||||
lang = "zh-cn"
|
|
||||||
|
|
||||||
def __init__(self, qiyes: list[Qiyr], uid: int, export_time: datetime) -> None:
|
|
||||||
self.uid = uid
|
|
||||||
self.qiyes = qiyes
|
|
||||||
self.qiyes.sort(key=lambda x: x.time)
|
|
||||||
if self.qiyes[0].id == 0: # 如果是从paimon.moe导入的,那么就给id赋值
|
|
||||||
for index, _ in enumerate(self.qiyes):
|
|
||||||
self.qiyes[index].id = index + 1
|
|
||||||
self.export_time = export_time
|
|
||||||
self.export_time = export_time
|
|
||||||
|
|
||||||
def export_json(self) -> dict:
|
|
||||||
json_d = {
|
|
||||||
"info": {
|
|
||||||
"uid": self.uid,
|
|
||||||
"lang": self.lang,
|
|
||||||
"export_time": self.export_time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
||||||
"export_timestamp": self.export_time.timestamp(),
|
|
||||||
"export_app": self.export_app,
|
|
||||||
"export_app_version": self.export_app_version,
|
|
||||||
"uigf_version": self.uigf_version,
|
|
||||||
},
|
|
||||||
"list": [],
|
|
||||||
}
|
|
||||||
for qiye in self.qiyes:
|
|
||||||
json_d["list"].append(qiye.qy2_json())
|
|
||||||
return json_d
|
|
||||||
|
|
||||||
wb = load_workbook(data)
|
|
||||||
|
|
||||||
xlsx_type = XlsxType.PAIMONMOE if len(wb.worksheets) == 6 else XlsxType.FXQ # 判断是paimon.moe还是非小酋导出的
|
|
||||||
|
|
||||||
paimonmoe_sheets = {
|
|
||||||
UIGFGachaType.BEGINNER: "Beginners' Wish",
|
|
||||||
UIGFGachaType.STANDARD: "Standard",
|
|
||||||
UIGFGachaType.CHARACTER: "Character Event",
|
|
||||||
UIGFGachaType.WEAPON: "Weapon Event",
|
|
||||||
}
|
|
||||||
fxq_sheets = {
|
|
||||||
UIGFGachaType.BEGINNER: "新手祈愿",
|
|
||||||
UIGFGachaType.STANDARD: "常驻祈愿",
|
|
||||||
UIGFGachaType.CHARACTER: "角色活动祈愿",
|
|
||||||
UIGFGachaType.WEAPON: "武器活动祈愿",
|
|
||||||
}
|
|
||||||
qiyes = []
|
|
||||||
if xlsx_type == XlsxType.PAIMONMOE:
|
|
||||||
ws = wb["Information"]
|
|
||||||
if ws["B2"].value != PAIMONMOE_VERSION:
|
|
||||||
raise Exception("PaimonMoe version not supported")
|
|
||||||
export_time = datetime.strptime(ws["B3"].value, "%Y-%m-%d %H:%M:%S")
|
|
||||||
for gacha_type in paimonmoe_sheets:
|
|
||||||
ws = wb[paimonmoe_sheets[gacha_type]]
|
|
||||||
for row in ws.iter_rows(min_row=2, values_only=True):
|
|
||||||
if row[0] is None:
|
|
||||||
break
|
|
||||||
qiyes.append(from_paimon_moe(gacha_type, row[0], row[1], row[2], row[3]))
|
|
||||||
else:
|
|
||||||
export_time = datetime.now()
|
|
||||||
for gacha_type in fxq_sheets:
|
|
||||||
ws = wb[fxq_sheets[gacha_type]]
|
|
||||||
for row in ws.iter_rows(min_row=2, values_only=True):
|
|
||||||
if row[0] is None:
|
|
||||||
break
|
|
||||||
qiyes.append(from_fxq(gacha_type, row[2], row[1], row[0], row[3], row[6]))
|
|
||||||
|
|
||||||
u = uigf(qiyes, 0, export_time)
|
|
||||||
return u.export_json()
|
|
||||||
|
|
||||||
async def import_from_file(self, user: User, message: Message, document: Document = None) -> None:
|
async def import_from_file(self, user: User, message: Message, document: Document = None) -> None:
|
||||||
if not document:
|
if not document:
|
||||||
document = message.document
|
document = message.document
|
||||||
@ -226,9 +107,11 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
elif document.file_name.endswith(".json"):
|
elif document.file_name.endswith(".json"):
|
||||||
file_type = "json"
|
file_type = "json"
|
||||||
else:
|
else:
|
||||||
await message.reply_text("文件格式错误,请发送符合 UIGF 标准的 json 格式的抽卡记录文件或者 paimon.moe、非小酋导出的 xlsx 格式的抽卡记录文件")
|
await message.reply_text("文件格式错误,请发送符合 UIGF 标准的抽卡记录文件或者 paimon.moe、非小酋导出的 xlsx 格式的抽卡记录文件")
|
||||||
|
return
|
||||||
if document.file_size > 2 * 1024 * 1024:
|
if document.file_size > 2 * 1024 * 1024:
|
||||||
await message.reply_text("文件过大,请发送小于 2 MB 的文件")
|
await message.reply_text("文件过大,请发送小于 2 MB 的文件")
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
data = BytesIO()
|
data = BytesIO()
|
||||||
await (await document.get_file()).download(out=data)
|
await (await document.get_file()).download(out=data)
|
||||||
@ -236,9 +119,17 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
# bytesio to json
|
# bytesio to json
|
||||||
data = data.getvalue().decode("utf-8")
|
data = data.getvalue().decode("utf-8")
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
else:
|
elif file_type == "xlsx":
|
||||||
data = self.convert_paimonmoe_to_uigf(data)
|
data = self.gacha_log.convert_xlsx_to_uigf(data, self.zh_dict)
|
||||||
except UnicodeDecodeError:
|
except PaimonMoeGachaLogFileError as exc:
|
||||||
|
await message.reply_text(
|
||||||
|
"导入失败,PaimonMoe的抽卡记录当前版本不支持\n" f"支持抽卡记录的版本为 {exc.support_version},你的抽卡记录版本为 {exc.file_version}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
except GachaLogFileError:
|
||||||
|
await message.reply_text("文件解析失败,请检查文件是否符合 UIGF 标准")
|
||||||
|
return
|
||||||
|
except (KeyError, IndexError, ValueError):
|
||||||
await message.reply_text("文件解析失败,请检查文件编码是否正确或符合 UIGF 标准")
|
await message.reply_text("文件解析失败,请检查文件编码是否正确或符合 UIGF 标准")
|
||||||
return
|
return
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@ -265,7 +156,7 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
user = update.effective_user
|
user = update.effective_user
|
||||||
args = get_all_args(context)
|
args = get_all_args(context)
|
||||||
logger.info(f"用户 {user.full_name}[{user.id}] 导入抽卡记录命令请求")
|
logger.info(f"用户 {user.full_name}[{user.id}] 导入抽卡记录命令请求")
|
||||||
authkey = self.from_url_get_authkey(args[0] if args else "")
|
authkey = from_url_get_authkey(args[0] if args else "")
|
||||||
if not args:
|
if not args:
|
||||||
if message.document:
|
if message.document:
|
||||||
await self.import_from_file(user, message)
|
await self.import_from_file(user, message)
|
||||||
@ -299,7 +190,8 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
await message.reply_text(
|
await message.reply_text(
|
||||||
"<b>开始导入祈愿历史记录:请通过 https://paimon.moe/wish/import 获取抽卡记录链接后发送给我"
|
"<b>开始导入祈愿历史记录:请通过 https://paimon.moe/wish/import 获取抽卡记录链接后发送给我"
|
||||||
"(非 paimon.moe 导出的文件数据)</b>\n\n"
|
"(非 paimon.moe 导出的文件数据)</b>\n\n"
|
||||||
"> 你还可以向派蒙发送从其他工具导出的 UIGF JSON 标准的记录文件\n"
|
"> 你还可以向派蒙发送从其他工具导出的 UIGF 标准的记录文件\n"
|
||||||
|
"> 或者从 paimon.moe 、非小酋 导出的 xlsx 记录文件\n"
|
||||||
"> 在绑定 Cookie 时添加 stoken 可能有特殊效果哦(仅限国服)\n"
|
"> 在绑定 Cookie 时添加 stoken 可能有特殊效果哦(仅限国服)\n"
|
||||||
"<b>注意:导入的数据将会与旧数据进行合并。</b>",
|
"<b>注意:导入的数据将会与旧数据进行合并。</b>",
|
||||||
parse_mode="html",
|
parse_mode="html",
|
||||||
@ -324,7 +216,7 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
if message.document:
|
if message.document:
|
||||||
await self.import_from_file(user, message)
|
await self.import_from_file(user, message)
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
authkey = self.from_url_get_authkey(message.text)
|
authkey = from_url_get_authkey(message.text)
|
||||||
reply = await message.reply_text("小派蒙正在从米哈游服务器获取数据,请稍后")
|
reply = await message.reply_text("小派蒙正在从米哈游服务器获取数据,请稍后")
|
||||||
await message.reply_chat_action(ChatAction.TYPING)
|
await message.reply_chat_action(ChatAction.TYPING)
|
||||||
text = await self._refresh_user_data(user, authkey=authkey)
|
text = await self._refresh_user_data(user, authkey=authkey)
|
||||||
@ -346,7 +238,7 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
except UserNotFoundError:
|
except UserNotFoundError:
|
||||||
await message.reply_text("你还没有导入抽卡记录哦~")
|
await message.reply_text("你还没有导入抽卡记录哦~")
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
_, status = await GachaLogService.load_history_info(str(user.id), str(client.uid), only_status=True)
|
_, status = await self.gacha_log.load_history_info(str(user.id), str(client.uid), only_status=True)
|
||||||
if not status:
|
if not status:
|
||||||
await message.reply_text("你还没有导入抽卡记录哦~")
|
await message.reply_text("你还没有导入抽卡记录哦~")
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
@ -361,7 +253,7 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
message = update.effective_message
|
message = update.effective_message
|
||||||
user = update.effective_user
|
user = update.effective_user
|
||||||
if message.text == "确定":
|
if message.text == "确定":
|
||||||
status = await GachaLogService.remove_history_info(str(user.id), str(context.chat_data["uid"]))
|
status = await self.gacha_log.remove_history_info(str(user.id), str(context.chat_data["uid"]))
|
||||||
await message.reply_text("抽卡记录已删除" if status else "抽卡记录删除失败")
|
await message.reply_text("抽卡记录已删除" if status else "抽卡记录删除失败")
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
await message.reply_text("已取消")
|
await message.reply_text("已取消")
|
||||||
@ -380,12 +272,18 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
if cid < 0:
|
if cid < 0:
|
||||||
raise ValueError("Invalid cid")
|
raise ValueError("Invalid cid")
|
||||||
client = await get_genshin_client(cid, need_cookie=False)
|
client = await get_genshin_client(cid, need_cookie=False)
|
||||||
_, status = await GachaLogService.load_history_info(str(cid), str(client.uid), only_status=True)
|
_, status = await self.gacha_log.load_history_info(str(cid), str(client.uid), only_status=True)
|
||||||
if not status:
|
if not status:
|
||||||
await message.reply_text("该用户还没有导入抽卡记录")
|
await message.reply_text("该用户还没有导入抽卡记录")
|
||||||
return
|
return
|
||||||
status = await GachaLogService.remove_history_info(str(cid), str(client.uid))
|
status = await self.gacha_log.remove_history_info(str(cid), str(client.uid))
|
||||||
await message.reply_text("抽卡记录已强制删除" if status else "抽卡记录删除失败")
|
await message.reply_text("抽卡记录已强制删除" if status else "抽卡记录删除失败")
|
||||||
|
except GachaLogNotFound:
|
||||||
|
await message.reply_text("派蒙没有找到你的抽卡记录,快来私聊派蒙导入吧~")
|
||||||
|
except GachaLogAccountNotFound:
|
||||||
|
await message.reply_text("导入失败,可能文件包含的祈愿记录所属 uid 与你当前绑定的 uid 不同")
|
||||||
|
except GachaLogFileError:
|
||||||
|
await message.reply_text("导入失败,数据格式错误")
|
||||||
except UserNotFoundError:
|
except UserNotFoundError:
|
||||||
await message.reply_text("该用户暂未绑定账号")
|
await message.reply_text("该用户暂未绑定账号")
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
@ -402,12 +300,15 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
try:
|
try:
|
||||||
client = await get_genshin_client(user.id, need_cookie=False)
|
client = await get_genshin_client(user.id, need_cookie=False)
|
||||||
await message.reply_chat_action(ChatAction.TYPING)
|
await message.reply_chat_action(ChatAction.TYPING)
|
||||||
state, text, path = await GachaLogService.gacha_log_to_uigf(str(user.id), str(client.uid))
|
path = await self.gacha_log.gacha_log_to_uigf(str(user.id), str(client.uid))
|
||||||
if state:
|
|
||||||
await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT)
|
await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT)
|
||||||
await message.reply_document(document=open(path, "rb+"), caption="抽卡记录导出文件")
|
await message.reply_document(document=open(path, "rb+"), caption="抽卡记录导出文件 - UIGF V2.2")
|
||||||
else:
|
except GachaLogNotFound:
|
||||||
await message.reply_text(text)
|
await message.reply_text("派蒙没有找到你的抽卡记录,快来私聊派蒙导入吧~")
|
||||||
|
except GachaLogAccountNotFound:
|
||||||
|
await message.reply_text("导入失败,可能文件包含的祈愿记录所属 uid 与你当前绑定的 uid 不同")
|
||||||
|
except GachaLogFileError:
|
||||||
|
await message.reply_text("导入失败,数据格式错误")
|
||||||
except UserNotFoundError:
|
except UserNotFoundError:
|
||||||
logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息")
|
logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息")
|
||||||
if filters.ChatType.GROUPS.filter(message):
|
if filters.ChatType.GROUPS.filter(message):
|
||||||
@ -433,7 +334,7 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
try:
|
try:
|
||||||
client = await get_genshin_client(user.id, need_cookie=False)
|
client = await get_genshin_client(user.id, need_cookie=False)
|
||||||
await message.reply_chat_action(ChatAction.TYPING)
|
await message.reply_chat_action(ChatAction.TYPING)
|
||||||
data = await GachaLogService.get_analysis(user.id, client, pool_type, self.assets_service)
|
data = await self.gacha_log.get_analysis(user.id, client, pool_type, self.assets_service)
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
reply_message = await message.reply_text(data)
|
reply_message = await message.reply_text(data)
|
||||||
if filters.ChatType.GROUPS.filter(message):
|
if filters.ChatType.GROUPS.filter(message):
|
||||||
@ -445,6 +346,12 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
"genshin/gacha_log/gacha_log.html", data, full_page=True, query_selector=".body_box"
|
"genshin/gacha_log/gacha_log.html", data, full_page=True, query_selector=".body_box"
|
||||||
)
|
)
|
||||||
await message.reply_photo(png_data)
|
await message.reply_photo(png_data)
|
||||||
|
except GachaLogNotFound:
|
||||||
|
await message.reply_text("派蒙没有找到你的抽卡记录,快来私聊派蒙导入吧~")
|
||||||
|
except GachaLogAccountNotFound:
|
||||||
|
await message.reply_text("导入失败,可能文件包含的祈愿记录所属 uid 与你当前绑定的 uid 不同")
|
||||||
|
except GachaLogFileError:
|
||||||
|
await message.reply_text("导入失败,数据格式错误")
|
||||||
except UserNotFoundError:
|
except UserNotFoundError:
|
||||||
logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息")
|
logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息")
|
||||||
if filters.ChatType.GROUPS.filter(message):
|
if filters.ChatType.GROUPS.filter(message):
|
||||||
@ -476,7 +383,7 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
client = await get_genshin_client(user.id, need_cookie=False)
|
client = await get_genshin_client(user.id, need_cookie=False)
|
||||||
group = filters.ChatType.GROUPS.filter(message)
|
group = filters.ChatType.GROUPS.filter(message)
|
||||||
await message.reply_chat_action(ChatAction.TYPING)
|
await message.reply_chat_action(ChatAction.TYPING)
|
||||||
data = await GachaLogService.get_pool_analysis(user.id, client, pool_type, self.assets_service, group)
|
data = await self.gacha_log.get_pool_analysis(user.id, client, pool_type, self.assets_service, group)
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
reply_message = await message.reply_text(data)
|
reply_message = await message.reply_text(data)
|
||||||
if filters.ChatType.GROUPS.filter(message):
|
if filters.ChatType.GROUPS.filter(message):
|
||||||
@ -496,6 +403,12 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
|
|||||||
else:
|
else:
|
||||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||||
await message.reply_photo(png_data)
|
await message.reply_photo(png_data)
|
||||||
|
except GachaLogNotFound:
|
||||||
|
await message.reply_text("派蒙没有找到你的抽卡记录,快来私聊派蒙导入吧~")
|
||||||
|
except GachaLogAccountNotFound:
|
||||||
|
await message.reply_text("导入失败,可能文件包含的祈愿记录所属 uid 与你当前绑定的 uid 不同")
|
||||||
|
except GachaLogFileError:
|
||||||
|
await message.reply_text("导入失败,数据格式错误")
|
||||||
except (UserNotFoundError, CookiesNotFoundError):
|
except (UserNotFoundError, CookiesNotFoundError):
|
||||||
logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息")
|
logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息")
|
||||||
if filters.ChatType.GROUPS.filter(message):
|
if filters.ChatType.GROUPS.filter(message):
|
||||||
|
@ -11,7 +11,7 @@ from core.plugin import Plugin, handler
|
|||||||
from core.sign import SignServices
|
from core.sign import SignServices
|
||||||
from core.user import UserService
|
from core.user import UserService
|
||||||
from core.user.error import UserNotFoundError
|
from core.user.error import UserNotFoundError
|
||||||
from modules.apihelper.gacha_log import GachaLog
|
from modules.gacha_log.log import GachaLog
|
||||||
from utils.bot import get_all_args
|
from utils.bot import get_all_args
|
||||||
from utils.decorators.admins import bot_admins_rights_check
|
from utils.decorators.admins import bot_admins_rights_check
|
||||||
from utils.helpers import get_genshin_client
|
from utils.helpers import get_genshin_client
|
||||||
|
@ -3,6 +3,7 @@ from telegram import Update
|
|||||||
from core.plugin import Plugin, handler
|
from core.plugin import Plugin, handler
|
||||||
from metadata.scripts.honey import update_honey_metadata
|
from metadata.scripts.honey import update_honey_metadata
|
||||||
from metadata.scripts.metadatas import update_metadata_from_ambr, update_metadata_from_github
|
from metadata.scripts.metadatas import update_metadata_from_ambr, update_metadata_from_github
|
||||||
|
from metadata.scripts.paimon_moe import update_paimon_moe_zh
|
||||||
from utils.decorators.admins import bot_admins_rights_check
|
from utils.decorators.admins import bot_admins_rights_check
|
||||||
from utils.log import logger
|
from utils.log import logger
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ class MetadataPlugin(Plugin):
|
|||||||
msg = await message.reply_text("正在刷新元数据,请耐心等待...")
|
msg = await message.reply_text("正在刷新元数据,请耐心等待...")
|
||||||
logger.info("正在从 github 上获取元数据")
|
logger.info("正在从 github 上获取元数据")
|
||||||
await update_metadata_from_github()
|
await update_metadata_from_github()
|
||||||
|
await update_paimon_moe_zh()
|
||||||
logger.info("正在从 ambr 上获取元数据")
|
logger.info("正在从 ambr 上获取元数据")
|
||||||
await update_metadata_from_ambr()
|
await update_metadata_from_ambr()
|
||||||
logger.info("正在从 honey 上获取元数据")
|
logger.info("正在从 honey 上获取元数据")
|
||||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user