Skip to content

Commit

Permalink
✨ Support gacha log rank
Browse files Browse the repository at this point in the history
  • Loading branch information
omg-xtao committed Sep 12, 2024
1 parent 69bc4f8 commit ca15939
Show file tree
Hide file tree
Showing 15 changed files with 752 additions and 8 deletions.
59 changes: 59 additions & 0 deletions alembic/versions/1220c5c80757_gacha_log_rank.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""gacha_log_rank
Revision ID: 1220c5c80757
Revises: 87c6195e5306
Create Date: 2024-09-12 12:02:16.283418
"""

from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = "1220c5c80757"
down_revision = "87c6195e5306"
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"gacha_log_rank",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("player_id", sa.BigInteger(), nullable=False),
sa.Column(
"type",
sa.Enum(
"CHARACTER",
"WEAPON",
"DEFAULT",
"DEFAULT_WEAPON",
"HUN",
"PET",
name="gachalogtypeenum",
),
nullable=False,
),
sa.Column("score_1", sa.BigInteger(), nullable=True),
sa.Column("score_2", sa.BigInteger(), nullable=True),
sa.Column("score_3", sa.BigInteger(), nullable=True),
sa.Column("score_4", sa.BigInteger(), nullable=True),
sa.Column("score_5", sa.BigInteger(), nullable=True),
sa.Column("data", sa.JSON(), nullable=True),
sa.Column(
"time_created",
sa.DateTime(),
server_default=sa.text("now()"),
nullable=True,
),
sa.Column("time_updated", sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint("id", "player_id", "type"),
mysql_charset="utf8mb4",
mysql_collate="utf8mb4_general_ci",
)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("gacha_log_rank")
# ### end Alembic commands ###
Empty file.
3 changes: 3 additions & 0 deletions core/services/gacha_log_rank/cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from gram_core.services.gacha_log_rank.cache import GachaLogRankCache

__all__ = ("GachaLogRankCache",)
7 changes: 7 additions & 0 deletions core/services/gacha_log_rank/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from gram_core.services.gacha_log_rank.models import GachaLogRank, GachaLogTypeEnum, GachaLogQueryTypeEnum

__all__ = (
"GachaLogRank",
"GachaLogTypeEnum",
"GachaLogQueryTypeEnum",
)
3 changes: 3 additions & 0 deletions core/services/gacha_log_rank/repositories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from gram_core.services.gacha_log_rank.repositories import GachaLogRankRepository

__all__ = ("GachaLogRankRepository",)
3 changes: 3 additions & 0 deletions core/services/gacha_log_rank/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from gram_core.services.gacha_log_rank.services import GachaLogRankService

__all__ = ("GachaLogRankService",)
34 changes: 28 additions & 6 deletions modules/gacha_log/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from simnet.models.starrail.wish import StarRailBannerType
from simnet.utils.player import recognize_starrail_server

from gram_core.services.gacha_log_rank.services import GachaLogRankService
from metadata.pool.pool import get_pool_by_id
from modules.gacha_log.const import GACHA_TYPE_LIST
from modules.gacha_log.error import (
Expand All @@ -35,6 +36,7 @@
SRGFModel,
)
from modules.gacha_log.online_view import GachaLogOnlineView
from modules.gacha_log.ranks import GachaLogRanks
from utils.const import PROJECT_ROOT
from utils.uid import mask_number

Expand All @@ -46,8 +48,14 @@
GACHA_LOG_PATH.mkdir(parents=True, exist_ok=True)


class GachaLog(GachaLogOnlineView):
def __init__(self, gacha_log_path: Path = GACHA_LOG_PATH):
class GachaLog(GachaLogOnlineView, GachaLogRanks):
def __init__(
self,
gacha_log_path: Path = GACHA_LOG_PATH,
gacha_log_rank_service: GachaLogRankService = None,
):
GachaLogOnlineView.__init__(self)
GachaLogRanks.__init__(self, gacha_log_rank_service)
self.gacha_log_path = gacha_log_path

@staticmethod
Expand Down Expand Up @@ -232,6 +240,7 @@ async def import_gacha_log_data(self, user_id: int, player_id: int, data: dict,
gacha_log.update_time = datetime.datetime.now()
gacha_log.import_type = import_type.value
await self.save_gacha_log_info(str(user_id), uid, gacha_log)
await self.recount_one_from_uid(user_id, player_id)
return new_num
except GachaLogAccountNotFound as e:
raise GachaLogAccountNotFound("导入失败,文件包含的祈愿记录所属 uid 与你当前绑定的 uid 不同") from e
Expand Down Expand Up @@ -337,7 +346,7 @@ async def get_all_5_star_items(self, data: List[GachaItem], assets: "AssetsServi
isUp, isBig = False, False
data = {
"name": item.name,
"icon": assets.avatar.square(item.name).as_uri(),
"icon": assets.avatar.square(item.name).as_uri() if assets else "",
"count": count,
"type": "角色",
"isUp": isUp,
Expand All @@ -348,7 +357,7 @@ async def get_all_5_star_items(self, data: List[GachaItem], assets: "AssetsServi
elif item.item_type == "光锥" and pool_name in {"光锥跃迁", "常驻跃迁"}:
data = {
"name": item.name,
"icon": assets.light_cone.icon(item.name).as_uri(),
"icon": assets.light_cone.icon(item.name).as_uri() if assets else "",
"count": count,
"type": "光锥",
"isUp": False,
Expand Down Expand Up @@ -376,7 +385,7 @@ async def get_all_4_star_items(data: List[GachaItem], assets: "AssetsService"):
if item.item_type == "角色":
data = {
"name": item.name,
"icon": assets.avatar.square(item.name).as_uri(),
"icon": assets.avatar.square(item.name).as_uri() if assets else "",
"count": count,
"type": "角色",
"time": item.time,
Expand All @@ -385,7 +394,7 @@ async def get_all_4_star_items(data: List[GachaItem], assets: "AssetsService"):
elif item.item_type == "光锥":
data = {
"name": item.name,
"icon": assets.light_cone.icon(item.name).as_uri(),
"icon": assets.light_cone.icon(item.name).as_uri() if assets else "",
"count": count,
"type": "光锥",
"time": item.time,
Expand Down Expand Up @@ -532,6 +541,19 @@ async def get_analysis(self, user_id: int, player_id: int, pool: StarRailBannerT
gacha_log, status = await self.load_history_info(str(user_id), str(player_id))
if not status:
raise GachaLogNotFound
return await self.get_analysis_data(gacha_log, pool, assets)

async def get_analysis_data(
self, gacha_log: "GachaLogInfo", pool: StarRailBannerType, assets: Optional["AssetsService"]
):
"""
获取抽卡记录分析数据
:param gacha_log: 抽卡记录
:param pool: 池子类型
:param assets: 资源服务
:return: 分析数据
"""
player_id = int(gacha_log.uid)
pool_name = GACHA_TYPE_LIST[pool]
if pool_name not in gacha_log.item_list:
raise GachaLogNotFound
Expand Down
147 changes: 147 additions & 0 deletions modules/gacha_log/ranks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
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)
2 changes: 2 additions & 0 deletions plugins/admin/set_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ async def set_command(self, update: "Update", context: "ContextTypes.DEFAULT_TYP
BotCommand("help", "帮助"),
BotCommand("warp_log", "查看跃迁记录"),
BotCommand("warp_log_online_view", "抽卡记录在线浏览"),
BotCommand("warp_log_rank", "抽卡排行榜"),
BotCommand("action_log", "查询登录记录"),
BotCommand("dailynote", "查询实时便笺"),
BotCommand("redeem", "(国际服)兑换 Key"),
Expand Down Expand Up @@ -78,6 +79,7 @@ async def set_command(self, update: "Update", context: "ContextTypes.DEFAULT_TYP
BotCommand("get_chat", "获取会话信息"),
BotCommand("add_block", "添加黑名单"),
BotCommand("del_block", "移除黑名单"),
BotCommand("warp_log_rank_recount", "重新统计抽卡排行榜"),
]
await context.bot.delete_my_commands()
await context.bot.set_my_commands(commands=group_command)
Expand Down
13 changes: 12 additions & 1 deletion plugins/starrail/wish_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from core.services.template.services import TemplateService
from gram_core.config import config
from gram_core.plugin.methods.inline_use_data import IInlineUseData
from gram_core.services.gacha_log_rank.services import GachaLogRankService
from modules.gacha_log.const import SRGF_VERSION, GACHA_TYPE_LIST_REVERSE
from modules.gacha_log.error import (
GachaLogAccountNotFound,
Expand Down Expand Up @@ -69,14 +70,15 @@ def __init__(
cookie_service: CookiesService,
head_icon: HeadIconService,
phone_theme: PhoneThemeService,
gacha_log_rank: GachaLogRankService,
):
self.template_service = template_service
self.players_service = players_service
self.assets_service = assets
self.cookie_service = cookie_service
self.head_icon = head_icon
self.phone_theme = phone_theme
self.gacha_log = GachaLog()
self.gacha_log = GachaLog(gacha_log_rank_service=gacha_log_rank)
self.wish_photo = None

async def get_player_id(self, user_id: int, player_id: int, offset: int) -> int:
Expand Down Expand Up @@ -561,6 +563,15 @@ async def command_start_upload_web(self, update: "Update", context: "ContextType
logger.error("申请在线查看跃迁记录失败", exc_info=e)
await message.reply_text("申请在线查看跃迁记录失败,请联系管理员")

@handler.command(command="warp_log_rank_recount", block=False, admin=True)
async def wish_log_rank_recount(self, update: "Update", _: "ContextTypes.DEFAULT_TYPE") -> None:
user = update.effective_user
logger.info("用户 %s[%s] wish_log_rank_recount 命令请求", user.full_name, user.id)
message = update.effective_message
reply = await message.reply_text("正在重新统计抽卡记录排行榜")
await self.gacha_log.recount_all_data(reply)
await reply.edit_text("重新统计完成")

@staticmethod
async def get_migrate_data(
old_user_id: int, new_user_id: int, old_players: List["Player"]
Expand Down
Loading

0 comments on commit ca15939

Please sign in to comment.