PamGram/modules/gacha_log/ranks.py
2024-09-12 21:12:23 +08:00

148 lines
5.4 KiB
Python

import asyncio
import contextlib
from abc import abstractmethod
from pathlib import Path
from typing import List, Optional, TYPE_CHECKING, Dict
from simnet.models.starrail.wish import StarRailBannerType
from core.services.gacha_log_rank.services import GachaLogRankService
from core.services.gacha_log_rank.models import GachaLogRank, GachaLogTypeEnum, GachaLogQueryTypeEnum
from modules.gacha_log.error import GachaLogNotFound
from modules.gacha_log.models import GachaLogInfo, ImportType
from utils.log import logger
if TYPE_CHECKING:
from core.dependence.assets import AssetsService
from telegram import Message
class GachaLogError(Exception):
"""抽卡记录异常"""
class GachaLogRanks:
"""抽卡记录排行榜"""
gacha_log_path: Path
ITEM_LIST_MAP = {
"角色跃迁": GachaLogTypeEnum.CHARACTER,
"光锥跃迁": GachaLogTypeEnum.WEAPON,
"常驻跃迁": GachaLogTypeEnum.DEFAULT,
}
ITEM_LIST_MAP_REV = {
GachaLogTypeEnum.CHARACTER: "角色跃迁",
GachaLogTypeEnum.WEAPON: "光锥跃迁",
GachaLogTypeEnum.DEFAULT: "常驻跃迁",
}
BANNER_TYPE_MAP = {
"角色跃迁": StarRailBannerType.CHARACTER,
"光锥跃迁": StarRailBannerType.WEAPON,
"常驻跃迁": StarRailBannerType.PERMANENT,
}
SCORE_TYPE_MAP = {
"五星平均": GachaLogQueryTypeEnum.FIVE_STAR_AVG,
"UP平均": GachaLogQueryTypeEnum.UP_STAR_AVG,
"小保底不歪": GachaLogQueryTypeEnum.NO_WARP,
}
def __init__(
self,
gacha_log_rank_service: GachaLogRankService = None,
):
self.gacha_log_rank_service = gacha_log_rank_service
@staticmethod
@abstractmethod
async def load_json(path):
"""加载json文件"""
@abstractmethod
async def get_analysis_data(
self, gacha_log: "GachaLogInfo", pool: StarRailBannerType, assets: Optional["AssetsService"]
):
"""
获取抽卡记录分析数据
:param gacha_log: 抽卡记录
:param pool: 池子类型
:param assets: 资源服务
:return: 分析数据
"""
def parse_analysis_data(self, player_id: int, rank_type: "GachaLogTypeEnum", data: Dict) -> GachaLogRank:
line = data["line"]
total = data["allNum"]
rank = GachaLogRank(player_id=player_id, type=rank_type, score_1=total)
for l1 in line:
for l2 in l1:
label = l2["lable"]
if label in self.SCORE_TYPE_MAP:
gacha_log_type = self.SCORE_TYPE_MAP[label]
value = int(float(l2["num"]) * 100)
setattr(rank, gacha_log_type.value, value)
return rank
async def recount_one_data(self, file_path: Path) -> List[GachaLogRank]:
"""重新计算一个文件的数据"""
try:
gacha_log = GachaLogInfo.parse_obj(await self.load_json(file_path))
if gacha_log.get_import_type != ImportType.PaiGram:
raise GachaLogError("不支持的抽卡记录类型")
except ValueError as e:
raise GachaLogError from e
player_id = int(gacha_log.uid)
data = []
for k, v in self.BANNER_TYPE_MAP.items():
rank_type = self.ITEM_LIST_MAP[k]
try:
gacha_log_data = await self.get_analysis_data(gacha_log, v, None)
except GachaLogNotFound:
continue
rank = self.parse_analysis_data(player_id, rank_type, gacha_log_data)
data.append(rank)
return data
async def recount_one_from_uid(self, user_id: int, uid: int):
save_path = self.gacha_log_path / f"{user_id}-{uid}.json"
await self.recount_one(save_path)
async def recount_one(self, file_path: Path):
if not file_path.exists():
return
try:
ranks = await self.recount_one_data(file_path)
if ranks:
await self.add_or_update(ranks)
except GachaLogError:
logger.warning("更新抽卡排名失败 file[%s]", file_path)
async def add_or_update(self, ranks: List["GachaLogRank"]):
"""添加或更新用户数据"""
old_ranks = await self.gacha_log_rank_service.get_rank_by_user_id(ranks[0].player_id)
old_ranks_map = {r.type: r for r in old_ranks}
for rank in ranks:
old_rank = old_ranks_map.get(rank.type)
if old_rank:
old_rank.update_by_new(rank)
await self.gacha_log_rank_service.update(old_rank)
else:
await self.gacha_log_rank_service.add(rank)
async def recount_all_data(self, message: "Message"):
"""重新计算所有数据"""
for key1 in GachaLogTypeEnum:
for key2 in GachaLogQueryTypeEnum:
await self.gacha_log_rank_service.del_all_cache_by_type(key1, key2) # noqa
files = [f for f in self.gacha_log_path.glob("*.json") if len(f.stem.split("-")) == 2]
tasks = []
for idx, f in enumerate(files):
tasks.append(self.recount_one(f))
if len(tasks) >= 10:
await asyncio.gather(*tasks)
tasks.clear()
if idx % 10 == 1:
with contextlib.suppress(Exception):
await message.edit_text(f"已处理 {idx + 1}/{len(files)} 个文件")
if tasks:
await asyncio.gather(*tasks)