From 118a94c69ab775153db62e3256d0e870c927a288 Mon Sep 17 00:00:00 2001 From: ssttkkl Date: Sun, 26 Nov 2023 20:21:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=86=E7=A6=BBrepo=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 27 +++ nonebot_plugin_nagabus/data/naga.py | 163 +++++++++++++++- .../matchers/naga_analyze.py | 33 ++-- nonebot_plugin_nagabus/naga/service.py | 179 ++++-------------- 4 files changed, 242 insertions(+), 160 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..27b84ad --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,27 @@ +default_install_hook_types: [pre-commit, prepare-commit-msg] +ci: + autofix_commit_msg: ":rotating_light: auto fix by pre-commit hooks" + autofix_prs: true + autoupdate_branch: master + autoupdate_schedule: monthly + autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks" +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.292 + hooks: + - id: ruff + args: [--fix] + stages: [commit] + + - repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + stages: [commit] + + - repo: https://github.com/psf/black + rev: 23.9.1 + hooks: + - id: black + stages: [commit] + diff --git a/nonebot_plugin_nagabus/data/naga.py b/nonebot_plugin_nagabus/data/naga.py index 43ae528..3987e90 100644 --- a/nonebot_plugin_nagabus/data/naga.py +++ b/nonebot_plugin_nagabus/data/naga.py @@ -1,13 +1,16 @@ +import json from enum import IntEnum from typing import Optional -from datetime import datetime +from datetime import datetime, timezone -from sqlalchemy import ForeignKey +from nonebot import logger +from sqlalchemy import ForeignKey, select, update from sqlalchemy.orm import Mapped, relationship, mapped_column from .base import SqlModel from .utils import UTCDateTime -from ..naga.model import NagaOrderStatus +from .session import get_session +from ..naga.model import NagaReport, NagaGameRule, NagaOrderStatus class NagaOrderSource(IntEnum): @@ -49,3 +52,157 @@ class MajsoulOrderOrm(SqlModel): passive_deletes=True, lazy="joined", ) + + +async def get_orders(t_begin: datetime, t_end: datetime) -> list[NagaOrderOrm]: + sess = get_session() + stmt = select(NagaOrderOrm).where( + NagaOrderOrm.create_time >= t_begin, + NagaOrderOrm.create_time < t_end, + NagaOrderOrm.status == NagaOrderStatus.ok, + ) + return list((await sess.execute(stmt)).scalars()) + + +async def get_local_majsoul_order( + majsoul_uuid: str, kyoku: int, honba: int, model_type: str +) -> Optional[NagaOrderOrm]: + sess = get_session() + stmt = select(MajsoulOrderOrm).where( + MajsoulOrderOrm.paipu_uuid == majsoul_uuid, + MajsoulOrderOrm.kyoku == kyoku, + MajsoulOrderOrm.honba == honba, + MajsoulOrderOrm.model_type == model_type, + ) + + order_orm: Optional[MajsoulOrderOrm] = ( + await sess.execute(stmt) + ).scalar_one_or_none() + if order_orm is not None: + if ( + order_orm.order.status == NagaOrderStatus.ok + or datetime.now(tz=timezone.utc).timestamp() + - order_orm.order.update_time.timestamp() + < 90 + ): + return order_orm.order + else: # 超过90s仍未分析完成则删除重来 + logger.opt(colors=True).info( + f"Delete majsoul paipu {majsoul_uuid} " + f"(kyoku: {kyoku}, honba: {honba}) " + f"analyze order: {order_orm.naga_haihu_id}, " + f"because it takes over 90 seconds and still not done" + ) + await sess.delete(order_orm.order) + await sess.delete(order_orm) + await sess.commit() + return None + + +async def new_local_majsoul_order( + haihu_id: str, + customer_id: int, + majsoul_uuid: str, + kyoku: int, + honba: int, + model_type: str, +): + sess = get_session() + order_orm = NagaOrderOrm( + haihu_id=haihu_id, + customer_id=customer_id, + cost_np=10, + source=NagaOrderSource.majsoul, + model_type=model_type, + status=NagaOrderStatus.analyzing, + create_time=datetime.now(tz=timezone.utc), + update_time=datetime.now(tz=timezone.utc), + ) + + majsoul_order_orm = MajsoulOrderOrm( + naga_haihu_id=haihu_id, + paipu_uuid=majsoul_uuid, + kyoku=kyoku, + honba=honba, + model_type=model_type, + order=order_orm, + ) + + sess.add(order_orm) + sess.add(majsoul_order_orm) + await sess.commit() + + +async def update_local_majsoul_order(haihu_id: str, report: NagaReport): + sess = get_session() + stmt = ( + update(NagaOrderOrm) + .where(NagaOrderOrm.haihu_id == haihu_id) + .values( + status=NagaOrderStatus.ok, + naga_report=json.dumps(report), + update_time=datetime.now(timezone.utc), + ) + ) + await sess.execute(stmt) + await sess.commit() + + +async def get_local_order(haihu_id: str, model_type: str) -> Optional[NagaOrderOrm]: + sess = get_session() + stmt = select(NagaOrderOrm).where( + NagaOrderOrm.haihu_id == haihu_id, + NagaOrderOrm.model_type == model_type, + ) + + order_orm: Optional[NagaOrderOrm] = (await sess.execute(stmt)).scalar_one_or_none() + if order_orm is not None: + if ( + order_orm.status == NagaOrderStatus.ok + or datetime.now(tz=timezone.utc).timestamp() + - order_orm.update_time.timestamp() + < 300 + ): + return order_orm + else: # 超过90s仍未分析完成则删除重来 + logger.opt(colors=True).info( + f"Delete tenhou paipu {haihu_id} analyze order " + f"because it takes over 90 seconds and still not done" + ) + await sess.delete(order_orm) + await sess.commit() + return None + + +async def new_local_order( + haihu_id: str, customer_id: int, rule: NagaGameRule, model_type: str +): + sess = get_session() + order_orm = NagaOrderOrm( + haihu_id=haihu_id, + customer_id=customer_id, + cost_np=50 if rule == NagaGameRule.hanchan else 30, + source=NagaOrderSource.tenhou, + model_type=model_type, + status=NagaOrderStatus.analyzing, + create_time=datetime.now(tz=timezone.utc), + update_time=datetime.now(tz=timezone.utc), + ) + + sess.add(order_orm) + await sess.commit() + + +async def update_local_order(haihu_id: str, report: NagaReport): + sess = get_session() + stmt = ( + update(NagaOrderOrm) + .where(NagaOrderOrm.haihu_id == haihu_id) + .values( + status=NagaOrderStatus.ok, + naga_report=json.dumps(report), + update_time=datetime.now(timezone.utc), + ) + ) + await sess.execute(stmt) + await sess.commit() diff --git a/nonebot_plugin_nagabus/matchers/naga_analyze.py b/nonebot_plugin_nagabus/matchers/naga_analyze.py index 621ff7d..b0aba12 100644 --- a/nonebot_plugin_nagabus/matchers/naga_analyze.py +++ b/nonebot_plugin_nagabus/matchers/naga_analyze.py @@ -4,13 +4,15 @@ from nonebot import on_command from nonebot.params import CommandArg from nonebot.internal.params import Depends -from nonebot.internal.matcher import Matcher from nonebot_plugin_saa import MessageFactory from ssttkkl_nonebot_utils.integer import decode_integer from nonebot_plugin_session import Session, extract_session from ssttkkl_nonebot_utils.errors.errors import BadRequestError from ssttkkl_nonebot_utils.nonebot import default_command_start from ssttkkl_nonebot_utils.interceptor.handle_error import handle_error +from nonebot_plugin_access_control_api.service.contextvars import ( + current_rate_limit_token, +) from ssttkkl_nonebot_utils.interceptor.with_graceful_shutdown import ( with_graceful_shutdown, ) @@ -26,9 +28,15 @@ analyze_srv = ac.create_subservice("analyze") -async def analyze_majsoul( - matcher: Matcher, session: Session, uuid: str, kyoku: int, honba: int -): +async def _retire_token(): + try: + token = current_rate_limit_token.get() + await token.retire() + except LookupError: + pass + + +async def analyze_majsoul(session: Session, uuid: str, kyoku: int, honba: int): try: report, cost_np = await naga.analyze_majsoul(uuid, kyoku, honba, session) await MessageFactory( @@ -36,8 +44,7 @@ async def analyze_majsoul( ).send(reply=True) if cost_np == 0: - token = matcher.state["ac_token"] - await token.retire() + await _retire_token() await MessageFactory("由于此前已解析过该局,本次解析消耗0NP").send(reply=True) else: await MessageFactory(f"本次解析消耗{cost_np}NP").send(reply=True) @@ -54,15 +61,14 @@ async def analyze_majsoul( raise BadRequestError(f"请输入正确的场次与本场({'、'.join(kyoku_honba)})") from e -async def analyze_tenhou(matcher: Matcher, session: Session, haihu_id: str, seat: int): +async def analyze_tenhou(session: Session, haihu_id: str, seat: int): report, cost_np = await naga.analyze_tenhou(haihu_id, seat, session) await MessageFactory( f"https://naga.dmv.nico/htmls/{report.report_id}.html?tw={seat}" ).send(reply=True) if cost_np == 0: - token = matcher.state["ac_token"] - await token.retire() + await _retire_token() await MessageFactory("由于此前已解析过该局,本次解析消耗0NP").send(reply=True) else: await MessageFactory(f"本次解析消耗{cost_np}NP").send(reply=True) @@ -85,7 +91,7 @@ async def analyze_tenhou(matcher: Matcher, session: Session, haihu_id: str, seat @analyze_srv.patch_handler(retire_on_throw=True) @with_handling_reaction() async def naga_analyze( - matcher: Matcher, cmd_args=CommandArg(), session: Session = Depends(extract_session) + cmd_args=CommandArg(), session: Session = Depends(extract_session) ): args = cmd_args.extract_plain_text().split(" ") if "maj-soul" in args[0]: @@ -115,9 +121,8 @@ async def naga_analyze( except ValueError: pass - await analyze_majsoul( - matcher, session, uuid, kyoku, honba - ) # 如果未指定场次本场,则让其发送该局的场次本场信息 + # 如果未指定场次本场,则让其发送该局的场次本场信息 + await analyze_majsoul(session, uuid, kyoku, honba) elif "tenhou" in args[0]: tenhou_url = args[0].strip() @@ -129,7 +134,7 @@ async def naga_analyze( if "tw" in tenhou_query and len(tenhou_query["tw"]) > 0: seat = int(tenhou_query["tw"][0]) - await analyze_tenhou(matcher, session, haihu_id, seat) + await analyze_tenhou(session, haihu_id, seat) else: await MessageFactory( "用法:\n" diff --git a/nonebot_plugin_nagabus/naga/service.py b/nonebot_plugin_nagabus/naga/service.py index b78095c..cba1d0f 100644 --- a/nonebot_plugin_nagabus/naga/service.py +++ b/nonebot_plugin_nagabus/naga/service.py @@ -2,15 +2,14 @@ import json import asyncio from asyncio import Lock +from typing import Union +from datetime import datetime from inspect import isawaitable -from typing import Union, Optional -from datetime import datetime, timezone from collections.abc import Mapping, Sequence from httpx import Cookies from nonebot import logger from monthdelta import monthdelta -from sqlalchemy import select, update from nonebot_plugin_session import Session from tensoul.downloader import MajsoulDownloadError from nonebot_plugin_session_orm import get_session_persist_id @@ -20,10 +19,8 @@ from .fake_api import FakeNagaApi from ..datastore import plugin_data from .utils import model_type_to_str -from ..data.session import get_session from ..data.mjs import get_majsoul_paipu from .api import NagaApi, OrderReportList -from ..data.naga import NagaOrderOrm, MajsoulOrderOrm, NagaOrderSource from .errors import ( OrderError, InvalidGameError, @@ -40,6 +37,15 @@ NagaHanchanModelType, NagaServiceUserStatistic, ) +from ..data.naga import ( + get_orders, + get_local_order, + new_local_order, + update_local_order, + get_local_majsoul_order, + new_local_majsoul_order, + update_local_majsoul_order, +) DURATION = 2 @@ -124,7 +130,7 @@ async def set_cookies(self, cookies: Mapping[str, str]): await plugin_data.config.set("naga_cookies", cookies) self.cookies = Cookies(dict(cookies)) logger.info( - f"naga_cookies set to {'; '.join((f'{kv[0]}={kv[1]}' for kv in cookies.items()))}" + f"naga_cookies set to {'; '.join(f'{kv[0]}={kv[1]}' for kv in cookies.items())}" ) async def _get_report(self, haihu_id: str) -> NagaReport: @@ -214,9 +220,9 @@ async def callback(order_report: OrderReportList): def _handle_model_type( rule: NagaGameRule, model_type: Union[ - None, Sequence[NagaHanchanModelType], Sequence[NagaHanchanModelType] + None, Sequence[NagaHanchanModelType], Sequence[NagaTonpuuModelType] ], - ) -> Union[Sequence[NagaHanchanModelType], Sequence[NagaHanchanModelType]]: + ) -> Union[Sequence[NagaHanchanModelType], Sequence[NagaTonpuuModelType]]: if rule == NagaGameRule.hanchan: if model_type is None: model_type = [ @@ -246,8 +252,6 @@ async def analyze_majsoul( None, Sequence[NagaHanchanModelType], Sequence[NagaTonpuuModelType] ] = None, ) -> NagaServiceOrder: - sess = get_session() - try: data = await get_majsoul_paipu(majsoul_uuid) except MajsoulDownloadError as e: @@ -293,42 +297,15 @@ async def analyze_majsoul( haihu_id = "" new_order = False - async def _get_local_order() -> Optional[NagaOrderOrm]: - stmt = select(MajsoulOrderOrm).where( - MajsoulOrderOrm.paipu_uuid == majsoul_uuid, - MajsoulOrderOrm.kyoku == kyoku, - MajsoulOrderOrm.honba == honba, - MajsoulOrderOrm.model_type == model_type_str, - ) - - order_orm: Optional[MajsoulOrderOrm] = ( - await sess.execute(stmt) - ).scalar_one_or_none() - if order_orm is not None: - if ( - order_orm.order.status == NagaOrderStatus.ok - or datetime.now(tz=timezone.utc).timestamp() - - order_orm.order.update_time.timestamp() - < 90 - ): - return order_orm.order - else: # 超过90s仍未分析完成则删除重来 - logger.opt(colors=True).info( - f"Delete majsoul paipu {majsoul_uuid} " - f"(kyoku: {kyoku}, honba: {honba}) " - f"analyze order: {order_orm.naga_haihu_id}, " - f"because it takes over 90 seconds and still not done" - ) - await sess.delete(order_orm.order) - await sess.delete(order_orm) - await sess.commit() - return None - # 加锁防止重复下单 - local_order = await _get_local_order() + local_order = await get_local_majsoul_order( + majsoul_uuid, kyoku, honba, model_type_str + ) if local_order is None: async with self._majsoul_order_mutex: - local_order = await _get_local_order() + local_order = await get_local_majsoul_order( + majsoul_uuid, kyoku, honba, model_type_str + ) if local_order is None: # 不存在记录,安排解析 logger.opt(colors=True).info( @@ -350,30 +327,15 @@ async def _get_local_order() -> Optional[NagaOrderOrm]: new_order = True session_persist_id = await get_session_persist_id(session) - order_orm = NagaOrderOrm( - haihu_id=haihu_id, - customer_id=session_persist_id, - cost_np=10, - source=NagaOrderSource.majsoul, - model_type=model_type_str, - status=NagaOrderStatus.analyzing, - create_time=datetime.now(tz=timezone.utc), - update_time=datetime.now(tz=timezone.utc), - ) - - majsoul_order_orm = MajsoulOrderOrm( - naga_haihu_id=haihu_id, - paipu_uuid=majsoul_uuid, - kyoku=kyoku, - honba=honba, - model_type=model_type_str, - order=order_orm, + await new_local_majsoul_order( + haihu_id, + session_persist_id, + majsoul_uuid, + kyoku, + honba, + model_type_str, ) - sess.add(order_orm) - sess.add(majsoul_order_orm) - await sess.commit() - if local_order is not None: # 存在记录 if local_order.status == NagaOrderStatus.ok: @@ -408,18 +370,7 @@ async def _get_local_order() -> Optional[NagaOrderOrm]: f"(kyoku: {kyoku}, honba: {honba}) " f"analyze report: {haihu_id}..." ) - stmt = ( - update(NagaOrderOrm) - .where(NagaOrderOrm.haihu_id == haihu_id) - .values( - status=NagaOrderStatus.ok, - naga_report=json.dumps(report), - update_time=datetime.now(timezone.utc), - ) - ) - await sess.execute(stmt) - await sess.commit() - + await update_local_majsoul_order(haihu_id, report) return NagaServiceOrder(report, 10) else: return NagaServiceOrder(report, 0) @@ -430,7 +381,7 @@ async def _order_tenhou( seat: int, rule: NagaGameRule, model_type: Union[ - None, Sequence[NagaHanchanModelType], Sequence[NagaHanchanModelType] + None, Sequence[NagaHanchanModelType], Sequence[NagaTonpuuModelType] ] = None, ): res = await self.api.analyze_tenhou(haihu_id, seat, rule, model_type) @@ -445,11 +396,9 @@ async def analyze_tenhou( session: Session, *, model_type: Union[ - None, Sequence[NagaHanchanModelType], Sequence[NagaHanchanModelType] + None, Sequence[NagaHanchanModelType], Sequence[NagaTonpuuModelType] ] = None, ) -> NagaServiceOrder: - sess = get_session() - if not self._tenhou_haihu_id_reg.match(haihu_id): raise InvalidGameError(f"invalid haihu_id: {haihu_id}") @@ -473,37 +422,11 @@ async def analyze_tenhou( new_order = False - async def _get_local_order() -> Optional[NagaOrderOrm]: - stmt = select(NagaOrderOrm).where( - NagaOrderOrm.haihu_id == haihu_id, - NagaOrderOrm.model_type == model_type_str, - ) - - order_orm: Optional[NagaOrderOrm] = ( - await sess.execute(stmt) - ).scalar_one_or_none() - if order_orm is not None: - if ( - order_orm.status == NagaOrderStatus.ok - or datetime.now(tz=timezone.utc).timestamp() - - order_orm.update_time.timestamp() - < 300 - ): - return order_orm - else: # 超过90s仍未分析完成则删除重来 - logger.opt(colors=True).info( - f"Delete tenhou paipu {haihu_id} analyze order " - f"because it takes over 90 seconds and still not done" - ) - await sess.delete(order_orm) - await sess.commit() - return None - # 加锁防止重复下单 - local_order = await _get_local_order() + local_order = await get_local_order(haihu_id, model_type_str) if local_order is None: async with self._tenhou_order_mutex: - local_order = await _get_local_order() + local_order = await get_local_order(haihu_id, model_type_str) if local_order is None: # 不存在记录,安排解析 logger.opt(colors=True).info( @@ -515,21 +438,10 @@ async def _get_local_order() -> Optional[NagaOrderOrm]: new_order = True session_persist_id = await get_session_persist_id(session) - - order_orm = NagaOrderOrm( - haihu_id=haihu_id, - customer_id=session_persist_id, - cost_np=50 if rule == NagaGameRule.hanchan else 30, - source=NagaOrderSource.tenhou, - model_type=model_type_str, - status=NagaOrderStatus.analyzing, - create_time=datetime.now(tz=timezone.utc), - update_time=datetime.now(tz=timezone.utc), + await new_local_order( + haihu_id, session_persist_id, rule, model_type_str ) - sess.add(order_orm) - await sess.commit() - if local_order is not None: # 存在记录 if local_order.status == NagaOrderStatus.ok: @@ -554,35 +466,16 @@ async def _get_local_order() -> Optional[NagaOrderOrm]: logger.opt(colors=True).debug( f"Updating tenhou paipu {haihu_id}) " "analyze report..." ) - stmt = ( - update(NagaOrderOrm) - .where(NagaOrderOrm.haihu_id == haihu_id) - .values( - status=NagaOrderStatus.ok, - naga_report=json.dumps(report), - update_time=datetime.now(timezone.utc), - ) - ) - await sess.execute(stmt) - await sess.commit() + await update_local_order(haihu_id, report) return NagaServiceOrder(report, 50) else: return NagaServiceOrder(report, 0) async def statistic(self, year: int, month: int) -> list[NagaServiceUserStatistic]: - sess = get_session() - t_begin = datetime(year, month, 1) t_end = datetime(year, month, 1) + monthdelta(months=1) - - stmt = select(NagaOrderOrm).where( - NagaOrderOrm.create_time >= t_begin, - NagaOrderOrm.create_time < t_end, - NagaOrderOrm.status == NagaOrderStatus.ok, - ) - - orders = (await sess.execute(stmt)).scalars() + orders = await get_orders(t_begin, t_end) statistic = {}