🎨 使用 black 格式化所有代码

This commit is contained in:
Chuangbo Li 2022-10-10 19:07:28 +08:00 committed by GitHub
parent c44b785118
commit 345edb9fe5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
82 changed files with 2313 additions and 2072 deletions

View File

@ -42,9 +42,7 @@ def import_models():
try: try:
import_module(pkg) # 导入 models import_module(pkg) # 导入 models
except Exception as e: # pylint: disable=W0703 except Exception as e: # pylint: disable=W0703
logger.error( logger.error(f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]')
f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]'
)
# register our models for alembic to auto-generate migrations # register our models for alembic to auto-generate migrations

View File

@ -1,9 +1,8 @@
from sqlmodel import SQLModel, Field from sqlmodel import SQLModel, Field
class Admin(SQLModel, table=True): class Admin(SQLModel, table=True):
__table_args__ = dict(mysql_charset='utf8mb4', mysql_collate="utf8mb4_general_ci") __table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci")
id: int = Field(primary_key=True) id: int = Field(primary_key=True)
user_id: int = Field(foreign_key="user.user_id") user_id: int = Field(foreign_key="user.user_id")

View File

@ -7,7 +7,6 @@ from utils.log import logger
class AioBrowser(Service): class AioBrowser(Service):
def __init__(self, loop=None): def __init__(self, loop=None):
self.browser: Optional[Browser] = None self.browser: Optional[Browser] = None
self._playwright: Optional[Playwright] = None self._playwright: Optional[Playwright] = None
@ -15,16 +14,16 @@ class AioBrowser(Service):
async def start(self): async def start(self):
if self._playwright is None: if self._playwright is None:
logger.info("正在尝试启动 [blue]Playwright[/]", extra={'markup': True}) logger.info("正在尝试启动 [blue]Playwright[/]", extra={"markup": True})
self._playwright = await async_playwright().start() self._playwright = await async_playwright().start()
logger.success("[blue]Playwright[/] 启动成功", extra={'markup': True}) logger.success("[blue]Playwright[/] 启动成功", extra={"markup": True})
if self.browser is None: if self.browser is None:
logger.info("正在尝试启动 [blue]Browser[/]", extra={'markup': True}) logger.info("正在尝试启动 [blue]Browser[/]", extra={"markup": True})
try: try:
self.browser = await self._playwright.chromium.launch(timeout=5000) self.browser = await self._playwright.chromium.launch(timeout=5000)
logger.success("[blue]Browser[/] 启动成功", extra={'markup': True}) logger.success("[blue]Browser[/] 启动成功", extra={"markup": True})
except TimeoutError as err: except TimeoutError as err:
logger.warning("[blue]Browser[/] 启动失败", extra={'markup': True}) logger.warning("[blue]Browser[/] 启动失败", extra={"markup": True})
raise err raise err
return self.browser return self.browser

View File

@ -20,13 +20,12 @@ from core.service import Service
class MTProto(Service): class MTProto(Service):
async def get_session(self): async def get_session(self):
async with aiofiles.open(self.session_path, mode='r') as f: async with aiofiles.open(self.session_path, mode="r") as f:
return await f.read() return await f.read()
async def set_session(self, b: str): async def set_session(self, b: str):
async with aiofiles.open(self.session_path, mode='w+') as f: async with aiofiles.open(self.session_path, mode="w+") as f:
await f.write(b) await f.write(b)
def session_exists(self): def session_exists(self):
@ -53,8 +52,13 @@ class MTProto(Service):
if bot.config.mtproto.api_hash is None: if bot.config.mtproto.api_hash is None:
logger.info("MTProto 服务需要的 api_hash 未配置 本次服务 client 为 None") logger.info("MTProto 服务需要的 api_hash 未配置 本次服务 client 为 None")
return return
self.client = Client(api_id=bot.config.mtproto.api_id, api_hash=bot.config.mtproto.api_hash, name=self.name, self.client = Client(
bot_token=bot.config.bot_token, proxy=self.proxy) api_id=bot.config.mtproto.api_id,
api_hash=bot.config.mtproto.api_hash,
name=self.name,
bot_token=bot.config.bot_token,
proxy=self.proxy,
)
await self.client.start() await self.client.start()
async def stop(self): # pylint: disable=W0221 async def stop(self): # pylint: disable=W0221

View File

@ -12,8 +12,7 @@ class MySQL(Service):
def from_config(cls, config: BotConfig) -> Self: def from_config(cls, config: BotConfig) -> Self:
return cls(**config.mysql.dict()) return cls(**config.mysql.dict())
def __init__(self, host: str = "127.0.0.1", port: int = 3306, username: str = "root", # nosec B107 def __init__(self, host: str, port: int, username: str, password: str, database: str):
password: str = "", database: str = ""): # nosec B107
self.database = database self.database = database
self.password = password self.password = password
self.user = username self.user = username

View File

@ -22,21 +22,21 @@ class RedisDB(Service):
async def ping(self): async def ping(self):
if await self.client.ping(): if await self.client.ping():
logger.info("连接 [red]Redis[/] 成功", extra={'markup': True}) logger.info("连接 [red]Redis[/] 成功", extra={"markup": True})
else: else:
logger.info("连接 [red]Redis[/] 失败", extra={'markup': True}) logger.info("连接 [red]Redis[/] 失败", extra={"markup": True})
raise RuntimeError("连接 Redis 失败") raise RuntimeError("连接 Redis 失败")
async def start(self): # pylint: disable=W0221 async def start(self): # pylint: disable=W0221
if self._loop is None: if self._loop is None:
self._loop = asyncio.get_running_loop() self._loop = asyncio.get_running_loop()
logger.info("正在尝试建立与 [red]Redis[/] 连接", extra={'markup': True}) logger.info("正在尝试建立与 [red]Redis[/] 连接", extra={"markup": True})
try: try:
await self.ping() await self.ping()
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
pass pass
except Exception as exc: except Exception as exc:
logger.exception("尝试连接 [red]Redis[/] 失败,使用 [red]fakeredis[/] 模拟", exc_info=exc, extra={'markup': True}) logger.exception("尝试连接 [red]Redis[/] 失败,使用 [red]fakeredis[/] 模拟", exc_info=exc, extra={"markup": True})
self.client = fakeredis.aioredis.FakeRedis() self.client = fakeredis.aioredis.FakeRedis()
await self.ping() await self.ping()

View File

@ -16,8 +16,7 @@ async def clean_message(context: CallbackContext):
if "not found" in str(exc): if "not found" in str(exc):
logger.warning(f"删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败 消息不存在") logger.warning(f"删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败 消息不存在")
elif "Message can't be deleted" in str(exc): elif "Message can't be deleted" in str(exc):
logger.warning( logger.warning(f"删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败 消息无法删除 可能是没有授权")
f"删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败 消息无法删除 可能是没有授权")
else: else:
logger.error(f"删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败") logger.error(f"删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败")
logger.exception(exc) logger.exception(exc)
@ -30,10 +29,14 @@ async def clean_message(context: CallbackContext):
def add_delete_message_job(context: CallbackContext, chat_id: int, message_id: int, delete_seconds: int): def add_delete_message_job(context: CallbackContext, chat_id: int, message_id: int, delete_seconds: int):
context.job_queue.run_once(callback=clean_message, when=delete_seconds, data=message_id, context.job_queue.run_once(
name=f"{chat_id}|{message_id}|clean_message", chat_id=chat_id, callback=clean_message,
job_kwargs={"replace_existing": True, when=delete_seconds,
"id": f"{chat_id}|{message_id}|clean_message"}) data=message_id,
name=f"{chat_id}|{message_id}|clean_message",
chat_id=chat_id,
job_kwargs={"replace_existing": True, "id": f"{chat_id}|{message_id}|clean_message"},
)
class _BasePlugin: class _BasePlugin:
@ -43,9 +46,8 @@ class _BasePlugin:
class _Conversation(_BasePlugin): class _Conversation(_BasePlugin):
@conversation.fallback @conversation.fallback
@handler.command(command='cancel', block=True) @handler.command(command="cancel", block=True)
async def cancel(self, update: Update, _: CallbackContext) -> int: async def cancel(self, update: Update, _: CallbackContext) -> int:
await update.effective_message.reply_text("退出命令", reply_markup=ReplyKeyboardRemove()) await update.effective_message.reply_text("退出命令", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END return ConversationHandler.END

View File

@ -23,6 +23,7 @@ from telegram.ext.filters import StatusUpdate
from core.config import BotConfig, config # pylint: disable=W0611 from core.config import BotConfig, config # pylint: disable=W0611
from core.error import ServiceNotFoundError from core.error import ServiceNotFoundError
# noinspection PyProtectedMember # noinspection PyProtectedMember
from core.plugin import Plugin, _Plugin from core.plugin import Plugin, _Plugin
from core.service import Service from core.service import Service
@ -32,10 +33,10 @@ from utils.log import logger
if TYPE_CHECKING: if TYPE_CHECKING:
from telegram import Update from telegram import Update
__all__ = ['bot'] __all__ = ["bot"]
T = TypeVar('T') T = TypeVar("T")
PluginType = TypeVar('PluginType', bound=_Plugin) PluginType = TypeVar("PluginType", bound=_Plugin)
class Bot: class Bot:
@ -57,7 +58,7 @@ class Bot:
def _inject(self, signature: inspect.Signature, target: Callable[..., T]) -> T: def _inject(self, signature: inspect.Signature, target: Callable[..., T]) -> T:
kwargs = {} kwargs = {}
for name, parameter in signature.parameters.items(): for name, parameter in signature.parameters.items():
if name != 'self' and parameter.annotation != inspect.Parameter.empty: if name != "self" and parameter.annotation != inspect.Parameter.empty:
if value := self._services.get(parameter.annotation): if value := self._services.get(parameter.annotation):
kwargs[name] = value kwargs[name] = value
return target(**kwargs) return target(**kwargs)
@ -76,11 +77,11 @@ class Bot:
def _gen_pkg(self, root: Path) -> Iterator[str]: def _gen_pkg(self, root: Path) -> Iterator[str]:
"""生成可以用于 import_module 导入的字符串""" """生成可以用于 import_module 导入的字符串"""
for path in root.iterdir(): for path in root.iterdir():
if not path.name.startswith('_'): if not path.name.startswith("_"):
if path.is_dir(): if path.is_dir():
yield from self._gen_pkg(path) yield from self._gen_pkg(path)
elif path.suffix == '.py': elif path.suffix == ".py":
yield str(path.relative_to(PROJECT_ROOT).with_suffix('')).replace(os.sep, '.') yield str(path.relative_to(PROJECT_ROOT).with_suffix("")).replace(os.sep, ".")
async def install_plugins(self): async def install_plugins(self):
"""安装插件""" """安装插件"""
@ -89,8 +90,7 @@ class Bot:
import_module(pkg) # 导入插件 import_module(pkg) # 导入插件
except Exception as e: # pylint: disable=W0703 except Exception as e: # pylint: disable=W0703
logger.exception( logger.exception(
f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]', f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]', extra={"markup": True}
extra={'markup': True}
) )
continue # 如有错误则继续 continue # 如有错误则继续
callback_dict: Dict[int, List[Callable]] = {} callback_dict: Dict[int, List[Callable]] = {}
@ -98,7 +98,7 @@ class Bot:
path = f"{plugin_cls.__module__}.{plugin_cls.__name__}" path = f"{plugin_cls.__module__}.{plugin_cls.__name__}"
try: try:
plugin: PluginType = self.init_inject(plugin_cls) plugin: PluginType = self.init_inject(plugin_cls)
if hasattr(plugin, '__async_init__'): if hasattr(plugin, "__async_init__"):
await self.async_inject(plugin.__async_init__) await self.async_inject(plugin.__async_init__)
handlers = plugin.handlers handlers = plugin.handlers
self.app.add_handlers(handlers) self.app.add_handlers(handlers)
@ -115,46 +115,44 @@ class Bot:
for callback, block in error_handlers.items(): for callback, block in error_handlers.items():
self.app.add_error_handler(callback, block) self.app.add_error_handler(callback, block)
if error_handlers: if error_handlers:
logger.debug(f"插件 \"{path}\" 添加了 {len(error_handlers)} 个 error handler") logger.debug(f'插件 "{path}" 添加了 {len(error_handlers)} 个 error handler')
if jobs := plugin.jobs: if jobs := plugin.jobs:
logger.debug(f'插件 "{path}" 添加了 {len(jobs)} 个任务') logger.debug(f'插件 "{path}" 添加了 {len(jobs)} 个任务')
logger.success(f'插件 "{path}" 载入成功') logger.success(f'插件 "{path}" 载入成功')
except Exception as e: # pylint: disable=W0703 except Exception as e: # pylint: disable=W0703
logger.exception( logger.exception(
f'在安装插件 \"{path}\" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]', f'在安装插件 "{path}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]', extra={"markup": True}
extra={'markup': True}
) )
if callback_dict: if callback_dict:
num = sum(len(callback_dict[i]) for i in callback_dict) num = sum(len(callback_dict[i]) for i in callback_dict)
async def _new_chat_member_callback(update: 'Update', context: 'CallbackContext'): async def _new_chat_member_callback(update: "Update", context: "CallbackContext"):
nonlocal callback nonlocal callback
for _, value in callback_dict.items(): for _, value in callback_dict.items():
for callback in value: for callback in value:
await callback(update, context) await callback(update, context)
self.app.add_handler(MessageHandler( self.app.add_handler(
callback=_new_chat_member_callback, filters=StatusUpdate.NEW_CHAT_MEMBERS, block=False MessageHandler(callback=_new_chat_member_callback, filters=StatusUpdate.NEW_CHAT_MEMBERS, block=False)
)) )
logger.success( logger.success(
f'成功添加了 {num} 个针对 [blue]{StatusUpdate.NEW_CHAT_MEMBERS}[/] 的 [blue]MessageHandler[/]', f"成功添加了 {num} 个针对 [blue]{StatusUpdate.NEW_CHAT_MEMBERS}[/] 的 [blue]MessageHandler[/]",
extra={'markup': True} extra={"markup": True},
) )
async def _start_base_services(self): async def _start_base_services(self):
for pkg in self._gen_pkg(PROJECT_ROOT / 'core/base'): for pkg in self._gen_pkg(PROJECT_ROOT / "core/base"):
try: try:
import_module(pkg) import_module(pkg)
except Exception as e: # pylint: disable=W0703 except Exception as e: # pylint: disable=W0703
logger.exception( logger.exception(
f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]', f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]', extra={"markup": True}
extra={'markup': True}
) )
continue continue
for base_service_cls in Service.__subclasses__(): for base_service_cls in Service.__subclasses__():
try: try:
if hasattr(base_service_cls, 'from_config'): if hasattr(base_service_cls, "from_config"):
instance = base_service_cls.from_config(self._config) instance = base_service_cls.from_config(self._config)
else: else:
instance = self.init_inject(base_service_cls) instance = self.init_inject(base_service_cls)
@ -168,15 +166,14 @@ class Bot:
async def start_services(self): async def start_services(self):
"""启动服务""" """启动服务"""
await self._start_base_services() await self._start_base_services()
for path in (PROJECT_ROOT / 'core').iterdir(): for path in (PROJECT_ROOT / "core").iterdir():
if not path.name.startswith('_') and path.is_dir() and path.name != 'base': if not path.name.startswith("_") and path.is_dir() and path.name != "base":
pkg = str(path.relative_to(PROJECT_ROOT).with_suffix('')).replace(os.sep, '.') pkg = str(path.relative_to(PROJECT_ROOT).with_suffix("")).replace(os.sep, ".")
try: try:
import_module(pkg) import_module(pkg)
except Exception as e: # pylint: disable=W0703 except Exception as e: # pylint: disable=W0703
logger.exception( logger.exception(
f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]', f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]', extra={"markup": True}
extra={'markup': True}
) )
continue continue
@ -184,11 +181,11 @@ class Bot:
"""关闭服务""" """关闭服务"""
if not self._services: if not self._services:
return return
logger.info('正在关闭服务') logger.info("正在关闭服务")
for _, service in filter(lambda x: not isinstance(x[1], TgApplication), self._services.items()): for _, service in filter(lambda x: not isinstance(x[1], TgApplication), self._services.items()):
async with timeout(5): async with timeout(5):
try: try:
if hasattr(service, 'stop'): if hasattr(service, "stop"):
if inspect.iscoroutinefunction(service.stop): if inspect.iscoroutinefunction(service.stop):
await service.stop() await service.stop()
else: else:
@ -197,16 +194,19 @@ class Bot:
except CancelledError: except CancelledError:
logger.warning(f'服务 "{service.__class__.__name__}" 关闭超时') logger.warning(f'服务 "{service.__class__.__name__}" 关闭超时')
except Exception as e: # pylint: disable=W0703 except Exception as e: # pylint: disable=W0703
logger.exception(f"服务 \"{service.__class__.__name__}\" 关闭失败: \n{type(e).__name__}: {e}") logger.exception(f'服务 "{service.__class__.__name__}" 关闭失败: \n{type(e).__name__}: {e}')
async def _post_init(self, context: CallbackContext) -> NoReturn: async def _post_init(self, context: CallbackContext) -> NoReturn:
logger.info('开始初始化 genshin.py 相关资源') logger.info("开始初始化 genshin.py 相关资源")
try: try:
# 替换为 fastgit 镜像源 # 替换为 fastgit 镜像源
for i in dir(genshin.utility.extdb): for i in dir(genshin.utility.extdb):
if "_URL" in i: if "_URL" in i:
setattr(genshin.utility.extdb, i, setattr(
getattr(genshin.utility.extdb, i).replace("githubusercontent.com", "fastgit.org")) genshin.utility.extdb,
i,
getattr(genshin.utility.extdb, i).replace("githubusercontent.com", "fastgit.org"),
)
await genshin.utility.update_characters_enka() await genshin.utility.update_characters_enka()
except Exception as exc: except Exception as exc:
logger.error("初始化 genshin.py 相关资源失败") logger.error("初始化 genshin.py 相关资源失败")
@ -214,16 +214,16 @@ class Bot:
else: else:
logger.success("初始化 genshin.py 相关资源成功") logger.success("初始化 genshin.py 相关资源成功")
self._services.update({CallbackContext: context}) self._services.update({CallbackContext: context})
logger.info('开始初始化服务') logger.info("开始初始化服务")
await self.start_services() await self.start_services()
logger.info('开始安装插件') logger.info("开始安装插件")
await self.install_plugins() await self.install_plugins()
logger.info('BOT 初始化成功') logger.info("BOT 初始化成功")
def launch(self) -> NoReturn: def launch(self) -> NoReturn:
"""启动机器人""" """启动机器人"""
self._running = True self._running = True
logger.info('正在初始化BOT') logger.info("正在初始化BOT")
self.app = ( self.app = (
TgApplication.builder() TgApplication.builder()
.rate_limiter(AIORateLimiter()) .rate_limiter(AIORateLimiter())
@ -238,11 +238,11 @@ class Bot:
self.app.run_polling(close_loop=False) self.app.run_polling(close_loop=False)
break break
except TimedOut: except TimedOut:
logger.warning("连接至 [blue]telegram[/] 服务器失败,正在重试", extra={'markup': True}) logger.warning("连接至 [blue]telegram[/] 服务器失败,正在重试", extra={"markup": True})
continue continue
except NetworkError as e: except NetworkError as e:
logger.exception() logger.exception()
if 'SSLZeroReturnError' in str(e): if "SSLZeroReturnError" in str(e):
logger.error("代理服务出现异常, 请检查您的代理服务是否配置成功.") logger.error("代理服务出现异常, 请检查您的代理服务是否配置成功.")
else: else:
logger.error("网络连接出现问题, 请检查您的网络状况.") logger.error("网络连接出现问题, 请检查您的网络状况.")
@ -267,7 +267,7 @@ class Bot:
def add_service(self, service: T) -> NoReturn: def add_service(self, service: T) -> NoReturn:
"""添加服务。若已经有同类型的服务,则会抛出异常""" """添加服务。若已经有同类型的服务,则会抛出异常"""
if type(service) in self._services: if type(service) in self._services:
raise ValueError(f"Service \"{type(service)}\" is already existed.") raise ValueError(f'Service "{type(service)}" is already existed.')
self.update_service(service) self.update_service(service)
def update_service(self, service: T): def update_service(self, service: T):

View File

@ -11,7 +11,7 @@ class CookiesStatusEnum(int, enum.Enum):
class Cookies(SQLModel): class Cookies(SQLModel):
__table_args__ = dict(mysql_charset='utf8mb4', mysql_collate="utf8mb4_general_ci") __table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci")
id: int = Field(primary_key=True) id: int = Field(primary_key=True)
user_id: Optional[int] = Field(foreign_key="user.user_id") user_id: Optional[int] = Field(foreign_key="user.user_id")
@ -20,8 +20,8 @@ class Cookies(SQLModel):
class HyperionCookie(Cookies, table=True): class HyperionCookie(Cookies, table=True):
__tablename__ = 'mihoyo_cookies' __tablename__ = "mihoyo_cookies"
class HoyolabCookie(Cookies, table=True): class HoyolabCookie(Cookies, table=True):
__tablename__ = 'hoyoverse_cookies' __tablename__ = "hoyoverse_cookies"

View File

@ -3,6 +3,5 @@ from typing import Union
class ServiceNotFoundError(Exception): class ServiceNotFoundError(Exception):
def __init__(self, name: Union[str, type]): def __init__(self, name: Union[str, type]):
super().__init__(f"No service named '{name if isinstance(name, str) else name.__name__}'") super().__init__(f"No service named '{name if isinstance(name, str) else name.__name__}'")

View File

@ -48,7 +48,7 @@ class GameMaterialService:
self._cache = cache self._cache = cache
self._hyperion = Hyperion() self._hyperion = Hyperion()
self._collections = [428421, 1164644] if collections is None else collections self._collections = [428421, 1164644] if collections is None else collections
self._special = ['雷电将军', '珊瑚宫心海', '菲谢尔', '托马', '八重神子', '九条裟罗', '辛焱', '神里绫华'] self._special = ["雷电将军", "珊瑚宫心海", "菲谢尔", "托马", "八重神子", "九条裟罗", "辛焱", "神里绫华"]
async def _get_material_from_hyperion(self, collection_id: int, character_name: str) -> int: async def _get_material_from_hyperion(self, collection_id: int, character_name: str) -> int:
post_id: int = -1 post_id: int = -1

View File

@ -7,43 +7,42 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypeVar, Un
# noinspection PyProtectedMember # noinspection PyProtectedMember
from telegram._utils.defaultvalue import DEFAULT_TRUE from telegram._utils.defaultvalue import DEFAULT_TRUE
# noinspection PyProtectedMember # noinspection PyProtectedMember
from telegram._utils.types import DVInput, JSONDict from telegram._utils.types import DVInput, JSONDict
from telegram.ext import BaseHandler, ConversationHandler, Job from telegram.ext import BaseHandler, ConversationHandler, Job
# noinspection PyProtectedMember # noinspection PyProtectedMember
from telegram.ext._utils.types import JobCallback from telegram.ext._utils.types import JobCallback
from telegram.ext.filters import BaseFilter from telegram.ext.filters import BaseFilter
from typing_extensions import ParamSpec from typing_extensions import ParamSpec
__all__ = [ __all__ = ["Plugin", "handler", "conversation", "job", "error_handler"]
'Plugin', 'handler', 'conversation', 'job', 'error_handler'
]
P = ParamSpec('P') P = ParamSpec("P")
T = TypeVar('T') T = TypeVar("T")
HandlerType = TypeVar('HandlerType', bound=BaseHandler) HandlerType = TypeVar("HandlerType", bound=BaseHandler)
TimeType = Union[float, datetime.timedelta, datetime.datetime, datetime.time] TimeType = Union[float, datetime.timedelta, datetime.datetime, datetime.time]
_Module = import_module('telegram.ext') _Module = import_module("telegram.ext")
_NORMAL_HANDLER_ATTR_NAME = "_handler_data" _NORMAL_HANDLER_ATTR_NAME = "_handler_data"
_CONVERSATION_HANDLER_ATTR_NAME = "_conversation_data" _CONVERSATION_HANDLER_ATTR_NAME = "_conversation_data"
_JOB_ATTR_NAME = "_job_data" _JOB_ATTR_NAME = "_job_data"
_EXCLUDE_ATTRS = ['handlers', 'jobs', 'error_handlers'] _EXCLUDE_ATTRS = ["handlers", "jobs", "error_handlers"]
class _Plugin: class _Plugin:
def _make_handler(self, datas: Union[List[Dict], Dict]) -> List[HandlerType]: def _make_handler(self, datas: Union[List[Dict], Dict]) -> List[HandlerType]:
result = [] result = []
if isinstance(datas, list): if isinstance(datas, list):
for data in filter(lambda x: x, datas): for data in filter(lambda x: x, datas):
func = getattr(self, data.pop('func')) func = getattr(self, data.pop("func"))
result.append(data.pop('type')(callback=func, **data.pop('kwargs'))) result.append(data.pop("type")(callback=func, **data.pop("kwargs")))
else: else:
func = getattr(self, datas.pop('func')) func = getattr(self, datas.pop("func"))
result.append(datas.pop('type')(callback=func, **datas.pop('kwargs'))) result.append(datas.pop("type")(callback=func, **datas.pop("kwargs")))
return result return result
@property @property
@ -52,14 +51,12 @@ class _Plugin:
for attr in dir(self): for attr in dir(self):
# noinspection PyUnboundLocalVariable # noinspection PyUnboundLocalVariable
if ( if (
not (attr.startswith('_') or attr in _EXCLUDE_ATTRS) not (attr.startswith("_") or attr in _EXCLUDE_ATTRS)
and and isinstance(func := getattr(self, attr), MethodType)
isinstance(func := getattr(self, attr), MethodType) and (datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
and
(datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
): ):
for data in datas: for data in datas:
if data['type'] not in ['error', 'new_chat_member']: if data["type"] not in ["error", "new_chat_member"]:
result.extend(self._make_handler(data)) result.extend(self._make_handler(data))
return result return result
@ -69,15 +66,13 @@ class _Plugin:
for attr in dir(self): for attr in dir(self):
# noinspection PyUnboundLocalVariable # noinspection PyUnboundLocalVariable
if ( if (
not (attr.startswith('_') or attr in _EXCLUDE_ATTRS) not (attr.startswith("_") or attr in _EXCLUDE_ATTRS)
and and isinstance(func := getattr(self, attr), MethodType)
isinstance(func := getattr(self, attr), MethodType) and (datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
and
(datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
): ):
for data in datas: for data in datas:
if data and data['type'] == 'new_chat_member': if data and data["type"] == "new_chat_member":
result.append((data['priority'], func)) result.append((data["priority"], func))
return result return result
@ -87,34 +82,30 @@ class _Plugin:
for attr in dir(self): for attr in dir(self):
# noinspection PyUnboundLocalVariable # noinspection PyUnboundLocalVariable
if ( if (
not (attr.startswith('_') or attr in _EXCLUDE_ATTRS) not (attr.startswith("_") or attr in _EXCLUDE_ATTRS)
and and isinstance(func := getattr(self, attr), MethodType)
isinstance(func := getattr(self, attr), MethodType) and (datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
and
(datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
): ):
for data in datas: for data in datas:
if data and data['type'] == 'error': if data and data["type"] == "error":
result.update({func: data['block']}) result.update({func: data["block"]})
return result return result
@property @property
def jobs(self) -> List[Job]: def jobs(self) -> List[Job]:
from core.bot import bot from core.bot import bot
result = [] result = []
for attr in dir(self): for attr in dir(self):
# noinspection PyUnboundLocalVariable # noinspection PyUnboundLocalVariable
if ( if (
not (attr.startswith('_') or attr in _EXCLUDE_ATTRS) not (attr.startswith("_") or attr in _EXCLUDE_ATTRS)
and and isinstance(func := getattr(self, attr), MethodType)
isinstance(func := getattr(self, attr), MethodType) and (datas := getattr(func, _JOB_ATTR_NAME, None))
and
(datas := getattr(func, _JOB_ATTR_NAME, None))
): ):
for data in datas: for data in datas:
_job = getattr(bot.job_queue, data.pop('type'))( _job = getattr(bot.job_queue, data.pop("type"))(
callback=func, **data.pop('kwargs'), callback=func, **data.pop("kwargs"), **{key: data.pop(key) for key in list(data.keys())}
**{key: data.pop(key) for key in list(data.keys())}
) )
result.append(_job) result.append(_job)
return result return result
@ -138,30 +129,27 @@ class _Conversation(_Plugin):
for attr in dir(self): for attr in dir(self):
# noinspection PyUnboundLocalVariable # noinspection PyUnboundLocalVariable
if ( if (
not (attr.startswith('_') or attr == 'handlers') not (attr.startswith("_") or attr == "handlers")
and and isinstance(func := getattr(self, attr), Callable)
isinstance(func := getattr(self, attr), Callable) and (handler_datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
and
(handler_datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
): ):
_handlers = self._make_handler(handler_datas) _handlers = self._make_handler(handler_datas)
if conversation_data := getattr(func, _CONVERSATION_HANDLER_ATTR_NAME, None): if conversation_data := getattr(func, _CONVERSATION_HANDLER_ATTR_NAME, None):
if (_type := conversation_data.pop('type')) == 'entry': if (_type := conversation_data.pop("type")) == "entry":
entry_points.extend(_handlers) entry_points.extend(_handlers)
elif _type == 'state': elif _type == "state":
if (key := conversation_data.pop('state')) in states: if (key := conversation_data.pop("state")) in states:
states[key].extend(_handlers) states[key].extend(_handlers)
else: else:
states[key] = _handlers states[key] = _handlers
elif _type == 'fallback': elif _type == "fallback":
fallbacks.extend(_handlers) fallbacks.extend(_handlers)
else: else:
result.extend(_handlers) result.extend(_handlers)
if entry_points or states or fallbacks: if entry_points or states or fallbacks:
result.append( result.append(
ConversationHandler( ConversationHandler(
entry_points, states, fallbacks, entry_points, states, fallbacks, **self.__class__._conversation_kwargs # pylint: disable=W0212
**self.__class__._conversation_kwargs # pylint: disable=W0212
) )
) )
return result return result
@ -180,7 +168,7 @@ class _Handler:
return getattr(_Module, f"{self.__class__.__name__.strip('_')}Handler") return getattr(_Module, f"{self.__class__.__name__.strip('_')}Handler")
def __call__(self, func: Callable[P, T]) -> Callable[P, T]: def __call__(self, func: Callable[P, T]) -> Callable[P, T]:
data = {'type': self._type, 'func': func.__name__, 'kwargs': self.kwargs} data = {"type": self._type, "func": func.__name__, "kwargs": self.kwargs}
if hasattr(func, _NORMAL_HANDLER_ATTR_NAME): if hasattr(func, _NORMAL_HANDLER_ATTR_NAME):
handler_datas = getattr(func, _NORMAL_HANDLER_ATTR_NAME) handler_datas = getattr(func, _NORMAL_HANDLER_ATTR_NAME)
handler_datas.append(data) handler_datas.append(data)
@ -192,9 +180,9 @@ class _Handler:
class _CallbackQuery(_Handler): class _CallbackQuery(_Handler):
def __init__( def __init__(
self, self,
pattern: Union[str, Pattern, type, Callable[[object], Optional[bool]]] = None, pattern: Union[str, Pattern, type, Callable[[object], Optional[bool]]] = None,
block: DVInput[bool] = DEFAULT_TRUE, block: DVInput[bool] = DEFAULT_TRUE,
): ):
super(_CallbackQuery, self).__init__(pattern=pattern, block=block) super(_CallbackQuery, self).__init__(pattern=pattern, block=block)
@ -221,10 +209,7 @@ class _Command(_Handler):
class _InlineQuery(_Handler): class _InlineQuery(_Handler):
def __init__( def __init__(
self, self, pattern: Union[str, Pattern] = None, block: DVInput[bool] = DEFAULT_TRUE, chat_types: List[str] = None
pattern: Union[str, Pattern] = None,
block: DVInput[bool] = DEFAULT_TRUE,
chat_types: List[str] = None
): ):
super().__init__(pattern=pattern, block=block, chat_types=chat_types) super().__init__(pattern=pattern, block=block, chat_types=chat_types)
@ -237,7 +222,7 @@ class _MessageNewChatMembers(_Handler):
def __call__(self, func: Callable[P, T] = None) -> Callable[P, T]: def __call__(self, func: Callable[P, T] = None) -> Callable[P, T]:
self.func = self.func or func self.func = self.func or func
data = {'type': 'new_chat_member', 'priority': self.priority} data = {"type": "new_chat_member", "priority": self.priority}
if hasattr(func, _NORMAL_HANDLER_ATTR_NAME): if hasattr(func, _NORMAL_HANDLER_ATTR_NAME):
handler_datas = getattr(func, _NORMAL_HANDLER_ATTR_NAME) handler_datas = getattr(func, _NORMAL_HANDLER_ATTR_NAME)
handler_datas.append(data) handler_datas.append(data)
@ -248,7 +233,11 @@ class _MessageNewChatMembers(_Handler):
class _Message(_Handler): class _Message(_Handler):
def __init__(self, filters: "BaseFilter", block: DVInput[bool] = DEFAULT_TRUE, ): def __init__(
self,
filters: "BaseFilter",
block: DVInput[bool] = DEFAULT_TRUE,
):
super(_Message, self).__init__(filters=filters, block=block) super(_Message, self).__init__(filters=filters, block=block)
new_chat_members = _MessageNewChatMembers new_chat_members = _MessageNewChatMembers
@ -271,11 +260,11 @@ class _PreCheckoutQuery(_Handler):
class _Prefix(_Handler): class _Prefix(_Handler):
def __init__( def __init__(
self, self,
prefix: str, prefix: str,
command: str, command: str,
filters: BaseFilter = None, filters: BaseFilter = None,
block: DVInput[bool] = DEFAULT_TRUE, block: DVInput[bool] = DEFAULT_TRUE,
): ):
super(_Prefix, self).__init__(prefix=prefix, command=command, filters=filters, block=block) super(_Prefix, self).__init__(prefix=prefix, command=command, filters=filters, block=block)
@ -298,10 +287,7 @@ class _StringRegex(_Handler):
class _Type(_Handler): class _Type(_Handler):
# noinspection PyShadowingBuiltins # noinspection PyShadowingBuiltins
def __init__( def __init__(
self, self, type: Type, strict: bool = False, block: DVInput[bool] = DEFAULT_TRUE # pylint: disable=redefined-builtin
type: Type, # pylint: disable=redefined-builtin
strict: bool = False,
block: DVInput[bool] = DEFAULT_TRUE
): ):
super(_Type, self).__init__(type=type, strict=strict, block=block) super(_Type, self).__init__(type=type, strict=strict, block=block)
@ -342,7 +328,7 @@ class error_handler:
def __call__(self, func: Callable[P, T] = None) -> Callable[P, T]: def __call__(self, func: Callable[P, T] = None) -> Callable[P, T]:
self._func = func or self._func self._func = func or self._func
data = {'type': 'error', 'block': self._block} data = {"type": "error", "block": self._block}
if hasattr(func, _NORMAL_HANDLER_ATTR_NAME): if hasattr(func, _NORMAL_HANDLER_ATTR_NAME):
handler_datas = getattr(func, _NORMAL_HANDLER_ATTR_NAME) handler_datas = getattr(func, _NORMAL_HANDLER_ATTR_NAME)
handler_datas.append(data) handler_datas.append(data)
@ -353,7 +339,7 @@ class error_handler:
def _entry(func: Callable[P, T]) -> Callable[P, T]: def _entry(func: Callable[P, T]) -> Callable[P, T]:
setattr(func, _CONVERSATION_HANDLER_ATTR_NAME, {'type': 'entry'}) setattr(func, _CONVERSATION_HANDLER_ATTR_NAME, {"type": "entry"})
return func return func
@ -362,12 +348,12 @@ class _State:
self.state = state self.state = state
def __call__(self, func: Callable[P, T] = None) -> Callable[P, T]: def __call__(self, func: Callable[P, T] = None) -> Callable[P, T]:
setattr(func, _CONVERSATION_HANDLER_ATTR_NAME, {'type': 'state', 'state': self.state}) setattr(func, _CONVERSATION_HANDLER_ATTR_NAME, {"type": "state", "state": self.state})
return func return func
def _fallback(func: Callable[P, T]) -> Callable[P, T]: def _fallback(func: Callable[P, T]) -> Callable[P, T]:
setattr(func, _CONVERSATION_HANDLER_ATTR_NAME, {'type': 'fallback'}) setattr(func, _CONVERSATION_HANDLER_ATTR_NAME, {"type": "fallback"})
return func return func
@ -382,8 +368,13 @@ class _Job:
kwargs: Dict = {} kwargs: Dict = {}
def __init__( def __init__(
self, name: str = None, data: object = None, chat_id: int = None, self,
user_id: int = None, job_kwargs: JSONDict = None, **kwargs name: str = None,
data: object = None,
chat_id: int = None,
user_id: int = None,
job_kwargs: JSONDict = None,
**kwargs,
): ):
self.name = name self.name = name
self.data = data self.data = data
@ -394,9 +385,13 @@ class _Job:
def __call__(self, func: JobCallback) -> JobCallback: def __call__(self, func: JobCallback) -> JobCallback:
data = { data = {
'name': self.name, 'data': self.data, 'chat_id': self.chat_id, 'user_id': self.user_id, "name": self.name,
'job_kwargs': self.job_kwargs, 'kwargs': self.kwargs, "data": self.data,
'type': re.sub(r'([A-Z])', lambda x: '_' + x.group().lower(), self.__class__.__name__).lstrip('_') "chat_id": self.chat_id,
"user_id": self.user_id,
"job_kwargs": self.job_kwargs,
"kwargs": self.kwargs,
"type": re.sub(r"([A-Z])", lambda x: "_" + x.group().lower(), self.__class__.__name__).lstrip("_"),
} }
if hasattr(func, _JOB_ATTR_NAME): if hasattr(func, _JOB_ATTR_NAME):
job_datas = getattr(func, _JOB_ATTR_NAME) job_datas = getattr(func, _JOB_ATTR_NAME)
@ -409,39 +404,69 @@ class _Job:
class _RunOnce(_Job): class _RunOnce(_Job):
def __init__( def __init__(
self, when: TimeType, self,
data: object = None, name: str = None, chat_id: int = None, user_id: int = None, job_kwargs: JSONDict = None when: TimeType,
data: object = None,
name: str = None,
chat_id: int = None,
user_id: int = None,
job_kwargs: JSONDict = None,
): ):
super().__init__(name, data, chat_id, user_id, job_kwargs, when=when) super().__init__(name, data, chat_id, user_id, job_kwargs, when=when)
class _RunRepeating(_Job): class _RunRepeating(_Job):
def __init__( def __init__(
self, interval: Union[float, datetime.timedelta], first: TimeType = None, last: TimeType = None, self,
data: object = None, name: str = None, chat_id: int = None, user_id: int = None, job_kwargs: JSONDict = None interval: Union[float, datetime.timedelta],
first: TimeType = None,
last: TimeType = None,
data: object = None,
name: str = None,
chat_id: int = None,
user_id: int = None,
job_kwargs: JSONDict = None,
): ):
super().__init__(name, data, chat_id, user_id, job_kwargs, interval=interval, first=first, last=last) super().__init__(name, data, chat_id, user_id, job_kwargs, interval=interval, first=first, last=last)
class _RunMonthly(_Job): class _RunMonthly(_Job):
def __init__( def __init__(
self, when: datetime.time, day: int, self,
data: object = None, name: str = None, chat_id: int = None, user_id: int = None, job_kwargs: JSONDict = None when: datetime.time,
day: int,
data: object = None,
name: str = None,
chat_id: int = None,
user_id: int = None,
job_kwargs: JSONDict = None,
): ):
super().__init__(name, data, chat_id, user_id, job_kwargs, when=when, day=day) super().__init__(name, data, chat_id, user_id, job_kwargs, when=when, day=day)
class _RunDaily(_Job): class _RunDaily(_Job):
def __init__( def __init__(
self, time: datetime.time, days: Tuple[int, ...] = tuple(range(7)), self,
data: object = None, name: str = None, chat_id: int = None, user_id: int = None, job_kwargs: JSONDict = None time: datetime.time,
days: Tuple[int, ...] = tuple(range(7)),
data: object = None,
name: str = None,
chat_id: int = None,
user_id: int = None,
job_kwargs: JSONDict = None,
): ):
super().__init__(name, data, chat_id, user_id, job_kwargs, time=time, days=days) super().__init__(name, data, chat_id, user_id, job_kwargs, time=time, days=days)
class _RunCustom(_Job): class _RunCustom(_Job):
def __init__(self, data: object = None, name: str = None, chat_id: int = None, user_id: int = None, def __init__(
job_kwargs: JSONDict = None): self,
data: object = None,
name: str = None,
chat_id: int = None,
user_id: int = None,
job_kwargs: JSONDict = None,
):
super().__init__(name, data, chat_id, user_id, job_kwargs) super().__init__(name, data, chat_id, user_id, job_kwargs)

View File

@ -7,7 +7,6 @@ from .models import Answer, Question
class QuizCache: class QuizCache:
def __init__(self, redis: RedisDB): def __init__(self, redis: RedisDB):
self.client = redis.client self.client = redis.client
self.question_qname = "quiz:question" self.question_qname = "quiz:question"
@ -16,8 +15,7 @@ class QuizCache:
async def get_all_question(self) -> List[Question]: async def get_all_question(self) -> List[Question]:
temp_list = [] temp_list = []
qname = self.question_qname + "id_list" qname = self.question_qname + "id_list"
data_list = [self.question_qname + f":{question_id}" for question_id in data_list = [self.question_qname + f":{question_id}" for question_id in await self.client.lrange(qname, 0, -1)]
await self.client.lrange(qname, 0, -1)]
data = await self.client.mget(data_list) data = await self.client.mget(data_list)
for i in data: for i in data:
temp_list.append(Question.de_json(ujson.loads(i))) temp_list.append(Question.de_json(ujson.loads(i)))

View File

@ -7,23 +7,20 @@ from utils.typedefs import JSONDict
class AnswerDB(SQLModel, table=True): class AnswerDB(SQLModel, table=True):
__tablename__ = 'answer' __tablename__ = "answer"
__table_args__ = dict(mysql_charset='utf8mb4', mysql_collate="utf8mb4_general_ci") __table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci")
id: int = Field(primary_key=True) id: int = Field(primary_key=True)
question_id: Optional[int] = Field( question_id: Optional[int] = Field(
sa_column=Column( sa_column=Column(Integer, ForeignKey("question.id", ondelete="RESTRICT", onupdate="RESTRICT"))
Integer,
ForeignKey("question.id", ondelete="RESTRICT", onupdate="RESTRICT")
)
) )
is_correct: Optional[bool] = Field() is_correct: Optional[bool] = Field()
text: Optional[str] = Field() text: Optional[str] = Field()
class QuestionDB(SQLModel, table=True): class QuestionDB(SQLModel, table=True):
__tablename__ = 'question' __tablename__ = "question"
__table_args__ = dict(mysql_charset='utf8mb4', mysql_collate="utf8mb4_general_ci") __table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci")
id: int = Field(primary_key=True) id: int = Field(primary_key=True)
text: Optional[str] = Field() text: Optional[str] = Field()

View File

@ -3,7 +3,7 @@ from types import FunctionType
from utils.log import logger from utils.log import logger
__all__ = ['Service', 'init_service'] __all__ = ["Service", "init_service"]
class Service(ABC): class Service(ABC):
@ -20,11 +20,12 @@ class Service(ABC):
def init_service(func: FunctionType): def init_service(func: FunctionType):
from core.bot import bot from core.bot import bot
if bot.is_running: if bot.is_running:
try: try:
service = bot.init_inject(func) service = bot.init_inject(func)
logger.success(f'服务 "{service.__class__.__name__}" 初始化成功') logger.success(f'服务 "{service.__class__.__name__}" 初始化成功')
bot.add_service(service) bot.add_service(service)
except Exception as e: # pylint: disable=W0703 except Exception as e: # pylint: disable=W0703
logger.exception(f'来自{func.__module__}的服务初始化失败:{e}') logger.exception(f"来自{func.__module__}的服务初始化失败:{e}")
return func return func

View File

@ -18,7 +18,7 @@ class SignStatusEnum(int, enum.Enum):
class Sign(SQLModel, table=True): class Sign(SQLModel, table=True):
__table_args__ = dict(mysql_charset='utf8mb4', mysql_collate="utf8mb4_general_ci") __table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci")
id: int = Field(primary_key=True) id: int = Field(primary_key=True)
user_id: int = Field(foreign_key="user.user_id") user_id: int = Field(foreign_key="user.user_id")

View File

@ -53,9 +53,16 @@ class TemplateService:
logger.debug(f"{template_name} 模板渲染使用了 {str(time.time() - start_time)}") logger.debug(f"{template_name} 模板渲染使用了 {str(time.time() - start_time)}")
return html return html
async def render(self, template_path: str, template_name: str, template_data: dict, async def render(
viewport: ViewportSize = None, full_page: bool = True, evaluate: Optional[str] = None, self,
query_selector: str = None) -> bytes: template_path: str,
template_name: str,
template_data: dict,
viewport: ViewportSize = None,
full_page: bool = True,
evaluate: Optional[str] = None,
query_selector: str = None,
) -> bytes:
"""模板渲染成图片 """模板渲染成图片
:param template_path: 模板目录 :param template_path: 模板目录
:param template_name: 模板文件名 :param template_name: 模板文件名

View File

@ -6,7 +6,7 @@ from utils.models.base import RegionEnum
class User(SQLModel, table=True): class User(SQLModel, table=True):
__table_args__ = dict(mysql_charset='utf8mb4', mysql_collate="utf8mb4_general_ci") __table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci")
id: int = Field(primary_key=True) id: int = Field(primary_key=True)
user_id: int = Field(unique=True) user_id: int = Field(unique=True)

View File

@ -33,4 +33,4 @@ class UserRepository:
async with self.mysql.Session() as session: async with self.mysql.Session() as session:
session = cast(AsyncSession, session) session = cast(AsyncSession, session)
session.add(user) session.add(user)
await session.commit() await session.commit()

View File

@ -3,7 +3,6 @@ from .repositories import UserRepository
class UserService: class UserService:
def __init__(self, user_repository: UserRepository) -> None: def __init__(self, user_repository: UserRepository) -> None:
self._repository: UserRepository = user_repository self._repository: UserRepository = user_repository

View File

@ -7,7 +7,6 @@ from utils.log import logger
class WikiService: class WikiService:
def __init__(self, cache: WikiCache): def __init__(self, cache: WikiCache):
self._cache = cache self._cache = cache
"""Redis 在这里的作用是作为持久化""" """Redis 在这里的作用是作为持久化"""

View File

@ -1,11 +1 @@
POOL_200 = [ POOL_200 = [{"five": ["常驻池"], "four": [], "from": "2020-09-15 06:00:00", "name": "常驻池", "to": "2050-09-15 17:59:59"}]
{
"five": [
"常驻池"
],
"four": [],
"from": "2020-09-15 06:00:00",
"name": "常驻池",
"to": "2050-09-15 17:59:59"
}
]

View File

@ -1,480 +1,254 @@
POOL_301 = [ POOL_301 = [
{ {
"five": [ "five": ["赛诺", "温迪"],
"赛诺", "four": ["久岐忍", "早柚", "坎蒂丝"],
"温迪"
],
"four": [
"久岐忍",
"早柚",
"坎蒂丝"
],
"from": "2022-09-28 06:00:00", "from": "2022-09-28 06:00:00",
"name": "劈裁冥昭|杯装之诗", "name": "劈裁冥昭|杯装之诗",
"to": "2022-10-14 17:59:59" "to": "2022-10-14 17:59:59",
}, },
{ {
"five": [ "five": ["甘雨", "心海"],
"甘雨", "four": ["行秋", "砂糖", "多莉"],
"心海"
],
"four": [
"行秋",
"砂糖",
"多莉"
],
"from": "2022-09-09 18:00:00", "from": "2022-09-09 18:00:00",
"name": "浮生孰来|浮岳虹珠", "name": "浮生孰来|浮岳虹珠",
"to": "2022-09-27 14:59:59" "to": "2022-09-27 14:59:59",
}, },
{ {
"five": [ "five": ["提纳里", "钟离"],
"提纳里", "four": ["云堇", "辛焱", "班尼特"],
"钟离"
],
"four": [
"云堇",
"辛焱",
"班尼特"
],
"from": "2022-08-24 06:00:00", "from": "2022-08-24 06:00:00",
"name": "巡御蘙荟|陵薮市朝", "name": "巡御蘙荟|陵薮市朝",
"to": "2022-09-09 17:59:59" "to": "2022-09-09 17:59:59",
}, },
{ {
"five": [ "five": ["宵宫"],
"宵宫" "four": ["云堇", "辛焱", "班尼特"],
],
"four": [
"云堇",
"辛焱",
"班尼特"
],
"from": "2022-08-02 18:00:00", "from": "2022-08-02 18:00:00",
"name": "焰色天河", "name": "焰色天河",
"to": "2022-08-23 14:59:59" "to": "2022-08-23 14:59:59",
}, },
{ {
"five": [ "five": ["枫原万叶", "可莉"],
"枫原万叶", "four": ["凝光", "鹿野院平藏", "托马"],
"可莉"
],
"four": [
"凝光",
"鹿野院平藏",
"托马"
],
"from": "2022-07-13 06:00:00", "from": "2022-07-13 06:00:00",
"name": "红叶逐荒波", "name": "红叶逐荒波",
"to": "2022-08-02 17:59:59" "to": "2022-08-02 17:59:59",
}, },
{ {
"five": [ "five": ["荒泷一斗"],
"荒泷一斗" "four": ["烟绯", "芭芭拉", "诺艾尔"],
],
"four": [
"烟绯",
"芭芭拉",
"诺艾尔"
],
"from": "2022-06-21 18:00:00", "from": "2022-06-21 18:00:00",
"name": "鬼门斗宴", "name": "鬼门斗宴",
"to": "2022-07-12 14:59:59" "to": "2022-07-12 14:59:59",
}, },
{ {
"five": [ "five": ["夜兰", ""],
"夜兰", "four": ["烟绯", "芭芭拉", "诺艾尔"],
""
],
"four": [
"烟绯",
"芭芭拉",
"诺艾尔"
],
"from": "2022-05-31 06:00:00", "from": "2022-05-31 06:00:00",
"name": "素霓伣天|烟火之邀", "name": "素霓伣天|烟火之邀",
"to": "2022-06-21 17:59:59" "to": "2022-06-21 17:59:59",
}, },
{ {
"five": [ "five": ["神里绫华"],
"神里绫华" "four": ["罗莎莉亚", "早柚", "雷泽"],
],
"four": [
"罗莎莉亚",
"早柚",
"雷泽"
],
"from": "2022-04-19 17:59:59", "from": "2022-04-19 17:59:59",
"name": "白鹭之庭", "name": "白鹭之庭",
"to": "2022-05-31 05:59:59" "to": "2022-05-31 05:59:59",
}, },
{ {
"five": [ "five": ["神里绫人", "温迪"],
"神里绫人", "four": ["香菱", "砂糖", "云堇"],
"温迪"
],
"four": [
"香菱",
"砂糖",
"云堇"
],
"from": "2022-03-30 06:00:00", "from": "2022-03-30 06:00:00",
"name": "苍流踏花|杯装之诗", "name": "苍流踏花|杯装之诗",
"to": "2022-04-19 17:59:59" "to": "2022-04-19 17:59:59",
}, },
{ {
"five": [ "five": ["雷电将军", "珊瑚宫心海"],
"雷电将军", "four": ["辛焱", "九条裟罗", "班尼特"],
"珊瑚宫心海"
],
"four": [
"辛焱",
"九条裟罗",
"班尼特"
],
"from": "2022-03-08 18:00:00", "from": "2022-03-08 18:00:00",
"name": "影寂天下人|浮岳虹珠", "name": "影寂天下人|浮岳虹珠",
"to": "2022-03-29 14:59:59" "to": "2022-03-29 14:59:59",
}, },
{ {
"five": [ "five": ["八重神子"],
"八重神子" "four": ["菲谢尔", "迪奥娜", "托马"],
],
"four": [
"菲谢尔",
"迪奥娜",
"托马"
],
"from": "2022-02-16 06:00:00", "from": "2022-02-16 06:00:00",
"name": "华紫樱绯", "name": "华紫樱绯",
"to": "2022-03-08 17:59:59" "to": "2022-03-08 17:59:59",
}, },
{ {
"five": [ "five": ["甘雨", "钟离"],
"甘雨", "four": ["行秋", "北斗", "烟绯"],
"钟离"
],
"four": [
"行秋",
"北斗",
"烟绯"
],
"from": "2022-01-25 18:00:00", "from": "2022-01-25 18:00:00",
"name": "浮生孰来|陵薮市朝", "name": "浮生孰来|陵薮市朝",
"to": "2022-02-15 14:59:59" "to": "2022-02-15 14:59:59",
}, },
{ {
"five": [ "five": ["申鹤", ""],
"申鹤", "four": ["云堇", "凝光", "重云"],
""
],
"four": [
"云堇",
"凝光",
"重云"
],
"from": "2022-01-05 06:00:00", "from": "2022-01-05 06:00:00",
"name": "出尘入世|烟火之邀", "name": "出尘入世|烟火之邀",
"to": "2022-01-25 17:59:59" "to": "2022-01-25 17:59:59",
}, },
{ {
"five": [ "five": ["荒泷一斗"],
"荒泷一斗" "four": ["五郎", "芭芭拉", "香菱"],
],
"four": [
"五郎",
"芭芭拉",
"香菱"
],
"from": "2021-12-14 18:00:00", "from": "2021-12-14 18:00:00",
"name": "鬼门斗宴", "name": "鬼门斗宴",
"to": "2022-01-04 14:59:59" "to": "2022-01-04 14:59:59",
}, },
{ {
"five": [ "five": ["阿贝多", "优菈"],
"阿贝多", "four": ["班尼特", "诺艾尔", "罗莎莉亚"],
"优菈"
],
"four": [
"班尼特",
"诺艾尔",
"罗莎莉亚"
],
"from": "2021-11-24 06:00:00", "from": "2021-11-24 06:00:00",
"name": "深秘之息|浪涌之瞬", "name": "深秘之息|浪涌之瞬",
"to": "2021-12-14 17:59:59" "to": "2021-12-14 17:59:59",
}, },
{ {
"five": [ "five": ["胡桃"],
"胡桃" "four": ["托马", "迪奥娜", "早柚"],
],
"four": [
"托马",
"迪奥娜",
"早柚"
],
"from": "2021-11-02 18:00:00", "from": "2021-11-02 18:00:00",
"name": "赤团开时", "name": "赤团开时",
"to": "2021-11-23 14:59:59" "to": "2021-11-23 14:59:59",
}, },
{ {
"five": [ "five": ["达达利亚"],
"达达利亚" "four": ["凝光", "重云", "烟绯"],
],
"four": [
"凝光",
"重云",
"烟绯"
],
"from": "2021-10-13 06:00:00", "from": "2021-10-13 06:00:00",
"name": "暂别冬都", "name": "暂别冬都",
"to": "2021-11-02 17:59:59" "to": "2021-11-02 17:59:59",
}, },
{ {
"five": [ "five": ["珊瑚宫心海"],
"珊瑚宫心海" "four": ["罗莎莉亚", "北斗", "行秋"],
],
"four": [
"罗莎莉亚",
"北斗",
"行秋"
],
"from": "2021-09-21 18:00:00", "from": "2021-09-21 18:00:00",
"name": "浮岳虹珠", "name": "浮岳虹珠",
"to": "2021-10-12 14:59:59" "to": "2021-10-12 14:59:59",
}, },
{ {
"five": [ "five": ["雷电将军"],
"雷电将军" "four": ["九条裟罗", "香菱", "砂糖"],
],
"four": [
"九条裟罗",
"香菱",
"砂糖"
],
"from": "2021-09-01 06:00:00", "from": "2021-09-01 06:00:00",
"name": "影寂天下人", "name": "影寂天下人",
"to": "2021-09-21 17:59:59" "to": "2021-09-21 17:59:59",
}, },
{ {
"five": [ "five": ["宵宫"],
"宵宫" "four": ["早柚", "迪奥娜", "辛焱"],
],
"four": [
"早柚",
"迪奥娜",
"辛焱"
],
"from": "2021-08-10 18:00:00", "from": "2021-08-10 18:00:00",
"name": "焰色天河", "name": "焰色天河",
"to": "2021-08-31 14:59:59" "to": "2021-08-31 14:59:59",
}, },
{ {
"five": [ "five": ["神里绫华"],
"神里绫华" "four": ["凝光", "重云", "烟绯"],
],
"four": [
"凝光",
"重云",
"烟绯"
],
"from": "2021-07-21 06:00:00", "from": "2021-07-21 06:00:00",
"name": "白鹭之庭", "name": "白鹭之庭",
"to": "2021-08-10 17:59:59" "to": "2021-08-10 17:59:59",
}, },
{ {
"five": [ "five": ["枫原万叶"],
"枫原万叶" "four": ["罗莎莉亚", "班尼特", "雷泽"],
],
"four": [
"罗莎莉亚",
"班尼特",
"雷泽"
],
"from": "2021-06-29 18:00:00", "from": "2021-06-29 18:00:00",
"name": "红叶逐荒波", "name": "红叶逐荒波",
"to": "2021-07-20 14:59:59" "to": "2021-07-20 14:59:59",
}, },
{ {
"five": [ "five": ["可莉"],
"可莉" "four": ["芭芭拉", "砂糖", "菲谢尔"],
],
"four": [
"芭芭拉",
"砂糖",
"菲谢尔"
],
"from": "2021-06-09 06:00:00", "from": "2021-06-09 06:00:00",
"name": "逃跑的太阳", "name": "逃跑的太阳",
"to": "2021-06-29 17:59:59" "to": "2021-06-29 17:59:59",
}, },
{ {
"five": [ "five": ["优菈"],
"优菈" "four": ["辛焱", "行秋", "北斗"],
],
"four": [
"辛焱",
"行秋",
"北斗"
],
"from": "2021-05-18 18:00:00", "from": "2021-05-18 18:00:00",
"name": "浪沫的旋舞", "name": "浪沫的旋舞",
"to": "2021-06-08 14:59:59" "to": "2021-06-08 14:59:59",
}, },
{ {
"five": [ "five": ["钟离"],
"钟离" "four": ["烟绯", "诺艾尔", "迪奥娜"],
],
"four": [
"烟绯",
"诺艾尔",
"迪奥娜"
],
"from": "2021-04-28 06:00:00", "from": "2021-04-28 06:00:00",
"name": "陵薮市朝", "name": "陵薮市朝",
"to": "2021-05-18 17:59:59" "to": "2021-05-18 17:59:59",
}, },
{ {
"five": [ "five": ["达达利亚"],
"达达利亚" "four": ["罗莎莉亚", "芭芭拉", "菲谢尔"],
],
"four": [
"罗莎莉亚",
"芭芭拉",
"菲谢尔"
],
"from": "2021-04-06 18:00:00", "from": "2021-04-06 18:00:00",
"name": "暂别冬都", "name": "暂别冬都",
"to": "2021-04-27 14:59:59" "to": "2021-04-27 14:59:59",
}, },
{ {
"five": [ "five": ["温迪"],
"温迪" "four": ["砂糖", "雷泽", "诺艾尔"],
],
"four": [
"砂糖",
"雷泽",
"诺艾尔"
],
"from": "2021-03-17 06:00:00", "from": "2021-03-17 06:00:00",
"name": "杯装之诗", "name": "杯装之诗",
"to": "2021-04-06 15:59:59" "to": "2021-04-06 15:59:59",
}, },
{ {
"five": [ "five": ["胡桃"],
"胡桃" "four": ["行秋", "香菱", "重云"],
],
"four": [
"行秋",
"香菱",
"重云"
],
"from": "2021-03-02 18:00:00", "from": "2021-03-02 18:00:00",
"name": "赤团开时", "name": "赤团开时",
"to": "2021-03-16 14:59:59" "to": "2021-03-16 14:59:59",
}, },
{ {
"five": [ "five": ["刻晴"],
"刻晴" "four": ["凝光", "班尼特", "芭芭拉"],
],
"four": [
"凝光",
"班尼特",
"芭芭拉"
],
"from": "2021-02-17 18:00:00", "from": "2021-02-17 18:00:00",
"name": "鱼龙灯昼", "name": "鱼龙灯昼",
"to": "2021-03-02 15:59:59" "to": "2021-03-02 15:59:59",
}, },
{ {
"five": [ "five": [""],
"" "four": ["迪奥娜", "北斗", "辛焱"],
],
"four": [
"迪奥娜",
"北斗",
"辛焱"
],
"from": "2021-02-03 06:00:00", "from": "2021-02-03 06:00:00",
"name": "烟火之邀", "name": "烟火之邀",
"to": "2021-02-17 15:59:59" "to": "2021-02-17 15:59:59",
}, },
{ {
"five": [ "five": ["甘雨"],
"甘雨" "four": ["香菱", "行秋", "诺艾尔"],
],
"four": [
"香菱",
"行秋",
"诺艾尔"
],
"from": "2021-01-12 18:00:00", "from": "2021-01-12 18:00:00",
"name": "浮生孰来", "name": "浮生孰来",
"to": "2021-02-02 14:59:59" "to": "2021-02-02 14:59:59",
}, },
{ {
"five": [ "five": ["阿贝多"],
"阿贝多" "four": ["菲谢尔", "砂糖", "班尼特"],
],
"four": [
"菲谢尔",
"砂糖",
"班尼特"
],
"from": "2020-12-23 06:00:00", "from": "2020-12-23 06:00:00",
"name": "深秘之息", "name": "深秘之息",
"to": "2021-01-12 15:59:59" "to": "2021-01-12 15:59:59",
}, },
{ {
"five": [ "five": ["钟离"],
"钟离" "four": ["辛焱", "雷泽", "重云"],
],
"four": [
"辛焱",
"雷泽",
"重云"
],
"from": "2020-12-01 18:00:00", "from": "2020-12-01 18:00:00",
"name": "陵薮市朝", "name": "陵薮市朝",
"to": "2020-12-22 14:59:59" "to": "2020-12-22 14:59:59",
}, },
{ {
"five": [ "five": ["达达利亚"],
"达达利亚" "four": ["迪奥娜", "北斗", "凝光"],
],
"four": [
"迪奥娜",
"北斗",
"凝光"
],
"from": "2020-11-11 06:00:00", "from": "2020-11-11 06:00:00",
"name": "暂别冬都", "name": "暂别冬都",
"to": "2020-12-01 15:59:59" "to": "2020-12-01 15:59:59",
}, },
{ {
"five": [ "five": ["可莉"],
"可莉" "four": ["行秋", "诺艾尔", "砂糖"],
],
"four": [
"行秋",
"诺艾尔",
"砂糖"
],
"from": "2020-10-20 18:00:00", "from": "2020-10-20 18:00:00",
"name": "闪焰的驻足", "name": "闪焰的驻足",
"to": "2020-11-10 14:59:59" "to": "2020-11-10 14:59:59",
}, },
{ {
"five": [ "five": ["温迪"],
"温迪" "four": ["芭芭拉", "菲谢尔", "香菱"],
],
"four": [
"芭芭拉",
"菲谢尔",
"香菱"
],
"from": "2020-9-28 06:00:00", "from": "2020-9-28 06:00:00",
"name": "杯装之诗", "name": "杯装之诗",
"to": "2020-10-18 17:59:59" "to": "2020-10-18 17:59:59",
} },
] ]

View File

@ -1,562 +1,247 @@
POOL_302 = [ POOL_302 = [
{ {
"five": [ "five": ["赤沙之杖", "终末嗟叹之诗"],
"赤沙之杖", "four": ["匣里龙吟", "玛海菈的水色", "西风长枪", "祭礼残章", "西风猎弓"],
"终末嗟叹之诗"
],
"four": [
"匣里龙吟",
"玛海菈的水色",
"西风长枪",
"祭礼残章",
"西风猎弓"
],
"from": "2022-09-28 06:00:00", "from": "2022-09-28 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2022-10-14 17:59:59" "to": "2022-10-14 17:59:59",
}, },
{ {
"five": [ "five": ["阿莫斯之弓", "不灭月华"],
"阿莫斯之弓", "four": ["祭礼剑", "西风大剑", "匣里灭辰", "昭心", "弓藏"],
"不灭月华"
],
"four": [
"祭礼剑",
"西风大剑",
"匣里灭辰",
"昭心",
"弓藏"
],
"from": "2022-09-09 18:00:00", "from": "2022-09-09 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2022-09-27 14:59:59" "to": "2022-09-27 14:59:59",
}, },
{ {
"five": [ "five": ["猎人之径", "贯虹之槊"],
"猎人之径", "four": ["西风剑", "钟剑", "西风长枪", "西风秘典", "绝弦"],
"贯虹之槊"
],
"four": [
"西风剑",
"钟剑",
"西风长枪",
"西风秘典",
"绝弦"
],
"from": "2022-08-24 06:00:00", "from": "2022-08-24 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2022-09-09 17:59:59" "to": "2022-09-09 17:59:59",
}, },
{ {
"five": [ "five": ["飞雷之弦振", "斫峰之刃"],
"飞雷之弦振", "four": ["暗巷的酒与诗", "暗巷猎手", "笛剑", "祭礼大剑", "匣里灭辰"],
"斫峰之刃"
],
"four": [
"暗巷的酒与诗",
"暗巷猎手",
"笛剑",
"祭礼大剑",
"匣里灭辰"
],
"from": "2022-08-02 18:00:00", "from": "2022-08-02 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2022-08-23 14:59:59" "to": "2022-08-23 14:59:59",
}, },
{ {
"five": [ "five": ["苍古自由之誓", "四风原典"],
"苍古自由之誓", "four": ["千岩古剑", "匣里龙吟", "匣里灭辰", "祭礼残章", "绝弦"],
"四风原典"
],
"four": [
"千岩古剑",
"匣里龙吟",
"匣里灭辰",
"祭礼残章",
"绝弦"
],
"from": "2022-07-13 06:00:00", "from": "2022-07-13 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2022-08-02 17:59:59" "to": "2022-08-02 17:59:59",
}, },
{ {
"five": [ "five": ["赤角石溃杵", "尘世之锁"],
"赤角石溃杵", "four": ["千岩古剑", "匣里龙吟", "匣里灭辰", "祭礼残章", "绝弦"],
"尘世之锁"
],
"four": [
"千岩古剑",
"匣里龙吟",
"匣里灭辰",
"祭礼残章",
"绝弦"
],
"from": "2022-06-21 18:00:00", "from": "2022-06-21 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2022-07-12 14:59:59" "to": "2022-07-12 14:59:59",
}, },
{ {
"five": [ "five": ["若水", "和璞鸢"],
"若水", "four": ["千岩长枪", "祭礼剑", "西风大剑", "昭心", "祭礼弓"],
"和璞鸢"
],
"four": [
"千岩长枪",
"祭礼剑",
"西风大剑",
"昭心",
"祭礼弓"
],
"from": "2022-05-31 06:00:00", "from": "2022-05-31 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2022-06-21 17:59:59" "to": "2022-06-21 17:59:59",
}, },
{ {
"five": [ "five": ["雾切之回光", "无工之剑"],
"雾切之回光", "four": ["西风剑", "钟剑", "西风长枪", "西风秘典", "西风猎弓"],
"无工之剑"
],
"four": [
"西风剑",
"钟剑",
"西风长枪",
"西风秘典",
"西风猎弓"
],
"from": "2022-04-19 17:59:59", "from": "2022-04-19 17:59:59",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2022-05-31 05:59:59" "to": "2022-05-31 05:59:59",
}, },
{ {
"five": [ "five": ["波乱月白经津", "终末嗟叹之诗"],
"波乱月白经津", "four": ["弓藏", "笛剑", "流浪乐章", "匣里灭辰", "祭礼大剑"],
"终末嗟叹之诗"
],
"four": [
"弓藏",
"笛剑",
"流浪乐章",
"匣里灭辰",
"祭礼大剑"
],
"from": "2022-03-30 06:00:00", "from": "2022-03-30 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2022-04-19 17:59:59" "to": "2022-04-19 17:59:59",
}, },
{ {
"five": [ "five": ["薙草之稻光", "不灭月华"],
"薙草之稻光", "four": ["恶王丸", "曚云之月", "匣里龙吟", "西风长枪", "祭礼残章"],
"不灭月华"
],
"four": [
"恶王丸",
"曚云之月",
"匣里龙吟",
"西风长枪",
"祭礼残章"
],
"from": "2022-03-08 18:00:00", "from": "2022-03-08 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2022-03-29 14:59:59" "to": "2022-03-29 14:59:59",
}, },
{ {
"five": [ "five": ["神乐之真意", "磐岩结绿"],
"神乐之真意", "four": ["祭礼剑", "雨裁", "断浪长鳍", "昭心", "绝弦"],
"磐岩结绿"
],
"four": [
"祭礼剑",
"雨裁",
"断浪长鳍",
"昭心",
"绝弦"
],
"from": "2022-02-16 06:00:00", "from": "2022-02-16 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2022-03-08 17:59:59" "to": "2022-03-08 17:59:59",
}, },
{ {
"five": [ "five": ["贯虹之槊", "阿莫斯之弓"],
"贯虹之槊", "four": ["西风剑", "千岩古剑", "匣里灭辰", "西风秘典", "祭礼弓"],
"阿莫斯之弓"
],
"four": [
"西风剑",
"千岩古剑",
"匣里灭辰",
"西风秘典",
"祭礼弓"
],
"from": "2022-01-25 18:00:00", "from": "2022-01-25 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2022-02-15 14:59:59" "to": "2022-02-15 14:59:59",
}, },
{ {
"five": [ "five": ["息灾", "和璞鸢"],
"息灾", "four": ["笛剑", "西风大剑", "千岩长枪", "流浪乐章", "西风猎弓"],
"和璞鸢"
],
"four": [
"笛剑",
"西风大剑",
"千岩长枪",
"流浪乐章",
"西风猎弓"
],
"from": "2022-01-05 06:00:00", "from": "2022-01-05 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2022-01-25 17:59:59" "to": "2022-01-25 17:59:59",
}, },
{ {
"five": [ "five": ["赤角石溃杵", "天空之翼"],
"赤角石溃杵", "four": ["暗巷闪光", "钟剑", "西风长枪", "祭礼残章", "幽夜华尔兹"],
"天空之翼"
],
"four": [
"暗巷闪光",
"钟剑",
"西风长枪",
"祭礼残章",
"幽夜华尔兹"
],
"from": "2021-12-14 18:00:00", "from": "2021-12-14 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2022-01-04 14:59:59" "to": "2022-01-04 14:59:59",
}, },
{ {
"five": [ "five": ["苍古自由之誓", "松籁响起之时"],
"苍古自由之誓", "four": ["匣里龙吟", "祭礼大剑", "匣里灭辰", "暗巷的酒与诗", "暗巷猎手"],
"松籁响起之时"
],
"four": [
"匣里龙吟",
"祭礼大剑",
"匣里灭辰",
"暗巷的酒与诗",
"暗巷猎手"
],
"from": "2021-11-24 06:00:00", "from": "2021-11-24 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-12-14 17:59:59" "to": "2021-12-14 17:59:59",
}, },
{ {
"five": [ "five": ["护摩之杖", "终末嗟叹之诗"],
"护摩之杖", "four": ["祭礼剑", "雨裁", "断浪长鳍", "流浪乐章", "曚云之月"],
"终末嗟叹之诗"
],
"four": [
"祭礼剑",
"雨裁",
"断浪长鳍",
"流浪乐章",
"曚云之月"
],
"from": "2021-11-02 18:00:00", "from": "2021-11-02 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-11-23 14:59:59" "to": "2021-11-23 14:59:59",
}, },
{ {
"five": [ "five": ["冬极白星", "尘世之锁"],
"冬极白星", "four": ["西风剑", "恶王丸", "西风长枪", "昭心", "弓藏"],
"尘世之锁"
],
"four": [
"西风剑",
"恶王丸",
"西风长枪",
"昭心",
"弓藏"
],
"from": "2021-10-13 06:00:00", "from": "2021-10-13 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-11-02 17:59:59" "to": "2021-11-02 17:59:59",
}, },
{ {
"five": [ "five": ["不灭月华", "磐岩结绿"],
"不灭月华", "four": ["笛剑", "西风大剑", "匣里灭辰", "西风秘典", "绝弦"],
"磐岩结绿"
],
"four": [
"笛剑",
"西风大剑",
"匣里灭辰",
"西风秘典",
"绝弦"
],
"from": "2021-09-21 18:00:00", "from": "2021-09-21 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-10-12 14:59:59" "to": "2021-10-12 14:59:59",
}, },
{ {
"five": [ "five": ["薙草之稻光", "无工之剑"],
"薙草之稻光", "four": ["匣里龙吟", "钟剑", "西风长枪", "流浪乐章", "祭礼弓"],
"无工之剑"
],
"four": [
"匣里龙吟",
"钟剑",
"西风长枪",
"流浪乐章",
"祭礼弓"
],
"from": "2021-09-01 06:00:00", "from": "2021-09-01 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-09-21 17:59:59" "to": "2021-09-21 17:59:59",
}, },
{ {
"five": [ "five": ["飞雷之弦振", "天空之刃"],
"飞雷之弦振", "four": ["祭礼剑", "雨裁", "匣里灭辰", "祭礼残章", "西风猎弓"],
"天空之刃"
],
"four": [
"祭礼剑",
"雨裁",
"匣里灭辰",
"祭礼残章",
"西风猎弓"
],
"from": "2021-08-10 18:00:00", "from": "2021-08-10 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-08-31 14:59:59" "to": "2021-08-31 14:59:59",
}, },
{ {
"five": [ "five": ["雾切之回光", "天空之脊"],
"雾切之回光", "four": ["西风剑", "祭礼大剑", "西风长枪", "西风秘典", "绝弦"],
"天空之脊"
],
"four": [
"西风剑",
"祭礼大剑",
"西风长枪",
"西风秘典",
"绝弦"
],
"from": "2021-07-21 06:00:00", "from": "2021-07-21 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-08-10 17:59:59" "to": "2021-08-10 17:59:59",
}, },
{ {
"five": [ "five": ["苍古自由之誓", "天空之卷"],
"苍古自由之誓", "four": ["暗巷闪光", "西风大剑", "匣里灭辰", "暗巷的酒与诗", "暗巷猎手"],
"天空之卷"
],
"four": [
"暗巷闪光",
"西风大剑",
"匣里灭辰",
"暗巷的酒与诗",
"暗巷猎手"
],
"from": "2021-06-29 18:00:00", "from": "2021-06-29 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-07-20 14:59:59" "to": "2021-07-20 14:59:59",
}, },
{ {
"five": [ "five": ["天空之傲", "四风原典"],
"天空之傲", "four": ["匣里龙吟", "钟剑", "西风长枪", "流浪乐章", "幽夜华尔兹"],
"四风原典"
],
"four": [
"匣里龙吟",
"钟剑",
"西风长枪",
"流浪乐章",
"幽夜华尔兹"
],
"from": "2021-06-09 06:00:00", "from": "2021-06-09 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-06-29 17:59:59" "to": "2021-06-29 17:59:59",
}, },
{ {
"five": [ "five": ["松籁响起之时", "风鹰剑"],
"松籁响起之时", "four": ["祭礼剑", "雨裁", "匣里灭辰", "祭礼残章", "弓藏"],
"风鹰剑"
],
"four": [
"祭礼剑",
"雨裁",
"匣里灭辰",
"祭礼残章",
"弓藏"
],
"from": "2021-05-18 18:00:00", "from": "2021-05-18 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-06-08 14:59:59" "to": "2021-06-08 14:59:59",
}, },
{ {
"five": [ "five": ["斫峰之刃", "尘世之锁"],
"斫峰之刃", "four": ["笛剑", "千岩古剑", "祭礼弓", "昭心", "千岩长枪"],
"尘世之锁"
],
"four": [
"笛剑",
"千岩古剑",
"祭礼弓",
"昭心",
"千岩长枪"
],
"from": "2021-04-28 06:00:00", "from": "2021-04-28 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-05-18 17:59:59" "to": "2021-05-18 17:59:59",
}, },
{ {
"five": [ "five": ["天空之翼", "四风原典"],
"天空之翼", "four": ["西风剑", "祭礼大剑", "暗巷猎手", "西风秘典", "西风长枪"],
"四风原典"
],
"four": [
"西风剑",
"祭礼大剑",
"暗巷猎手",
"西风秘典",
"西风长枪"
],
"from": "2021-04-06 18:00:00", "from": "2021-04-06 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-04-27 14:59:59" "to": "2021-04-27 14:59:59",
}, },
{ {
"five": [ "five": ["终末嗟叹之诗", "天空之刃"],
"终末嗟叹之诗", "four": ["暗巷闪光", "西风大剑", "西风猎弓", "暗巷的酒与诗", "匣里灭辰"],
"天空之刃"
],
"four": [
"暗巷闪光",
"西风大剑",
"西风猎弓",
"暗巷的酒与诗",
"匣里灭辰"
],
"from": "2021-03-17 06:00:00", "from": "2021-03-17 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-04-06 15:59:59" "to": "2021-04-06 15:59:59",
}, },
{ {
"five": [ "five": ["护摩之杖", "狼的末路"],
"护摩之杖", "four": ["匣里龙吟", "千岩古剑", "祭礼弓", "流浪乐章", "千岩长枪"],
"狼的末路"
],
"four": [
"匣里龙吟",
"千岩古剑",
"祭礼弓",
"流浪乐章",
"千岩长枪"
],
"from": "2021-02-23 18:00:00", "from": "2021-02-23 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-03-16 14:59:59" "to": "2021-03-16 14:59:59",
}, },
{ {
"five": [ "five": ["磐岩结绿", "和璞鸢"],
"磐岩结绿", "four": ["笛剑", "祭礼大剑", "弓藏", "昭心", "西风长枪"],
"和璞鸢"
],
"four": [
"笛剑",
"祭礼大剑",
"弓藏",
"昭心",
"西风长枪"
],
"from": "2021-02-03 06:00:00", "from": "2021-02-03 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-02-23 15:59:59" "to": "2021-02-23 15:59:59",
}, },
{ {
"five": [ "five": ["阿莫斯之弓", "天空之傲"],
"阿莫斯之弓", "four": ["祭礼剑", "钟剑", "匣里灭辰", "昭心", "西风猎弓"],
"天空之傲"
],
"four": [
"祭礼剑",
"钟剑",
"匣里灭辰",
"昭心",
"西风猎弓"
],
"from": "2021-01-12 18:00:00", "from": "2021-01-12 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-02-02 14:59:59" "to": "2021-02-02 14:59:59",
}, },
{ {
"five": [ "five": ["斫峰之刃", "天空之卷"],
"斫峰之刃", "four": ["西风剑", "西风大剑", "西风长枪", "祭礼残章", "绝弦"],
"天空之卷"
],
"four": [
"西风剑",
"西风大剑",
"西风长枪",
"祭礼残章",
"绝弦"
],
"from": "2020-12-23 06:00:00", "from": "2020-12-23 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2021-01-12 15:59:59" "to": "2021-01-12 15:59:59",
}, },
{ {
"five": [ "five": ["贯虹之槊", "无工之剑"],
"贯虹之槊", "four": ["匣里龙吟", "钟剑", "西风秘典", "西风猎弓", "匣里灭辰"],
"无工之剑"
],
"four": [
"匣里龙吟",
"钟剑",
"西风秘典",
"西风猎弓",
"匣里灭辰"
],
"from": "2020-12-01 18:00:00", "from": "2020-12-01 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2020-12-22 14:59:59" "to": "2020-12-22 14:59:59",
}, },
{ {
"five": [ "five": ["天空之翼", "尘世之锁"],
"天空之翼", "four": ["笛剑", "雨裁", "昭心", "弓藏", "西风长枪"],
"尘世之锁"
],
"four": [
"笛剑",
"雨裁",
"昭心",
"弓藏",
"西风长枪"
],
"from": "2020-11-11 06:00:00", "from": "2020-11-11 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2020-12-01 15:59:59" "to": "2020-12-01 15:59:59",
}, },
{ {
"five": [ "five": ["四风原典", "狼的末路"],
"四风原典", "four": ["祭礼剑", "祭礼大剑", "祭礼残章", "祭礼弓", "匣里灭辰"],
"狼的末路"
],
"four": [
"祭礼剑",
"祭礼大剑",
"祭礼残章",
"祭礼弓",
"匣里灭辰"
],
"from": "2020-10-20 18:00:00", "from": "2020-10-20 18:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2020-11-10 14:59:59" "to": "2020-11-10 14:59:59",
}, },
{ {
"five": [ "five": ["风鹰剑", "阿莫斯之弓"],
"风鹰剑", "four": ["祭礼剑", "祭礼大剑", "祭礼残章", "祭礼弓", "匣里灭辰"],
"阿莫斯之弓"
],
"four": [
"祭礼剑",
"祭礼大剑",
"祭礼残章",
"祭礼弓",
"匣里灭辰"
],
"from": "2020-09-28 06:00:00", "from": "2020-09-28 06:00:00",
"name": "神铸赋形", "name": "神铸赋形",
"to": "2020-10-18 17:59:59" "to": "2020-10-18 17:59:59",
} },
] ]

View File

@ -14,8 +14,12 @@ from utils.log import logger
from utils.typedefs import StrOrInt from utils.typedefs import StrOrInt
__all__ = [ __all__ = [
'get_avatar_data', 'get_artifact_data', 'get_material_data', 'get_namecard_data', 'get_weapon_data', "get_avatar_data",
'update_honey_metadata', "get_artifact_data",
"get_material_data",
"get_namecard_data",
"get_weapon_data",
"update_honey_metadata",
] ]
DATA_TYPE = Dict[StrOrInt, List[str]] DATA_TYPE = Dict[StrOrInt, List[str]]
@ -41,12 +45,12 @@ async def get_avatar_data() -> DATA_TYPE:
result = {} result = {}
url = "https://genshin.honeyhunterworld.com/fam_chars/?lang=CHS" url = "https://genshin.honeyhunterworld.com/fam_chars/?lang=CHS"
response = await request(url) response = await request(url)
chaos_data = re.findall(r'sortable_data\.push\((.*)\);\s*sortable_cur_page', response.text)[0] chaos_data = re.findall(r"sortable_data\.push\((.*)\);\s*sortable_cur_page", response.text)[0]
json_data = json.loads(chaos_data) # 转为 json json_data = json.loads(chaos_data) # 转为 json
for data in json_data: for data in json_data:
cid = int("10000" + re.findall(r'\d+', data[1])[0]) cid = int("10000" + re.findall(r"\d+", data[1])[0])
honey_id = re.findall(r"/(.*?)/", data[1])[0] honey_id = re.findall(r"/(.*?)/", data[1])[0]
name = re.findall(r'>(.*)<', data[1])[0] name = re.findall(r">(.*)<", data[1])[0]
rarity = int(re.findall(r">(\d)<", data[2])[0]) rarity = int(re.findall(r">(\d)<", data[2])[0])
result[cid] = [honey_id, name, rarity] result[cid] = [honey_id, name, rarity]
return result return result
@ -59,13 +63,13 @@ async def get_weapon_data() -> DATA_TYPE:
urls = [HONEY_HOST.join(f"fam_{i.lower()}/?lang=CHS") for i in WeaponType.__members__] urls = [HONEY_HOST.join(f"fam_{i.lower()}/?lang=CHS") for i in WeaponType.__members__]
for url in urls: for url in urls:
response = await request(url) response = await request(url)
chaos_data = re.findall(r'sortable_data\.push\((.*)\);\s*sortable_cur_page', response.text)[0] chaos_data = re.findall(r"sortable_data\.push\((.*)\);\s*sortable_cur_page", response.text)[0]
json_data = json.loads(chaos_data) # 转为 json json_data = json.loads(chaos_data) # 转为 json
for data in json_data: for data in json_data:
name = re.findall(r'>(.*)<', data[1])[0] name = re.findall(r">(.*)<", data[1])[0]
if name in ['「一心传」名刀', '石英大剑', '琥珀玥', '黑檀弓']: # 跳过特殊的武器 if name in ["「一心传」名刀", "石英大剑", "琥珀玥", "黑檀弓"]: # 跳过特殊的武器
continue continue
wid = int(re.findall(r'\d+', data[1])[0]) wid = int(re.findall(r"\d+", data[1])[0])
honey_id = re.findall(r"/(.*?)/", data[1])[0] honey_id = re.findall(r"/(.*?)/", data[1])[0]
rarity = int(re.findall(r">(\d)<", data[2])[0]) rarity = int(re.findall(r">(\d)<", data[2])[0])
result[wid] = [honey_id, name, rarity] result[wid] = [honey_id, name, rarity]
@ -75,27 +79,27 @@ async def get_weapon_data() -> DATA_TYPE:
async def get_material_data() -> DATA_TYPE: async def get_material_data() -> DATA_TYPE:
result = {} result = {}
weapon = [HONEY_HOST.join(f'fam_wep_{i}/?lang=CHS') for i in ['primary', 'secondary', 'common']] weapon = [HONEY_HOST.join(f"fam_wep_{i}/?lang=CHS") for i in ["primary", "secondary", "common"]]
talent = [HONEY_HOST.join(f'fam_talent_{i}/?lang=CHS') for i in ['book', 'boss', 'common', 'reward']] talent = [HONEY_HOST.join(f"fam_talent_{i}/?lang=CHS") for i in ["book", "boss", "common", "reward"]]
namecard = [HONEY_HOST.join("fam_nameplate/?lang=CHS")] namecard = [HONEY_HOST.join("fam_nameplate/?lang=CHS")]
urls = weapon + talent + namecard urls = weapon + talent + namecard
response = await request("https://api.ambr.top/v2/chs/material") response = await request("https://api.ambr.top/v2/chs/material")
ambr_data = json.loads(response.text)['data']['items'] ambr_data = json.loads(response.text)["data"]["items"]
for url in urls: for url in urls:
response = await request(url) response = await request(url)
chaos_data = re.findall(r'sortable_data\.push\((.*)\);\s*sortable_cur_page', response.text)[0] chaos_data = re.findall(r"sortable_data\.push\((.*)\);\s*sortable_cur_page", response.text)[0]
json_data = json.loads(chaos_data) # 转为 json json_data = json.loads(chaos_data) # 转为 json
for data in json_data: for data in json_data:
honey_id = re.findall(r'/(.*?)/', data[1])[0] honey_id = re.findall(r"/(.*?)/", data[1])[0]
name = re.findall(r'>(.*)<', data[1])[0] name = re.findall(r">(.*)<", data[1])[0]
rarity = int(re.findall(r">(\d)<", data[2])[0]) rarity = int(re.findall(r">(\d)<", data[2])[0])
mid = None mid = None
for mid, item in ambr_data.items(): for mid, item in ambr_data.items():
if name == item['name']: if name == item["name"]:
break break
mid = int(mid) or int(re.findall(r'\d+', data[1])[0]) mid = int(mid) or int(re.findall(r"\d+", data[1])[0])
result[mid] = [honey_id, name, rarity] result[mid] = [honey_id, name, rarity]
return result return result
@ -103,7 +107,7 @@ async def get_material_data() -> DATA_TYPE:
async def get_artifact_data() -> DATA_TYPE: async def get_artifact_data() -> DATA_TYPE:
async def get_first_id(_link) -> str: async def get_first_id(_link) -> str:
_response = await request(_link) _response = await request(_link)
_chaos_data = re.findall(r'sortable_data\.push\((.*)\);\s*sortable_cur_page', _response.text)[0] _chaos_data = re.findall(r"sortable_data\.push\((.*)\);\s*sortable_cur_page", _response.text)[0]
_json_data = json.loads(_chaos_data) _json_data = json.loads(_chaos_data)
return re.findall(r"/(.*?)/", _json_data[-1][1])[0] return re.findall(r"/(.*?)/", _json_data[-1][1])[0]
@ -111,21 +115,21 @@ async def get_artifact_data() -> DATA_TYPE:
url = "https://genshin.honeyhunterworld.com/fam_art_set/?lang=CHS" url = "https://genshin.honeyhunterworld.com/fam_art_set/?lang=CHS"
response = await request("https://api.ambr.top/v2/chs/reliquary") response = await request("https://api.ambr.top/v2/chs/reliquary")
ambr_data = json.loads(response.text)['data']['items'] ambr_data = json.loads(response.text)["data"]["items"]
response = await request(url) response = await request(url)
chaos_data = re.findall(r'sortable_data\.push\((.*)\);\s*sortable_cur_page', response.text)[0] chaos_data = re.findall(r"sortable_data\.push\((.*)\);\s*sortable_cur_page", response.text)[0]
json_data = json.loads(chaos_data) # 转为 json json_data = json.loads(chaos_data) # 转为 json
for data in json_data: for data in json_data:
honey_id = re.findall(r'/(.*?)/', data[1])[0] honey_id = re.findall(r"/(.*?)/", data[1])[0]
name = re.findall(r"alt=\"(.*?)\"", data[0])[0] name = re.findall(r"alt=\"(.*?)\"", data[0])[0]
link = HONEY_HOST.join(re.findall(r'href="(.*?)"', data[0])[0]) link = HONEY_HOST.join(re.findall(r'href="(.*?)"', data[0])[0])
first_id = await get_first_id(link) first_id = await get_first_id(link)
aid = None aid = None
for aid, item in ambr_data.items(): for aid, item in ambr_data.items():
if name == item['name']: if name == item["name"]:
break break
aid = aid or re.findall(r'\d+', data[1])[0] aid = aid or re.findall(r"\d+", data[1])[0]
result[aid] = [honey_id, name, first_id] result[aid] = [honey_id, name, first_id]
return result return result
@ -138,20 +142,21 @@ async def get_namecard_data() -> DATA_TYPE:
# noinspection PyProtectedMember # noinspection PyProtectedMember
from metadata.genshin import Data from metadata.genshin import Data
from metadata.scripts.metadatas import update_metadata_from_github from metadata.scripts.metadatas import update_metadata_from_github
await update_metadata_from_github() await update_metadata_from_github()
# noinspection PyPep8Naming # noinspection PyPep8Naming
NAMECARD_DATA = Data('namecard') NAMECARD_DATA = Data("namecard")
url = HONEY_HOST.join("fam_nameplate/?lang=CHS") url = HONEY_HOST.join("fam_nameplate/?lang=CHS")
result = {} result = {}
response = await request(url) response = await request(url)
chaos_data = re.findall(r'sortable_data\.push\((.*)\);\s*sortable_cur_page', response.text)[0] chaos_data = re.findall(r"sortable_data\.push\((.*)\);\s*sortable_cur_page", response.text)[0]
json_data = json.loads(chaos_data) json_data = json.loads(chaos_data)
for data in json_data: for data in json_data:
honey_id = re.findall(r'/(.*?)/', data[1])[0] honey_id = re.findall(r"/(.*?)/", data[1])[0]
name = re.findall(r"alt=\"(.*?)\"", data[0])[0] name = re.findall(r"alt=\"(.*?)\"", data[0])[0]
try: try:
nid = [key for key, value in NAMECARD_DATA.items() if value['name'] == name][0] nid = [key for key, value in NAMECARD_DATA.items() if value["name"] == name][0]
except IndexError: # 暂不支持 beta 的名片 except IndexError: # 暂不支持 beta 的名片
continue continue
rarity = int(re.findall(r">(\d)<", data[2])[0]) rarity = int(re.findall(r">(\d)<", data[2])[0])
@ -161,7 +166,7 @@ async def get_namecard_data() -> DATA_TYPE:
async def update_honey_metadata(overwrite: bool = True) -> FULL_DATA_TYPE | None: async def update_honey_metadata(overwrite: bool = True) -> FULL_DATA_TYPE | None:
path = PROJECT_ROOT.joinpath('metadata/data/honey.json') path = PROJECT_ROOT.joinpath("metadata/data/honey.json")
if not overwrite and path.exists(): if not overwrite and path.exists():
return return
avatar_data = await get_avatar_data() avatar_data = await get_avatar_data()
@ -176,13 +181,13 @@ async def update_honey_metadata(overwrite: bool = True) -> FULL_DATA_TYPE | None
logger.success("Namecard data is done.") logger.success("Namecard data is done.")
result = { result = {
'avatar': avatar_data, "avatar": avatar_data,
'weapon': weapon_data, "weapon": weapon_data,
'material': material_data, "material": material_data,
'artifact': artifact_data, "artifact": artifact_data,
'namecard': namecard_data, "namecard": namecard_data,
} }
path.parent.mkdir(parents=True, exist_ok=True) path.parent.mkdir(parents=True, exist_ok=True)
async with async_open(path, mode='w', encoding='utf-8') as file: async with async_open(path, mode="w", encoding="utf-8") as file:
await file.write(json.dumps(result, ensure_ascii=False)) await file.write(json.dumps(result, ensure_ascii=False))
return result return result

View File

@ -4,23 +4,23 @@ from httpx import AsyncClient, URL
from utils.const import AMBR_HOST, PROJECT_ROOT from utils.const import AMBR_HOST, PROJECT_ROOT
__all__ = ['update_metadata_from_ambr', 'update_metadata_from_github'] __all__ = ["update_metadata_from_ambr", "update_metadata_from_github"]
client = AsyncClient() client = AsyncClient()
async def update_metadata_from_ambr(overwrite: bool = True): async def update_metadata_from_ambr(overwrite: bool = True):
result = [] result = []
targets = ['material', 'weapon', 'avatar', 'reliquary'] targets = ["material", "weapon", "avatar", "reliquary"]
for target in targets: for target in targets:
path = PROJECT_ROOT.joinpath(f'metadata/data/{target}.json') path = PROJECT_ROOT.joinpath(f"metadata/data/{target}.json")
if not overwrite and path.exists(): if not overwrite and path.exists():
continue continue
url = AMBR_HOST.join(f"v2/chs/{target}") url = AMBR_HOST.join(f"v2/chs/{target}")
path.parent.mkdir(parents=True, exist_ok=True) path.parent.mkdir(parents=True, exist_ok=True)
response = await client.get(url) response = await client.get(url)
json_data = json.loads(response.text)['data']['items'] json_data = json.loads(response.text)["data"]["items"]
async with async_open(path, mode='w', encoding='utf-8') as file: async with async_open(path, mode="w", encoding="utf-8") as file:
data = json.dumps(json_data, ensure_ascii=False) data = json.dumps(json_data, ensure_ascii=False)
await file.write(data) await file.write(data)
result.append(json_data) result.append(json_data)
@ -28,7 +28,7 @@ async def update_metadata_from_ambr(overwrite: bool = True):
async def update_metadata_from_github(overwrite: bool = True): async def update_metadata_from_github(overwrite: bool = True):
path = PROJECT_ROOT.joinpath('metadata/data/namecard.json') path = PROJECT_ROOT.joinpath("metadata/data/namecard.json")
if not overwrite and path.exists(): if not overwrite and path.exists():
return return
@ -41,25 +41,27 @@ async def update_metadata_from_github(overwrite: bool = True):
material_json_data = json.loads((await client.get(material_url)).text) material_json_data = json.loads((await client.get(material_url)).text)
data = {} data = {}
for namecard_data in filter(lambda x: x.get('materialType', None) == 'MATERIAL_NAMECARD', material_json_data): for namecard_data in filter(lambda x: x.get("materialType", None) == "MATERIAL_NAMECARD", material_json_data):
name = text_map_json_data[str(namecard_data['nameTextMapHash'])] name = text_map_json_data[str(namecard_data["nameTextMapHash"])]
icon = namecard_data['icon'] icon = namecard_data["icon"]
navbar = namecard_data['picPath'][0] navbar = namecard_data["picPath"][0]
banner = namecard_data['picPath'][1] banner = namecard_data["picPath"][1]
rank = namecard_data['rankLevel'] rank = namecard_data["rankLevel"]
description = text_map_json_data[str(namecard_data['descTextMapHash'])].replace('\\n', '\n') description = text_map_json_data[str(namecard_data["descTextMapHash"])].replace("\\n", "\n")
data.update({ data.update(
str(namecard_data['id']): { {
"id": namecard_data['id'], str(namecard_data["id"]): {
"name": name, "id": namecard_data["id"],
"rank": rank, "name": name,
"icon": icon, "rank": rank,
"navbar": navbar, "icon": icon,
"profile": banner, "navbar": navbar,
"description": description, "profile": banner,
"description": description,
}
} }
}) )
async with async_open(path, mode='w', encoding='utf-8') as file: async with async_open(path, mode="w", encoding="utf-8") as file:
data = json.dumps(data, ensure_ascii=False) data = json.dumps(data, ensure_ascii=False)
await file.write(data) await file.write(data)
return data return data

View File

@ -4,103 +4,274 @@ import functools
from metadata.genshin import WEAPON_DATA from metadata.genshin import WEAPON_DATA
__all__ = [ __all__ = ["roles", "weapons", "roleToId", "roleToName", "weaponToName", "weaponToId"]
'roles', 'weapons',
'roleToId', 'roleToName', 'weaponToName', 'weaponToId'
]
# noinspection SpellCheckingInspection # noinspection SpellCheckingInspection
roles = { roles = {
20000000: [ 20000000: ["主角", "旅行者", "卑鄙的外乡人", "荣誉骑士", "", "风主", "岩主", "雷主", "草主", "履刑者", "抽卡不歪真君"],
'主角', '旅行者', '卑鄙的外乡人', '荣誉骑士', '', '风主', '岩主', '雷主', '草主', '履刑者', '抽卡不歪真君' 10000002: ["神里绫华", "Ayaka", "ayaka", "Kamisato Ayaka", "神里", "绫华", "神里凌华", "凌华", "白鹭公主", "神里大小 姐"],
10000003: ["", "Jean", "jean", "团长", "代理团长", "琴团长", "蒲公英骑士"],
10000005: ["", "Aether", "aether", "男主", "男主角", "龙哥", "空哥"],
10000006: ["丽莎", "Lisa", "lisa", "图书管理员", "图书馆管理员", "蔷薇魔女"],
10000007: ["", "Lumine", "lumine", "女主", "女主角", "", "", "黄毛阿姨", "荧妹"],
10000014: ["芭芭拉", "Barbara", "barbara", "巴巴拉", "拉粑粑", "拉巴巴", "内鬼", "加湿器", "闪耀偶像", "偶像"],
10000015: ["凯亚", "Kaeya", "kaeya", "盖亚", "凯子哥", "凯鸭", "矿工", "矿工头子", "骑兵队长", "凯子", "凝冰渡海真君"],
10000016: [
"迪卢克",
"Diluc",
"diluc",
"卢姥爷",
"姥爷",
"卢老爷",
"卢锅巴",
"正义人",
"正e人",
"正E人",
"卢本伟",
"暗夜英雄",
"卢卢伯爵",
"落魄了",
"落魄了家人们",
], ],
10000002: ['神里绫华', 'Ayaka', 'ayaka', 'Kamisato Ayaka', '神里', '绫华', '神里凌华', '凌华', '白鹭公主', 10000020: ["雷泽", "Razor", "razor", "狼少年", "狼崽子", "狼崽", "卢皮卡", "小狼", "小狼狗"],
'神里大小 姐'], 10000021: ["安柏", "Amber", "amber", "安伯", "兔兔伯爵", "飞行冠军", "侦查骑士", "点火姬", "点火机", "打火机", "打火姬"],
10000003: ['', 'Jean', 'jean', '团长', '代理团长', '琴团长', '蒲公英骑士'], 10000022: [
10000005: ['', 'Aether', 'aether', '男主', '男主角', '龙哥', '空哥'], "温迪",
10000006: ['丽莎', 'Lisa', 'lisa', '图书管理员', '图书馆管理员', '蔷薇魔女'], "Venti",
10000007: ['', 'Lumine', 'lumine', '女主', '女主角', '', '', '黄毛阿姨', '荧妹'], "venti",
10000014: ['芭芭拉', 'Barbara', 'barbara', '巴巴拉', '拉粑粑', '拉巴巴', '内鬼', '加湿器', '闪耀偶像', '偶像'], "温蒂",
10000015: ['凯亚', 'Kaeya', 'kaeya', '盖亚', '凯子哥', '凯鸭', '矿工', '矿工头子', '骑兵队长', '凯子', "风神",
'凝冰渡海真君'], "卖唱的",
10000016: ['迪卢克', 'Diluc', 'diluc', '卢姥爷', '姥爷', '卢老爷', '卢锅巴', '正义人', '正e人', '正E人', '卢本伟', "巴巴托斯",
'暗夜英雄', '卢卢伯爵', '落魄了', '落魄了家人们'], "巴巴脱丝",
10000020: ['雷泽', 'Razor', 'razor', '狼少年', '狼崽子', '狼崽', '卢皮卡', '小狼', '小狼狗'], "芭芭托斯",
10000021: ['安柏', 'Amber', 'amber', '安伯', '兔兔伯爵', '飞行冠军', '侦查骑士', '点火姬', '点火机', '打火机', "芭芭脱丝",
'打火姬'], "干点正事",
10000022: ['温迪', 'Venti', 'venti', '温蒂', '风神', '卖唱的', '巴巴托斯', '巴巴脱丝', '芭芭托斯', '芭芭脱丝', "不干正事",
'干点正事', '不干正事', '吟游诗人', '诶嘿', '唉嘿', '摸鱼'], "吟游诗人",
10000023: ['香菱', 'Xiangling', 'xiangling', '香玲', '锅巴', '厨师', '万民堂厨师', '香师傅'], "诶嘿",
10000024: ['北斗', 'Beidou', 'beidou', '大姐头', '大姐', '无冕的龙王', '龙王'], "唉嘿",
10000025: ['行秋', 'Xingqiu', 'xingqiu', '秋秋人', '秋妹妹', '书呆子', '水神', '飞云商会二少爷'], "摸鱼",
10000026: ['', 'Xiao', 'xiao', '杏仁豆腐', '打桩机', '插秧', '三眼五显仙人', '三眼五显真人', '降魔大圣', ],
'护法夜叉', '快乐风男', '无聊', '靖妖傩舞', '矮子仙人', '三点五尺仙人', '跳跳虎'], 10000023: ["香菱", "Xiangling", "xiangling", "香玲", "锅巴", "厨师", "万民堂厨师", "香师傅"],
10000027: ['凝光', 'Ningguang', 'ningguang', '富婆', '天权星'], 10000024: ["北斗", "Beidou", "beidou", "大姐头", "大姐", "无冕的龙王", "龙王"],
10000029: ['可莉', 'Klee', 'klee', '嘟嘟可', '火花骑士', '蹦蹦炸弹', '炸鱼', '放火烧山', '放火烧山真君', 10000025: ["行秋", "Xingqiu", "xingqiu", "秋秋人", "秋妹妹", "书呆子", "水神", "飞云商会二少爷"],
'蒙德最强战力', '逃跑的太阳', '啦啦啦', '哒哒哒', '炸弹人', '禁闭室'], 10000026: [
10000030: ['钟离', 'Zhongli', 'zhongli', '摩拉克斯', '岩王爷', '岩神', '钟师傅', '天动万象', '岩王帝君', '未来可期', "",
'帝君', '拒收病婿'], "Xiao",
10000031: ['菲谢尔', 'Fischl', 'fischl', '皇女', '小艾米', '小艾咪', '奥兹', '断罪皇女', '中二病', '中二少女', "xiao",
'中二皇女', '奥兹发射器'], "杏仁豆腐",
10000032: ['班尼特', 'Bennett', 'bennett', '点赞哥', '点赞', '倒霉少年', '倒霉蛋', '霹雳闪雷真君', '班神', '班爷', "打桩机",
'倒霉', '火神', '六星真神'], "插秧",
10000033: ['达达利亚', 'Tartaglia', 'tartaglia', 'Childe', 'childe', 'Ajax', 'ajax', '达达鸭', '达达利鸭', '公子', "三眼五显仙人",
'玩具销售员', '玩具推销员', '钱包', '鸭鸭', '愚人众末席'], "三眼五显真人",
10000034: ['诺艾尔', 'Noelle', 'noelle', '女仆', '高达', '岩王帝姬'], "降魔大圣",
10000035: ['七七', 'Qiqi', 'qiqi', '僵尸', '肚饿真君', '度厄真君', '77'], "护法夜叉",
10000036: ['重云', 'Chongyun', 'chongyun', '纯阳之体', '冰棍'], "快乐风男",
10000037: ['甘雨', 'Ganyu', 'ganyu', '椰羊', '椰奶', '王小美'], "无聊",
10000038: ['阿贝多', 'Albedo', 'albedo', '可莉哥哥', '升降机', '升降台', '电梯', '白垩之子', '贝爷', '白垩', "靖妖傩舞",
'阿贝少', '花呗多', '阿贝夕', 'abd', '阿师傅'], "矮子仙人",
10000039: ['迪奥娜', 'Diona', 'diona', '迪欧娜', 'dio', 'dio娜', '冰猫', '猫猫', '猫娘', '喵喵', '调酒师'], "三点五尺仙人",
10000041: ['莫娜', 'Mona', 'mona', '穷鬼', '穷光蛋', '', '莫纳', '占星术士', '占星师', '讨龙真君', '半部讨龙真君', "跳跳虎",
'阿斯托洛吉斯·莫娜·梅姬斯图斯', '梅姬斯图斯', '梅姬斯图斯卿'], ],
10000042: ['刻晴', 'Keqing', 'keqing', '刻情', '氪晴', '刻师傅', '刻师父', '牛杂', '牛杂师傅', '斩尽牛杂', '免疫', 10000027: ["凝光", "Ningguang", "ningguang", "富婆", "天权星"],
'免疫免疫', '屁斜剑法', '玉衡星', '阿晴', ' 啊晴'], 10000029: [
10000043: ['砂糖', 'Sucrose', 'sucrose', '雷莹术士', '雷萤术士', '雷荧术士'], "可莉",
10000044: ['辛焱', 'Xinyan', 'xinyan', '辛炎', '黑妹', '摇滚'], "Klee",
10000045: ['罗莎莉亚', 'Rosaria', 'rosaria', '罗莎莉娅', '白色史莱姆', '白史莱姆', '修女', '罗莎利亚', '罗莎利娅', "klee",
'罗沙莉亚', '罗沙莉娅', '罗沙利亚', '罗沙利娅', '萝莎莉亚', '萝莎莉娅', '萝莎利亚', '萝莎利娅', "嘟嘟可",
'萝沙莉亚', '萝沙莉娅', '萝沙利亚', '萝沙利娅'], "火花骑士",
10000046: ['胡桃', 'HuTao', 'hutao', 'Hu Tao', 'hu tao', 'Hutao', '胡 淘', '往生堂堂主', '火化', '抬棺的', '蝴蝶', "蹦蹦炸弹",
'核桃', '堂主', '胡堂主', '雪霁梅香'], "炸鱼",
10000047: ['枫原万叶', 'Kazuha', 'kazuha', 'Kaedehara Kazuha', '万叶', '叶天帝', '天帝', '叶师傅'], "放火烧山",
10000048: ['烟绯', 'Yanfei', 'yanfei', '烟老师', '律师', '罗翔'], "放火烧山真君",
10000049: ['宵宫', 'Yoimiya', 'yoimiya', '霄宫', '烟花', '肖宫', '肖工', '绷带女孩'], "蒙德最强战力",
10000050: ['托马', 'Thoma', 'thoma', '家政官', '太郎丸', '地头蛇', '男仆', '拖马'], "逃跑的太阳",
10000051: ['优菈', 'Eula', 'eula', '优拉', '尤拉', '尤菈', '浪花骑士', '记仇', '劳伦斯'], "啦啦啦",
10000052: ['雷电将军', 'Raiden Shogun', 'Raiden', 'raiden', '雷神', '将军', '雷军', '巴尔', '阿影', '', "哒哒哒",
'巴尔泽布', '煮饭婆', '奶香一刀', '无想一刀', '宅女'], "炸弹人",
10000053: ['早柚', 'Sayu', 'sayu', '小狸猫', '狸 猫', '忍者'], "禁闭室",
10000054: ['珊瑚宫心海', 'Kokomi', 'kokomi', 'Sangonomiya Kokomi', '心海', '军师', '珊瑚宫', '书记', '观赏鱼', ],
'水母', '', '美人鱼'], 10000030: ["钟离", "Zhongli", "zhongli", "摩拉克斯", "岩王爷", "岩神", "钟师傅", "天动万象", "岩王帝君", "未来可期", "帝君", "拒收病婿"],
10000055: ['五郎', 'Gorou', 'gorou', '柴犬', '土狗', '希娜', '希娜小姐'], 10000031: ["菲谢尔", "Fischl", "fischl", "皇女", "小艾米", "小艾咪", "奥兹", "断罪皇女", "中二病", "中二少女", "中二皇女", "奥兹发射器"],
10000056: ['九条裟罗', 'Sara', 'sara', 'Kujou Sara', '九条', '九条沙罗', '裟罗', '沙罗', '天狗'], 10000032: ["班尼特", "Bennett", "bennett", "点赞哥", "点赞", "倒霉少年", "倒霉蛋", "霹雳闪雷真君", "班神", "班爷", "倒霉", "火神", "六星真神"],
10000057: ['荒泷一斗', 'Itto', 'itto', 'Arataki Itto', '荒龙一斗', '荒泷天下第一斗', '一斗', '一抖', '荒泷', '1斗', 10000033: [
'牛牛', '斗子哥', '牛子哥', '牛子', '孩子 王', '斗虫', '巧乐兹', '放牛的'], "达达利亚",
10000058: ['八重神子', 'Miko', 'miko', 'Yae Miko', '八重', '神子', '狐狸', '想得美哦', '巫女', '屑狐狸', '骚狐狸', "Tartaglia",
'八重宫司', '婶子', '小八'], "tartaglia",
10000059: ['鹿野院平藏', 'Heizou', 'heizou', 'shikanoin heizou', 'heizo', '鹿野苑', '鹿野院', '平藏', '鹿野苑平藏', "Childe",
'鹿野', '小鹿'], "childe",
10000060: ['夜兰', 'Yelan', 'yelan', '夜阑', '叶 澜', '腋兰', '夜天后'], "Ajax",
10000062: ['埃洛伊', 'Aloy', 'aloy'], "ajax",
10000063: ['申鹤', 'Shenhe', 'shenhe', '神鹤', '小姨', '小姨子', '审鹤'], "达达鸭",
10000064: ['云堇', 'YunJin', 'yunjin', 'Yun Jin', 'yun jin', '云瑾', '云先生', '云锦', '神女劈观'], "达达利鸭",
10000065: ['久岐忍', 'Kuki', 'kuki', 'Kuki Shinobu', 'Shinobu', 'shinobu', '97忍', '小忍', '久歧忍', '97', '茄忍', "公子",
'阿忍', '忍姐'], "玩具销售员",
10000066: ['神里绫人', 'Ayato', 'ayato', 'Kamisato Ayato', '绫人', '神里凌人', '凌人', '0人', '神人', '零人', "玩具推销员",
'大舅哥'], "钱包",
10000067: ['柯莱', 'Collei', 'collei', '柯来', '科莱', '科来', '小天使', '须弥安柏', '须弥飞行冠军', '见习巡林员', "鸭鸭",
'克莱', '草安伯'], "愚人众末席",
10000068: ['多莉', 'Dori', 'dori', '多利', '多力', '多丽', '奸商'], ],
10000069: ['提纳里', 'Tighnari', 'tighnari', '小提', '提那里', '缇娜里', '提哪里', '', '柯莱老师', '柯莱师傅', 10000034: ["诺艾尔", "Noelle", "noelle", "女仆", "高达", "岩王帝姬"],
'巡林官', 10000035: ["七七", "Qiqi", "qiqi", "僵尸", "肚饿真君", "度厄真君", "77"],
'提那里'], 10000036: ["重云", "Chongyun", "chongyun", "纯阳之体", "冰棍"],
10000070: ['妮露', 'Nilou', 'nilou', '尼露', '尼禄'], 10000037: ["甘雨", "Ganyu", "ganyu", "椰羊", "椰奶", "王小美"],
10000071: ['赛诺', 'Cyno', 'cyno', '赛洛'], 10000038: [
10000072: ['坎蒂丝', 'Candace', 'candace', '坎迪斯'], "阿贝多",
10000073: ['纳西妲', 'Nahida', 'nahida', '草王', '草神', '小吉祥草王', '草萝莉', '纳西坦'], "Albedo",
10000074: ['莱依拉', 'Layla', 'layla', '拉一拉'], "albedo",
"可莉哥哥",
"升降机",
"升降台",
"电梯",
"白垩之子",
"贝爷",
"白垩",
"阿贝少",
"花呗多",
"阿贝夕",
"abd",
"阿师傅",
],
10000039: ["迪奥娜", "Diona", "diona", "迪欧娜", "dio", "dio娜", "冰猫", "猫猫", "猫娘", "喵喵", "调酒师"],
10000041: [
"莫娜",
"Mona",
"mona",
"穷鬼",
"穷光蛋",
"",
"莫纳",
"占星术士",
"占星师",
"讨龙真君",
"半部讨龙真君",
"阿斯托洛吉斯·莫娜·梅姬斯图斯",
"梅姬斯图斯",
"梅姬斯图斯卿",
],
10000042: [
"刻晴",
"Keqing",
"keqing",
"刻情",
"氪晴",
"刻师傅",
"刻师父",
"牛杂",
"牛杂师傅",
"斩尽牛杂",
"免疫",
"免疫免疫",
"屁斜剑法",
"玉衡星",
"阿晴",
" 啊晴",
],
10000043: ["砂糖", "Sucrose", "sucrose", "雷莹术士", "雷萤术士", "雷荧术士"],
10000044: ["辛焱", "Xinyan", "xinyan", "辛炎", "黑妹", "摇滚"],
10000045: [
"罗莎莉亚",
"Rosaria",
"rosaria",
"罗莎莉娅",
"白色史莱姆",
"白史莱姆",
"修女",
"罗莎利亚",
"罗莎利娅",
"罗沙莉亚",
"罗沙莉娅",
"罗沙利亚",
"罗沙利娅",
"萝莎莉亚",
"萝莎莉娅",
"萝莎利亚",
"萝莎利娅",
"萝沙莉亚",
"萝沙莉娅",
"萝沙利亚",
"萝沙利娅",
],
10000046: [
"胡桃",
"HuTao",
"hutao",
"Hu Tao",
"hu tao",
"Hutao",
"胡 淘",
"往生堂堂主",
"火化",
"抬棺的",
"蝴蝶",
"核桃",
"堂主",
"胡堂主",
"雪霁梅香",
],
10000047: ["枫原万叶", "Kazuha", "kazuha", "Kaedehara Kazuha", "万叶", "叶天帝", "天帝", "叶师傅"],
10000048: ["烟绯", "Yanfei", "yanfei", "烟老师", "律师", "罗翔"],
10000049: ["宵宫", "Yoimiya", "yoimiya", "霄宫", "烟花", "肖宫", "肖工", "绷带女孩"],
10000050: ["托马", "Thoma", "thoma", "家政官", "太郎丸", "地头蛇", "男仆", "拖马"],
10000051: ["优菈", "Eula", "eula", "优拉", "尤拉", "尤菈", "浪花骑士", "记仇", "劳伦斯"],
10000052: [
"雷电将军",
"Raiden Shogun",
"Raiden",
"raiden",
"雷神",
"将军",
"雷军",
"巴尔",
"阿影",
"",
"巴尔泽布",
"煮饭婆",
"奶香一刀",
"无想一刀",
"宅女",
],
10000053: ["早柚", "Sayu", "sayu", "小狸猫", "狸 猫", "忍者"],
10000054: ["珊瑚宫心海", "Kokomi", "kokomi", "Sangonomiya Kokomi", "心海", "军师", "珊瑚宫", "书记", "观赏鱼", "水母", "", "美人鱼"],
10000055: ["五郎", "Gorou", "gorou", "柴犬", "土狗", "希娜", "希娜小姐"],
10000056: ["九条裟罗", "Sara", "sara", "Kujou Sara", "九条", "九条沙罗", "裟罗", "沙罗", "天狗"],
10000057: [
"荒泷一斗",
"Itto",
"itto",
"Arataki Itto",
"荒龙一斗",
"荒泷天下第一斗",
"一斗",
"一抖",
"荒泷",
"1斗",
"牛牛",
"斗子哥",
"牛子哥",
"牛子",
"孩子 王",
"斗虫",
"巧乐兹",
"放牛的",
],
10000058: ["八重神子", "Miko", "miko", "Yae Miko", "八重", "神子", "狐狸", "想得美哦", "巫女", "屑狐狸", "骚狐狸", "八重宫司", "婶子", "小八"],
10000059: ["鹿野院平藏", "Heizou", "heizou", "shikanoin heizou", "heizo", "鹿野苑", "鹿野院", "平藏", "鹿野苑平藏", "鹿野", "小鹿"],
10000060: ["夜兰", "Yelan", "yelan", "夜阑", "叶 澜", "腋兰", "夜天后"],
10000062: ["埃洛伊", "Aloy", "aloy"],
10000063: ["申鹤", "Shenhe", "shenhe", "神鹤", "小姨", "小姨子", "审鹤"],
10000064: ["云堇", "YunJin", "yunjin", "Yun Jin", "yun jin", "云瑾", "云先生", "云锦", "神女劈观"],
10000065: ["久岐忍", "Kuki", "kuki", "Kuki Shinobu", "Shinobu", "shinobu", "97忍", "小忍", "久歧忍", "97", "茄忍", "阿忍", "忍姐"],
10000066: ["神里绫人", "Ayato", "ayato", "Kamisato Ayato", "绫人", "神里凌人", "凌人", "0人", "神人", "零人", "大舅哥"],
10000067: ["柯莱", "Collei", "collei", "柯来", "科莱", "科来", "小天使", "须弥安柏", "须弥飞行冠军", "见习巡林员", "克莱", "草安伯"],
10000068: ["多莉", "Dori", "dori", "多利", "多力", "多丽", "奸商"],
10000069: ["提纳里", "Tighnari", "tighnari", "小提", "提那里", "缇娜里", "提哪里", "", "柯莱老师", "柯莱师傅", "巡林官", "提那里"],
10000070: ["妮露", "Nilou", "nilou", "尼露", "尼禄"],
10000071: ["赛诺", "Cyno", "cyno", "赛洛"],
10000072: ["坎蒂丝", "Candace", "candace", "坎迪斯"],
10000073: ["纳西妲", "Nahida", "nahida", "草王", "草神", "小吉祥草王", "草萝莉", "纳西坦"],
10000074: ["莱依拉", "Layla", "layla", "拉一拉"],
} }
not_real_roles = [10000073, 10000074] not_real_roles = [10000073, 10000074]
weapons = { weapons = {
@ -110,37 +281,29 @@ weapons = {
"贯虹之槊": ["贯虹", "岩枪", "盾枪"], "贯虹之槊": ["贯虹", "岩枪", "盾枪"],
"赤角石溃杵": ["赤角", "石溃杵"], "赤角石溃杵": ["赤角", "石溃杵"],
"尘世之锁": ["尘世锁", "尘世", "盾书", ""], "尘世之锁": ["尘世锁", "尘世", "盾书", ""],
"终末嗟叹之诗": ["终末", "终末弓", "叹气弓", "乐团弓"], "终末嗟叹之诗": ["终末", "终末弓", "叹气弓", "乐团弓"],
"松籁响起之时": ["松籁", "乐团大剑", "松剑"], "松籁响起之时": ["松籁", "乐团大剑", "松剑"],
"苍古自由之誓": ["苍古", "乐团剑"], "苍古自由之誓": ["苍古", "乐团剑"],
"「渔获」": ["鱼叉", "渔叉"], "「渔获」": ["鱼叉", "渔叉"],
"衔珠海皇": ["海皇", "咸鱼剑", "咸鱼大剑"], "衔珠海皇": ["海皇", "咸鱼剑", "咸鱼大剑"],
"匣里日月": ["日月"], "匣里日月": ["日月"],
"匣里灭辰": ["灭辰"], "匣里灭辰": ["灭辰"],
"匣里龙吟": ["龙吟"], "匣里龙吟": ["龙吟"],
"天空之翼": ["天空弓"], "天空之翼": ["天空弓"],
"天空之刃": ["天空剑"], "天空之刃": ["天空剑"],
"天空之卷": ["天空书", "厕纸"], "天空之卷": ["天空书", "厕纸"],
"天空之脊": ["天空枪", "薄荷枪"], "天空之脊": ["天空枪", "薄荷枪"],
"天空之傲": ["天空大剑"], "天空之傲": ["天空大剑"],
"四风原典": ["四风"], "四风原典": ["四风"],
"试作斩岩": ["斩岩"], "试作斩岩": ["斩岩"],
"试作星镰": ["星镰"], "试作星镰": ["星镰"],
"试作金珀": ["金珀"], "试作金珀": ["金珀"],
"试作古华": ["古华"], "试作古华": ["古华"],
"试作澹月": ["澹月"], "试作澹月": ["澹月"],
"千岩长枪": ["千岩枪"], "千岩长枪": ["千岩枪"],
"千岩古剑": ["千岩剑", "千岩大剑"], "千岩古剑": ["千岩剑", "千岩大剑"],
"暗巷闪光": ["暗巷剑"], "暗巷闪光": ["暗巷剑"],
"暗巷猎手": ["暗巷弓"], "暗巷猎手": ["暗巷弓"],
"阿莫斯之弓": ["阿莫斯", "ams", "痛苦弓"], "阿莫斯之弓": ["阿莫斯", "ams", "痛苦弓"],
"雾切之回光": ["雾切"], "雾切之回光": ["雾切"],
"飞雷之弦振": ["飞雷", "飞雷弓"], "飞雷之弦振": ["飞雷", "飞雷弓"],
@ -154,7 +317,6 @@ weapons = {
"不灭月华": ["月华"], "不灭月华": ["月华"],
"波乱月白经津": ["波乱", "月白", "波乱月白", "经津", "波波津"], "波乱月白经津": ["波乱", "月白", "波乱月白", "经津", "波波津"],
"若水": ["麒麟弓", "Aqua", "aqua"], "若水": ["麒麟弓", "Aqua", "aqua"],
"昭心": ["糟心"], "昭心": ["糟心"],
"幽夜华尔兹": ["幽夜", "幽夜弓", "华尔兹", "皇女弓"], "幽夜华尔兹": ["幽夜", "幽夜弓", "华尔兹", "皇女弓"],
"雪葬的星银": ["雪葬", "星银", "雪葬星银", "雪山大剑"], "雪葬的星银": ["雪葬", "星银", "雪葬星银", "雪山大剑"],
@ -172,15 +334,13 @@ weapons = {
"嘟嘟可故事集": ["嘟嘟可"], "嘟嘟可故事集": ["嘟嘟可"],
"辰砂之纺锤": ["辰砂", "辰砂纺锤", "纺锤"], "辰砂之纺锤": ["辰砂", "辰砂纺锤", "纺锤"],
"白辰之环": ["白辰", "白辰环"], "白辰之环": ["白辰", "白辰环"],
"决斗之枪": ["决斗枪", "决斗", "月卡枪"], "决斗之枪": ["决斗枪", "决斗", "月卡枪"],
"螭骨剑": ["螭骨", "丈育剑", "离骨剑", "月卡大剑"], "螭骨剑": ["螭骨", "丈育剑", "离骨剑", "月卡大剑"],
"黑剑": ["月卡剑"], "黑剑": ["月卡剑"],
"苍翠猎弓": ["绿弓", "月卡弓"], "苍翠猎弓": ["绿弓", "月卡弓"],
"讨龙英杰谭": ["讨龙"], "讨龙英杰谭": ["讨龙"],
"神射手之誓": ["脚气弓", "神射手"], "神射手之誓": ["脚气弓", "神射手"],
"黑缨枪": ["史莱姆枪"] "黑缨枪": ["史莱姆枪"],
} }
@ -209,4 +369,4 @@ def weaponToName(shortname: str) -> str:
@functools.lru_cache() @functools.lru_cache()
def weaponToId(name: str) -> int | None: def weaponToId(name: str) -> int | None:
"""获取武器ID""" """获取武器ID"""
return next((int(key) for key, value in WEAPON_DATA.items() if weaponToName(name) in value['name']), None) return next((int(key) for key, value in WEAPON_DATA.items() if weaponToName(name) in value["name"]), None)

View File

@ -9,20 +9,54 @@ def get_format_sub_item(artifact_attr: dict):
def get_comment(get_rate_num): def get_comment(get_rate_num):
data = {"1": ["破玩意谁能用啊,谁都用不了吧", "喂了吧,这东西做狗粮还能有点用", "抽卡有保底,圣遗物没有下限", data = {
"未来可期呢(笑)", "你出门一定很安全", "你是不是得罪米哈游了?", "……宁就是班尼特本特?", "1": [
"丢人!你给我退出提瓦特(", "不能说很糟糕,只能说特别不好"], "破玩意谁能用啊,谁都用不了吧",
"2": ["淡如清泉镇的圣水,莫得提升", "你怎么不强化啊?", "嗯嗯嗯好好好可以可以可以挺好挺好(敷衍)", "喂了吧,这东西做狗粮还能有点用",
"这就是日常,下一个", "洗洗还能吃bushi", "下次一定行……?", "派蒙平静地点了个赞", "抽卡有保底,圣遗物没有下限",
"不知道该说什么,就当留个纪念吧"], "未来可期呢(笑)",
"3": ["不能说有质变,只能说有提升", "过渡用的话没啥问题,大概", "再努努力吧", "嗯,差不多能用", "你出门一定很安全",
"这很合理", "达成成就“合格圣遗物”", "嗯,及格了,过渡用挺好的", "中规中矩,有待提升"], "你是不是得罪米哈游了?",
"4": ["以普遍理性而论,很好", "算是个很不戳的圣遗物了!", "很好,很有精神!", "再努努力,超越一下自己", "……宁就是班尼特本特?",
"感觉可以戴着它大杀四方了", "这就是大佬背包里的平均水平吧", "先锁上呗,这波不亏", "达成成就“高分圣遗物”", "丢人!你给我退出提瓦特(",
"这波对输出有很大提升啊(认真)", "我也想拥有这种分数的圣遗物(切实)"], "不能说很糟糕,只能说特别不好",
"5": ["多吃点好的,出门注意安全", "晒吧,欧不可耻,只是可恨", "没啥好说的,让我自闭一会", "达成成就“高分圣遗物”", ],
"怕不是以后开宝箱只能开出卷心菜", "吃了吗?没吃的话,吃我一拳", "我觉得这个游戏有问题", "这合理吗", "2": [
"这东西没啥用,给我吧(柠檬)", " "]} "淡如清泉镇的圣水,莫得提升",
"你怎么不强化啊?",
"嗯嗯嗯好好好可以可以可以挺好挺好(敷衍)",
"这就是日常,下一个",
"洗洗还能吃bushi",
"下次一定行……?",
"派蒙平静地点了个赞",
"不知道该说什么,就当留个纪念吧",
],
"3": ["不能说有质变,只能说有提升", "过渡用的话没啥问题,大概", "再努努力吧", "嗯,差不多能用", "这很合理", "达成成就“合格圣遗物”", "嗯,及格了,过渡用挺好的", "中规中矩,有待提升"],
"4": [
"以普遍理性而论,很好",
"算是个很不戳的圣遗物了!",
"很好,很有精神!",
"再努努力,超越一下自己",
"感觉可以戴着它大杀四方了",
"这就是大佬背包里的平均水平吧",
"先锁上呗,这波不亏",
"达成成就“高分圣遗物”",
"这波对输出有很大提升啊(认真)",
"我也想拥有这种分数的圣遗物(切实)",
],
"5": [
"多吃点好的,出门注意安全",
"晒吧,欧不可耻,只是可恨",
"没啥好说的,让我自闭一会",
"达成成就“高分圣遗物”",
"怕不是以后开宝箱只能开出卷心菜",
"吃了吗?没吃的话,吃我一拳",
"我觉得这个游戏有问题",
"这合理吗",
"这东西没啥用,给我吧(柠檬)",
" ",
],
}
try: try:
data_ = int(float(get_rate_num)) data_ = int(float(get_rate_num))
except ValueError: except ValueError:
@ -36,22 +70,22 @@ class ArtifactOcrRate:
OCR_URL = "https://api.genshin.pub/api/v1/app/ocr" OCR_URL = "https://api.genshin.pub/api/v1/app/ocr"
RATE_URL = "https://api.genshin.pub/api/v1/relic/rate" RATE_URL = "https://api.genshin.pub/api/v1/relic/rate"
HEADERS = { HEADERS = {
'authority': 'api.genshin.pub', "authority": "api.genshin.pub",
'accept': 'application/json, text/plain, */*', "accept": "application/json, text/plain, */*",
'accept-language': 'zh-CN,zh;q=0.9,zh-Hans;q=0.8,und;q=0.7,en;q=0.6,zh-Hant;q=0.5,ja;q=0.4', "accept-language": "zh-CN,zh;q=0.9,zh-Hans;q=0.8,und;q=0.7,en;q=0.6,zh-Hant;q=0.5,ja;q=0.4",
'content-type': 'application/json;charset=UTF-8', "content-type": "application/json;charset=UTF-8",
'dnt': '1', "dnt": "1",
'origin': 'https://genshin.pub', "origin": "https://genshin.pub",
'referer': 'https://genshin.pub/', "referer": "https://genshin.pub/",
'sec-ch-ua': '"Chromium";v="104", " Not A;Brand";v="99"', "sec-ch-ua": '"Chromium";v="104", " Not A;Brand";v="99"',
'sec-ch-ua-mobile': '?0', "sec-ch-ua-mobile": "?0",
'sec-ch-ua-platform': '"Windows"', "sec-ch-ua-platform": '"Windows"',
'sec-fetch-dest': 'empty', "sec-fetch-dest": "empty",
'sec-fetch-mode': 'cors', "sec-fetch-mode": "cors",
'sec-fetch-site': 'same-site', "sec-fetch-site": "same-site",
'sec-gpc': '1', "sec-gpc": "1",
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
'Chrome/104.0.5112.115 Safari/537.36', "Chrome/104.0.5112.115 Safari/537.36",
} }
def __init__(self): def __init__(self):

View File

@ -43,8 +43,14 @@ class PostInfo(BaseModel):
created_at = post["created_at"] created_at = post["created_at"]
user = _data_post["user"] # 用户数据 user = _data_post["user"] # 用户数据
user_uid = user["uid"] # 用户ID user_uid = user["uid"] # 用户ID
return PostInfo(_data=data, post_id=post_id, user_uid=user_uid, subject=subject, image_urls=image_urls, return PostInfo(
created_at=created_at) _data=data,
post_id=post_id,
user_uid=user_uid,
subject=subject,
image_urls=image_urls,
created_at=created_at,
)
def __getitem__(self, item): def __getitem__(self, item):
return self._data[item] return self._data[item]

View File

@ -16,7 +16,7 @@ RECOGNIZE_SERVER = {
def get_device_id(name: str) -> str: def get_device_id(name: str) -> str:
return str(uuid.uuid3(uuid.NAMESPACE_URL, name)).replace('-', '').upper() return str(uuid.uuid3(uuid.NAMESPACE_URL, name)).replace("-", "").upper()
def md5(text: str) -> str: def md5(text: str) -> str:
@ -26,7 +26,7 @@ def md5(text: str) -> str:
def random_text(num: int) -> str: def random_text(num: int) -> str:
return ''.join(random.sample(string.ascii_lowercase + string.digits, num)) return "".join(random.sample(string.ascii_lowercase + string.digits, num))
def timestamp() -> int: def timestamp() -> int:
@ -58,6 +58,6 @@ def get_recognize_server(uid: int) -> str:
def get_headers(): def get_headers():
headers = { headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36", "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36",
} }
return headers return headers

View File

@ -20,8 +20,10 @@ class Hyperion:
POST_FULL_URL = "https://bbs-api.mihoyo.com/post/wapi/getPostFull" POST_FULL_URL = "https://bbs-api.mihoyo.com/post/wapi/getPostFull"
POST_FULL_IN_COLLECTION_URL = "https://bbs-api.mihoyo.com/post/wapi/getPostFullInCollection" POST_FULL_IN_COLLECTION_URL = "https://bbs-api.mihoyo.com/post/wapi/getPostFullInCollection"
GET_NEW_LIST_URL = "https://bbs-api.mihoyo.com/post/wapi/getNewsList" GET_NEW_LIST_URL = "https://bbs-api.mihoyo.com/post/wapi/getNewsList"
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " \ USER_AGENT = (
"Chrome/90.0.4430.72 Safari/537.36" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/90.0.4430.72 Safari/537.36"
)
def __init__(self): def __init__(self):
self.client = HOYORequest(headers=self.get_headers()) self.client = HOYORequest(headers=self.get_headers())
@ -42,34 +44,31 @@ class Hyperion:
if entries is None: if entries is None:
return -1 return -1
try: try:
art_id = int(entries.get('article_id')) art_id = int(entries.get("article_id"))
except (IndexError, ValueError, TypeError): except (IndexError, ValueError, TypeError):
return -1 return -1
return art_id return art_id
def get_headers(self, referer: str = "https://bbs.mihoyo.com/"): def get_headers(self, referer: str = "https://bbs.mihoyo.com/"):
return { return {"User-Agent": self.USER_AGENT, "Referer": referer}
"User-Agent": self.USER_AGENT,
"Referer": referer
}
@staticmethod @staticmethod
def get_list_url_params(forum_id: int, is_good: bool = False, is_hot: bool = False, def get_list_url_params(forum_id: int, is_good: bool = False, is_hot: bool = False, page_size: int = 20) -> dict:
page_size: int = 20) -> dict:
params = { params = {
"forum_id": forum_id, "forum_id": forum_id,
"gids": 2, "gids": 2,
"is_good": is_good, "is_good": is_good,
"is_hot": is_hot, "is_hot": is_hot,
"page_size": page_size, "page_size": page_size,
"sort_type": 1 "sort_type": 1,
} }
return params return params
@staticmethod @staticmethod
def get_images_params(resize: int = 600, quality: int = 80, auto_orient: int = 0, interlace: int = 1, def get_images_params(
images_format: str = "jpg"): resize: int = 600, quality: int = 80, auto_orient: int = 0, interlace: int = 1, images_format: str = "jpg"
):
""" """
image/resize,s_600/quality,q_80/auto-orient,0/interlace,1/format,jpg image/resize,s_600/quality,q_80/auto-orient,0/interlace,1/format,jpg
:param resize: 图片大小 :param resize: 图片大小
@ -79,25 +78,19 @@ class Hyperion:
:param images_format: 图片格式 :param images_format: 图片格式
:return: :return:
""" """
params = f"image/resize,s_{resize}/quality,q_{quality}/auto-orient," \ params = (
f"{auto_orient}/interlace,{interlace}/format,{images_format}" f"image/resize,s_{resize}/quality,q_{quality}/auto-orient,"
f"{auto_orient}/interlace,{interlace}/format,{images_format}"
)
return {"x-oss-process": params} return {"x-oss-process": params}
async def get_post_full_in_collection(self, collection_id: int, gids: int = 2, order_type=1) -> JSONDict: async def get_post_full_in_collection(self, collection_id: int, gids: int = 2, order_type=1) -> JSONDict:
params = { params = {"collection_id": collection_id, "gids": gids, "order_type": order_type}
"collection_id": collection_id,
"gids": gids,
"order_type": order_type
}
response = await self.client.get(url=self.POST_FULL_IN_COLLECTION_URL, params=params) response = await self.client.get(url=self.POST_FULL_IN_COLLECTION_URL, params=params)
return response return response
async def get_post_info(self, gids: int, post_id: int, read: int = 1) -> PostInfo: async def get_post_info(self, gids: int, post_id: int, read: int = 1) -> PostInfo:
params = { params = {"gids": gids, "post_id": post_id, "read": read}
"gids": gids,
"post_id": post_id,
"read": read
}
response = await self.client.get(self.POST_FULL_URL, params=params) response = await self.client.get(self.POST_FULL_URL, params=params)
return PostInfo.paste_data(response) return PostInfo.paste_data(response)
@ -128,11 +121,7 @@ class Hyperion:
?gids=2&page_size=20&type=3 ?gids=2&page_size=20&type=3
:return: :return:
""" """
params = { params = {"gids": gids, "page_size": page_size, "type": type_id}
"gids": gids,
"page_size": page_size,
"type": type_id
}
response = await self.client.get(url=self.GET_NEW_LIST_URL, params=params) response = await self.client.get(url=self.GET_NEW_LIST_URL, params=params)
return response return response
@ -144,12 +133,14 @@ class GachaInfo:
GACHA_LIST_URL = "https://webstatic.mihoyo.com/hk4e/gacha_info/cn_gf01/gacha/list.json" GACHA_LIST_URL = "https://webstatic.mihoyo.com/hk4e/gacha_info/cn_gf01/gacha/list.json"
GACHA_INFO_URL = "https://webstatic.mihoyo.com/hk4e/gacha_info/cn_gf01/%s/zh-cn.json" GACHA_INFO_URL = "https://webstatic.mihoyo.com/hk4e/gacha_info/cn_gf01/%s/zh-cn.json"
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " \ USER_AGENT = (
"Chrome/90.0.4430.72 Safari/537.36" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/90.0.4430.72 Safari/537.36"
)
def __init__(self): def __init__(self):
self.headers = { self.headers = {
'User-Agent': self.USER_AGENT, "User-Agent": self.USER_AGENT,
} }
self.client = HOYORequest(headers=self.headers) self.client = HOYORequest(headers=self.headers)
self.cache = {} self.cache = {}
@ -177,32 +168,35 @@ class GachaInfo:
class SignIn: class SignIn:
LOGIN_URL = "https://webapi.account.mihoyo.com/Api/login_by_mobilecaptcha" LOGIN_URL = "https://webapi.account.mihoyo.com/Api/login_by_mobilecaptcha"
S_TOKEN_URL = "https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?" \ S_TOKEN_URL = (
"login_ticket={0}&token_types=3&uid={1}" "https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?" "login_ticket={0}&token_types=3&uid={1}"
)
BBS_URL = "https://api-takumi.mihoyo.com/account/auth/api/webLoginByMobile" BBS_URL = "https://api-takumi.mihoyo.com/account/auth/api/webLoginByMobile"
USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " \ USER_AGENT = (
"AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15"
)
HEADERS = { HEADERS = {
"Host": "webapi.account.mihoyo.com", "Host": "webapi.account.mihoyo.com",
"Connection": "keep-alive", "Connection": "keep-alive",
"sec-ch-ua": "\".Not/A)Brand\";v=\"99\", \"Microsoft Edge\";v=\"103\", \"Chromium\";v=\"103\"", "sec-ch-ua": '".Not/A)Brand";v="99", "Microsoft Edge";v="103", "Chromium";v="103"',
"DNT": "1", "DNT": "1",
"x-rpc-device_model": "OS X 10.15.7", "x-rpc-device_model": "OS X 10.15.7",
"sec-ch-ua-mobile": "?0", "sec-ch-ua-mobile": "?0",
"User-Agent": USER_AGENT, "User-Agent": USER_AGENT,
'x-rpc-device_id': get_device_id(USER_AGENT), "x-rpc-device_id": get_device_id(USER_AGENT),
"Accept": "application/json, text/plain, */*", "Accept": "application/json, text/plain, */*",
"x-rpc-device_name": "Microsoft Edge 103.0.1264.62", "x-rpc-device_name": "Microsoft Edge 103.0.1264.62",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"x-rpc-client_type": "4", "x-rpc-client_type": "4",
"sec-ch-ua-platform": "\"macOS\"", "sec-ch-ua-platform": '"macOS"',
"Origin": "https://user.mihoyo.com", "Origin": "https://user.mihoyo.com",
"Sec-Fetch-Site": "same-site", "Sec-Fetch-Site": "same-site",
"Sec-Fetch-Mode": "cors", "Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty", "Sec-Fetch-Dest": "empty",
"Referer": "https://user.mihoyo.com/", "Referer": "https://user.mihoyo.com/",
"Accept-Encoding": "gzip, deflate, br", "Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6" "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
} }
BBS_HEADERS = { BBS_HEADERS = {
"Host": "api-takumi.mihoyo.com", "Host": "api-takumi.mihoyo.com",
@ -213,7 +207,7 @@ class SignIn:
"Accept": "application/json, text/plain, */*", "Accept": "application/json, text/plain, */*",
"User-Agent": USER_AGENT, "User-Agent": USER_AGENT,
"Referer": "https://bbs.mihoyo.com/", "Referer": "https://bbs.mihoyo.com/",
"Accept-Language": "zh-CN,zh-Hans;q=0.9" "Accept-Language": "zh-CN,zh-Hans;q=0.9",
} }
def __init__(self, phone: int): def __init__(self, phone: int):
@ -251,7 +245,7 @@ class SignIn:
data = await self.client.post( data = await self.client.post(
self.LOGIN_URL, self.LOGIN_URL,
data={"mobile": str(self.phone), "mobile_captcha": str(captcha), "source": "user.mihoyo.com"}, data={"mobile": str(self.phone), "mobile_captcha": str(captcha), "source": "user.mihoyo.com"},
headers=self.HEADERS headers=self.HEADERS,
) )
res_json = data.json() res_json = data.json()
if self.check_error(res_json): if self.check_error(res_json):
@ -265,8 +259,7 @@ class SignIn:
async def get_s_token(self): async def get_s_token(self):
data = await self.client.get( data = await self.client.get(
self.S_TOKEN_URL.format(self.cookie["login_ticket"], self.uid), self.S_TOKEN_URL.format(self.cookie["login_ticket"], self.uid), headers={"User-Agent": self.USER_AGENT}
headers={"User-Agent": self.USER_AGENT}
) )
res_json = data.json() res_json = data.json()
res_data = res_json.get("data", {}).get("list", []) res_data = res_json.get("data", {}).get("list", [])
@ -283,8 +276,8 @@ class SignIn:
"mobile": str(self.phone), "mobile": str(self.phone),
"captcha": str(captcha), "captcha": str(captcha),
"action_type": "login", "action_type": "login",
"token_type": 6 "token_type": 6,
} },
) )
res_json = data.json() res_json = data.json()
if self.check_error(res_json): if self.check_error(res_json):

View File

@ -8,9 +8,9 @@ from modules.apihelper.typedefs import POST_DATA, JSON_DATA
class HOYORequest(HTTPXRequest): class HOYORequest(HTTPXRequest):
async def get(
async def get(self, url: str, *args, de_json: bool = True, re_json_data: bool = False, **kwargs) \ self, url: str, *args, de_json: bool = True, re_json_data: bool = False, **kwargs
-> Union[POST_DATA, JSON_DATA, bytes]: ) -> Union[POST_DATA, JSON_DATA, bytes]:
try: try:
response = await self._client.get(url=url, *args, **kwargs) response = await self._client.get(url=url, *args, **kwargs)
except httpx.TimeoutException as err: except httpx.TimeoutException as err:

View File

@ -6,7 +6,6 @@ import httpx
class HTTPXRequest(AbstractAsyncContextManager): class HTTPXRequest(AbstractAsyncContextManager):
def __init__(self, *args, headers=None, **kwargs): def __init__(self, *args, headers=None, **kwargs):
self._client = httpx.AsyncClient(headers=headers, *args, **kwargs) self._client = httpx.AsyncClient(headers=headers, *args, **kwargs)
@ -18,8 +17,9 @@ class HTTPXRequest(AbstractAsyncContextManager):
await self.shutdown() await self.shutdown()
raise exc raise exc
async def __aexit__(self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], async def __aexit__(
exc_tb: Optional[TracebackType]) -> None: self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
) -> None:
await self.initialize() await self.initialize()

View File

@ -15,29 +15,33 @@ with open(_fix_skills_level_file, "r", encoding="utf-8") as f:
class ArtifactStatsTheory: class ArtifactStatsTheory:
def __init__(self, character_name: str): def __init__(self, character_name: str):
self.character_name = character_name self.character_name = character_name
fight_prop_rule_list = fight_prop_rule_data.get(self.character_name, []) fight_prop_rule_list = fight_prop_rule_data.get(self.character_name, [])
self.main_prop = [FightProp(fight_prop_rule) for fight_prop_rule in fight_prop_rule_list] self.main_prop = [FightProp(fight_prop_rule) for fight_prop_rule in fight_prop_rule_list]
if not self.main_prop: if not self.main_prop:
self.main_prop = [FightProp.FIGHT_PROP_CRITICAL, FightProp.FIGHT_PROP_CRITICAL_HURT, self.main_prop = [
FightProp.FIGHT_PROP_ATTACK_PERCENT] FightProp.FIGHT_PROP_CRITICAL,
FightProp.FIGHT_PROP_CRITICAL_HURT,
FightProp.FIGHT_PROP_ATTACK_PERCENT,
]
# 修正要评分的数值词条 # 修正要评分的数值词条
if FightProp.FIGHT_PROP_ATTACK_PERCENT in self.main_prop and FightProp.FIGHT_PROP_ATTACK not in self.main_prop: if FightProp.FIGHT_PROP_ATTACK_PERCENT in self.main_prop and FightProp.FIGHT_PROP_ATTACK not in self.main_prop:
self.main_prop.append(FightProp.FIGHT_PROP_ATTACK) self.main_prop.append(FightProp.FIGHT_PROP_ATTACK)
if FightProp.FIGHT_PROP_HP_PERCENT in self.main_prop and FightProp.FIGHT_PROP_HP not in self.main_prop: if FightProp.FIGHT_PROP_HP_PERCENT in self.main_prop and FightProp.FIGHT_PROP_HP not in self.main_prop:
self.main_prop.append(FightProp.FIGHT_PROP_HP) self.main_prop.append(FightProp.FIGHT_PROP_HP)
if FightProp.FIGHT_PROP_DEFENSE_PERCENT in self.main_prop and \ if (
FightProp.FIGHT_PROP_DEFENSE not in self.main_prop: FightProp.FIGHT_PROP_DEFENSE_PERCENT in self.main_prop
and FightProp.FIGHT_PROP_DEFENSE not in self.main_prop
):
self.main_prop.append(FightProp.FIGHT_PROP_DEFENSE) self.main_prop.append(FightProp.FIGHT_PROP_DEFENSE)
def theory(self, sub_stats: EquipmentsStats) -> float: def theory(self, sub_stats: EquipmentsStats) -> float:
"""圣遗物副词条评分 """圣遗物副词条评分
Args: Args:
sub_stats: 圣遗物对象 sub_stats: 圣遗物对象
Returns: Returns:
返回得分 返回得分
""" """
score: float = 0 score: float = 0
if sub_stats.prop_id in map(lambda x: x.name, self.main_prop): if sub_stats.prop_id in map(lambda x: x.name, self.main_prop):

View File

@ -16,7 +16,7 @@ from pydantic import (
) )
from typing_extensions import Self from typing_extensions import Self
__all__ = ['Model', 'WikiModel', 'HONEY_HOST'] __all__ = ["Model", "WikiModel", "HONEY_HOST"]
HONEY_HOST = URL("https://genshin.honeyhunterworld.com/") HONEY_HOST = URL("https://genshin.honeyhunterworld.com/")
@ -39,13 +39,13 @@ class WikiModel(Model):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
"""wiki所用到的基类 """wiki所用到的基类
Attributes: Attributes:
id (:obj:`int`): ID id (:obj:`int`): ID
name (:obj:`str`): 名称 name (:obj:`str`): 名称
rarity (:obj:`int`): 星级 rarity (:obj:`int`): 星级
_client (:class:`httpx.AsyncClient`): 发起 http 请求的 client _client (:class:`httpx.AsyncClient`): 发起 http 请求的 client
""" """
_client: ClassVar[AsyncClient] = AsyncClient() _client: ClassVar[AsyncClient] = AsyncClient()
id: str id: str
@ -107,7 +107,7 @@ class WikiModel(Model):
返回对应的 WikiModel 返回对应的 WikiModel
""" """
response = await cls._client_get(url) response = await cls._client_get(url)
return await cls._parse_soup(BeautifulSoup(response.text, 'lxml')) return await cls._parse_soup(BeautifulSoup(response.text, "lxml"))
@classmethod @classmethod
async def get_by_id(cls, id_: str) -> Self: async def get_by_id(cls, id_: str) -> Self:
@ -152,7 +152,7 @@ class WikiModel(Model):
返回能爬到的所有的 WikiModel 所组成的 List 返回能爬到的所有的 WikiModel 所组成的 List
""" """
queue: Queue[Self] = Queue() # 存放 Model 的队列 queue: Queue[Self] = Queue() # 存放 Model 的队列
signal = Value('i', 0) # 一个用于异步任务同步的信号 signal = Value("i", 0) # 一个用于异步任务同步的信号
async def task(u): async def task(u):
# 包装的爬虫任务 # 包装的爬虫任务
@ -196,18 +196,18 @@ class WikiModel(Model):
""" """
urls = cls.scrape_urls() urls = cls.scrape_urls()
queue: Queue[Union[str, Tuple[str, URL]]] = Queue() # 存放 Model 的队列 queue: Queue[Union[str, Tuple[str, URL]]] = Queue() # 存放 Model 的队列
signal = Value('i', len(urls)) # 一个用于异步任务同步的信号,初始值为存放所需要爬取的页面数 signal = Value("i", len(urls)) # 一个用于异步任务同步的信号,初始值为存放所需要爬取的页面数
async def task(page: URL): async def task(page: URL):
"""包装的爬虫任务""" """包装的爬虫任务"""
response = await cls._client_get(page) response = await cls._client_get(page)
# 从页面中获取对应的 chaos data (未处理的json格式字符串) # 从页面中获取对应的 chaos data (未处理的json格式字符串)
chaos_data = re.findall(r'sortable_data\.push\((.*)\);\s*sortable_cur_page', response.text)[0] chaos_data = re.findall(r"sortable_data\.push\((.*)\);\s*sortable_cur_page", response.text)[0]
json_data = json.loads(chaos_data) # 转为 json json_data = json.loads(chaos_data) # 转为 json
for data in json_data: # 遍历 json for data in json_data: # 遍历 json
data_name = re.findall(r'>(.*)<', data[1])[0] # 获取 Model 的名称 data_name = re.findall(r">(.*)<", data[1])[0] # 获取 Model 的名称
if with_url: # 如果需要返回对应的 url if with_url: # 如果需要返回对应的 url
data_url = HONEY_HOST.join(re.findall(r'\"(.*?)\"', data[0])[0]) data_url = HONEY_HOST.join(re.findall(r"\"(.*?)\"", data[0])[0])
await queue.put((data_name, data_url)) await queue.put((data_name, data_url))
else: else:
await queue.put(data_name) await queue.put(data_name)

View File

@ -15,6 +15,7 @@ class Birth(Model):
day: day:
month: month:
""" """
day: int day: int
month: int month: int
@ -26,6 +27,7 @@ class CharacterAscension(Model):
level: 等级突破材料 level: 等级突破材料
skill: 技能/天赋培养材料 skill: 技能/天赋培养材料
""" """
level: List[str] = [] level: List[str] = []
skill: List[str] = [] skill: List[str] = []
@ -42,6 +44,7 @@ class CharacterState(Model):
CD: 暴击伤害 CD: 暴击伤害
bonus: 突破属性 bonus: 突破属性
""" """
level: str level: str
HP: int HP: int
ATK: float ATK: float
@ -97,23 +100,23 @@ class Character(WikiModel):
return [HONEY_HOST.join("fam_chars/?lang=CHS")] return [HONEY_HOST.join("fam_chars/?lang=CHS")]
@classmethod @classmethod
async def _parse_soup(cls, soup: BeautifulSoup) -> 'Character': async def _parse_soup(cls, soup: BeautifulSoup) -> "Character":
"""解析角色页""" """解析角色页"""
soup = soup.select('.wp-block-post-content')[0] soup = soup.select(".wp-block-post-content")[0]
tables = soup.find_all('table') tables = soup.find_all("table")
table_rows = tables[0].find_all('tr') table_rows = tables[0].find_all("tr")
def get_table_text(row_num: int) -> str: def get_table_text(row_num: int) -> str:
"""一个快捷函数,用于返回表格对应行的最后一个单元格中的文本""" """一个快捷函数,用于返回表格对应行的最后一个单元格中的文本"""
return table_rows[row_num].find_all('td')[-1].text.replace('\xa0', '') return table_rows[row_num].find_all("td")[-1].text.replace("\xa0", "")
id_ = re.findall(r'img/(.*?_\d+)_.*', table_rows[0].find('img').attrs['src'])[0] id_ = re.findall(r"img/(.*?_\d+)_.*", table_rows[0].find("img").attrs["src"])[0]
name = get_table_text(0) name = get_table_text(0)
if name != '旅行者': # 如果角色名不是 旅行者 if name != "旅行者": # 如果角色名不是 旅行者
title = get_table_text(1) title = get_table_text(1)
occupation = get_table_text(2) occupation = get_table_text(2)
association = Association.convert(get_table_text(3).lower().title()) association = Association.convert(get_table_text(3).lower().title())
rarity = len(table_rows[4].find_all('img')) rarity = len(table_rows[4].find_all("img"))
weapon_type = WeaponType[get_table_text(5)] weapon_type = WeaponType[get_table_text(5)]
element = Element[get_table_text(6)] element = Element[get_table_text(6)]
birth = Birth(day=int(get_table_text(7)), month=int(get_table_text(8))) birth = Birth(day=int(get_table_text(7)), month=int(get_table_text(8)))
@ -123,11 +126,11 @@ class Character(WikiModel):
en_cv = get_table_text(13) en_cv = get_table_text(13)
kr_cv = get_table_text(14) kr_cv = get_table_text(14)
else: else:
name = '' if id_.endswith('5') else '' name = "" if id_.endswith("5") else ""
title = get_table_text(0) title = get_table_text(0)
occupation = get_table_text(1) occupation = get_table_text(1)
association = Association.convert(get_table_text(2).lower().title()) association = Association.convert(get_table_text(2).lower().title())
rarity = len(table_rows[3].find_all('img')) rarity = len(table_rows[3].find_all("img"))
weapon_type = WeaponType[get_table_text(4)] weapon_type = WeaponType[get_table_text(4)]
element = Element[get_table_text(5)] element = Element[get_table_text(5)]
birth = None birth = None
@ -139,30 +142,50 @@ class Character(WikiModel):
description = get_table_text(-3) description = get_table_text(-3)
ascension = CharacterAscension( ascension = CharacterAscension(
level=[ level=[
target[0] for i in table_rows[-2].find_all('a') target[0]
if (target := re.findall(r'/(.*)/', i.attrs['href'])) # 过滤掉错误的材料(honey网页的bug) for i in table_rows[-2].find_all("a")
if (target := re.findall(r"/(.*)/", i.attrs["href"])) # 过滤掉错误的材料(honey网页的bug)
], ],
skill=[re.findall(r'/(.*)/', i.attrs['href'])[0] for i in table_rows[-1].find_all('a')] skill=[re.findall(r"/(.*)/", i.attrs["href"])[0] for i in table_rows[-1].find_all("a")],
) )
stats = [] stats = []
for row in tables[2].find_all('tr')[1:]: for row in tables[2].find_all("tr")[1:]:
cells = row.find_all('td') cells = row.find_all("td")
stats.append( stats.append(
CharacterState( CharacterState(
level=cells[0].text, HP=cells[1].text, ATK=cells[2].text, DEF=cells[3].text, level=cells[0].text,
CR=cells[4].text, CD=cells[5].text, bonus=cells[6].text HP=cells[1].text,
ATK=cells[2].text,
DEF=cells[3].text,
CR=cells[4].text,
CD=cells[5].text,
bonus=cells[6].text,
) )
) )
return Character( return Character(
id=id_, name=name, title=title, occupation=occupation, association=association, weapon_type=weapon_type, id=id_,
element=element, birth=birth, constellation=constellation, cn_cv=cn_cv, jp_cv=jp_cv, rarity=rarity, name=name,
en_cv=en_cv, kr_cv=kr_cv, description=description, ascension=ascension, stats=stats title=title,
occupation=occupation,
association=association,
weapon_type=weapon_type,
element=element,
birth=birth,
constellation=constellation,
cn_cv=cn_cv,
jp_cv=jp_cv,
rarity=rarity,
en_cv=en_cv,
kr_cv=kr_cv,
description=description,
ascension=ascension,
stats=stats,
) )
@classmethod @classmethod
async def get_url_by_name(cls, name: str) -> Optional[URL]: async def get_url_by_name(cls, name: str) -> Optional[URL]:
# 重写此函数的目的是处理主角名字的 ID # 重写此函数的目的是处理主角名字的 ID
_map = {'': "playergirl_007", '': "playerboy_005"} _map = {"": "playergirl_007", "": "playerboy_005"}
if (id_ := _map.get(name)) is not None: if (id_ := _map.get(name)) is not None:
return await cls.get_url_by_id(id_) return await cls.get_url_by_id(id_)
return await super(Character, cls).get_url_by_name(name) return await super(Character, cls).get_url_by_name(name)
@ -170,8 +193,8 @@ class Character(WikiModel):
@property @property
def icon(self) -> CharacterIcon: def icon(self) -> CharacterIcon:
return CharacterIcon( return CharacterIcon(
icon=str(HONEY_HOST.join(f'/img/{self.id}_icon.webp')), icon=str(HONEY_HOST.join(f"/img/{self.id}_icon.webp")),
side=str(HONEY_HOST.join(f'/img/{self.id}_side_icon.webp')), side=str(HONEY_HOST.join(f"/img/{self.id}_side_icon.webp")),
gacha=str(HONEY_HOST.join(f'/img/{self.id}_gacha_card.webp')), gacha=str(HONEY_HOST.join(f"/img/{self.id}_gacha_card.webp")),
splash=str(HONEY_HOST.join(f'/img/{self.id}_gacha_splash.webp')) splash=str(HONEY_HOST.join(f"/img/{self.id}_gacha_splash.webp")),
) )

View File

@ -6,21 +6,21 @@ from httpx import URL
from modules.wiki.base import HONEY_HOST, WikiModel from modules.wiki.base import HONEY_HOST, WikiModel
__all__ = ['Material'] __all__ = ["Material"]
WEEKDAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
class Material(WikiModel): class Material(WikiModel):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
"""武器、角色培养素材 """武器、角色培养素材
Attributes: Attributes:
type: 类型 type: 类型
weekdays: 每周开放的时间 weekdays: 每周开放的时间
source: 获取方式 source: 获取方式
description: 描述 description: 描述
""" """
type: str type: str
source: Optional[List[str]] = None source: Optional[List[str]] = None
weekdays: Optional[List[int]] = None weekdays: Optional[List[int]] = None
@ -28,8 +28,8 @@ class Material(WikiModel):
@staticmethod @staticmethod
def scrape_urls() -> List[URL]: def scrape_urls() -> List[URL]:
weapon = [HONEY_HOST.join(f'fam_wep_{i}/?lang=CHS') for i in ['primary', 'secondary', 'common']] weapon = [HONEY_HOST.join(f"fam_wep_{i}/?lang=CHS") for i in ["primary", "secondary", "common"]]
talent = [HONEY_HOST.join(f'fam_talent_{i}/?lang=CHS') for i in ['book', 'boss', 'common', 'reward']] talent = [HONEY_HOST.join(f"fam_talent_{i}/?lang=CHS") for i in ["book", "boss", "common", "reward"]]
return weapon + talent return weapon + talent
@classmethod @classmethod
@ -39,37 +39,37 @@ class Material(WikiModel):
@classmethod @classmethod
async def _parse_soup(cls, soup: BeautifulSoup) -> "Material": async def _parse_soup(cls, soup: BeautifulSoup) -> "Material":
"""解析突破素材页""" """解析突破素材页"""
soup = soup.select('.wp-block-post-content')[0] soup = soup.select(".wp-block-post-content")[0]
tables = soup.find_all('table') tables = soup.find_all("table")
table_rows = tables[0].find_all('tr') table_rows = tables[0].find_all("tr")
def get_table_row(target: str): def get_table_row(target: str):
"""一个便捷函数,用于返回对应表格头的对应行的最后一个单元格中的文本""" """一个便捷函数,用于返回对应表格头的对应行的最后一个单元格中的文本"""
for row in table_rows: for row in table_rows:
if target in row.find('td').text: if target in row.find("td").text:
return row.find_all('td')[-1] return row.find_all("td")[-1]
def get_table_text(row_num: int) -> str: def get_table_text(row_num: int) -> str:
"""一个便捷函数,用于返回表格对应行的最后一个单元格中的文本""" """一个便捷函数,用于返回表格对应行的最后一个单元格中的文本"""
return table_rows[row_num].find_all('td')[-1].text.replace('\xa0', '') return table_rows[row_num].find_all("td")[-1].text.replace("\xa0", "")
id_ = re.findall(r'/img/(.*?)\.webp', str(table_rows[0]))[0] id_ = re.findall(r"/img/(.*?)\.webp", str(table_rows[0]))[0]
name = get_table_text(0) name = get_table_text(0)
rarity = len(table_rows[3].find_all('img')) rarity = len(table_rows[3].find_all("img"))
type_ = get_table_text(1) type_ = get_table_text(1)
if (item_source := get_table_row('Item Source')) is not None: if (item_source := get_table_row("Item Source")) is not None:
item_source = list( item_source = list(
# filter 在这里的作用是过滤掉为空的数据 # filter 在这里的作用是过滤掉为空的数据
filter(lambda x: x, item_source.encode_contents().decode().split('<br/>')) filter(lambda x: x, item_source.encode_contents().decode().split("<br/>"))
) )
if (alter_source := get_table_row('Alternative Item')) is not None: if (alter_source := get_table_row("Alternative Item")) is not None:
alter_source = list( alter_source = list(
# filter 在这里的作用是过滤掉为空的数据 # filter 在这里的作用是过滤掉为空的数据
filter(lambda x: x, alter_source.encode_contents().decode().split('<br/>')) filter(lambda x: x, alter_source.encode_contents().decode().split("<br/>"))
) )
source = list(sorted(set((item_source or []) + (alter_source or [])))) source = list(sorted(set((item_source or []) + (alter_source or []))))
if (weekdays := get_table_row('Weekday')) is not None: if (weekdays := get_table_row("Weekday")) is not None:
weekdays = [*(WEEKDAYS.index(weekdays.text.replace('\xa0', '').split(',')[0]) + 3 * i for i in range(2)), 6] weekdays = [*(WEEKDAYS.index(weekdays.text.replace("\xa0", "").split(",")[0]) + 3 * i for i in range(2)), 6]
description = get_table_text(-1) description = get_table_text(-1)
return Material( return Material(
id=id_, name=name, rarity=rarity, type=type_, description=description, source=source, weekdays=weekdays id=id_, name=name, rarity=rarity, type=type_, description=description, source=source, weekdays=weekdays
@ -77,4 +77,4 @@ class Material(WikiModel):
@property @property
def icon(self) -> str: def icon(self) -> str:
return str(HONEY_HOST.join(f'/img/{self.id}.webp')) return str(HONEY_HOST.join(f"/img/{self.id}.webp"))

View File

@ -6,41 +6,43 @@ from typing_extensions import Self
from modules.wiki.base import HONEY_HOST from modules.wiki.base import HONEY_HOST
__all__ = [ __all__ = [
'Element', "Element",
'WeaponType', "WeaponType",
'AttributeType', "AttributeType",
'Association', "Association",
] ]
class Element(Enum): class Element(Enum):
"""元素""" """元素"""
Pyro = ''
Hydro = '' Pyro = ""
Electro = '' Hydro = ""
Cryo = '' Electro = ""
Dendro = '' Cryo = ""
Anemo = '' Dendro = ""
Geo = '' Anemo = ""
Multi = '' # 主角 Geo = ""
Multi = "" # 主角
_WEAPON_ICON_MAP = { _WEAPON_ICON_MAP = {
'Sword': HONEY_HOST.join('img/s_23101.png'), "Sword": HONEY_HOST.join("img/s_23101.png"),
'Claymore': HONEY_HOST.join('img/s_163101.png'), "Claymore": HONEY_HOST.join("img/s_163101.png"),
'Polearm': HONEY_HOST.join('img/s_233101.png'), "Polearm": HONEY_HOST.join("img/s_233101.png"),
'Catalyst': HONEY_HOST.join('img/s_43101.png'), "Catalyst": HONEY_HOST.join("img/s_43101.png"),
'Bow': HONEY_HOST.join('img/s_213101.png'), "Bow": HONEY_HOST.join("img/s_213101.png"),
} }
class WeaponType(Enum): class WeaponType(Enum):
"""武器类型""" """武器类型"""
Sword = '单手剑'
Claymore = '双手剑' Sword = "单手剑"
Polearm = '长柄武器' Claymore = "双手剑"
Catalyst = '法器' Polearm = "长柄武器"
Bow = '' Catalyst = "法器"
Bow = ""
def icon_url(self) -> str: def icon_url(self) -> str:
return str(_WEAPON_ICON_MAP.get(self.name)) return str(_WEAPON_ICON_MAP.get(self.name))
@ -49,17 +51,17 @@ class WeaponType(Enum):
_ATTR_TYPE_MAP = { _ATTR_TYPE_MAP = {
# 这个字典用于将 Honey 页面中遇到的 属性的缩写的字符 转为 AttributeType 的字符 # 这个字典用于将 Honey 页面中遇到的 属性的缩写的字符 转为 AttributeType 的字符
# 例如 Honey 页面上写的 HP% 则对应 HP_p # 例如 Honey 页面上写的 HP% 则对应 HP_p
"HP": ['Health'], "HP": ["Health"],
"HP_p": ['HP%', 'Health %'], "HP_p": ["HP%", "Health %"],
"ATK": ['Attack'], "ATK": ["Attack"],
"ATK_p": ['Atk%', 'Attack %'], "ATK_p": ["Atk%", "Attack %"],
"DEF": ['Defense'], "DEF": ["Defense"],
"DEF_p": ['Def%', 'Defense %'], "DEF_p": ["Def%", "Defense %"],
"EM": ['Elemental Mastery'], "EM": ["Elemental Mastery"],
"ER": ['ER%', 'Energy Recharge %'], "ER": ["ER%", "Energy Recharge %"],
"CR": ['CrR%', 'Critical Rate %', 'CritRate%'], "CR": ["CrR%", "Critical Rate %", "CritRate%"],
"CD": ['Crd%', 'Critical Damage %', 'CritDMG%'], "CD": ["Crd%", "Critical Damage %", "CritDMG%"],
"PD": ['Phys%', 'Physical Damage %'], "PD": ["Phys%", "Physical Damage %"],
"HB": [], "HB": [],
"Pyro": [], "Pyro": [],
"Hydro": [], "Hydro": [],
@ -73,6 +75,7 @@ _ATTR_TYPE_MAP = {
class AttributeType(Enum): class AttributeType(Enum):
"""属性枚举类。包含了武器和圣遗物的属性。""" """属性枚举类。包含了武器和圣遗物的属性。"""
HP = "生命" HP = "生命"
HP_p = "生命%" HP_p = "生命%"
ATK = "攻击力" ATK = "攻击力"
@ -85,13 +88,13 @@ class AttributeType(Enum):
CD = "暴击伤害" CD = "暴击伤害"
PD = "物理伤害加成" PD = "物理伤害加成"
HB = "治疗加成" HB = "治疗加成"
Pyro = '火元素伤害加成' Pyro = "火元素伤害加成"
Hydro = '水元素伤害加成' Hydro = "水元素伤害加成"
Electro = '雷元素伤害加成' Electro = "雷元素伤害加成"
Cryo = '冰元素伤害加成' Cryo = "冰元素伤害加成"
Dendro = '草元素伤害加成' Dendro = "草元素伤害加成"
Anemo = '风元素伤害加成' Anemo = "风元素伤害加成"
Geo = '岩元素伤害加成' Geo = "岩元素伤害加成"
@classmethod @classmethod
def convert(cls, string: str) -> Optional[Self]: def convert(cls, string: str) -> Optional[Self]:
@ -102,23 +105,24 @@ class AttributeType(Enum):
_ASSOCIATION_MAP = { _ASSOCIATION_MAP = {
'Other': ['Mainactor', 'Ranger', 'Fatui'], "Other": ["Mainactor", "Ranger", "Fatui"],
'Snezhnaya': [], "Snezhnaya": [],
'Sumeru': [], "Sumeru": [],
'Inazuma': [], "Inazuma": [],
'Liyue': [], "Liyue": [],
'Mondstadt': [], "Mondstadt": [],
} }
class Association(Enum): class Association(Enum):
"""角色所属地区""" """角色所属地区"""
Other = '其它'
Snezhnaya = '至冬' Other = "其它"
Sumeru = '须弥' Snezhnaya = "至冬"
Inazuma = '稻妻' Sumeru = "须弥"
Liyue = '璃月' Inazuma = "稻妻"
Mondstadt = '蒙德' Liyue = "璃月"
Mondstadt = "蒙德"
@classmethod @classmethod
def convert(cls, string: str) -> Optional[Self]: def convert(cls, string: str) -> Optional[Self]:

View File

@ -8,11 +8,12 @@ from httpx import URL
from modules.wiki.base import Model, HONEY_HOST, WikiModel from modules.wiki.base import Model, HONEY_HOST, WikiModel
from modules.wiki.other import AttributeType, WeaponType from modules.wiki.other import AttributeType, WeaponType
__all__ = ['Weapon', 'WeaponAffix', 'WeaponAttribute'] __all__ = ["Weapon", "WeaponAffix", "WeaponAttribute"]
class WeaponAttribute(Model): class WeaponAttribute(Model):
"""武器词条""" """武器词条"""
type: AttributeType type: AttributeType
value: str value: str
@ -25,6 +26,7 @@ class WeaponAffix(Model):
description: 技能描述 description: 技能描述
""" """
name: str name: str
description: List[str] description: List[str]
@ -69,38 +71,36 @@ class Weapon(WikiModel):
return [HONEY_HOST.join(f"fam_{i.lower()}/?lang=CHS") for i in WeaponType.__members__] return [HONEY_HOST.join(f"fam_{i.lower()}/?lang=CHS") for i in WeaponType.__members__]
@classmethod @classmethod
async def _parse_soup(cls, soup: BeautifulSoup) -> 'Weapon': async def _parse_soup(cls, soup: BeautifulSoup) -> "Weapon":
"""解析武器页""" """解析武器页"""
soup = soup.select('.wp-block-post-content')[0] soup = soup.select(".wp-block-post-content")[0]
tables = soup.find_all('table') tables = soup.find_all("table")
table_rows = tables[0].find_all('tr') table_rows = tables[0].find_all("tr")
def get_table_text(row_num: int) -> str: def get_table_text(row_num: int) -> str:
"""一个快捷函数,用于返回表格对应行的最后一个单元格中的文本""" """一个快捷函数,用于返回表格对应行的最后一个单元格中的文本"""
return table_rows[row_num].find_all('td')[-1].text.replace('\xa0', '') return table_rows[row_num].find_all("td")[-1].text.replace("\xa0", "")
def find_table(select: str): def find_table(select: str):
"""一个快捷函数,用于寻找对应表格头的表格""" """一个快捷函数,用于寻找对应表格头的表格"""
return list(filter(lambda x: select in ' '.join(x.attrs['class']), tables)) return list(filter(lambda x: select in " ".join(x.attrs["class"]), tables))
id_ = re.findall(r'/img/(.*?)_gacha', str(table_rows[0]))[0] id_ = re.findall(r"/img/(.*?)_gacha", str(table_rows[0]))[0]
weapon_type = WeaponType[get_table_text(1).split(',')[-1].strip()] weapon_type = WeaponType[get_table_text(1).split(",")[-1].strip()]
name = get_table_text(0) name = get_table_text(0)
rarity = len(table_rows[2].find_all('img')) rarity = len(table_rows[2].find_all("img"))
attack = float(get_table_text(4)) attack = float(get_table_text(4))
ascension = [re.findall(r'/(.*)/', tag.attrs['href'])[0] for tag in table_rows[-1].find_all('a')] ascension = [re.findall(r"/(.*)/", tag.attrs["href"])[0] for tag in table_rows[-1].find_all("a")]
if rarity > 2: # 如果是 3 星及其以上的武器 if rarity > 2: # 如果是 3 星及其以上的武器
attribute = WeaponAttribute( attribute = WeaponAttribute(
type=AttributeType.convert( type=AttributeType.convert(tables[2].find("thead").find("tr").find_all("td")[2].text.split(" ")[1]),
tables[2].find('thead').find('tr').find_all('td')[2].text.split(' ')[1] value=get_table_text(6),
), )
value=get_table_text(6) affix = WeaponAffix(
name=get_table_text(7), description=[i.find_all("td")[1].text for i in tables[3].find_all("tr")[1:]]
) )
affix = WeaponAffix(name=get_table_text(7), description=[
i.find_all('td')[1].text for i in tables[3].find_all('tr')[1:]
])
description = get_table_text(-1) if len(tables) < 11 else get_table_text(9) description = get_table_text(-1) if len(tables) < 11 else get_table_text(9)
if story_table := find_table('quotes'): if story_table := find_table("quotes"):
story = story_table[0].text.strip() story = story_table[0].text.strip()
else: else:
story = None story = None
@ -109,15 +109,24 @@ class Weapon(WikiModel):
description = get_table_text(5) description = get_table_text(5)
story = tables[-1].text.strip() story = tables[-1].text.strip()
stats = [] stats = []
for row in tables[2].find_all('tr')[1:]: for row in tables[2].find_all("tr")[1:]:
cells = row.find_all('td') cells = row.find_all("td")
if rarity > 2: if rarity > 2:
stats.append(WeaponState(level=cells[0].text, ATK=cells[1].text, bonus=cells[2].text)) stats.append(WeaponState(level=cells[0].text, ATK=cells[1].text, bonus=cells[2].text))
else: else:
stats.append(WeaponState(level=cells[0].text, ATK=cells[1].text)) stats.append(WeaponState(level=cells[0].text, ATK=cells[1].text))
return Weapon( return Weapon(
id=id_, name=name, rarity=rarity, attack=attack, attribute=attribute, affix=affix, weapon_type=weapon_type, id=id_,
story=story, stats=stats, description=description, ascension=ascension name=name,
rarity=rarity,
attack=attack,
attribute=attribute,
affix=affix,
weapon_type=weapon_type,
story=story,
stats=stats,
description=description,
ascension=ascension,
) )
@classmethod @classmethod
@ -125,16 +134,14 @@ class Weapon(WikiModel):
# 重写此函数的目的是名字去重,例如单手剑页面中有三个 “「一心传」名刀” # 重写此函数的目的是名字去重,例如单手剑页面中有三个 “「一心传」名刀”
name_list = [i async for i in cls._name_list_generator(with_url=with_url)] name_list = [i async for i in cls._name_list_generator(with_url=with_url)]
if with_url: if with_url:
return [ return [(i[0], list(i[1])[0][1]) for i in itertools.groupby(name_list, lambda x: x[0])]
(i[0], list(i[1])[0][1]) for i in itertools.groupby(name_list, lambda x: x[0])
]
else: else:
return [i[0] for i in itertools.groupby(name_list, lambda x: x)] return [i[0] for i in itertools.groupby(name_list, lambda x: x)]
@property @property
def icon(self) -> WeaponIcon: def icon(self) -> WeaponIcon:
return WeaponIcon( return WeaponIcon(
icon=str(HONEY_HOST.join(f'/img/{self.id}.webp')), icon=str(HONEY_HOST.join(f"/img/{self.id}.webp")),
awakened=str(HONEY_HOST.join(f'/img/{self.id}_awaken_icon.webp')), awakened=str(HONEY_HOST.join(f"/img/{self.id}_awaken_icon.webp")),
gacha=str(HONEY_HOST.join(f'/img/{self.id}_gacha_icon.webp')), gacha=str(HONEY_HOST.join(f"/img/{self.id}_gacha_icon.webp")),
) )

View File

@ -21,11 +21,13 @@ from utils.log import logger
class AbyssUnlocked(Exception): class AbyssUnlocked(Exception):
"""根本没动""" """根本没动"""
pass pass
class NoMostKills(Exception): class NoMostKills(Exception):
"""挑战了但是数据没刷新""" """挑战了但是数据没刷新"""
pass pass
@ -33,11 +35,11 @@ class Abyss(Plugin, BasePlugin):
"""深渊数据查询""" """深渊数据查询"""
def __init__( def __init__(
self, self,
user_service: UserService = None, user_service: UserService = None,
cookies_service: CookiesService = None, cookies_service: CookiesService = None,
template_service: TemplateService = None, template_service: TemplateService = None,
assets_service: AssetsService = None assets_service: AssetsService = None,
): ):
self.template_service = template_service self.template_service = template_service
self.cookies_service = cookies_service self.cookies_service = cookies_service
@ -77,20 +79,20 @@ class Abyss(Plugin, BasePlugin):
}, },
"strongest_strike": { "strongest_strike": {
"icon": await self.assets_service.avatar(ranks.strongest_strike[0].id).side(), "icon": await self.assets_service.avatar(ranks.strongest_strike[0].id).side(),
"value": ranks.strongest_strike[0].value "value": ranks.strongest_strike[0].value,
}, },
"most_damage_taken": { "most_damage_taken": {
"icon": await self.assets_service.avatar(ranks.most_damage_taken[0].id).side(), "icon": await self.assets_service.avatar(ranks.most_damage_taken[0].id).side(),
"value": ranks.most_damage_taken[0].value "value": ranks.most_damage_taken[0].value,
}, },
"most_bursts_used": { "most_bursts_used": {
"icon": await self.assets_service.avatar(ranks.most_bursts_used[0].id).side(), "icon": await self.assets_service.avatar(ranks.most_bursts_used[0].id).side(),
"value": ranks.most_bursts_used[0].value "value": ranks.most_bursts_used[0].value,
}, },
"most_skills_used": { "most_skills_used": {
"icon": await self.assets_service.avatar(ranks.most_skills_used[0].id).side(), "icon": await self.assets_service.avatar(ranks.most_skills_used[0].id).side(),
"value": ranks.most_skills_used[0].value "value": ranks.most_skills_used[0].value,
} },
} }
# most_kills # most_kills
most_played_list = ranks.most_played most_played_list = ranks.most_played
@ -98,7 +100,7 @@ class Abyss(Plugin, BasePlugin):
temp = { temp = {
"icon": await self.assets_service.avatar(most_played.id).icon(), "icon": await self.assets_service.avatar(most_played.id).icon(),
"value": most_played.value, "value": most_played.value,
"background": self._get_role_star_bg(most_played.rarity) "background": self._get_role_star_bg(most_played.rarity),
} }
abyss_data["most_played_list"].append(temp) abyss_data["most_played_list"].append(temp)
return abyss_data return abyss_data
@ -132,7 +134,7 @@ class Abyss(Plugin, BasePlugin):
await message.reply_text("本次深渊旅行者还没挑战呢,咕咕咕~~~") await message.reply_text("本次深渊旅行者还没挑战呢,咕咕咕~~~")
return return
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
png_data = await self.template_service.render('genshin/abyss', "abyss.html", abyss_data, png_data = await self.template_service.render(
{"width": 865, "height": 504}, full_page=False) "genshin/abyss", "abyss.html", abyss_data, {"width": 865, "height": 504}, full_page=False
await message.reply_photo(png_data, filename=f"abyss_{user.id}.png", )
allow_sending_without_reply=True) await message.reply_photo(png_data, filename=f"abyss_{user.id}.png", allow_sending_without_reply=True)

View File

@ -18,15 +18,17 @@ COMMAND_RESULT = 1
class ArtifactRate(Plugin.Conversation, BasePlugin.Conversation): class ArtifactRate(Plugin.Conversation, BasePlugin.Conversation):
"""圣遗物评分""" """圣遗物评分"""
STAR_KEYBOARD = [[ STAR_KEYBOARD = [
InlineKeyboardButton( [InlineKeyboardButton(f"{i}", callback_data=f"artifact_ocr_rate_data|star|{i}") for i in range(1, 6)]
f"{i}", callback_data=f"artifact_ocr_rate_data|star|{i}") for i in range(1, 6) ]
]]
LEVEL_KEYBOARD = [[ LEVEL_KEYBOARD = [
InlineKeyboardButton( [
f"{i * 5 + j}", callback_data=f"artifact_ocr_rate_data|level|{i * 5 + j}") for j in range(1, 6) InlineKeyboardButton(f"{i * 5 + j}", callback_data=f"artifact_ocr_rate_data|level|{i * 5 + j}")
] for i in range(0, 4)] for j in range(1, 6)
]
for i in range(0, 4)
]
def __init__(self): def __init__(self):
self.artifact_rate = ArtifactOcrRate() self.artifact_rate = ArtifactOcrRate()
@ -39,19 +41,21 @@ class ArtifactRate(Plugin.Conversation, BasePlugin.Conversation):
return artifact_attr.get("message", "API请求错误") return artifact_attr.get("message", "API请求错误")
return "API请求错误" return "API请求错误"
rate_result = rate_result_req.json() rate_result = rate_result_req.json()
return "*圣遗物评分结果*\n" \ return (
f"主属性:{escape_markdown(artifact_attr['main_item']['name'], version=2)}\n" \ "*圣遗物评分结果*\n"
f"{escape_markdown(get_format_sub_item(artifact_attr), version=2)}" \ f"主属性:{escape_markdown(artifact_attr['main_item']['name'], version=2)}\n"
f'`--------------------`\n' \ f"{escape_markdown(get_format_sub_item(artifact_attr), version=2)}"
f"总分:{escape_markdown(rate_result['total_percent'], version=2)}\n" \ f"`--------------------`\n"
f"主词条:{escape_markdown(rate_result['main_percent'], version=2)}\n" \ f"总分:{escape_markdown(rate_result['total_percent'], version=2)}\n"
f"副词条:{escape_markdown(rate_result['sub_percent'], version=2)}\n" \ f"主词条:{escape_markdown(rate_result['main_percent'], version=2)}\n"
f'`--------------------`\n' \ f"副词条:{escape_markdown(rate_result['sub_percent'], version=2)}\n"
f"{escape_markdown(get_comment(rate_result['total_percent']), version=2)}\n" \ f"`--------------------`\n"
"_评分、识图均来自 genshin\\.pub_" f"{escape_markdown(get_comment(rate_result['total_percent']), version=2)}\n"
"_评分、识图均来自 genshin\\.pub_"
)
@conversation.entry_point @conversation.entry_point
@handler.command(command='artifact_rate', filters=filters.ChatType.PRIVATE, block=True) @handler.command(command="artifact_rate", filters=filters.ChatType.PRIVATE, block=True)
@handler.message(filters=filters.Regex(r"^圣遗物评分(.*)"), block=True) @handler.message(filters=filters.Regex(r"^圣遗物评分(.*)"), block=True)
@handler.message(filters=filters.CaptionRegex(r"^圣遗物评分(.*)"), block=True) @handler.message(filters=filters.CaptionRegex(r"^圣遗物评分(.*)"), block=True)
@error_callable @error_callable
@ -95,15 +99,12 @@ class ArtifactRate(Plugin.Conversation, BasePlugin.Conversation):
artifact_attr = artifact_attr_req.json() artifact_attr = artifact_attr_req.json()
context.user_data["artifact_attr"] = artifact_attr context.user_data["artifact_attr"] = artifact_attr
if artifact_attr.get("star") is None: if artifact_attr.get("star") is None:
await message.reply_text("无法识别圣遗物星级,请选择圣遗物星级", await message.reply_text("无法识别圣遗物星级,请选择圣遗物星级", reply_markup=InlineKeyboardMarkup(self.STAR_KEYBOARD))
reply_markup=InlineKeyboardMarkup(self.STAR_KEYBOARD))
return COMMAND_RESULT return COMMAND_RESULT
if artifact_attr.get("level") is None: if artifact_attr.get("level") is None:
await message.reply_text("无法识别圣遗物等级,请选择圣遗物等级", await message.reply_text("无法识别圣遗物等级,请选择圣遗物等级", reply_markup=InlineKeyboardMarkup(self.LEVEL_KEYBOARD))
reply_markup=InlineKeyboardMarkup(self.LEVEL_KEYBOARD))
return COMMAND_RESULT return COMMAND_RESULT
reply_message = await message.reply_text("识图成功!\n" reply_message = await message.reply_text("识图成功!\n" "正在评分中...")
"正在评分中...")
rate_text = await self.get_rate(artifact_attr) rate_text = await self.get_rate(artifact_attr)
await reply_message.edit_text(rate_text, parse_mode=ParseMode.MARKDOWN_V2) await reply_message.edit_text(rate_text, parse_mode=ParseMode.MARKDOWN_V2)
return ConversationHandler.END return ConversationHandler.END
@ -138,12 +139,10 @@ class ArtifactRate(Plugin.Conversation, BasePlugin.Conversation):
await query.edit_message_text("数据错误") await query.edit_message_text("数据错误")
return ConversationHandler.END return ConversationHandler.END
if artifact_attr.get("level") is None: if artifact_attr.get("level") is None:
await query.edit_message_text("无法识别圣遗物等级,请选择圣遗物等级", await query.edit_message_text("无法识别圣遗物等级,请选择圣遗物等级", reply_markup=InlineKeyboardMarkup(self.LEVEL_KEYBOARD))
reply_markup=InlineKeyboardMarkup(self.LEVEL_KEYBOARD))
return COMMAND_RESULT return COMMAND_RESULT
if artifact_attr.get("star") is None: if artifact_attr.get("star") is None:
await query.edit_message_text("无法识别圣遗物星级,请选择圣遗物星级", await query.edit_message_text("无法识别圣遗物星级,请选择圣遗物星级", reply_markup=InlineKeyboardMarkup(self.STAR_KEYBOARD))
reply_markup=InlineKeyboardMarkup(self.STAR_KEYBOARD))
return COMMAND_RESULT return COMMAND_RESULT
await query.edit_message_text("正在评分中...") await query.edit_message_text("正在评分中...")
rate_text = await self.get_rate(artifact_attr) rate_text = await self.get_rate(artifact_attr)

View File

@ -43,7 +43,7 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
self.user_service = user_service self.user_service = user_service
@conversation.entry_point @conversation.entry_point
@handler.command(command='setcookies', filters=filters.ChatType.PRIVATE, block=True) @handler.command(command="setcookies", filters=filters.ChatType.PRIVATE, block=True)
@restricts() @restricts()
@error_callable @error_callable
async def command_start(self, update: Update, context: CallbackContext) -> int: async def command_start(self, update: Update, context: CallbackContext) -> int:
@ -56,12 +56,12 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
context.chat_data["add_user_command_data"] = cookies_command_data context.chat_data["add_user_command_data"] = cookies_command_data
text = f'你好 {user.mention_markdown_v2()} {escape_markdown("!请选择要绑定的服务器!或回复退出取消操作")}' text = f'你好 {user.mention_markdown_v2()} {escape_markdown("!请选择要绑定的服务器!或回复退出取消操作")}'
reply_keyboard = [['米游社', 'HoYoLab'], ["退出"]] reply_keyboard = [["米游社", "HoYoLab"], ["退出"]]
await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)) await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
return CHECK_SERVER return CHECK_SERVER
@conversation.entry_point @conversation.entry_point
@handler.command(command='mlogin', filters=filters.ChatType.PRIVATE, block=True) @handler.command(command="mlogin", filters=filters.ChatType.PRIVATE, block=True)
@error_callable @error_callable
async def choose_method(self, update: Update, context: CallbackContext) -> int: async def choose_method(self, update: Update, context: CallbackContext) -> int:
user = update.effective_user user = update.effective_user
@ -112,21 +112,25 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
add_user_command_data.user = user_info add_user_command_data.user = user_info
add_user_command_data.region = region add_user_command_data.region = region
await message.reply_text(f"请输入{bbs_name}的Cookies或回复退出取消操作", reply_markup=ReplyKeyboardRemove()) await message.reply_text(f"请输入{bbs_name}的Cookies或回复退出取消操作", reply_markup=ReplyKeyboardRemove())
javascript = "javascript:(()=>{_=(n)=>{for(i in(r=document.cookie.split(';'))){var a=r[i].split('=');if(a[" \ javascript = (
"0].trim()==n)return a[1]}};c=_('account_id')||alert('无效的Cookie,请重新登录!');c&&confirm(" \ "javascript:(()=>{_=(n)=>{for(i in(r=document.cookie.split(';'))){var a=r[i].split('=');if(a["
"'将Cookie复制到剪贴板?')&&copy(document.cookie)})(); " "0].trim()==n)return a[1]}};c=_('account_id')||alert('无效的Cookie,请重新登录!');c&&confirm("
"'将Cookie复制到剪贴板?')&&copy(document.cookie)})(); "
)
javascript_android = "javascript:(()=>{prompt('',document.cookie)})();" javascript_android = "javascript:(()=>{prompt('',document.cookie)})();"
help_message = f"*关于如何获取Cookies*\n\n" \ help_message = (
f"PC\n" \ f"*关于如何获取Cookies*\n\n"
f"[1、打开{bbs_name}并登录]({bbs_url})\n" \ f"PC\n"
f"2、按F12打开开发者工具\n" \ f"[1、打开{bbs_name}并登录]({bbs_url})\n"
f"3、{escape_markdown('将开发者工具切换至控制台(Console)页签', version=2)}\n" \ f"2、按F12打开开发者工具\n"
f"4、复制下方的代码并将其粘贴在控制台中按下回车\n" \ f"3、{escape_markdown('将开发者工具切换至控制台(Console)页签', version=2)}\n"
f"`{escape_markdown(javascript, version=2, entity_type='code')}`\n\n" \ f"4、复制下方的代码并将其粘贴在控制台中按下回车\n"
f"Android\n" \ f"`{escape_markdown(javascript, version=2, entity_type='code')}`\n\n"
f"[1、通过 Via 浏览器打开{bbs_name}并登录]({bbs_url})\n" \ f"Android\n"
f"2、复制下方的代码并将其粘贴在地址栏中点击右侧箭头\n" \ f"[1、通过 Via 浏览器打开{bbs_name}并登录]({bbs_url})\n"
f"`{escape_markdown(javascript_android, version=2, entity_type='code')}`" f"2、复制下方的代码并将其粘贴在地址栏中点击右侧箭头\n"
f"`{escape_markdown(javascript_android, version=2, entity_type='code')}`"
)
await message.reply_markdown_v2(help_message, disable_web_page_preview=True) await message.reply_markdown_v2(help_message, disable_web_page_preview=True)
return INPUT_COOKIES return INPUT_COOKIES
@ -149,8 +153,9 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
return CHECK_PHONE return CHECK_PHONE
add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data") add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data")
add_user_command_data.phone = phone add_user_command_data.phone = phone
await message.reply_text("请打开 https://user.mihoyo.com/#/login/captcha ,输入手机号并获取验证码," await message.reply_text(
"然后将收到的验证码发送给我(请不要在网页上进行登录)") "请打开 https://user.mihoyo.com/#/login/captcha ,输入手机号并获取验证码," "然后将收到的验证码发送给我(请不要在网页上进行登录)"
)
return CHECK_CAPTCHA return CHECK_CAPTCHA
@conversation.state(state=CHECK_CAPTCHA) @conversation.state(state=CHECK_CAPTCHA)
@ -175,8 +180,7 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
try: try:
success = await client.login(captcha) success = await client.login(captcha)
if not success: if not success:
await message.reply_text( await message.reply_text("登录失败:可能是验证码错误,注意不要在登录页面使用掉验证码,如果验证码已经使用,请重新获取验证码!")
"登录失败:可能是验证码错误,注意不要在登录页面使用掉验证码,如果验证码已经使用,请重新获取验证码!")
return ConversationHandler.END return ConversationHandler.END
await client.get_s_token() await client.get_s_token()
except Exception: except Exception:
@ -184,16 +188,15 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
return ConversationHandler.END return ConversationHandler.END
add_user_command_data.sign_in_client = client add_user_command_data.sign_in_client = client
await message.reply_text( await message.reply_text(
"请再次打开 https://user.mihoyo.com/#/login/captcha ,输入手机号并获取验证码(需要等待一分钟)," "请再次打开 https://user.mihoyo.com/#/login/captcha ,输入手机号并获取验证码(需要等待一分钟)," "然后将收到的验证码发送给我(请不要在网页上进行登录)"
"然后将收到的验证码发送给我(请不要在网页上进行登录)") )
return CHECK_CAPTCHA return CHECK_CAPTCHA
else: else:
client = add_user_command_data.sign_in_client client = add_user_command_data.sign_in_client
try: try:
success = await client.get_token(captcha) success = await client.get_token(captcha)
if not success: if not success:
await message.reply_text( await message.reply_text("登录失败:可能是验证码错误,注意不要在登录页面使用掉验证码,如果验证码已经使用,请重新获取验证码!")
"登录失败:可能是验证码错误,注意不要在登录页面使用掉验证码,如果验证码已经使用,请重新获取验证码!")
return ConversationHandler.END return ConversationHandler.END
except Exception: except Exception:
await message.reply_text("登录失败:米游社返回了错误的数据,请稍后再试!") await message.reply_text("登录失败:米游社返回了错误的数据,请稍后再试!")
@ -249,21 +252,24 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
await message.reply_text("Cookies已经过期请检查是否正确", reply_markup=ReplyKeyboardRemove()) await message.reply_text("Cookies已经过期请检查是否正确", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END return ConversationHandler.END
except GenshinException as exc: except GenshinException as exc:
await message.reply_text(f"获取账号信息发生错误,错误信息为 {str(exc)}请检查Cookie或者账号是否正常", await message.reply_text(
reply_markup=ReplyKeyboardRemove()) f"获取账号信息发生错误,错误信息为 {str(exc)}请检查Cookie或者账号是否正常", reply_markup=ReplyKeyboardRemove()
)
return ConversationHandler.END return ConversationHandler.END
except (AttributeError, ValueError): except (AttributeError, ValueError):
await message.reply_text("Cookies错误请检查是否正确", reply_markup=ReplyKeyboardRemove()) await message.reply_text("Cookies错误请检查是否正确", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END return ConversationHandler.END
add_user_command_data.game_uid = user_info.uid add_user_command_data.game_uid = user_info.uid
reply_keyboard = [['确认', '退出']] reply_keyboard = [["确认", "退出"]]
await message.reply_text("获取角色基础信息成功,请检查是否正确!") await message.reply_text("获取角色基础信息成功,请检查是否正确!")
logger.info(f"用户 {user.full_name}[{user.id}] 获取账号 {user_info.nickname}[{user_info.uid}] 信息成功") logger.info(f"用户 {user.full_name}[{user.id}] 获取账号 {user_info.nickname}[{user_info.uid}] 信息成功")
text = f"*角色信息*\n" \ text = (
f"角色名称:{escape_markdown(user_info.nickname, version=2)}\n" \ f"*角色信息*\n"
f"角色等级:{user_info.level}\n" \ f"角色名称:{escape_markdown(user_info.nickname, version=2)}\n"
f"UID`{user_info.uid}`\n" \ f"角色等级:{user_info.level}\n"
f"服务器名称:`{user_info.server_name}`\n" f"UID`{user_info.uid}`\n"
f"服务器名称:`{user_info.server_name}`\n"
)
await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)) await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
return COMMAND_RESULT return COMMAND_RESULT
@ -280,11 +286,15 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
elif message.text == "确认": elif message.text == "确认":
if add_user_command_data.user is None: if add_user_command_data.user is None:
if add_user_command_data.region == RegionEnum.HYPERION: if add_user_command_data.region == RegionEnum.HYPERION:
user_db = User(user_id=user.id, yuanshen_uid=add_user_command_data.game_uid, user_db = User(
region=add_user_command_data.region) user_id=user.id,
yuanshen_uid=add_user_command_data.game_uid,
region=add_user_command_data.region,
)
elif add_user_command_data.region == RegionEnum.HOYOLAB: elif add_user_command_data.region == RegionEnum.HOYOLAB:
user_db = User(user_id=user.id, genshin_uid=add_user_command_data.game_uid, user_db = User(
region=add_user_command_data.region) user_id=user.id, genshin_uid=add_user_command_data.game_uid, region=add_user_command_data.region
)
else: else:
await message.reply_text("数据错误") await message.reply_text("数据错误")
return ConversationHandler.END return ConversationHandler.END
@ -301,11 +311,13 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
return ConversationHandler.END return ConversationHandler.END
await self.user_service.update_user(user_db) await self.user_service.update_user(user_db)
if add_user_command_data.cookies_database_data is None: if add_user_command_data.cookies_database_data is None:
await self.cookies_service.add_cookies(user.id, add_user_command_data.cookies, await self.cookies_service.add_cookies(
add_user_command_data.region) user.id, add_user_command_data.cookies, add_user_command_data.region
)
else: else:
await self.cookies_service.update_cookies(user.id, add_user_command_data.cookies, await self.cookies_service.update_cookies(
add_user_command_data.region) user.id, add_user_command_data.cookies, add_user_command_data.region
)
logger.info(f"用户 {user.full_name}[{user.id}] 绑定账号成功") logger.info(f"用户 {user.full_name}[{user.id}] 绑定账号成功")
await message.reply_text("保存成功", reply_markup=ReplyKeyboardRemove()) await message.reply_text("保存成功", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END return ConversationHandler.END

View File

@ -39,14 +39,14 @@ from utils.log import logger
INTERVAL = 1 INTERVAL = 1
DATA_TYPE = Dict[str, List[List[str]]] DATA_TYPE = Dict[str, List[List[str]]]
DATA_FILE_PATH = Path(__file__).joinpath('../daily.json').resolve() DATA_FILE_PATH = Path(__file__).joinpath("../daily.json").resolve()
DOMAINS = ['忘却之峡', '太山府', '菫色之庭', '昏识塔', '塞西莉亚苗圃', '震雷连山密宫', '砂流之庭', '有顶塔'] DOMAINS = ["忘却之峡", "太山府", "菫色之庭", "昏识塔", "塞西莉亚苗圃", "震雷连山密宫", "砂流之庭", "有顶塔"]
DOMAIN_AREA_MAP = dict(zip(DOMAINS, ['蒙德', '璃月', '稻妻', '须弥'] * 2)) DOMAIN_AREA_MAP = dict(zip(DOMAINS, ["蒙德", "璃月", "稻妻", "须弥"] * 2))
WEEK_MAP = ['', '', '', '', '', '', ''] WEEK_MAP = ["", "", "", "", "", "", ""]
def sort_item(items: List['ItemData']) -> Iterable['ItemData']: def sort_item(items: List["ItemData"]) -> Iterable["ItemData"]:
"""对武器和角色进行排序 """对武器和角色进行排序
排序规则持有星级 > 等级 > 命座/精炼) > 未持有星级 > 等级 > 命座/精炼 排序规则持有星级 > 等级 > 命座/精炼) > 未持有星级 > 等级 > 命座/精炼
@ -60,14 +60,17 @@ def sort_item(items: List['ItemData']) -> Iterable['ItemData']:
ArkoWrapper(x[1]) ArkoWrapper(x[1])
.sort(lambda y: y.rarity, reverse=True) .sort(lambda y: y.rarity, reverse=True)
.groupby(lambda y: y.rarity) # 根据星级分组并排序 .groupby(lambda y: y.rarity) # 根据星级分组并排序
.map(lambda y: ( .map(
ArkoWrapper(y[1]) lambda y: (
.sort(lambda z: z.refinement or z.constellation or -1, reverse=True) ArkoWrapper(y[1])
.groupby(lambda z: z.refinement or z.constellation or -1) # 根据命座/精炼进行分组并排序 .sort(lambda z: z.refinement or z.constellation or -1, reverse=True)
.map(lambda i: ArkoWrapper(i[1]).sort(lambda j: j.id)) .groupby(lambda z: z.refinement or z.constellation or -1) # 根据命座/精炼进行分组并排序
)) .map(lambda i: ArkoWrapper(i[1]).sort(lambda j: j.id))
)
)
) )
).flat(3) )
.flat(3)
) )
@ -87,7 +90,7 @@ def get_material_serial_name(names: Iterable[str]) -> str:
if sub_string in ArkoWrapper(all_substrings(name_b)): if sub_string in ArkoWrapper(all_substrings(name_b)):
result.append(sub_string) result.append(sub_string)
result = ArkoWrapper(result).sort(len, reverse=True)[0] result = ArkoWrapper(result).sort(len, reverse=True)[0]
chars = {'': 0, '': 0} chars = {"": 0, "": 0}
for char, k in chars.items(): for char, k in chars.items():
result = result.split(char)[k] result = result.split(char)[k]
return result return result
@ -95,6 +98,7 @@ def get_material_serial_name(names: Iterable[str]) -> str:
class DailyMaterial(Plugin, BasePlugin): class DailyMaterial(Plugin, BasePlugin):
"""每日素材表""" """每日素材表"""
data: DATA_TYPE data: DATA_TYPE
locks: Tuple[Lock] = (Lock(), Lock()) locks: Tuple[Lock] = (Lock(), Lock())
@ -122,39 +126,47 @@ class DailyMaterial(Plugin, BasePlugin):
async def _get_data_from_user(self, user: User) -> Tuple[Optional[Client], Dict[str, List[Any]]]: async def _get_data_from_user(self, user: User) -> Tuple[Optional[Client], Dict[str, List[Any]]]:
"""获取已经绑定的账号的角色、武器信息""" """获取已经绑定的账号的角色、武器信息"""
client = None client = None
user_data = {'avatar': [], 'weapon': []} user_data = {"avatar": [], "weapon": []}
try: try:
logger.debug("尝试获取已绑定的原神账号") logger.debug("尝试获取已绑定的原神账号")
client = await get_genshin_client(user.id) client = await get_genshin_client(user.id)
logger.debug(f"获取账号数据成功: UID={client.uid}") logger.debug(f"获取账号数据成功: UID={client.uid}")
characters = await client.get_genshin_characters(client.uid) characters = await client.get_genshin_characters(client.uid)
for character in characters: for character in characters:
if character.name == '旅行者': # 跳过主角 if character.name == "旅行者": # 跳过主角
continue continue
cid = AVATAR_DATA[str(character.id)]['id'] cid = AVATAR_DATA[str(character.id)]["id"]
weapon = character.weapon weapon = character.weapon
user_data['avatar'].append( user_data["avatar"].append(
ItemData( ItemData(
id=cid, name=character.name, rarity=character.rarity, level=character.level, id=cid,
name=character.name,
rarity=character.rarity,
level=character.level,
constellation=character.constellation, constellation=character.constellation,
icon=(await self.assets_service.avatar(cid).icon()).as_uri() icon=(await self.assets_service.avatar(cid).icon()).as_uri(),
) )
) )
user_data['weapon'].append( user_data["weapon"].append(
ItemData( ItemData(
id=str(weapon.id), name=weapon.name, level=weapon.level, rarity=weapon.rarity, id=str(weapon.id),
name=weapon.name,
level=weapon.level,
rarity=weapon.rarity,
refinement=weapon.refinement, refinement=weapon.refinement,
icon=(await getattr( # 判定武器的突破次数是否大于 2 ;若是, 则将图标替换为 awakened (觉醒) 的图标 icon=(
self.assets_service.weapon(weapon.id), 'icon' if weapon.ascension < 2 else 'awaken' await getattr( # 判定武器的突破次数是否大于 2 ;若是, 则将图标替换为 awakened (觉醒) 的图标
)()).as_uri(), self.assets_service.weapon(weapon.id), "icon" if weapon.ascension < 2 else "awaken"
c_path=(await self.assets_service.avatar(cid).side()).as_uri() )()
).as_uri(),
c_path=(await self.assets_service.avatar(cid).side()).as_uri(),
) )
) )
except (UserNotFoundError, CookiesNotFoundError): except (UserNotFoundError, CookiesNotFoundError):
logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息") logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息")
return client, user_data return client, user_data
@handler.command('daily_material', block=False) @handler.command("daily_material", block=False)
@restricts(restricts_time_of_groups=20, without_overlapping=True) @restricts(restricts_time_of_groups=20, without_overlapping=True)
@error_callable @error_callable
async def daily_material(self, update: Update, context: CallbackContext): async def daily_material(self, update: Update, context: CallbackContext):
@ -171,15 +183,13 @@ class DailyMaterial(Plugin, BasePlugin):
weekday = now.weekday() - (1 if now.hour < 4 else 0) weekday = now.weekday() - (1 if now.hour < 4 else 0)
weekday = 6 if weekday < 0 else weekday weekday = 6 if weekday < 0 else weekday
time = now.strftime("%m-%d %H:%M") + " 星期" + WEEK_MAP[weekday] time = now.strftime("%m-%d %H:%M") + " 星期" + WEEK_MAP[weekday]
full = bool(args and args[-1] == 'full') # 判定最后一个参数是不是 full full = bool(args and args[-1] == "full") # 判定最后一个参数是不是 full
logger.info( logger.info(f'用户 {user.full_name}[{user.id}] 每日素材命令请求 || 参数 weekday="{WEEK_MAP[weekday]}" full={full}')
f"用户 {user.full_name}[{user.id}] 每日素材命令请求 || 参数 weekday=\"{WEEK_MAP[weekday]}\" full={full}")
if weekday == 6: if weekday == 6:
await update.message.reply_text( await update.message.reply_text(
("今天" if title == '今日' else '这天') + "是星期天, <b>全部素材都可以</b>刷哦~", ("今天" if title == "今日" else "这天") + "是星期天, <b>全部素材都可以</b>刷哦~", parse_mode=ParseMode.HTML
parse_mode=ParseMode.HTML
) )
return return
@ -196,26 +206,26 @@ class DailyMaterial(Plugin, BasePlugin):
await update.message.reply_chat_action(ChatAction.TYPING) await update.message.reply_chat_action(ChatAction.TYPING)
# 获取已经缓存的秘境素材信息 # 获取已经缓存的秘境素材信息
local_data = {'avatar': [], 'weapon': []} local_data = {"avatar": [], "weapon": []}
if not self.data: # 若没有缓存每日素材表的数据 if not self.data: # 若没有缓存每日素材表的数据
logger.info("正在获取每日素材缓存") logger.info("正在获取每日素材缓存")
self.data = await self._refresh_data() self.data = await self._refresh_data()
for domain, sche in self.data.items(): for domain, sche in self.data.items():
area = DOMAIN_AREA_MAP[domain] # 获取秘境所在的区域 area = DOMAIN_AREA_MAP[domain] # 获取秘境所在的区域
type_ = 'avatar' if DOMAINS.index(domain) < 4 else 'weapon' # 获取秘境的培养素材的类型:是天赋书还是武器突破材料 type_ = "avatar" if DOMAINS.index(domain) < 4 else "weapon" # 获取秘境的培养素材的类型:是天赋书还是武器突破材料
# 将读取到的数据存入 local_data 中 # 将读取到的数据存入 local_data 中
local_data[type_].append({'name': area, 'materials': sche[weekday][0], 'items': sche[weekday][1]}) local_data[type_].append({"name": area, "materials": sche[weekday][0], "items": sche[weekday][1]})
# 尝试获取用户已绑定的原神账号信息 # 尝试获取用户已绑定的原神账号信息
client, user_data = await self._get_data_from_user(user) client, user_data = await self._get_data_from_user(user)
await update.message.reply_chat_action(ChatAction.TYPING) await update.message.reply_chat_action(ChatAction.TYPING)
render_data = RenderData(title=title, time=time, uid=client.uid if client else client) render_data = RenderData(title=title, time=time, uid=client.uid if client else client)
for type_ in ['avatar', 'weapon']: for type_ in ["avatar", "weapon"]:
areas = [] areas = []
for area_data in local_data[type_]: # 遍历每个区域的信息:蒙德、璃月、稻妻、须弥 for area_data in local_data[type_]: # 遍历每个区域的信息:蒙德、璃月、稻妻、须弥
items = [] items = []
for id_ in area_data['items']: # 遍历所有该区域下当天weekday可以培养的角色、武器 for id_ in area_data["items"]: # 遍历所有该区域下当天weekday可以培养的角色、武器
added = False added = False
for i in user_data[type_]: # 从已经获取的角色数据中查找对应角色、武器 for i in user_data[type_]: # 从已经获取的角色数据中查找对应角色、武器
if id_ == str(i.id): if id_ == str(i.id):
@ -231,33 +241,42 @@ class DailyMaterial(Plugin, BasePlugin):
continue continue
if item[2] < 4: # 跳过 3 星及以下的武器 if item[2] < 4: # 跳过 3 星及以下的武器
continue continue
items.append(ItemData( # 添加角色数据中未找到的 items.append(
id=id_, name=item[1], rarity=item[2], ItemData( # 添加角色数据中未找到的
icon=(await getattr(self.assets_service, type_)(id_).icon()).as_uri() id=id_,
)) name=item[1],
rarity=item[2],
icon=(await getattr(self.assets_service, type_)(id_).icon()).as_uri(),
)
)
materials = [] materials = []
for mid in area_data['materials']: # 添加这个区域当天weekday的培养素材 for mid in area_data["materials"]: # 添加这个区域当天weekday的培养素材
path = (await self.assets_service.material(mid).icon()).as_uri() path = (await self.assets_service.material(mid).icon()).as_uri()
material = HONEY_DATA['material'][mid] material = HONEY_DATA["material"][mid]
materials.append(ItemData(id=mid, icon=path, name=material[1], rarity=material[2])) materials.append(ItemData(id=mid, icon=path, name=material[1], rarity=material[2]))
areas.append(AreaData( areas.append(
name=area_data['name'], materials=materials, items=sort_item(items), AreaData(
material_name=get_material_serial_name(map(lambda x: x.name, materials)) name=area_data["name"],
)) materials=materials,
setattr(render_data, {'avatar': 'character'}.get(type_, type_), areas) items=sort_item(items),
material_name=get_material_serial_name(map(lambda x: x.name, materials)),
)
)
setattr(render_data, {"avatar": "character"}.get(type_, type_), areas)
await update.message.reply_chat_action(ChatAction.TYPING) await update.message.reply_chat_action(ChatAction.TYPING)
render_tasks = [ render_tasks = [
asyncio.create_task( asyncio.create_task(
self.template_service.render( # 渲染角色素材页 self.template_service.render( # 渲染角色素材页
'genshin/daily_material', 'character.html', {'data': render_data}, {'width': 1164, 'height': 500} "genshin/daily_material", "character.html", {"data": render_data}, {"width": 1164, "height": 500}
) )
), ),
asyncio.create_task( asyncio.create_task(
self.template_service.render( # 渲染武器素材页 self.template_service.render( # 渲染武器素材页
'genshin/daily_material', 'weapon.html', {'data': render_data}, {'width': 1164, 'height': 500} "genshin/daily_material", "weapon.html", {"data": render_data}, {"width": 1164, "height": 500}
) )
)] ),
]
while not all(map(lambda x: x.done(), render_tasks)): while not all(map(lambda x: x.done(), render_tasks)):
await asyncio.sleep(0) await asyncio.sleep(0)
@ -267,25 +286,25 @@ class DailyMaterial(Plugin, BasePlugin):
self._add_delete_message_job(context, notice.chat_id, notice.message_id, 5) self._add_delete_message_job(context, notice.chat_id, notice.message_id, 5)
await update.message.reply_chat_action(ChatAction.UPLOAD_PHOTO) await update.message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
if full: # 是否发送原图 if full: # 是否发送原图
await update.message.reply_media_group([ await update.message.reply_media_group(
InputMediaDocument(character_img_data, filename="可培养角色.png"), [
InputMediaDocument(weapon_img_data, filename="可培养武器.png") InputMediaDocument(character_img_data, filename="可培养角色.png"),
]) InputMediaDocument(weapon_img_data, filename="可培养武器.png"),
]
)
else: else:
await update.message.reply_media_group( await update.message.reply_media_group(
[InputMediaPhoto(character_img_data), InputMediaPhoto(weapon_img_data)] [InputMediaPhoto(character_img_data), InputMediaPhoto(weapon_img_data)]
) )
logger.debug("角色、武器培养素材图发送成功") logger.debug("角色、武器培养素材图发送成功")
@handler.command('refresh_daily_material', block=False) @handler.command("refresh_daily_material", block=False)
@bot_admins_rights_check @bot_admins_rights_check
async def refresh(self, update: Update, context: CallbackContext): async def refresh(self, update: Update, context: CallbackContext):
user = update.effective_user user = update.effective_user
message = update.effective_message message = update.effective_message
logger.info( logger.info(f"用户 {user.full_name}[{user.id}] 刷新[bold]每日素材[/]缓存命令", extra={"markup": True})
f"用户 {user.full_name}[{user.id}] 刷新[bold]每日素材[/]缓存命令", extra={'markup': True}
)
if self.locks[0].locked(): if self.locks[0].locked():
notice = await message.reply_text("派蒙还在抄每日素材表呢,我有在好好工作哦~") notice = await message.reply_text("派蒙还在抄每日素材表呢,我有在好好工作哦~")
self._add_delete_message_job(context, notice.chat_id, notice.message_id, 10) self._add_delete_message_job(context, notice.chat_id, notice.message_id, 10)
@ -299,68 +318,61 @@ class DailyMaterial(Plugin, BasePlugin):
async with self.locks[0]: # 锁住第一把锁 async with self.locks[0]: # 锁住第一把锁
data = await self._refresh_data() data = await self._refresh_data()
notice = await notice.edit_text( notice = await notice.edit_text(
"每日素材表" + "每日素材表" + ("摘抄<b>完成!</b>" if data else "坏掉了!等会它再长好了之后我再抄。。。") + "\n正搬运每日素材的图标中。。。",
("摘抄<b>完成!</b>" if data else "坏掉了!等会它再长好了之后我再抄。。。") + parse_mode=ParseMode.HTML,
'\n正搬运每日素材的图标中。。。',
parse_mode=ParseMode.HTML
) )
self.data = data or self.data self.data = data or self.data
time = await self._download_icon(notice) time = await self._download_icon(notice)
async def job(_, n): async def job(_, n):
await n.edit_text( await n.edit_text(n.text_html.split("\n")[0] + "\n每日素材图标搬运<b>完成!</b>", parse_mode=ParseMode.HTML)
n.text_html.split('\n')[0] + "\n每日素材图标搬运<b>完成!</b>",
parse_mode=ParseMode.HTML
)
await asyncio.sleep(INTERVAL) await asyncio.sleep(INTERVAL)
await notice.delete() await notice.delete()
context.application.job_queue.run_once( context.application.job_queue.run_once(
partial(job, n=notice), when=time + INTERVAL, name='notice_msg_final_job' partial(job, n=notice), when=time + INTERVAL, name="notice_msg_final_job"
) )
async def _refresh_data(self, retry: int = 5) -> DATA_TYPE: async def _refresh_data(self, retry: int = 5) -> DATA_TYPE:
"""刷新来自 honey impact 的每日素材表""" """刷新来自 honey impact 的每日素材表"""
from bs4 import Tag from bs4 import Tag
result = {} result = {}
for i in range(retry): # 重复尝试 retry 次 for i in range(retry): # 重复尝试 retry 次
try: try:
response = await self.client.get("https://genshin.honeyhunterworld.com/?lang=CHS") response = await self.client.get("https://genshin.honeyhunterworld.com/?lang=CHS")
soup = BeautifulSoup(response.text, 'lxml') soup = BeautifulSoup(response.text, "lxml")
calendar = soup.select(".calendar_day_wrap")[0] calendar = soup.select(".calendar_day_wrap")[0]
key: str = '' key: str = ""
for tag in calendar: for tag in calendar:
tag: Tag tag: Tag
if tag.name == 'span': # 如果是秘境 if tag.name == "span": # 如果是秘境
key = tag.find('a').text key = tag.find("a").text
result[key] = [[[], []] for _ in range(7)] result[key] = [[[], []] for _ in range(7)]
for day, div in enumerate(tag.find_all('div')): for day, div in enumerate(tag.find_all("div")):
result[key][day][0] = [] result[key][day][0] = []
for a in div.find_all('a'): for a in div.find_all("a"):
honey_id = re.findall(r"/(.*)?/", a['href'])[0] honey_id = re.findall(r"/(.*)?/", a["href"])[0]
mid: str = [ mid: str = [i[0] for i in HONEY_DATA["material"].items() if i[1][0] == honey_id][0]
i[0]
for i in HONEY_DATA['material'].items()
if i[1][0] == honey_id
][0]
result[key][day][0].append(mid) result[key][day][0].append(mid)
else: # 如果是角色或武器 else: # 如果是角色或武器
id_ = re.findall(r"/(.*)?/", tag['href'])[0] id_ = re.findall(r"/(.*)?/", tag["href"])[0]
if tag.text.strip() == '旅行者': # 忽略主角 if tag.text.strip() == "旅行者": # 忽略主角
continue continue
id_ = ("" if id_.startswith('i_n') else "10000") + re.findall(r'\d+', id_)[0] id_ = ("" if id_.startswith("i_n") else "10000") + re.findall(r"\d+", id_)[0]
for day in map(int, tag.find('div')['data-days']): # 获取该角色/武器的可培养天 for day in map(int, tag.find("div")["data-days"]): # 获取该角色/武器的可培养天
result[key][day][1].append(id_) result[key][day][1].append(id_)
for stage, schedules in result.items(): for stage, schedules in result.items():
for day, _ in enumerate(schedules): for day, _ in enumerate(schedules):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
result[stage][day][1] = list(set(result[stage][day][1])) # 去重 result[stage][day][1] = list(set(result[stage][day][1])) # 去重
async with async_open(DATA_FILE_PATH, 'w', encoding='utf-8') as file: async with async_open(DATA_FILE_PATH, "w", encoding="utf-8") as file:
await file.write(json.dumps(result)) # pylint: disable=PY-W0079 await file.write(json.dumps(result)) # pylint: disable=PY-W0079
logger.info("每日素材刷新成功") logger.info("每日素材刷新成功")
break break
except (HTTPError, SSLZeroReturnError): except (HTTPError, SSLZeroReturnError):
from asyncio import sleep from asyncio import sleep
await sleep(1) await sleep(1)
if i <= retry - 1: if i <= retry - 1:
logger.warning("每日素材刷新失败, 正在重试") logger.warning("每日素材刷新失败, 正在重试")
@ -375,6 +387,7 @@ class DailyMaterial(Plugin, BasePlugin):
asset_list = [] asset_list = []
from time import time as time_ from time import time as time_
lock = asyncio.Lock() lock = asyncio.Lock()
the_time = Value(c_double, time_() - INTERVAL) the_time = Value(c_double, time_() - INTERVAL)
@ -382,20 +395,15 @@ class DailyMaterial(Plugin, BasePlugin):
async def edit_message(text): async def edit_message(text):
"""修改提示消息""" """修改提示消息"""
async with lock: async with lock:
if ( if message is not None and time_() >= (the_time.value + INTERVAL):
message is not None
and
time_() >= (the_time.value + INTERVAL)
):
with contextlib.suppress(TimedOut, RetryAfter): with contextlib.suppress(TimedOut, RetryAfter):
await message.edit_text( await message.edit_text(
'\n'.join(message.text_html.split('\n')[:2] + [text]), "\n".join(message.text_html.split("\n")[:2] + [text]), parse_mode=ParseMode.HTML
parse_mode=ParseMode.HTML
) )
the_time.value = time_() the_time.value = time_()
async def task(item_id, name, item_type): async def task(item_id, name, item_type):
logger.debug(f"正在开始下载 \"{name}\" 的图标素材") logger.debug(f'正在开始下载 "{name}" 的图标素材')
await edit_message(f"正在搬运 <b>{name}</b> 的图标素材。。。") await edit_message(f"正在搬运 <b>{name}</b> 的图标素材。。。")
asset: AssetsServiceType = getattr(self.assets_service, item_type)(item_id) # 获取素材对象 asset: AssetsServiceType = getattr(self.assets_service, item_type)(item_id) # 获取素材对象
asset_list.append(asset.honey_id) asset_list.append(asset.honey_id)
@ -403,7 +411,7 @@ class DailyMaterial(Plugin, BasePlugin):
# 并根据图标类型找到下载对应图标的函数 # 并根据图标类型找到下载对应图标的函数
for icon_type in asset.icon_types: for icon_type in asset.icon_types:
await getattr(asset, icon_type)(True) # 执行下载函数 await getattr(asset, icon_type)(True) # 执行下载函数
logger.debug(f"\"{name}\" 的图标素材下载成功") logger.debug(f'"{name}" 的图标素材下载成功')
await edit_message(f"正在搬运 <b>{name}</b> 的图标素材。。。<b>成功!</b>") await edit_message(f"正在搬运 <b>{name}</b> 的图标素材。。。<b>成功!</b>")
for TYPE, ITEMS in HONEY_DATA.items(): # 遍历每个对象 for TYPE, ITEMS in HONEY_DATA.items(): # 遍历每个对象
@ -431,7 +439,7 @@ class ItemData(BaseModel):
class AreaData(BaseModel): class AreaData(BaseModel):
name: Literal['蒙德', '璃月', '稻妻', '须弥'] # 区域名 name: Literal["蒙德", "璃月", "稻妻", "须弥"] # 区域名
material_name: str # 区域的材料系列名 material_name: str # 区域的材料系列名
materials: List[ItemData] = [] # 区域材料 materials: List[ItemData] = [] # 区域材料
items: Iterable[ItemData] = [] # 可培养的角色或武器 items: Iterable[ItemData] = [] # 可培养的角色或武器

View File

@ -5,8 +5,7 @@ from typing import Optional
from genshin import DataNotPublic from genshin import DataNotPublic
from telegram import Update from telegram import Update
from telegram.constants import ChatAction from telegram.constants import ChatAction
from telegram.ext import CommandHandler, MessageHandler, ConversationHandler, filters, \ from telegram.ext import CommandHandler, MessageHandler, ConversationHandler, filters, CallbackContext
CallbackContext
from core.baseplugin import BasePlugin from core.baseplugin import BasePlugin
from core.cookies.error import CookiesNotFoundError from core.cookies.error import CookiesNotFoundError
@ -24,8 +23,12 @@ from utils.log import logger
class DailyNote(Plugin, BasePlugin): class DailyNote(Plugin, BasePlugin):
"""每日便签""" """每日便签"""
def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None, def __init__(
template_service: TemplateService = None): self,
user_service: UserService = None,
cookies_service: CookiesService = None,
template_service: TemplateService = None,
):
self.template_service = template_service self.template_service = template_service
self.cookies_service = cookies_service self.cookies_service = cookies_service
self.user_service = user_service self.user_service = user_service
@ -34,11 +37,18 @@ class DailyNote(Plugin, BasePlugin):
async def _get_daily_note(self, client) -> bytes: async def _get_daily_note(self, client) -> bytes:
daily_info = await client.get_genshin_notes(client.uid) daily_info = await client.get_genshin_notes(client.uid)
day = datetime.datetime.now().strftime("%m-%d %H:%M") + " 星期" + "一二三四五六日"[datetime.datetime.now().weekday()] day = datetime.datetime.now().strftime("%m-%d %H:%M") + " 星期" + "一二三四五六日"[datetime.datetime.now().weekday()]
resin_recovery_time = daily_info.resin_recovery_time.strftime("%m-%d %H:%M") if \ resin_recovery_time = (
daily_info.max_resin - daily_info.current_resin else None daily_info.resin_recovery_time.strftime("%m-%d %H:%M")
realm_recovery_time = (datetime.datetime.now().astimezone() + if daily_info.max_resin - daily_info.current_resin
daily_info.remaining_realm_currency_recovery_time).strftime("%m-%d %H:%M") if \ else None
daily_info.max_realm_currency - daily_info.current_realm_currency else None )
realm_recovery_time = (
(datetime.datetime.now().astimezone() + daily_info.remaining_realm_currency_recovery_time).strftime(
"%m-%d %H:%M"
)
if daily_info.max_realm_currency - daily_info.current_realm_currency
else None
)
remained_time = None remained_time = None
for i in daily_info.expeditions: for i in daily_info.expeditions:
if remained_time: if remained_time:
@ -73,10 +83,11 @@ class DailyNote(Plugin, BasePlugin):
"max_resin_discounts": daily_info.max_resin_discounts, "max_resin_discounts": daily_info.max_resin_discounts,
"transformer": transformer, "transformer": transformer,
"transformer_ready": transformer_ready, "transformer_ready": transformer_ready,
"transformer_recovery_time": transformer_recovery_time "transformer_recovery_time": transformer_recovery_time,
} }
png_data = await self.template_service.render('genshin/daily_note', "daily_note.html", daily_data, png_data = await self.template_service.render(
{"width": 600, "height": 548}, full_page=False) "genshin/daily_note", "daily_note.html", daily_data, {"width": 600, "height": 548}, full_page=False
)
return png_data return png_data
@handler(CommandHandler, command="dailynote", block=False) @handler(CommandHandler, command="dailynote", block=False)

View File

@ -125,8 +125,9 @@ class Gacha(Plugin, BasePlugin):
data["items"].sort(key=take_rang, reverse=True) data["items"].sort(key=take_rang, reverse=True)
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
# 因为 gacha_info["title"] 返回的是 HTML 标签 尝试关闭自动转义 # 因为 gacha_info["title"] 返回的是 HTML 标签 尝试关闭自动转义
png_data = await self.template_service.render('genshin/gacha', "gacha.html", data, png_data = await self.template_service.render(
{"width": 1157, "height": 603}, False) "genshin/gacha", "gacha.html", data, {"width": 1157, "height": 603}, False
)
reply_message = await message.reply_photo(png_data) reply_message = await message.reply_photo(png_data)
if filters.ChatType.GROUPS.filter(message): if filters.ChatType.GROUPS.filter(message):

View File

@ -38,8 +38,7 @@ def check_ledger_month(context: CallbackContext) -> int:
elif re_data := re.findall(r"\d+", str(month)): elif re_data := re.findall(r"\d+", str(month)):
month = int(re_data[0]) month = int(re_data[0])
else: else:
num_dict = {"": 1, "": 2, "": 3, "": 4, "": 5, num_dict = {"": 1, "": 2, "": 3, "": 4, "": 5, "": 6, "": 7, "": 8, "": 9, "": 10}
"": 6, "": 7, "": 8, "": 9, "": 10}
month = sum(num_dict.get(i, 0) for i in str(month)) month = sum(num_dict.get(i, 0) for i in str(month))
# check right # check right
allow_month = [now_time.month] allow_month = [now_time.month]
@ -58,8 +57,12 @@ def check_ledger_month(context: CallbackContext) -> int:
class Ledger(Plugin, BasePlugin): class Ledger(Plugin, BasePlugin):
"""旅行札记""" """旅行札记"""
def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None, def __init__(
template_service: TemplateService = None): self,
user_service: UserService = None,
cookies_service: CookiesService = None,
template_service: TemplateService = None,
):
self.template_service = template_service self.template_service = template_service
self.cookies_service = cookies_service self.cookies_service = cookies_service
self.user_service = user_service self.user_service = user_service
@ -71,18 +74,26 @@ class Ledger(Plugin, BasePlugin):
except GenshinException as error: except GenshinException as error:
raise error raise error
color = ["#73a9c6", "#d56565", "#70b2b4", "#bd9a5a", "#739970", "#7a6da7", "#597ea0"] color = ["#73a9c6", "#d56565", "#70b2b4", "#bd9a5a", "#739970", "#7a6da7", "#597ea0"]
categories = [{"id": i.id, categories = [
"name": i.name, {
"color": color[i.id % len(color)], "id": i.id,
"amount": i.amount, "name": i.name,
"percentage": i.percentage} for i in diary_info.month_data.categories] "color": color[i.id % len(color)],
"amount": i.amount,
"percentage": i.percentage,
}
for i in diary_info.month_data.categories
]
color = [i["color"] for i in categories] color = [i["color"] for i in categories]
def format_amount(amount: int) -> str: def format_amount(amount: int) -> str:
return f"{round(amount / 10000, 2)}w" if amount >= 10000 else amount return f"{round(amount / 10000, 2)}w" if amount >= 10000 else amount
evaluate = """const { Pie } = G2Plot; evaluate = (
const data = JSON.parse(`""" + json.dumps(categories) + """`); """const { Pie } = G2Plot;
const data = JSON.parse(`"""
+ json.dumps(categories)
+ """`);
const piePlot = new Pie("chartContainer", { const piePlot = new Pie("chartContainer", {
renderer: "svg", renderer: "svg",
animation: false, animation: false,
@ -92,7 +103,9 @@ class Ledger(Plugin, BasePlugin):
colorField: "name", colorField: "name",
radius: 1, radius: 1,
innerRadius: 0.7, innerRadius: 0.7,
color: JSON.parse(`""" + json.dumps(color) + """`), color: JSON.parse(`"""
+ json.dumps(color)
+ """`),
meta: {}, meta: {},
label: { label: {
type: "inner", type: "inner",
@ -121,6 +134,7 @@ class Ledger(Plugin, BasePlugin):
legend:false, legend:false,
}); });
piePlot.render();""" piePlot.render();"""
)
ledger_data = { ledger_data = {
"uid": client.uid, "uid": client.uid,
"day": diary_info.month, "day": diary_info.month,
@ -132,9 +146,9 @@ class Ledger(Plugin, BasePlugin):
"last_mora": format_amount(diary_info.month_data.last_mora), "last_mora": format_amount(diary_info.month_data.last_mora),
"categories": categories, "categories": categories,
} }
png_data = await self.template_service.render('genshin/ledger', "ledger.html", ledger_data, png_data = await self.template_service.render(
{"width": 580, "height": 610}, "genshin/ledger", "ledger.html", ledger_data, {"width": 580, "height": 610}, evaluate=evaluate
evaluate=evaluate) )
return png_data return png_data
@handler(CommandHandler, command="ledger", block=False) @handler(CommandHandler, command="ledger", block=False)

View File

@ -60,6 +60,6 @@ class Map(Plugin, BasePlugin):
return return
img = Image.open(f"cache{sep}map.jpg") img = Image.open(f"cache{sep}map.jpg")
if img.size[0] > 2048 or img.size[1] > 2048: if img.size[0] > 2048 or img.size[1] > 2048:
await message.reply_document(open(f"cache{sep}map.jpg", mode='rb+'), caption=text) await message.reply_document(open(f"cache{sep}map.jpg", mode="rb+"), caption=text)
else: else:
await message.reply_photo(open(f"cache{sep}map.jpg", mode='rb+'), caption=text) await message.reply_photo(open(f"cache{sep}map.jpg", mode="rb+"), caption=text)

View File

@ -16,9 +16,9 @@ RESOURCE_ICON_OFFSET = (-int(150 * 0.5 * ZOOM), -int(150 * ZOOM))
class MapHelper: class MapHelper:
LABEL_URL = 'https://api-static.mihoyo.com/common/blackboard/ys_obc/v1/map/label/tree?app_sn=ys_obc' LABEL_URL = "https://api-static.mihoyo.com/common/blackboard/ys_obc/v1/map/label/tree?app_sn=ys_obc"
POINT_LIST_URL = 'https://api-static.mihoyo.com/common/blackboard/ys_obc/v1/map/point/list?map_id=2&app_sn=ys_obc' POINT_LIST_URL = "https://api-static.mihoyo.com/common/blackboard/ys_obc/v1/map/point/list?map_id=2&app_sn=ys_obc"
MAP_URL = 'https://api-static.mihoyo.com/common/map_user/ys_obc/v1/map/info?map_id=2&app_sn=ys_obc&lang=zh-cn' MAP_URL = "https://api-static.mihoyo.com/common/map_user/ys_obc/v1/map/info?map_id=2&app_sn=ys_obc&lang=zh-cn"
def __init__(self, cache_dir_name: str = "cache"): def __init__(self, cache_dir_name: str = "cache"):
self._current_dir = os.getcwd() self._current_dir = os.getcwd()
@ -109,11 +109,11 @@ class MapHelper:
map_info = map_info["data"]["info"]["detail"] map_info = map_info["data"]["info"]["detail"]
map_info = ujson.loads(map_info) map_info = ujson.loads(map_info)
map_url_list = map_info['slices'][0] map_url_list = map_info["slices"][0]
origin = map_info["origin"] origin = map_info["origin"]
x_start = map_info['total_size'][1] x_start = map_info["total_size"][1]
y_start = map_info['total_size'][1] y_start = map_info["total_size"][1]
x_end = 0 x_end = 0
y_end = 0 y_end = 0
for resource_point in self.all_resource_point_list: for resource_point in self.all_resource_point_list:
@ -223,7 +223,6 @@ class MapHelper:
class ResourceMap: class ResourceMap:
def __init__(self, all_resource_point_list: List[dict], map_icon: Image, center: List[float], resource_id: int): def __init__(self, all_resource_point_list: List[dict], map_icon: Image, center: List[float], resource_id: int):
self.all_resource_point_list = all_resource_point_list self.all_resource_point_list = all_resource_point_list
self.resource_id = resource_id self.resource_id = resource_id
@ -263,8 +262,9 @@ class ResourceMap:
# 这时地图已经裁切过了,要以裁切后的地图左上角为中心再转换一次坐标 # 这时地图已经裁切过了,要以裁切后的地图左上角为中心再转换一次坐标
x -= self.x_start x -= self.x_start
y -= self.y_start y -= self.y_start
self.map_image.paste(self.resource_icon, (x + RESOURCE_ICON_OFFSET[0], y + RESOURCE_ICON_OFFSET[1]), self.map_image.paste(
self.resource_icon) self.resource_icon, (x + RESOURCE_ICON_OFFSET[0], y + RESOURCE_ICON_OFFSET[1]), self.resource_icon
)
def crop(self): def crop(self):
# 把大地图裁切到只保留资源图标位置 # 把大地图裁切到只保留资源图标位置
@ -291,8 +291,7 @@ class ResourceMap:
self.y_start = center - 500 self.y_start = center - 500
self.y_end = center + 500 self.y_end = center + 500
self.map_image = self.map_image.crop((self.x_start, self.y_start, self.map_image = self.map_image.crop((self.x_start, self.y_start, self.x_end, self.y_end))
self.x_end, self.y_end))
def gen_jpg(self): def gen_jpg(self):
if not self.resource_xy_list: if not self.resource_xy_list:
@ -301,7 +300,7 @@ class ResourceMap:
os.mkdir("cache") # 查找 cache 目录 (缓存目录) 是否存在,如果不存在则创建 os.mkdir("cache") # 查找 cache 目录 (缓存目录) 是否存在,如果不存在则创建
self.crop() self.crop()
self.paste() self.paste()
self.map_image.save(f'cache{os.sep}map.jpg', format='JPEG') self.map_image.save(f"cache{os.sep}map.jpg", format="JPEG")
def get_resource_count(self): def get_resource_count(self):
return len(self.resource_xy_list) return len(self.resource_xy_list)

View File

@ -16,9 +16,7 @@ from utils.log import logger
class Material(Plugin, BasePlugin): class Material(Plugin, BasePlugin):
"""角色培养素材查询""" """角色培养素材查询"""
KEYBOARD = [[InlineKeyboardButton( KEYBOARD = [[InlineKeyboardButton(text="查看角色培养素材列表并查询", switch_inline_query_current_chat="查看角色培养素材列表并查询")]]
text="查看角色培养素材列表并查询",
switch_inline_query_current_chat="查看角色培养素材列表并查询")]]
def __init__(self, game_material_service: GameMaterialService = None): def __init__(self, game_material_service: GameMaterialService = None):
self.game_material_service = game_material_service self.game_material_service = game_material_service
@ -34,8 +32,9 @@ class Material(Plugin, BasePlugin):
if len(args) >= 1: if len(args) >= 1:
character_name = args[0] character_name = args[0]
else: else:
reply_message = await message.reply_text("请回复你要查询的培养素材的角色名", reply_message = await message.reply_text(
reply_markup=InlineKeyboardMarkup(self.KEYBOARD)) "请回复你要查询的培养素材的角色名", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
)
if filters.ChatType.GROUPS.filter(reply_message): if filters.ChatType.GROUPS.filter(reply_message):
self._add_delete_message_job(context, message.chat_id, message.message_id) self._add_delete_message_job(context, message.chat_id, message.message_id)
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id) self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
@ -43,8 +42,9 @@ class Material(Plugin, BasePlugin):
character_name = roleToName(character_name) character_name = roleToName(character_name)
url = await self.game_material_service.get_material(character_name) url = await self.game_material_service.get_material(character_name)
if not url: if not url:
reply_message = await message.reply_text(f"没有找到 {character_name} 的培养素材", reply_message = await message.reply_text(
reply_markup=InlineKeyboardMarkup(self.KEYBOARD)) f"没有找到 {character_name} 的培养素材", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
)
if filters.ChatType.GROUPS.filter(reply_message): if filters.ChatType.GROUPS.filter(reply_message):
self._add_delete_message_job(context, message.chat_id, message.message_id) self._add_delete_message_job(context, message.chat_id, message.message_id)
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id) self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
@ -52,7 +52,11 @@ class Material(Plugin, BasePlugin):
logger.info(f"用户 {user.full_name}[{user.id}] 查询角色培养素材命令请求 || 参数 {character_name}") logger.info(f"用户 {user.full_name}[{user.id}] 查询角色培养素材命令请求 || 参数 {character_name}")
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
file_path = await url_to_file(url, return_path=True) file_path = await url_to_file(url, return_path=True)
caption = "From 米游社 " \ caption = "From 米游社 " f"查看 [原图]({url})"
f"查看 [原图]({url})" await message.reply_photo(
await message.reply_photo(photo=open(file_path, "rb"), caption=caption, filename=f"{character_name}.png", photo=open(file_path, "rb"),
allow_sending_without_reply=True, parse_mode=ParseMode.MARKDOWN_V2) caption=caption,
filename=f"{character_name}.png",
allow_sending_without_reply=True,
parse_mode=ParseMode.MARKDOWN_V2,
)

View File

@ -50,8 +50,13 @@ class QuizPlugin(Plugin, BasePlugin):
return None return None
random.shuffle(_options) random.shuffle(_options)
index = _options.index(correct_option) index = _options.index(correct_option)
poll_message = await update.effective_message.reply_poll(question.text, _options, poll_message = await update.effective_message.reply_poll(
correct_option_id=index, is_anonymous=False, question.text,
open_period=self.time_out, type=Poll.QUIZ) _options,
correct_option_id=index,
is_anonymous=False,
open_period=self.time_out,
type=Poll.QUIZ,
)
self._add_delete_message_job(context, update.message.chat_id, update.message.message_id, 300) self._add_delete_message_job(context, update.message.chat_id, update.message.message_id, 300)
self._add_delete_message_job(context, poll_message.chat_id, poll_message.message_id, 300) self._add_delete_message_job(context, poll_message.chat_id, poll_message.message_id, 300)

View File

@ -35,8 +35,7 @@ class StrategyPlugin(Plugin, BasePlugin):
if len(args) >= 1: if len(args) >= 1:
character_name = args[0] character_name = args[0]
else: else:
reply_message = await message.reply_text("请回复你要查询的攻略的角色名", reply_message = await message.reply_text("请回复你要查询的攻略的角色名", reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
if filters.ChatType.GROUPS.filter(reply_message): if filters.ChatType.GROUPS.filter(reply_message):
self._add_delete_message_job(context, message.chat_id, message.message_id) self._add_delete_message_job(context, message.chat_id, message.message_id)
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id) self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
@ -44,8 +43,9 @@ class StrategyPlugin(Plugin, BasePlugin):
character_name = roleToName(character_name) character_name = roleToName(character_name)
url = await self.game_strategy_service.get_strategy(character_name) url = await self.game_strategy_service.get_strategy(character_name)
if url == "": if url == "":
reply_message = await message.reply_text(f"没有找到 {character_name} 的攻略", reply_message = await message.reply_text(
reply_markup=InlineKeyboardMarkup(self.KEYBOARD)) f"没有找到 {character_name} 的攻略", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
)
if filters.ChatType.GROUPS.filter(reply_message): if filters.ChatType.GROUPS.filter(reply_message):
self._add_delete_message_job(context, message.chat_id, message.message_id) self._add_delete_message_job(context, message.chat_id, message.message_id)
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id) self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
@ -53,7 +53,11 @@ class StrategyPlugin(Plugin, BasePlugin):
logger.info(f"用户 {user.full_name}[{user.id}] 查询角色攻略命令请求 || 参数 {character_name}") logger.info(f"用户 {user.full_name}[{user.id}] 查询角色攻略命令请求 || 参数 {character_name}")
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
file_path = await url_to_file(url, return_path=True) file_path = await url_to_file(url, return_path=True)
caption = "From 米游社 西风驿站 " \ caption = "From 米游社 西风驿站 " f"查看 [原图]({url})"
f"查看 [原图]({url})" await message.reply_photo(
await message.reply_photo(photo=open(file_path, "rb"), caption=caption, filename=f"{character_name}.png", photo=open(file_path, "rb"),
allow_sending_without_reply=True, parse_mode=ParseMode.MARKDOWN_V2) caption=caption,
filename=f"{character_name}.png",
allow_sending_without_reply=True,
parse_mode=ParseMode.MARKDOWN_V2,
)

View File

@ -31,14 +31,18 @@ CHECK_SERVER, CHECK_UID, COMMAND_RESULT = range(10100, 10103)
class SetUserUid(Plugin.Conversation, BasePlugin.Conversation): class SetUserUid(Plugin.Conversation, BasePlugin.Conversation):
"""UID用户绑定""" """UID用户绑定"""
def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None, def __init__(
public_cookies_service: PublicCookiesService = None): self,
user_service: UserService = None,
cookies_service: CookiesService = None,
public_cookies_service: PublicCookiesService = None,
):
self.public_cookies_service = public_cookies_service self.public_cookies_service = public_cookies_service
self.cookies_service = cookies_service self.cookies_service = cookies_service
self.user_service = user_service self.user_service = user_service
@conversation.entry_point @conversation.entry_point
@handler.command(command='setuid', filters=filters.ChatType.PRIVATE, block=True) @handler.command(command="setuid", filters=filters.ChatType.PRIVATE, block=True)
@restricts() @restricts()
@error_callable @error_callable
async def command_start(self, update: Update, context: CallbackContext) -> int: async def command_start(self, update: Update, context: CallbackContext) -> int:
@ -49,9 +53,11 @@ class SetUserUid(Plugin.Conversation, BasePlugin.Conversation):
if add_user_command_data is None: if add_user_command_data is None:
cookies_command_data = AddUserCommandData() cookies_command_data = AddUserCommandData()
context.chat_data["add_uid_command_data"] = cookies_command_data context.chat_data["add_uid_command_data"] = cookies_command_data
text = f'你好 {user.mention_markdown_v2()} ' \ text = (
f'{escape_markdown("请输入通行证UID非游戏UIDBOT将会通过通行证UID查找游戏UID。请选择要绑定的服务器或回复退出取消操作")}' f"你好 {user.mention_markdown_v2()} "
reply_keyboard = [['米游社', 'HoYoLab'], ["退出"]] f'{escape_markdown("请输入通行证UID非游戏UIDBOT将会通过通行证UID查找游戏UID。请选择要绑定的服务器或回复退出取消操作")}'
)
reply_keyboard = [["米游社", "HoYoLab"], ["退出"]]
await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)) await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
return CHECK_SERVER return CHECK_SERVER
@ -112,8 +118,9 @@ class SetUserUid(Plugin.Conversation, BasePlugin.Conversation):
if region == RegionEnum.HYPERION: if region == RegionEnum.HYPERION:
client = genshin.Client(cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.CHINESE) client = genshin.Client(cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.CHINESE)
elif region == RegionEnum.HOYOLAB: elif region == RegionEnum.HOYOLAB:
client = genshin.Client(cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.OVERSEAS, client = genshin.Client(
lang="zh-cn") cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.OVERSEAS, lang="zh-cn"
)
else: else:
return ConversationHandler.END return ConversationHandler.END
try: try:
@ -128,22 +135,20 @@ class SetUserUid(Plugin.Conversation, BasePlugin.Conversation):
logger.exception(exc) logger.exception(exc)
return ConversationHandler.END return ConversationHandler.END
if user_info.game != types.Game.GENSHIN: if user_info.game != types.Game.GENSHIN:
await message.reply_text("角色信息查询返回非原神游戏信息," await message.reply_text("角色信息查询返回非原神游戏信息," "请设置展示主界面为原神", reply_markup=ReplyKeyboardRemove())
"请设置展示主界面为原神", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END return ConversationHandler.END
reply_keyboard = [['确认', '退出']] reply_keyboard = [["确认", "退出"]]
await message.reply_text("获取角色基础信息成功,请检查是否正确!") await message.reply_text("获取角色基础信息成功,请检查是否正确!")
logger.info(f"用户 {user.full_name}[{user.id}] 获取账号 {user_info.nickname}[{user_info.uid}] 信息成功") logger.info(f"用户 {user.full_name}[{user.id}] 获取账号 {user_info.nickname}[{user_info.uid}] 信息成功")
text = f"*角色信息*\n" \ text = (
f"角色名称:{escape_markdown(user_info.nickname, version=2)}\n" \ f"*角色信息*\n"
f"角色等级:{user_info.level}\n" \ f"角色名称:{escape_markdown(user_info.nickname, version=2)}\n"
f"UID`{user_info.uid}`\n" \ f"角色等级:{user_info.level}\n"
f"服务器名称:`{user_info.server_name}`\n" f"UID`{user_info.uid}`\n"
add_user_command_data.game_uid = user_info.uid f"服务器名称:`{user_info.server_name}`\n"
await message.reply_markdown_v2(
text,
reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
) )
add_user_command_data.game_uid = user_info.uid
await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
return COMMAND_RESULT return COMMAND_RESULT
@conversation.state(state=COMMAND_RESULT) @conversation.state(state=COMMAND_RESULT)
@ -159,11 +164,15 @@ class SetUserUid(Plugin.Conversation, BasePlugin.Conversation):
elif message.text == "确认": elif message.text == "确认":
if add_user_command_data.user is None: if add_user_command_data.user is None:
if add_user_command_data.region == RegionEnum.HYPERION: if add_user_command_data.region == RegionEnum.HYPERION:
user_db = User(user_id=user.id, yuanshen_uid=add_user_command_data.game_uid, user_db = User(
region=add_user_command_data.region) user_id=user.id,
yuanshen_uid=add_user_command_data.game_uid,
region=add_user_command_data.region,
)
elif add_user_command_data.region == RegionEnum.HOYOLAB: elif add_user_command_data.region == RegionEnum.HOYOLAB:
user_db = User(user_id=user.id, genshin_uid=add_user_command_data.game_uid, user_db = User(
region=add_user_command_data.region) user_id=user.id, genshin_uid=add_user_command_data.game_uid, region=add_user_command_data.region
)
else: else:
await message.reply_text("数据错误") await message.reply_text("数据错误")
return ConversationHandler.END return ConversationHandler.END

View File

@ -55,13 +55,9 @@ class UserStatsPlugins(Plugin, BasePlugin):
except UserNotFoundError: except UserNotFoundError:
reply_message = await message.reply_text("未查询到账号信息,请先私聊派蒙绑定账号") reply_message = await message.reply_text("未查询到账号信息,请先私聊派蒙绑定账号")
if filters.ChatType.GROUPS.filter(message): if filters.ChatType.GROUPS.filter(message):
self._add_delete_message_job( self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
context, reply_message.chat_id, reply_message.message_id, 30
)
self._add_delete_message_job( self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
context, message.chat_id, message.message_id, 30
)
return return
except TooManyRequestPublicCookies: except TooManyRequestPublicCookies:
await message.reply_text("用户查询次数过多 请稍后重试") await message.reply_text("用户查询次数过多 请稍后重试")
@ -72,9 +68,7 @@ class UserStatsPlugins(Plugin, BasePlugin):
await message.reply_text("角色数据有误 估计是派蒙晕了") await message.reply_text("角色数据有误 估计是派蒙晕了")
return return
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
await message.reply_photo( await message.reply_photo(png_data, filename=f"{client.uid}.png", allow_sending_without_reply=True)
png_data, filename=f"{client.uid}.png", allow_sending_without_reply=True
)
async def render(self, client: Client, uid: Optional[int] = None) -> bytes: async def render(self, client: Client, uid: Optional[int] = None) -> bytes:
if uid is None: if uid is None:
@ -109,7 +103,7 @@ class UserStatsPlugins(Plugin, BasePlugin):
("雷神瞳", "electroculi"), ("雷神瞳", "electroculi"),
("草神瞳", "dendroculi"), ("草神瞳", "dendroculi"),
], ],
"style": secrets.choice(["mondstadt", "liyue"]) "style": secrets.choice(["mondstadt", "liyue"]),
} }
# html = await self.template_service.render_async( # html = await self.template_service.render_async(

View File

@ -20,15 +20,13 @@ from utils.log import logger
class WeaponPlugin(Plugin, BasePlugin): class WeaponPlugin(Plugin, BasePlugin):
"""武器查询""" """武器查询"""
KEYBOARD = [[ KEYBOARD = [[InlineKeyboardButton(text="查看武器列表并查询", switch_inline_query_current_chat="查看武器列表并查询")]]
InlineKeyboardButton(text="查看武器列表并查询", switch_inline_query_current_chat="查看武器列表并查询")
]]
def __init__( def __init__(
self, self,
template_service: TemplateService = None, template_service: TemplateService = None,
wiki_service: WikiService = None, wiki_service: WikiService = None,
assets_service: AssetsService = None assets_service: AssetsService = None,
): ):
self.wiki_service = wiki_service self.wiki_service = wiki_service
self.template_service = template_service self.template_service = template_service
@ -45,8 +43,7 @@ class WeaponPlugin(Plugin, BasePlugin):
if len(args) >= 1: if len(args) >= 1:
weapon_name = args[0] weapon_name = args[0]
else: else:
reply_message = await message.reply_text("请回复你要查询的武器", reply_message = await message.reply_text("请回复你要查询的武器", reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
if filters.ChatType.GROUPS.filter(reply_message): if filters.ChatType.GROUPS.filter(reply_message):
self._add_delete_message_job(context, message.chat_id, message.message_id) self._add_delete_message_job(context, message.chat_id, message.message_id)
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id) self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
@ -59,8 +56,9 @@ class WeaponPlugin(Plugin, BasePlugin):
weapon_data = weapon weapon_data = weapon
break break
else: else:
reply_message = await message.reply_text(f"没有找到 {weapon_name}", reply_message = await message.reply_text(
reply_markup=InlineKeyboardMarkup(self.KEYBOARD)) f"没有找到 {weapon_name}", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
)
if filters.ChatType.GROUPS.filter(reply_message): if filters.ChatType.GROUPS.filter(reply_message):
self._add_delete_message_job(context, message.chat_id, message.message_id) self._add_delete_message_job(context, message.chat_id, message.message_id)
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id) self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
@ -70,8 +68,8 @@ class WeaponPlugin(Plugin, BasePlugin):
async def input_template_data(_weapon_data: Weapon): async def input_template_data(_weapon_data: Weapon):
if weapon.rarity > 2: if weapon.rarity > 2:
bonus = _weapon_data.stats[-1].bonus bonus = _weapon_data.stats[-1].bonus
if '%' in bonus: if "%" in bonus:
bonus = str(round(float(bonus.rstrip('%')))) + '%' bonus = str(round(float(bonus.rstrip("%")))) + "%"
else: else:
bonus = str(round(float(bonus))) bonus = str(round(float(bonus)))
_template_data = { _template_data = {
@ -80,12 +78,12 @@ class WeaponPlugin(Plugin, BasePlugin):
"progression_secondary_stat_value": bonus, "progression_secondary_stat_value": bonus,
"progression_secondary_stat_name": _weapon_data.attribute.type.value, "progression_secondary_stat_name": _weapon_data.attribute.type.value,
"weapon_info_source_img": ( "weapon_info_source_img": (
await self.assets_service.weapon(honey_id_to_game_id(_weapon_data.id, 'weapon')).icon() await self.assets_service.weapon(honey_id_to_game_id(_weapon_data.id, "weapon")).icon()
).as_uri(), ).as_uri(),
"weapon_info_max_level": _weapon_data.stats[-1].level, "weapon_info_max_level": _weapon_data.stats[-1].level,
"progression_base_atk": round(_weapon_data.stats[-1].ATK), "progression_base_atk": round(_weapon_data.stats[-1].ATK),
"weapon_info_source_list": [ "weapon_info_source_list": [
(await self.assets_service.material(honey_id_to_game_id(mid, 'material')).icon()).as_uri() (await self.assets_service.material(honey_id_to_game_id(mid, "material")).icon()).as_uri()
for mid in _weapon_data.ascension[-3:] for mid in _weapon_data.ascension[-3:]
], ],
"special_ability_name": _weapon_data.affix.name, "special_ability_name": _weapon_data.affix.name,
@ -95,25 +93,27 @@ class WeaponPlugin(Plugin, BasePlugin):
_template_data = { _template_data = {
"weapon_name": _weapon_data.name, "weapon_name": _weapon_data.name,
"weapon_info_type_img": await url_to_file(_weapon_data.weapon_type.icon_url()), "weapon_info_type_img": await url_to_file(_weapon_data.weapon_type.icon_url()),
"progression_secondary_stat_value": ' ', "progression_secondary_stat_value": " ",
"progression_secondary_stat_name": '无其它属性加成', "progression_secondary_stat_name": "无其它属性加成",
"weapon_info_source_img": ( "weapon_info_source_img": (
await self.assets_service.weapon(honey_id_to_game_id(_weapon_data.id, 'weapon')).icon() await self.assets_service.weapon(honey_id_to_game_id(_weapon_data.id, "weapon")).icon()
).as_uri(), ).as_uri(),
"weapon_info_max_level": _weapon_data.stats[-1].level, "weapon_info_max_level": _weapon_data.stats[-1].level,
"progression_base_atk": round(_weapon_data.stats[-1].ATK), "progression_base_atk": round(_weapon_data.stats[-1].ATK),
"weapon_info_source_list": [ "weapon_info_source_list": [
(await self.assets_service.material(honey_id_to_game_id(mid, 'material')).icon()).as_uri() (await self.assets_service.material(honey_id_to_game_id(mid, "material")).icon()).as_uri()
for mid in _weapon_data.ascension[-3:] for mid in _weapon_data.ascension[-3:]
], ],
"special_ability_name": '', "special_ability_name": "",
"special_ability_info": _weapon_data.description, "special_ability_info": _weapon_data.description,
} }
return _template_data return _template_data
template_data = await input_template_data(weapon_data) template_data = await input_template_data(weapon_data)
png_data = await self.template_service.render('genshin/weapon', "weapon.html", template_data, png_data = await self.template_service.render(
{"width": 540, "height": 540}) "genshin/weapon", "weapon.html", template_data, {"width": 540, "height": 540}
)
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
await message.reply_photo(png_data, filename=f"{template_data['weapon_name']}.png", await message.reply_photo(
allow_sending_without_reply=True) png_data, filename=f"{template_data['weapon_name']}.png", allow_sending_without_reply=True
)

View File

@ -8,7 +8,6 @@ from utils.log import logger
class PublicCookies(Plugin): class PublicCookies(Plugin):
def __init__(self, public_cookies_service: PublicCookiesService = None): def __init__(self, public_cookies_service: PublicCookiesService = None):
self.public_cookies_service = public_cookies_service self.public_cookies_service = public_cookies_service

View File

@ -19,7 +19,6 @@ from utils.log import logger
class PostHandlerData: class PostHandlerData:
def __init__(self): def __init__(self):
self.post_text: str = "" self.post_text: str = ""
self.post_images: Optional[List[ArtworkImage]] = None self.post_images: Optional[List[ArtworkImage]] = None
@ -41,7 +40,7 @@ class Post(Plugin.Conversation, BasePlugin):
self.bbs = Hyperion() self.bbs = Hyperion()
@conversation.entry_point @conversation.entry_point
@handler.command(command='post', filters=filters.ChatType.PRIVATE, block=True) @handler.command(command="post", filters=filters.ChatType.PRIVATE, block=True)
@restricts() @restricts()
@bot_admins_rights_check @bot_admins_rights_check
@error_callable @error_callable
@ -53,10 +52,8 @@ class Post(Plugin.Conversation, BasePlugin):
if post_handler_data is None: if post_handler_data is None:
post_handler_data = PostHandlerData() post_handler_data = PostHandlerData()
context.chat_data["post_handler_data"] = post_handler_data context.chat_data["post_handler_data"] = post_handler_data
text = f"✿✿ヽ(°▽°)ノ✿ 你好! {user.username} \n" \ text = f"✿✿ヽ(°▽°)ノ✿ 你好! {user.username} \n" "只需复制URL回复即可 \n" "退出投稿只需回复退出"
"只需复制URL回复即可 \n" \ reply_keyboard = [["退出"]]
"退出投稿只需回复退出"
reply_keyboard = [['退出']]
await message.reply_text(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, True, True)) await message.reply_text(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, True, True))
return CHECK_POST return CHECK_POST
@ -77,17 +74,16 @@ class Post(Plugin.Conversation, BasePlugin):
post_info = await self.bbs.get_post_info(2, post_id) post_info = await self.bbs.get_post_info(2, post_id)
post_images = await self.bbs.get_images_by_post_id(2, post_id) post_images = await self.bbs.get_images_by_post_id(2, post_id)
post_data = post_info["post"]["post"] post_data = post_info["post"]["post"]
post_subject = post_data['subject'] post_subject = post_data["subject"]
post_soup = BeautifulSoup(post_data["content"], features="html.parser") post_soup = BeautifulSoup(post_data["content"], features="html.parser")
post_p = post_soup.find_all('p') post_p = post_soup.find_all("p")
post_text = f"*{escape_markdown(post_subject, version=2)}*\n" \ post_text = f"*{escape_markdown(post_subject, version=2)}*\n" f"\n"
f"\n"
for p in post_p: for p in post_p:
post_text += f"{escape_markdown(p.get_text(), version=2)}\n" post_text += f"{escape_markdown(p.get_text(), version=2)}\n"
post_text += f"[source](https://bbs.mihoyo.com/ys/article/{post_id})" post_text += f"[source](https://bbs.mihoyo.com/ys/article/{post_id})"
if len(post_text) >= MessageLimit.CAPTION_LENGTH: if len(post_text) >= MessageLimit.CAPTION_LENGTH:
await message.reply_markdown_v2(post_text) await message.reply_markdown_v2(post_text)
post_text = post_text[0:MessageLimit.CAPTION_LENGTH] post_text = post_text[0 : MessageLimit.CAPTION_LENGTH]
await message.reply_text(f"警告!图片字符描述已经超过 {MessageLimit.CAPTION_LENGTH} 个字,已经切割并发送原文本") await message.reply_text(f"警告!图片字符描述已经超过 {MessageLimit.CAPTION_LENGTH} 个字,已经切割并发送原文本")
try: try:
if len(post_images) > 1: if len(post_images) > 1:
@ -138,8 +134,7 @@ class Post(Plugin.Conversation, BasePlugin):
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data") post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
photo_len = len(post_handler_data.post_images) photo_len = len(post_handler_data.post_images)
message = update.effective_message message = update.effective_message
await message.reply_text("请回复你要删除的图片的序列从1开始如果删除多张图片回复的序列请以空格作为分隔符" await message.reply_text("请回复你要删除的图片的序列从1开始如果删除多张图片回复的序列请以空格作为分隔符" f"当前一共有 {photo_len} 张图片")
f"当前一共有 {photo_len} 张图片")
return GTE_DELETE_PHOTO return GTE_DELETE_PHOTO
@conversation.state(state=GTE_DELETE_PHOTO) @conversation.state(state=GTE_DELETE_PHOTO)
@ -175,8 +170,7 @@ class Post(Plugin.Conversation, BasePlugin):
logger.error("从配置文件获取频道信息发生错误,退出任务", error) logger.error("从配置文件获取频道信息发生错误,退出任务", error)
await message.reply_text("从配置文件获取频道信息发生错误,退出任务", reply_markup=ReplyKeyboardRemove()) await message.reply_text("从配置文件获取频道信息发生错误,退出任务", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END return ConversationHandler.END
await message.reply_text("请选择你要推送的频道", await message.reply_text("请选择你要推送的频道", reply_markup=ReplyKeyboardMarkup(reply_keyboard, True, True))
reply_markup=ReplyKeyboardMarkup(reply_keyboard, True, True))
return GET_POST_CHANNEL return GET_POST_CHANNEL
@conversation.state(state=GET_POST_CHANNEL) @conversation.state(state=GET_POST_CHANNEL)
@ -200,8 +194,7 @@ class Post(Plugin.Conversation, BasePlugin):
return ConversationHandler.END return ConversationHandler.END
post_handler_data.channel_id = channel_id post_handler_data.channel_id = channel_id
reply_keyboard = [["确认", "退出"]] reply_keyboard = [["确认", "退出"]]
await message.reply_text("请核对你修改的信息", await message.reply_text("请核对你修改的信息", reply_markup=ReplyKeyboardMarkup(reply_keyboard, True, True))
reply_markup=ReplyKeyboardMarkup(reply_keyboard, True, True))
return SEND_POST return SEND_POST
async def add_tags(self, update: Update, _: CallbackContext) -> int: async def add_tags(self, update: Update, _: CallbackContext) -> int:
@ -273,8 +266,9 @@ class Post(Plugin.Conversation, BasePlugin):
await context.bot.send_media_group(channel_id, media=media) await context.bot.send_media_group(channel_id, media=media)
elif len(post_images) == 1: elif len(post_images) == 1:
image = post_images[0] image = post_images[0]
await context.bot.send_photo(channel_id, photo=image.data, caption=post_text, await context.bot.send_photo(
parse_mode=ParseMode.MARKDOWN_V2) channel_id, photo=image.data, caption=post_text, parse_mode=ParseMode.MARKDOWN_V2
)
elif len(post_images) == 0: elif len(post_images) == 0:
await context.bot.send_message(channel_id, post_text, parse_mode=ParseMode.MARKDOWN_V2) await context.bot.send_message(channel_id, post_text, parse_mode=ParseMode.MARKDOWN_V2)
else: else:
@ -287,4 +281,3 @@ class Post(Plugin.Conversation, BasePlugin):
return ConversationHandler.END return ConversationHandler.END
await message.reply_text("推送成功", reply_markup=ReplyKeyboardRemove()) await message.reply_text("推送成功", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END return ConversationHandler.END

View File

@ -72,8 +72,9 @@ class GroupJoiningVerification(Plugin):
job = context.job job = context.job
logger.info(f"踢出用户 user_id[{job.user_id}] 在 chat_id[{job.chat_id}]") logger.info(f"踢出用户 user_id[{job.user_id}] 在 chat_id[{job.chat_id}]")
try: try:
await context.bot.ban_chat_member(chat_id=job.chat_id, user_id=job.user_id, await context.bot.ban_chat_member(
until_date=int(time.time()) + self.kick_time) chat_id=job.chat_id, user_id=job.user_id, until_date=int(time.time()) + self.kick_time
)
except BadRequest as exc: except BadRequest as exc:
logger.error(f"Auth模块在 chat_id[{job.chat_id}] user_id[{job.user_id}] 执行kick失败") logger.error(f"Auth模块在 chat_id[{job.chat_id}] user_id[{job.user_id}] 执行kick失败")
logger.exception(exc) logger.exception(exc)
@ -88,8 +89,7 @@ class GroupJoiningVerification(Plugin):
if "not found" in str(exc): if "not found" in str(exc):
logger.warning(f"Auth模块删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败 消息不存在") logger.warning(f"Auth模块删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败 消息不存在")
elif "Message can't be deleted" in str(exc): elif "Message can't be deleted" in str(exc):
logger.warning( logger.warning(f"Auth模块删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败 消息无法删除 可能是没有授权")
f"Auth模块删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败 消息无法删除 可能是没有授权")
else: else:
logger.error(f"Auth模块删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败") logger.error(f"Auth模块删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败")
logger.exception(exc) logger.exception(exc)
@ -106,7 +106,6 @@ class GroupJoiningVerification(Plugin):
@handler(CallbackQueryHandler, pattern=r"^auth_admin\|", block=False) @handler(CallbackQueryHandler, pattern=r"^auth_admin\|", block=False)
@restricts(without_overlapping=True) @restricts(without_overlapping=True)
async def admin(self, update: Update, context: CallbackContext) -> None: async def admin(self, update: Update, context: CallbackContext) -> None:
async def admin_callback(callback_query_data: str) -> Tuple[str, int]: async def admin_callback(callback_query_data: str) -> Tuple[str, int]:
_data = callback_query_data.split("|") _data = callback_query_data.split("|")
_result = _data[1] _result = _data[1]
@ -122,8 +121,7 @@ class GroupJoiningVerification(Plugin):
chat_administrators = await self.get_chat_administrators(context, chat_id=chat.id) chat_administrators = await self.get_chat_administrators(context, chat_id=chat.id)
if not self.is_admin(chat_administrators, user.id): if not self.is_admin(chat_administrators, user.id):
logger.debug(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 非群管理") logger.debug(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 非群管理")
await callback_query.answer(text="你不是管理!\n" await callback_query.answer(text="你不是管理!\n" "再乱点我叫西风骑士团、千岩军和天领奉行了!", show_alert=True)
"再乱点我叫西风骑士团、千岩军和天领奉行了!", show_alert=True)
return return
result, user_id = await admin_callback(callback_query.data) result, user_id = await admin_callback(callback_query.data)
try: try:
@ -139,22 +137,21 @@ class GroupJoiningVerification(Plugin):
await self.restore_member(context, chat.id, user_id) await self.restore_member(context, chat.id, user_id)
if schedule := context.job_queue.scheduler.get_job(f"{chat.id}|{user_id}|auth_kick"): if schedule := context.job_queue.scheduler.get_job(f"{chat.id}|{user_id}|auth_kick"):
schedule.remove() schedule.remove()
await message.edit_text(f"{user_info}{user.mention_markdown_v2()} 放行", await message.edit_text(f"{user_info}{user.mention_markdown_v2()} 放行", parse_mode=ParseMode.MARKDOWN_V2)
parse_mode=ParseMode.MARKDOWN_V2)
logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 被管理放行") logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 被管理放行")
elif result == "kick": elif result == "kick":
await callback_query.answer(text="驱离", show_alert=False) await callback_query.answer(text="驱离", show_alert=False)
await context.bot.ban_chat_member(chat.id, user_id) await context.bot.ban_chat_member(chat.id, user_id)
await message.edit_text(f"{user_info}{user.mention_markdown_v2()} 驱离", await message.edit_text(f"{user_info}{user.mention_markdown_v2()} 驱离", parse_mode=ParseMode.MARKDOWN_V2)
parse_mode=ParseMode.MARKDOWN_V2)
logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 被管理踢出") logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 被管理踢出")
elif result == "unban": elif result == "unban":
await callback_query.answer(text="解除驱离", show_alert=False) await callback_query.answer(text="解除驱离", show_alert=False)
await self.restore_member(context, chat.id, user_id) await self.restore_member(context, chat.id, user_id)
if schedule := context.job_queue.scheduler.get_job(f"{chat.id}|{user_id}|auth_kick"): if schedule := context.job_queue.scheduler.get_job(f"{chat.id}|{user_id}|auth_kick"):
schedule.remove() schedule.remove()
await message.edit_text(f"{user_info}{user.mention_markdown_v2()} 解除驱离", await message.edit_text(
parse_mode=ParseMode.MARKDOWN_V2) f"{user_info}{user.mention_markdown_v2()} 解除驱离", parse_mode=ParseMode.MARKDOWN_V2
)
logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 被管理解除封禁") logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 被管理解除封禁")
else: else:
logger.warning(f"auth 模块 admin 函数 发现未知命令 result[{result}]") logger.warning(f"auth 模块 admin 函数 发现未知命令 result[{result}]")
@ -165,7 +162,6 @@ class GroupJoiningVerification(Plugin):
@handler(CallbackQueryHandler, pattern=r"^auth_challenge\|", block=False) @handler(CallbackQueryHandler, pattern=r"^auth_challenge\|", block=False)
@restricts(without_overlapping=True) @restricts(without_overlapping=True)
async def query(self, update: Update, context: CallbackContext) -> None: async def query(self, update: Update, context: CallbackContext) -> None:
async def query_callback(callback_query_data: str) -> Tuple[int, bool, str, str]: async def query_callback(callback_query_data: str) -> Tuple[int, bool, str, str]:
_data = callback_query_data.split("|") _data = callback_query_data.split("|")
_user_id = int(_data[1]) _user_id = int(_data[1])
@ -176,8 +172,10 @@ class GroupJoiningVerification(Plugin):
_result = _answer.is_correct _result = _answer.is_correct
_answer_encode = _answer.text _answer_encode = _answer.text
_question_encode = _question.text _question_encode = _question.text
logger.debug(f"query_callback函数返回 user_id[{_user_id}] result[{_result}] \n" logger.debug(
f"question_encode[{_question_encode}] answer_encode[{_answer_encode}]") f"query_callback函数返回 user_id[{_user_id}] result[{_result}] \n"
f"question_encode[{_question_encode}] answer_encode[{_answer_encode}]"
)
return _user_id, _result, _question_encode, _answer_encode return _user_id, _result, _question_encode, _answer_encode
callback_query = update.callback_query callback_query = update.callback_query
@ -187,36 +185,43 @@ class GroupJoiningVerification(Plugin):
user_id, result, question, answer = await query_callback(callback_query.data) user_id, result, question, answer = await query_callback(callback_query.data)
logger.info(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 点击Auth认证命令 ") logger.info(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 点击Auth认证命令 ")
if user.id != user_id: if user.id != user_id:
await callback_query.answer(text="这不是你的验证!\n" await callback_query.answer(text="这不是你的验证!\n" "再乱点再按我叫西风骑士团、千岩军和天领奉行了!", show_alert=True)
"再乱点再按我叫西风骑士团、千岩军和天领奉行了!", show_alert=True)
return return
logger.info( logger.info(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 认证结果为 {'通过' if result else '失败'}")
f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 认证结果为 {'通过' if result else '失败'}")
if result: if result:
buttons = [[InlineKeyboardButton("驱离", callback_data=f"auth_admin|kick|{user.id}")]] buttons = [[InlineKeyboardButton("驱离", callback_data=f"auth_admin|kick|{user.id}")]]
await callback_query.answer(text="验证成功", show_alert=False) await callback_query.answer(text="验证成功", show_alert=False)
await self.restore_member(context, chat.id, user_id) await self.restore_member(context, chat.id, user_id)
if schedule := context.job_queue.scheduler.get_job(f"{chat.id}|{user.id}|auth_kick"): if schedule := context.job_queue.scheduler.get_job(f"{chat.id}|{user.id}|auth_kick"):
schedule.remove() schedule.remove()
text = f"{user.mention_markdown_v2()} 验证成功,向着星辰与深渊!\n" \ text = (
f"问题:{escape_markdown(question, version=2)} \n" \ f"{user.mention_markdown_v2()} 验证成功,向着星辰与深渊!\n"
f"回答:{escape_markdown(answer, version=2)}" f"问题:{escape_markdown(question, version=2)} \n"
f"回答:{escape_markdown(answer, version=2)}"
)
logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 验证成功") logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 验证成功")
else: else:
buttons = [[InlineKeyboardButton("驱离", callback_data=f"auth_admin|kick|{user.id}"), buttons = [
InlineKeyboardButton("撤回驱离", callback_data=f"auth_admin|unban|{user.id}")]] [
InlineKeyboardButton("驱离", callback_data=f"auth_admin|kick|{user.id}"),
InlineKeyboardButton("撤回驱离", callback_data=f"auth_admin|unban|{user.id}"),
]
]
await callback_query.answer(text=f"验证失败,请在 {self.time_out} 秒后重试", show_alert=True) await callback_query.answer(text=f"验证失败,请在 {self.time_out} 秒后重试", show_alert=True)
await asyncio.sleep(3) await asyncio.sleep(3)
await context.bot.ban_chat_member(chat_id=chat.id, user_id=user_id, await context.bot.ban_chat_member(
until_date=int(time.time()) + self.kick_time) chat_id=chat.id, user_id=user_id, until_date=int(time.time()) + self.kick_time
text = f"{user.mention_markdown_v2()} 验证失败,已经赶出提瓦特大陆!\n" \ )
f"问题:{escape_markdown(question, version=2)} \n" \ text = (
f"回答:{escape_markdown(answer, version=2)}" f"{user.mention_markdown_v2()} 验证失败,已经赶出提瓦特大陆!\n"
f"问题:{escape_markdown(question, version=2)} \n"
f"回答:{escape_markdown(answer, version=2)}"
)
logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 验证失败") logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 验证失败")
try: try:
await message.edit_text(text, reply_markup=InlineKeyboardMarkup(buttons), parse_mode=ParseMode.MARKDOWN_V2) await message.edit_text(text, reply_markup=InlineKeyboardMarkup(buttons), parse_mode=ParseMode.MARKDOWN_V2)
except BadRequest as exc: except BadRequest as exc:
if 'are exactly the same as ' in str(exc): if "are exactly the same as " in str(exc):
logger.warning("编辑消息发生异常,可能为用户点按多次键盘导致") logger.warning("编辑消息发生异常,可能为用户点按多次键盘导致")
else: else:
raise exc raise exc
@ -254,8 +259,9 @@ class GroupJoiningVerification(Plugin):
await message.reply_text("旅行者!!!派蒙的问题清单你还没给我!!快去私聊我给我问题!") await message.reply_text("旅行者!!!派蒙的问题清单你还没给我!!快去私聊我给我问题!")
return return
try: try:
await context.bot.restrict_chat_member(chat_id=message.chat.id, user_id=user.id, await context.bot.restrict_chat_member(
permissions=ChatPermissions(can_send_messages=False)) chat_id=message.chat.id, user_id=user.id, permissions=ChatPermissions(can_send_messages=False)
)
except BadRequest as err: except BadRequest as err:
if "Not enough rights" in str(err): if "Not enough rights" in str(err):
logger.warning(f"权限不够 chat_id[{message.chat_id}]") logger.warning(f"权限不够 chat_id[{message.chat_id}]")
@ -290,45 +296,59 @@ class GroupJoiningVerification(Plugin):
), ),
] ]
) )
reply_message = f"*欢迎来到「提瓦特」世界!* \n" \ reply_message = (
f"问题: {escape_markdown(question.text, version=2)} \n" \ f"*欢迎来到「提瓦特」世界!* \n" f"问题: {escape_markdown(question.text, version=2)} \n" f"请在 {self.time_out}S 内回答问题"
f"请在 {self.time_out}S 内回答问题" )
logger.debug(f"发送入群验证问题 question_id[{question.question_id}] question[{question.text}] \n" logger.debug(
f"{user.full_name}[{user.id}] 在 {chat.title}[{chat.id}]") f"发送入群验证问题 question_id[{question.question_id}] question[{question.text}] \n"
f"{user.full_name}[{user.id}] 在 {chat.title}[{chat.id}]"
)
try: try:
question_message = await message.reply_markdown_v2(reply_message, question_message = await message.reply_markdown_v2(
reply_markup=InlineKeyboardMarkup(buttons)) reply_message, reply_markup=InlineKeyboardMarkup(buttons)
)
except BadRequest as exc: except BadRequest as exc:
await message.reply_text("派蒙分心了一下,不小心忘记你了,你只能先退出群再重新进来吧。") await message.reply_text("派蒙分心了一下,不小心忘记你了,你只能先退出群再重新进来吧。")
raise exc raise exc
context.job_queue.run_once(callback=self.kick_member_job, when=self.time_out, context.job_queue.run_once(
name=f"{chat.id}|{user.id}|auth_kick", chat_id=chat.id, user_id=user.id, callback=self.kick_member_job,
job_kwargs={"replace_existing": True, "id": f"{chat.id}|{user.id}|auth_kick"}) when=self.time_out,
context.job_queue.run_once(callback=self.clean_message_job, when=self.time_out, data=message.message_id, name=f"{chat.id}|{user.id}|auth_kick",
name=f"{chat.id}|{user.id}|auth_clean_join_message", chat_id=chat.id,
chat_id=chat.id, user_id=user.id, user_id=user.id,
job_kwargs={"replace_existing": True, job_kwargs={"replace_existing": True, "id": f"{chat.id}|{user.id}|auth_kick"},
"id": f"{chat.id}|{user.id}|auth_clean_join_message"}) )
context.job_queue.run_once(callback=self.clean_message_job, when=self.time_out, context.job_queue.run_once(
data=question_message.message_id, callback=self.clean_message_job,
name=f"{chat.id}|{user.id}|auth_clean_question_message", when=self.time_out,
chat_id=chat.id, user_id=user.id, data=message.message_id,
job_kwargs={"replace_existing": True, name=f"{chat.id}|{user.id}|auth_clean_join_message",
"id": f"{chat.id}|{user.id}|auth_clean_question_message"}) chat_id=chat.id,
user_id=user.id,
job_kwargs={"replace_existing": True, "id": f"{chat.id}|{user.id}|auth_clean_join_message"},
)
context.job_queue.run_once(
callback=self.clean_message_job,
when=self.time_out,
data=question_message.message_id,
name=f"{chat.id}|{user.id}|auth_clean_question_message",
chat_id=chat.id,
user_id=user.id,
job_kwargs={"replace_existing": True, "id": f"{chat.id}|{user.id}|auth_clean_question_message"},
)
if self.mtp and (question_message.id - message.id - 1): if self.mtp and (question_message.id - message.id - 1):
from pyrogram.errors import BadRequest as MTPBadRequest, FloodWait as MTPFloodWait from pyrogram.errors import BadRequest as MTPBadRequest, FloodWait as MTPFloodWait
try: try:
messages_list = await self.mtp.get_messages( messages_list = await self.mtp.get_messages(
chat.id, chat.id, message_ids=list(range(message.id + 1, question_message.id))
message_ids=list(range(message.id + 1, question_message.id))
) )
for find_message in messages_list: for find_message in messages_list:
if find_message.empty: if find_message.empty:
continue continue
if find_message.from_user and find_message.from_user.id == user.id: if find_message.from_user and find_message.from_user.id == user.id:
await self.mtp.delete_messages(chat_id=chat.id, message_ids=find_message.id) await self.mtp.delete_messages(chat_id=chat.id, message_ids=find_message.id)
logger.info(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 验证缝隙间发送消息" logger.info(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 验证缝隙间发送消息" "现已删除")
"现已删除")
except MTPFloodWait: except MTPFloodWait:
logger.warning("调用 mtp 触发洪水限制") logger.warning("调用 mtp 触发洪水限制")
continue continue

View File

@ -24,7 +24,6 @@ if not os.path.exists(report_dir):
class ErrorHandler(Plugin): class ErrorHandler(Plugin):
@error_handler(block=False) # pylint: disable=E1123, E1120 @error_handler(block=False) # pylint: disable=E1123, E1120
async def error_handler(self, update: object, context: CallbackContext) -> None: async def error_handler(self, update: object, context: CallbackContext) -> None:
"""记录错误并发送消息通知开发人员。 logger the error and send a telegram message to notify the developer.""" """记录错误并发送消息通知开发人员。 logger the error and send a telegram message to notify the developer."""
@ -52,20 +51,23 @@ class ErrorHandler(Plugin):
file_name = f"error_{update.update_id if isinstance(update, Update) else int(time.time())}.txt" file_name = f"error_{update.update_id if isinstance(update, Update) else int(time.time())}.txt"
log_file = os.path.join(report_dir, file_name) log_file = os.path.join(report_dir, file_name)
try: try:
async with aiofiles.open(log_file, mode='w+', encoding='utf-8') as f: async with aiofiles.open(log_file, mode="w+", encoding="utf-8") as f:
await f.write(error_text) await f.write(error_text)
except Exception as exc: except Exception as exc:
logger.error("保存日记失败") logger.error("保存日记失败")
logger.exception(exc) logger.exception(exc)
try: try:
if 'make sure that only one bot instance is running' in tb_string: if "make sure that only one bot instance is running" in tb_string:
logger.error("其他机器人在运行,请停止!") logger.error("其他机器人在运行,请停止!")
return return
if 'Message is not modified' in tb_string: if "Message is not modified" in tb_string:
logger.error("消息未修改") logger.error("消息未修改")
return return
await context.bot.send_document(chat_id=notice_chat_id, document=open(log_file, "rb"), await context.bot.send_document(
caption=f"Error: \"{context.error.__class__.__name__}\"") chat_id=notice_chat_id,
document=open(log_file, "rb"),
caption=f'Error: "{context.error.__class__.__name__}"',
)
except (BadRequest, Forbidden) as exc: except (BadRequest, Forbidden) as exc:
logger.error("发送日记失败") logger.error("发送日记失败")
logger.exception(exc) logger.exception(exc)
@ -76,12 +78,15 @@ class ErrorHandler(Plugin):
try: try:
if effective_message is not None: if effective_message is not None:
chat = effective_message.chat chat = effective_message.chat
logger.info(f"尝试通知用户 {effective_user.full_name}[{effective_user.id}] " logger.info(
f"{chat.full_name}[{chat.id}]" f"尝试通知用户 {effective_user.full_name}[{effective_user.id}] "
f"的 update_id[{update.update_id}] 错误信息") f"{chat.full_name}[{chat.id}]"
f"的 update_id[{update.update_id}] 错误信息"
)
text = "出错了呜呜呜 ~ 派蒙这边发生了点问题无法处理!" text = "出错了呜呜呜 ~ 派蒙这边发生了点问题无法处理!"
await context.bot.send_message(effective_message.chat_id, text, reply_markup=ReplyKeyboardRemove(), await context.bot.send_message(
parse_mode=ParseMode.HTML) effective_message.chat_id, text, reply_markup=ReplyKeyboardRemove(), parse_mode=ParseMode.HTML
)
except (BadRequest, Forbidden) as exc: except (BadRequest, Forbidden) as exc:
logger.error(f"发送 update_id[{update.update_id}] 错误信息失败 错误信息为") logger.error(f"发送 update_id[{update.update_id}] 错误信息失败 错误信息为")
logger.exception(exc) logger.exception(exc)

View File

@ -25,15 +25,14 @@ class GetChat(Plugin):
@staticmethod @staticmethod
def parse_group_chat(chat: Chat, admins: List[ChatMember]) -> str: def parse_group_chat(chat: Chat, admins: List[ChatMember]) -> str:
text = f"群 ID<code>{chat.id}</code>\n" \ text = f"群 ID<code>{chat.id}</code>\n" f"群名称:<code>{chat.title}</code>\n"
f"群名称:<code>{chat.title}</code>\n"
if chat.username: if chat.username:
text += f"群用户名:<code>{chat.username}</code>\n" text += f"群用户名:<code>{chat.username}</code>\n"
if chat.description: if chat.description:
text += f"群简介:<code>{chat.description}</code>\n" text += f"群简介:<code>{chat.description}</code>\n"
if admins: if admins:
for admin in admins: for admin in admins:
text += f"<a href=\"tg://user?id={admin.user.id}\">{admin.user.full_name}</a> " text += f'<a href="tg://user?id={admin.user.id}">{admin.user.full_name}</a> '
if isinstance(admin, ChatMemberAdministrator): if isinstance(admin, ChatMemberAdministrator):
text += "C" if admin.can_change_info else "_" text += "C" if admin.can_change_info else "_"
text += "D" if admin.can_delete_messages else "_" text += "D" if admin.can_delete_messages else "_"
@ -49,9 +48,11 @@ class GetChat(Plugin):
return text return text
async def parse_private_chat(self, chat: Chat) -> str: async def parse_private_chat(self, chat: Chat) -> str:
text = f"<a href=\"tg://user?id={chat.id}\">MENTION</a>\n" \ text = (
f"用户 ID<code>{chat.id}</code>\n" \ f'<a href="tg://user?id={chat.id}">MENTION</a>\n'
f"用户名称:<code>{chat.full_name}</code>\n" f"用户 ID<code>{chat.id}</code>\n"
f"用户名称:<code>{chat.full_name}</code>\n"
)
if chat.username: if chat.username:
text += f"用户名:@{chat.username}\n" text += f"用户名:@{chat.username}\n"
try: try:
@ -70,8 +71,7 @@ class GetChat(Plugin):
await get_genshin_client(chat.id) await get_genshin_client(chat.id)
except CookiesNotFoundError: except CookiesNotFoundError:
temp = "UID 绑定" temp = "UID 绑定"
text += f"<code>{temp}</code>\n" \ text += f"<code>{temp}</code>\n" f"游戏 ID<code>{uid}</code>"
f"游戏 ID<code>{uid}</code>"
with contextlib.suppress(Exception): with contextlib.suppress(Exception):
gacha_log, status = await GachaLog.load_history_info(str(chat.id), str(uid)) gacha_log, status = await GachaLog.load_history_info(str(chat.id), str(uid))
if status: if status:

View File

@ -36,9 +36,11 @@ class Inline(Plugin):
id=str(uuid4()), id=str(uuid4()),
title=weapons_name, title=weapons_name,
description=f"查看武器列表并查询 {weapons_name}", description=f"查看武器列表并查询 {weapons_name}",
input_message_content=InputTextMessageContent(f"武器查询{weapons_name}", input_message_content=InputTextMessageContent(
parse_mode=ParseMode.MARKDOWN_V2) f"武器查询{weapons_name}", parse_mode=ParseMode.MARKDOWN_V2
)) ),
)
)
elif "查看角色攻略列表并查询" == args[0]: elif "查看角色攻略列表并查询" == args[0]:
characters_list = await self.wiki_service.get_characters_name_list() characters_list = await self.wiki_service.get_characters_name_list()
for role_name in characters_list: for role_name in characters_list:
@ -47,9 +49,11 @@ class Inline(Plugin):
id=str(uuid4()), id=str(uuid4()),
title=role_name, title=role_name,
description=f"查看角色攻略列表并查询 {role_name}", description=f"查看角色攻略列表并查询 {role_name}",
input_message_content=InputTextMessageContent(f"角色攻略查询{role_name}", input_message_content=InputTextMessageContent(
parse_mode=ParseMode.MARKDOWN_V2) f"角色攻略查询{role_name}", parse_mode=ParseMode.MARKDOWN_V2
)) ),
)
)
elif "查看角色培养素材列表并查询" == args[0]: elif "查看角色培养素材列表并查询" == args[0]:
characters_list = await self.wiki_service.get_characters_name_list() characters_list = await self.wiki_service.get_characters_name_list()
for role_name in characters_list: for role_name in characters_list:
@ -58,9 +62,11 @@ class Inline(Plugin):
id=str(uuid4()), id=str(uuid4()),
title=role_name, title=role_name,
description=f"查看角色培养素材列表并查询 {role_name}", description=f"查看角色培养素材列表并查询 {role_name}",
input_message_content=InputTextMessageContent(f"角色培养素材查询{role_name}", input_message_content=InputTextMessageContent(
parse_mode=ParseMode.MARKDOWN_V2) f"角色培养素材查询{role_name}", parse_mode=ParseMode.MARKDOWN_V2
)) ),
)
)
if not results_list: if not results_list:
results_list.append( results_list.append(
@ -69,7 +75,8 @@ class Inline(Plugin):
title="好像找不到问题呢", title="好像找不到问题呢",
description="这个问题我也不知道,因为我就是个应急食品。", description="这个问题我也不知道,因为我就是个应急食品。",
input_message_content=InputTextMessageContent("这个问题我也不知道,因为我就是个应急食品。"), input_message_content=InputTextMessageContent("这个问题我也不知道,因为我就是个应急食品。"),
)) )
)
try: try:
await ilq.answer( await ilq.answer(
results=results_list, results=results_list,

View File

@ -13,7 +13,6 @@ debug_log = os.path.join(current_dir, "logs", "debug", "debug.log")
class Log(Plugin): class Log(Plugin):
@handler(CommandHandler, command="send_log", block=False) @handler(CommandHandler, command="send_log", block=False)
@bot_admins_rights_check @bot_admins_rights_check
async def send_log(self, update: Update, _: CallbackContext): async def send_log(self, update: Update, _: CallbackContext):
@ -21,10 +20,10 @@ class Log(Plugin):
logger.info(f"用户 {user.full_name}[{user.id}] send_log 命令请求") logger.info(f"用户 {user.full_name}[{user.id}] send_log 命令请求")
message = update.effective_message message = update.effective_message
if os.path.exists(error_log) and os.path.getsize(error_log) > 0: if os.path.exists(error_log) and os.path.getsize(error_log) > 0:
await message.reply_document(open(error_log, mode='rb+'), caption="Error Log") await message.reply_document(open(error_log, mode="rb+"), caption="Error Log")
else: else:
await message.reply_text("错误日记未找到") await message.reply_text("错误日记未找到")
if os.path.exists(debug_log) and os.path.getsize(debug_log) > 0: if os.path.exists(debug_log) and os.path.getsize(debug_log) > 0:
await message.reply_document(open(debug_log, mode='rb+'), caption="Debug Log") await message.reply_document(open(debug_log, mode="rb+"), caption="Debug Log")
else: else:
await message.reply_text("调试日记未找到") await message.reply_text("调试日记未找到")

View File

@ -7,7 +7,6 @@ from utils.log import logger
class BotJoiningGroupsVerification(Plugin): class BotJoiningGroupsVerification(Plugin):
def __init__(self, bot_admin_service: BotAdminService = None): def __init__(self, bot_admin_service: BotAdminService = None):
self.bot_admin_service = bot_admin_service self.bot_admin_service = bot_admin_service
@ -25,8 +24,7 @@ class BotJoiningGroupsVerification(Plugin):
logger.info(f"用户 {from_user.full_name}[{from_user.id}] 在群 {chat.title}[{chat.id}] 邀请BOT") logger.info(f"用户 {from_user.full_name}[{from_user.id}] 在群 {chat.title}[{chat.id}] 邀请BOT")
admin_list = await self.bot_admin_service.get_admin_list() admin_list = await self.bot_admin_service.get_admin_list()
if from_user.id in admin_list: if from_user.id in admin_list:
await context.bot.send_message(message.chat_id, await context.bot.send_message(message.chat_id, "感谢邀请小派蒙到本群!请使用 /help 查看咱已经学会的功能。")
'感谢邀请小派蒙到本群!请使用 /help 查看咱已经学会的功能。')
quit_status = False quit_status = False
else: else:
logger.info(f"未知用户 在群 {chat.title}[{chat.id}] 邀请BOT") logger.info(f"未知用户 在群 {chat.title}[{chat.id}] 邀请BOT")

View File

@ -8,16 +8,13 @@ from utils.log import logger
class MetadataPlugin(Plugin): class MetadataPlugin(Plugin):
@handler.command("refresh_metadata")
@handler.command('refresh_metadata')
@bot_admins_rights_check @bot_admins_rights_check
async def refresh(self, update: Update, _) -> None: async def refresh(self, update: Update, _) -> None:
user = update.effective_user user = update.effective_user
message = update.effective_message message = update.effective_message
logger.info( logger.info(f"用户 {user.full_name}[{user.id}] 刷新[bold]metadata[/]缓存命令", extra={"markup": True})
f"用户 {user.full_name}[{user.id}] 刷新[bold]metadata[/]缓存命令", extra={'markup': True}
)
msg = await message.reply_text("正在刷新元数据,请耐心等待...") msg = await message.reply_text("正在刷新元数据,请耐心等待...")
logger.info("正在从 github 上获取元数据") logger.info("正在从 github 上获取元数据")

View File

@ -23,7 +23,7 @@ from utils.log import logger
GET_NEW_CORRECT_ANSWER, GET_NEW_CORRECT_ANSWER,
GET_NEW_WRONG_ANSWER, GET_NEW_WRONG_ANSWER,
QUESTION_EDIT, QUESTION_EDIT,
SAVE_QUESTION SAVE_QUESTION,
) = range(10300, 10308) ) = range(10300, 10308)
@ -43,7 +43,7 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
self.time_out = 120 self.time_out = 120
@conversation.entry_point @conversation.entry_point
@handler.command(command='set_quiz', filters=filters.ChatType.PRIVATE, block=True) @handler.command(command="set_quiz", filters=filters.ChatType.PRIVATE, block=True)
@restricts() @restricts()
@bot_admins_rights_check @bot_admins_rights_check
@error_callable @error_callable
@ -56,32 +56,20 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
quiz_command_data = QuizCommandData() quiz_command_data = QuizCommandData()
context.chat_data["quiz_command_data"] = quiz_command_data context.chat_data["quiz_command_data"] = quiz_command_data
text = f'你好 {user.mention_markdown_v2()} {escape_markdown("!请选择你的操作!")}' text = f'你好 {user.mention_markdown_v2()} {escape_markdown("!请选择你的操作!")}'
reply_keyboard = [ reply_keyboard = [["查看问题", "添加问题"], ["重载问题"], ["退出"]]
["查看问题", "添加问题"],
["重载问题"],
["退出"]
]
await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)) await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
return CHECK_COMMAND return CHECK_COMMAND
async def view_command(self, update: Update, _: CallbackContext) -> int: async def view_command(self, update: Update, _: CallbackContext) -> int:
_ = self _ = self
keyboard = [ keyboard = [[InlineKeyboardButton(text="选择问题", switch_inline_query_current_chat="查看问题 ")]]
[ await update.message.reply_text("请回复你要查看的问题", reply_markup=InlineKeyboardMarkup(keyboard))
InlineKeyboardButton(text="选择问题", switch_inline_query_current_chat="查看问题 ")
]
]
await update.message.reply_text("请回复你要查看的问题",
reply_markup=InlineKeyboardMarkup(keyboard))
return CHECK_COMMAND return CHECK_COMMAND
@conversation.state(state=CHECK_QUESTION) @conversation.state(state=CHECK_QUESTION)
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True) @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
async def check_question(self, update: Update, _: CallbackContext) -> int: async def check_question(self, update: Update, _: CallbackContext) -> int:
reply_keyboard = [ reply_keyboard = [["删除问题"], ["退出"]]
["删除问题"],
["退出"]
]
await update.message.reply_text("请选择你的操作", reply_markup=ReplyKeyboardMarkup(reply_keyboard)) await update.message.reply_text("请选择你的操作", reply_markup=ReplyKeyboardMarkup(reply_keyboard))
return CHECK_COMMAND return CHECK_COMMAND
@ -124,8 +112,7 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
return ConversationHandler.END return ConversationHandler.END
except ResponseError as error: except ResponseError as error:
logger.error("重载问题失败", error) logger.error("重载问题失败", error)
await update.message.reply_text("重载问题失败异常抛出Redis请求错误异常详情错误请看日记", await update.message.reply_text("重载问题失败异常抛出Redis请求错误异常详情错误请看日记", reply_markup=ReplyKeyboardRemove())
reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END return ConversationHandler.END
await update.message.reply_text("重载成功", reply_markup=ReplyKeyboardRemove()) await update.message.reply_text("重载成功", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END return ConversationHandler.END
@ -137,8 +124,7 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
quiz_command_data.new_question = "" quiz_command_data.new_question = ""
quiz_command_data.new_correct_answer = "" quiz_command_data.new_correct_answer = ""
quiz_command_data.status = 1 quiz_command_data.status = 1
await update.message.reply_text("请回复你要添加的问题,或发送 /cancel 取消操作", await update.message.reply_text("请回复你要添加的问题,或发送 /cancel 取消操作", reply_markup=ReplyKeyboardRemove())
reply_markup=ReplyKeyboardRemove())
return GET_NEW_QUESTION return GET_NEW_QUESTION
@conversation.state(state=GET_NEW_QUESTION) @conversation.state(state=GET_NEW_QUESTION)
@ -146,8 +132,7 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
async def get_new_question(self, update: Update, context: CallbackContext) -> int: async def get_new_question(self, update: Update, context: CallbackContext) -> int:
message = update.effective_message message = update.effective_message
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data") quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
reply_text = f"问题:`{escape_markdown(update.message.text, version=2)}`\n" \ reply_text = f"问题:`{escape_markdown(update.message.text, version=2)}`\n" f"请填写正确答案:"
f"请填写正确答案:"
quiz_command_data.new_question = message.text quiz_command_data.new_question = message.text
await update.message.reply_markdown_v2(reply_text) await update.message.reply_markdown_v2(reply_text)
return GET_NEW_CORRECT_ANSWER return GET_NEW_CORRECT_ANSWER
@ -156,19 +141,20 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True) @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
async def get_new_correct_answer(self, update: Update, context: CallbackContext) -> int: async def get_new_correct_answer(self, update: Update, context: CallbackContext) -> int:
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data") quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
reply_text = f"正确答案:`{escape_markdown(update.message.text, version=2)}`\n" \ reply_text = f"正确答案:`{escape_markdown(update.message.text, version=2)}`\n" f"请填写错误答案:"
f"请填写错误答案:"
await update.message.reply_markdown_v2(reply_text) await update.message.reply_markdown_v2(reply_text)
quiz_command_data.new_correct_answer = update.message.text quiz_command_data.new_correct_answer = update.message.text
return GET_NEW_WRONG_ANSWER return GET_NEW_WRONG_ANSWER
@conversation.state(state=GET_NEW_WRONG_ANSWER) @conversation.state(state=GET_NEW_WRONG_ANSWER)
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True) @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
@handler.command(command='finish_edit', block=True) @handler.command(command="finish_edit", block=True)
async def get_new_wrong_answer(self, update: Update, context: CallbackContext) -> int: async def get_new_wrong_answer(self, update: Update, context: CallbackContext) -> int:
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data") quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
reply_text = f"错误答案:`{escape_markdown(update.message.text, version=2)}`\n" \ reply_text = (
f"可继续填写,并使用 {escape_markdown('/finish', version=2)} 结束。" f"错误答案:`{escape_markdown(update.message.text, version=2)}`\n"
f"可继续填写,并使用 {escape_markdown('/finish', version=2)} 结束。"
)
await update.message.reply_markdown_v2(reply_text) await update.message.reply_markdown_v2(reply_text)
quiz_command_data.new_wrong_answer.append(update.message.text) quiz_command_data.new_wrong_answer.append(update.message.text)
return GET_NEW_WRONG_ANSWER return GET_NEW_WRONG_ANSWER
@ -176,13 +162,14 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
async def finish_edit(self, update: Update, context: CallbackContext): async def finish_edit(self, update: Update, context: CallbackContext):
_ = self _ = self
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data") quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
reply_text = f"问题:`{escape_markdown(quiz_command_data.new_question, version=2)}`\n" \ reply_text = (
f"正确答案:`{escape_markdown(quiz_command_data.new_correct_answer, version=2)}`\n" \ f"问题:`{escape_markdown(quiz_command_data.new_question, version=2)}`\n"
f"错误答案:`{escape_markdown(' '.join(quiz_command_data.new_wrong_answer), version=2)}`" f"正确答案:`{escape_markdown(quiz_command_data.new_correct_answer, version=2)}`\n"
f"错误答案:`{escape_markdown(' '.join(quiz_command_data.new_wrong_answer), version=2)}`"
)
await update.message.reply_markdown_v2(reply_text) await update.message.reply_markdown_v2(reply_text)
reply_keyboard = [["保存并重载配置", "抛弃修改并退出"]] reply_keyboard = [["保存并重载配置", "抛弃修改并退出"]]
await update.message.reply_text("请核对问题,并选择下一步操作。", await update.message.reply_text("请核对问题,并选择下一步操作。", reply_markup=ReplyKeyboardMarkup(reply_keyboard))
reply_markup=ReplyKeyboardMarkup(reply_keyboard))
return SAVE_QUESTION return SAVE_QUESTION
@conversation.state(state=SAVE_QUESTION) @conversation.state(state=SAVE_QUESTION)
@ -195,19 +182,18 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
elif update.message.text == "保存并重载配置": elif update.message.text == "保存并重载配置":
if quiz_command_data.status == 1: if quiz_command_data.status == 1:
answer = [ answer = [
Answer(text=wrong_answer, is_correct=False) for wrong_answer in Answer(text=wrong_answer, is_correct=False) for wrong_answer in quiz_command_data.new_wrong_answer
quiz_command_data.new_wrong_answer
] ]
answer.append(Answer(text=quiz_command_data.new_correct_answer, is_correct=True)) answer.append(Answer(text=quiz_command_data.new_correct_answer, is_correct=True))
await self.quiz_service.save_quiz( await self.quiz_service.save_quiz(Question(text=quiz_command_data.new_question))
Question(text=quiz_command_data.new_question))
await update.message.reply_text("保存成功", reply_markup=ReplyKeyboardRemove()) await update.message.reply_text("保存成功", reply_markup=ReplyKeyboardRemove())
try: try:
await self.quiz_service.refresh_quiz() await self.quiz_service.refresh_quiz()
except ResponseError as error: except ResponseError as error:
logger.error("重载问题失败", error) logger.error("重载问题失败", error)
await update.message.reply_text("重载问题失败异常抛出Redis请求错误异常详情错误请看日记", await update.message.reply_text(
reply_markup=ReplyKeyboardRemove()) "重载问题失败异常抛出Redis请求错误异常详情错误请看日记", reply_markup=ReplyKeyboardRemove()
)
return ConversationHandler.END return ConversationHandler.END
await update.message.reply_text("重载配置成功", reply_markup=ReplyKeyboardRemove()) await update.message.reply_text("重载配置成功", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END return ConversationHandler.END
@ -239,8 +225,7 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
await self.quiz_service.refresh_quiz() await self.quiz_service.refresh_quiz()
except ResponseError as error: except ResponseError as error:
logger.error("重载问题失败", error) logger.error("重载问题失败", error)
await update.message.reply_text("重载问题失败异常抛出Redis请求错误异常详情错误请看日记", await update.message.reply_text("重载问题失败异常抛出Redis请求错误异常详情错误请看日记", reply_markup=ReplyKeyboardRemove())
reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END return ConversationHandler.END
await update.message.reply_text("重载配置成功", reply_markup=ReplyKeyboardRemove()) await update.message.reply_text("重载配置成功", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END return ConversationHandler.END

View File

@ -7,7 +7,6 @@ from utils.decorators.restricts import restricts
class StartPlugin(Plugin): class StartPlugin(Plugin):
@handler(CommandHandler, command="start", block=False) @handler(CommandHandler, command="start", block=False)
@restricts() @restricts()
async def start(self, update: Update, context: CallbackContext) -> None: async def start(self, update: Update, context: CallbackContext) -> None:
@ -15,8 +14,10 @@ class StartPlugin(Plugin):
message = update.effective_message message = update.effective_message
args = context.args args = context.args
if args is not None and len(args) >= 1 and args[0] == "inline_message": if args is not None and len(args) >= 1 and args[0] == "inline_message":
await message.reply_markdown_v2(f"你好 {user.mention_markdown_v2()} {escape_markdown('!我是派蒙 ')}\n" await message.reply_markdown_v2(
f"{escape_markdown('发送 /help 命令即可查看命令帮助')}") f"你好 {user.mention_markdown_v2()} {escape_markdown('!我是派蒙 ')}\n"
f"{escape_markdown('发送 /help 命令即可查看命令帮助')}"
)
return return
await message.reply_markdown_v2(f"你好 {user.mention_markdown_v2()} {escape_markdown('!我是派蒙 ')}") await message.reply_markdown_v2(f"你好 {user.mention_markdown_v2()} {escape_markdown('!我是派蒙 ')}")

View File

@ -21,7 +21,6 @@ UPDATE_DATA = os.path.join(current_dir, "data", "update.json")
class UpdatePlugin(Plugin): class UpdatePlugin(Plugin):
def __init__(self): def __init__(self):
self._lock = asyncio.Lock() self._lock = asyncio.Lock()
@ -59,6 +58,6 @@ class UpdatePlugin(Plugin):
await execute(f"{executable} -m poetry install --extras all") await execute(f"{executable} -m poetry install --extras all")
logger.info(f"更新成功 正在重启") logger.info(f"更新成功 正在重启")
await reply_text.edit_text("更新成功 正在重启") await reply_text.edit_text("更新成功 正在重启")
async with async_open(UPDATE_DATA, mode='w', encoding='utf-8') as file: async with async_open(UPDATE_DATA, mode="w", encoding="utf-8") as file:
await file.write(reply_text.to_json()) await file.write(reply_text.to_json())
raise SystemExit raise SystemExit

View File

@ -0,0 +1,477 @@
<!DOCTYPE html>
<html lang="zh-ch">
<head>
<meta charset="UTF-8" />
<title>abyss</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
<script src="../../js/tailwindcss-3.1.8.js"></script>
<link type="text/css" href="../../styles/public.css" rel="stylesheet" />
<style>
#container {
max-width: 785px;
background-image: url("./../abyss/background/abyss-bg-grad.png");
background-color: rgb(11, 23, 44);
}
.item-not-owned {
position: relative;
}
.item-not-owned::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgb(0 0 0 / 50%);
border-radius: 5px;
}
</style>
</head>
<body>
<div
class="mx-auto flex flex-col h-full bg-contain bg-no-repeat py-6"
id="container"
>
<div class="title text-2xl my-4 text-yellow-500 text-center">
深境螺旋 - 推荐配队
</div>
<div
class="flex flex-row px-6 py-1 my-1 text-neutral-100 bg-white bg-opacity-10 justify-evenly text-sm"
>
<div>UID 12345678</div>
<div>版本 3.1</div>
</div>
<div class="mx-auto flex flex-col px-6 py-1 text-black my-1">
<div class="text-center mr-auto text-yellow-500">推荐配队 1</div>
<div class="flex my-2 space-x-4">
<div class="flex-shrink-0 rounded-lg overflow-hidden" style="
background-color: rgb(233, 229, 220)
">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg4.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000039/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">迪奥娜</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg5.png');
background-color: rgb(233, 229, 220)
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000041/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">莫娜</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg5.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000002/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">神里绫华</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg5.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000047/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">枫原万叶</div>
</div>
<div>
<div class="text-neutral-300">上半</div>
<div class="text-neutral-400 text-sm">
<span class="">使用率</span>
<span class="">5.99%</span>
</div>
</div>
</div>
<div class="flex my-2 space-x-4">
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg4.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000023/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">香菱</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg5.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000052/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">雷电将军</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg4.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000025/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">行秋</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg4.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000032/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">班尼特</div>
</div>
<div class="text-neutral-200">
<div>上半</div>
<div>使用率: 22.34%</div>
</div>
</div>
</div>
<div class="mx-auto flex flex-col px-6 py-1 text-black my-1">
<div class="text-center mr-auto text-yellow-500">推荐配队 2</div>
<div class="flex my-2 space-x-4">
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg5.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000002/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">神里绫华</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg5.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000037/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">甘雨</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden item-not-owned">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg5.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000054/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">珊瑚宫心海</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg5.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000047/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">枫原万叶</div>
</div>
<div class="text-neutral-200">
<div>上半</div>
<div>使用率: 5.42%</div>
</div>
</div>
<div class="flex my-2 space-x-4">
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg4.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000023/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">香菱</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg5.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000052/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">雷电将军</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg4.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000025/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">行秋</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg4.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000032/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">班尼特</div>
</div>
<div class="text-neutral-200">
<div>上半</div>
<div>使用率: 22.34%</div>
</div>
</div>
</div>
<div class="mx-auto flex flex-col px-6 py-1 text-black my-1">
<div class="text-center mr-auto text-yellow-500">推荐配队 3</div>
<div class="flex my-2 space-x-4">
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg4.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000045/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">罗莎莉亚</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg5.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000002/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">神里绫华</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden item-not-owned">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg5.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000054/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">珊瑚宫心海</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg5.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000047/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">枫原万叶</div>
</div>
<div class="text-neutral-200">
<div>上半</div>
<div>使用率: 5.13%</div>
</div>
</div>
<div class="flex my-2 space-x-4">
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg4.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000023/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">香菱</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg5.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000052/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">雷电将军</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg4.png');
background-color: rgb(233, 229, 220)
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000025/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">行秋</div>
</div>
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
style="
background-image: url('./../abyss/background/roleStarBg4.png');
"
>
<img
src="file:///Users/cat/github/TGPaimonBot/resources/assets/avatar/10000032/icon.png"
alt=""
/>
</div>
<div class="text-center bg-neutral-200">班尼特</div>
</div>
<div class="text-neutral-200">
<div>上半</div>
<div>使用率: 22.34%</div>
</div>
</div>
</div>
<div class="mt-6 text-center p-1 text-neutral-400 text-xs">
数据来源:游创工坊
</div>
</div>
</body>
</html>

2
run.py
View File

@ -5,5 +5,5 @@ def main():
bot.launch() bot.launch()
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@ -20,17 +20,22 @@ async def artifact_rate():
# noinspection PyShadowingNames # noinspection PyShadowingNames
@pytest.mark.asyncio @pytest.mark.asyncio
class TestArtifactOcrRate: class TestArtifactOcrRate:
@staticmethod @staticmethod
@flaky(3, 1) @flaky(3, 1)
async def test_rate_artifact(artifact_rate): async def test_rate_artifact(artifact_rate):
artifact_attr = { artifact_attr = {
'name': '翠绿的猎人之冠', 'pos': '理之冠', 'star': 5, 'level': 20, "name": "翠绿的猎人之冠",
'main_item': {'type': 'cr', 'name': '暴击率', 'value': '31.1%'}, "pos": "理之冠",
'sub_item': [{'type': 'hp', 'name': '生命值', 'value': '9.3%'}, "star": 5,
{'type': 'df', 'name': '防御力', 'value': '46'}, "level": 20,
{'type': 'atk', 'name': '攻击力', 'value': '49'}, "main_item": {"type": "cr", "name": "暴击率", "value": "31.1%"},
{'type': 'cd', 'name': '暴击伤害', 'value': '10.9%'}]} "sub_item": [
{"type": "hp", "name": "生命值", "value": "9.3%"},
{"type": "df", "name": "防御力", "value": "46"},
{"type": "atk", "name": "攻击力", "value": "49"},
{"type": "cd", "name": "暴击伤害", "value": "10.9%"},
],
}
assert await artifact_rate.rate_artifact(artifact_attr) assert await artifact_rate.rate_artifact(artifact_attr)
@staticmethod @staticmethod

View File

@ -28,13 +28,13 @@ async def test_get_post_info(hyperion):
post_info = await hyperion.get_post_info(2, 29023709) post_info = await hyperion.get_post_info(2, 29023709)
assert post_info assert post_info
assert isinstance(post_info, PostInfo) assert isinstance(post_info, PostInfo)
assert post_info["post"]["post"]["post_id"] == '29023709' assert post_info["post"]["post"]["post_id"] == "29023709"
assert post_info.post_id == 29023709 assert post_info.post_id == 29023709
assert post_info["post"]["post"]["subject"] == "《原神》长期项目启动·概念PV" assert post_info["post"]["post"]["subject"] == "《原神》长期项目启动·概念PV"
assert post_info.subject == "《原神》长期项目启动·概念PV" assert post_info.subject == "《原神》长期项目启动·概念PV"
assert len(post_info["post"]["post"]["images"]) == 1 assert len(post_info["post"]["post"]["images"]) == 1
post_soup = BeautifulSoup(post_info["post"]["post"]["content"], features="html.parser") post_soup = BeautifulSoup(post_info["post"]["post"]["content"], features="html.parser")
assert post_soup.find_all('p') assert post_soup.find_all("p")
# noinspection PyShadowingNames # noinspection PyShadowingNames

View File

@ -24,32 +24,32 @@ def event_loop():
@pytest.mark.asyncio @pytest.mark.asyncio
class TestWeapon: class TestWeapon:
@staticmethod @staticmethod
@flaky(3, 1) @flaky(3, 1)
async def test_get_by_id(): async def test_get_by_id():
weapon = await Weapon.get_by_id('i_n11417') weapon = await Weapon.get_by_id("i_n11417")
assert weapon.name == '原木刀' assert weapon.name == "原木刀"
assert weapon.rarity == 4 assert weapon.rarity == 4
assert weapon.attack == 43.73 assert weapon.attack == 43.73
assert weapon.attribute.type.value == '元素充能效率' assert weapon.attribute.type.value == "元素充能效率"
assert weapon.affix.name == '森林的瑞佑' assert weapon.affix.name == "森林的瑞佑"
@staticmethod @staticmethod
@flaky(3, 1) @flaky(3, 1)
async def test_get_by_name(): async def test_get_by_name():
weapon = await Weapon.get_by_name('风鹰剑') weapon = await Weapon.get_by_name("风鹰剑")
assert weapon.id == 'i_n11501' assert weapon.id == "i_n11501"
assert weapon.rarity == 5 assert weapon.rarity == 5
assert weapon.attack == 47.54 assert weapon.attack == 47.54
assert weapon.attribute.type.value == '物理伤害加成' assert weapon.attribute.type.value == "物理伤害加成"
assert weapon.affix.name == '西风之鹰的抗争' assert weapon.affix.name == "西风之鹰的抗争"
assert '听凭风引,便是正义与自由之风' in weapon.story assert "听凭风引,便是正义与自由之风" in weapon.story
@staticmethod @staticmethod
@flaky(3, 1) @flaky(3, 1)
async def test_name_list(): async def test_name_list():
from httpx import URL from httpx import URL
async for name in Weapon._name_list_generator(with_url=True): async for name in Weapon._name_list_generator(with_url=True):
assert isinstance(name[0], str) assert isinstance(name[0], str)
assert isinstance(name[1], URL) assert isinstance(name[1], URL)
@ -57,34 +57,34 @@ class TestWeapon:
@pytest.mark.asyncio @pytest.mark.asyncio
class TestCharacter: class TestCharacter:
@staticmethod @staticmethod
@flaky(3, 1) @flaky(3, 1)
async def test_get_by_id(): async def test_get_by_id():
character = await Character.get_by_id('ayaka_002') character = await Character.get_by_id("ayaka_002")
assert character.name == '神里绫华' assert character.name == "神里绫华"
assert character.title == '白鹭霜华' assert character.title == "白鹭霜华"
assert character.occupation == '社奉行' assert character.occupation == "社奉行"
assert character.association.value == '稻妻' assert character.association.value == "稻妻"
assert character.cn_cv == '小N' assert character.cn_cv == "小N"
@staticmethod @staticmethod
@flaky(3, 1) @flaky(3, 1)
async def test_get_by_name(): async def test_get_by_name():
character = await Character.get_by_name('神里绫华') character = await Character.get_by_name("神里绫华")
assert character.name == '神里绫华' assert character.name == "神里绫华"
assert character.title == '白鹭霜华' assert character.title == "白鹭霜华"
assert character.occupation == '社奉行' assert character.occupation == "社奉行"
assert character.association.value == '稻妻' assert character.association.value == "稻妻"
assert character.cn_cv == '小N' assert character.cn_cv == "小N"
main_character = await Character.get_by_name('') main_character = await Character.get_by_name("")
assert main_character.constellation == '旅人座' assert main_character.constellation == "旅人座"
assert main_character.cn_cv == '宴宁&多多poi' assert main_character.cn_cv == "宴宁&多多poi"
@staticmethod @staticmethod
@flaky(3, 1) @flaky(3, 1)
async def test_name_list(): async def test_name_list():
from httpx import URL from httpx import URL
async for name in Character._name_list_generator(with_url=True): async for name in Character._name_list_generator(with_url=True):
assert isinstance(name[0], str) assert isinstance(name[0], str)
assert isinstance(name[1], URL) assert isinstance(name[1], URL)
@ -92,41 +92,41 @@ class TestCharacter:
@pytest.mark.asyncio @pytest.mark.asyncio
class TestMaterial: class TestMaterial:
@staticmethod @staticmethod
@flaky(3, 1) @flaky(3, 1)
async def test_get_by_id(): async def test_get_by_id():
material = await Material.get_by_id('i_504') material = await Material.get_by_id("i_504")
assert material.name == '高塔孤王的碎梦' assert material.name == "高塔孤王的碎梦"
assert material.type == '武器突破素材' assert material.type == "武器突破素材"
assert '合成获得' in material.source assert "合成获得" in material.source
assert '巴巴托斯' in material.description assert "巴巴托斯" in material.description
material = await Material.get_by_id('i_483') material = await Material.get_by_id("i_483")
assert material.name == '凶将之手眼' assert material.name == "凶将之手眼"
assert material.type == '角色培养素材' assert material.type == "角色培养素材"
assert '70级以上永恒的守护者挑战奖励' in material.source assert "70级以上永恒的守护者挑战奖励" in material.source
assert '所见即所为' in material.description assert "所见即所为" in material.description
@staticmethod @staticmethod
@flaky(3, 1) @flaky(3, 1)
async def test_get_by_name(): async def test_get_by_name():
material = await Material.get_by_name('地脉的新芽') material = await Material.get_by_name("地脉的新芽")
assert material.id == 'i_73' assert material.id == "i_73"
assert material.type == '角色培养素材' assert material.type == "角色培养素材"
assert '60级以上深渊法师掉落' in material.source assert "60级以上深渊法师掉落" in material.source
assert '勃发' in material.description assert "勃发" in material.description
material = await Material.get_by_name('「黄金」的教导') material = await Material.get_by_name("「黄金」的教导")
assert material.id == 'i_431' assert material.id == "i_431"
assert material.type == '天赋培养素材' assert material.type == "天赋培养素材"
assert 2 in material.weekdays assert 2 in material.weekdays
assert '土的象' in material.description assert "土的象" in material.description
@staticmethod @staticmethod
@flaky(3, 1) @flaky(3, 1)
async def test_name_list(): async def test_name_list():
from httpx import URL from httpx import URL
async for name in Material._name_list_generator(with_url=True): async for name in Material._name_list_generator(with_url=True):
assert isinstance(name[0], str) assert isinstance(name[0], str)
assert isinstance(name[1], URL) assert isinstance(name[1], URL)
@ -134,11 +134,11 @@ class TestMaterial:
@pytest.mark.asyncio @pytest.mark.asyncio
class TestAll: class TestAll:
@staticmethod @staticmethod
@flaky(3, 1) @flaky(3, 1)
async def make_test(target: Type[WikiModel]): async def make_test(target: Type[WikiModel]):
from httpx import URL from httpx import URL
name_list = await target.get_name_list(with_url=True) name_list = await target.get_name_list(with_url=True)
name_len = len(name_list) name_len = len(name_list)
assert name_len != 0 assert name_len != 0

View File

@ -22,8 +22,7 @@ class BaseObject:
return getattr(self, item) return getattr(self, item)
except AttributeError as exc: except AttributeError as exc:
raise KeyError( raise KeyError(
f"Objects of type {self.__class__.__name__} don't have an attribute called " f"Objects of type {self.__class__.__name__} don't have an attribute called " f"`{item}`."
f"`{item}`."
) from exc ) from exc
def __getstate__(self) -> Dict[str, Union[str, object]]: def __getstate__(self) -> Dict[str, Union[str, object]]:
@ -47,7 +46,11 @@ class BaseObject:
# 添加插槽可减少内存使用,并允许更快的属性访问 # 添加插槽可减少内存使用,并允许更快的属性访问
__slots__ = () __slots__ = ()
def _get_attrs(self, include_private: bool = False, recursive: bool = False, ) -> Dict[str, Union[str, object]]: def _get_attrs(
self,
include_private: bool = False,
recursive: bool = False,
) -> Dict[str, Union[str, object]]:
data = {} data = {}
if not recursive: if not recursive:
try: try:

View File

@ -4,19 +4,24 @@ from pathlib import Path
from httpx import URL from httpx import URL
__all__ = [ __all__ = [
'PROJECT_ROOT', 'PLUGIN_DIR', 'RESOURCE_DIR', "PROJECT_ROOT",
'NOT_SET', "PLUGIN_DIR",
'HONEY_HOST', 'ENKA_HOST', 'AMBR_HOST', 'CELESTIA_HOST', "RESOURCE_DIR",
"NOT_SET",
"HONEY_HOST",
"ENKA_HOST",
"AMBR_HOST",
"CELESTIA_HOST",
] ]
# 项目根目录 # 项目根目录
PROJECT_ROOT = Path(__file__).joinpath('../..').resolve() PROJECT_ROOT = Path(__file__).joinpath("../..").resolve()
# Core 目录 # Core 目录
CORE_DIR = PROJECT_ROOT / 'core' CORE_DIR = PROJECT_ROOT / "core"
# 插件目录 # 插件目录
PLUGIN_DIR = PROJECT_ROOT / 'plugins' PLUGIN_DIR = PROJECT_ROOT / "plugins"
# 资源目录 # 资源目录
RESOURCE_DIR = PROJECT_ROOT / 'resources' RESOURCE_DIR = PROJECT_ROOT / "resources"
NOT_SET = object() NOT_SET = object()

View File

@ -22,9 +22,9 @@ async def send_user_notification(update: Update, _: CallbackContext, text: str):
logger.warning("错误的消息类型\n" + json.dumps(update_str, indent=2, ensure_ascii=False)) logger.warning("错误的消息类型\n" + json.dumps(update_str, indent=2, ensure_ascii=False))
return return
chat = message.chat chat = message.chat
logger.info(f"尝试通知用户 {effective_user.full_name}[{effective_user.id}] " logger.info(
f"{chat.full_name}[{chat.id}]" f"尝试通知用户 {effective_user.full_name}[{effective_user.id}] " f"{chat.full_name}[{chat.id}]" f"的 错误信息[{text}]"
f"的 错误信息[{text}]") )
try: try:
await message.reply_text(text, reply_markup=ReplyKeyboardRemove(), allow_sending_without_reply=True) await message.reply_text(text, reply_markup=ReplyKeyboardRemove(), allow_sending_without_reply=True)
except BadRequest as exc: except BadRequest as exc:
@ -81,8 +81,7 @@ def error_callable(func: Callable) -> Callable:
if exc.retcode in (10001, -100): if exc.retcode in (10001, -100):
await send_user_notification(update, context, "出错了呜呜呜 ~ Cookies无效请尝试重新绑定账户") await send_user_notification(update, context, "出错了呜呜呜 ~ Cookies无效请尝试重新绑定账户")
elif exc.retcode == 10103: elif exc.retcode == 10103:
await send_user_notification(update, context, "出错了呜呜呜 ~ Cookie有效但没有绑定到游戏帐户" await send_user_notification(update, context, "出错了呜呜呜 ~ Cookie有效但没有绑定到游戏帐户" "请尝试重新绑定邮游戏账户")
"请尝试重新绑定邮游戏账户")
else: else:
logger.warning("Cookie错误") logger.warning("Cookie错误")
logger.exception(exc) logger.exception(exc)

View File

@ -11,8 +11,12 @@ from utils.log import logger
_lock = asyncio.Lock() _lock = asyncio.Lock()
def restricts(restricts_time: int = 9, restricts_time_of_groups: Optional[int] = None, return_data: Any = None, def restricts(
without_overlapping: bool = False): restricts_time: int = 9,
restricts_time_of_groups: Optional[int] = None,
return_data: Any = None,
without_overlapping: bool = False,
):
"""用于装饰在指定函数预防洪水攻击的装饰器 """用于装饰在指定函数预防洪水攻击的装饰器
被修饰的函数生声明必须为 被修饰的函数生声明必须为

View File

@ -20,12 +20,14 @@ from utils.error import UrlResourcesNotFoundError
from utils.log import logger from utils.log import logger
from utils.models.base import RegionEnum from utils.models.base import RegionEnum
T = TypeVar('T') T = TypeVar("T")
P = ParamSpec('P') P = ParamSpec("P")
USER_AGENT: str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " \ USER_AGENT: str = (
"Chrome/90.0.4430.72 Safari/537.36" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
REQUEST_HEADERS: dict = {'User-Agent': USER_AGENT} "Chrome/90.0.4430.72 Safari/537.36"
)
REQUEST_HEADERS: dict = {"User-Agent": USER_AGENT}
current_dir = os.getcwd() current_dir = os.getcwd()
cache_dir = os.path.join(current_dir, "cache") cache_dir = os.path.join(current_dir, "cache")
if not os.path.exists(cache_dir): if not os.path.exists(cache_dir):
@ -74,7 +76,7 @@ async def url_to_file(url: str, return_path: bool = False) -> str:
if data.status_code != 200: if data.status_code != 200:
logger.error(f"url_to_file 获取url[{url}] 错误 status_code[f{data.status_code}]") logger.error(f"url_to_file 获取url[{url}] 错误 status_code[f{data.status_code}]")
raise UrlResourcesNotFoundError(url) raise UrlResourcesNotFoundError(url)
async with aiofiles.open(file_dir, mode='wb') as f: async with aiofiles.open(file_dir, mode="wb") as f:
await f.write(data.content) await f.write(data.content)
logger.debug(f"url_to_file 获取url[{url}] 并下载到 file_dir[{file_dir}]") logger.debug(f"url_to_file 获取url[{url}] 并下载到 file_dir[{file_dir}]")
@ -101,8 +103,9 @@ async def get_genshin_client(user_id: int, region: Optional[RegionEnum] = None,
client = genshin.Client(cookies=cookies, game=types.Game.GENSHIN, region=types.Region.CHINESE, uid=uid) client = genshin.Client(cookies=cookies, game=types.Game.GENSHIN, region=types.Region.CHINESE, uid=uid)
elif region == RegionEnum.HOYOLAB: elif region == RegionEnum.HOYOLAB:
uid = user.genshin_uid uid = user.genshin_uid
client = genshin.Client(cookies=cookies, client = genshin.Client(
game=types.Game.GENSHIN, region=types.Region.OVERSEAS, lang="zh-cn", uid=uid) cookies=cookies, game=types.Game.GENSHIN, region=types.Region.OVERSEAS, lang="zh-cn", uid=uid
)
else: else:
raise TypeError("region is not RegionEnum.NULL") raise TypeError("region is not RegionEnum.NULL")
return client return client
@ -121,8 +124,9 @@ async def get_public_genshin_client(user_id: int) -> Tuple[Client, Optional[int]
client = genshin.Client(cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.CHINESE) client = genshin.Client(cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.CHINESE)
elif region == RegionEnum.HOYOLAB: elif region == RegionEnum.HOYOLAB:
uid = user.genshin_uid uid = user.genshin_uid
client = genshin.Client(cookies=cookies.cookies, client = genshin.Client(
game=types.Game.GENSHIN, region=types.Region.OVERSEAS, lang="zh-cn") cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.OVERSEAS, lang="zh-cn"
)
else: else:
raise TypeError("region is not RegionEnum.NULL") raise TypeError("region is not RegionEnum.NULL")
return client, uid return client, uid
@ -142,25 +146,18 @@ def region_server(uid: Union[int, str]) -> RegionEnum:
async def execute(command, pass_error=True): async def execute(command, pass_error=True):
""" Executes command and returns output, with the option of enabling stderr. """ """Executes command and returns output, with the option of enabling stderr."""
executor = await create_subprocess_shell( executor = await create_subprocess_shell(command, stdout=PIPE, stderr=PIPE, stdin=PIPE)
command,
stdout=PIPE,
stderr=PIPE,
stdin=PIPE
)
stdout, stderr = await executor.communicate() stdout, stderr = await executor.communicate()
if pass_error: if pass_error:
try: try:
result = str(stdout.decode().strip()) \ result = str(stdout.decode().strip()) + str(stderr.decode().strip())
+ str(stderr.decode().strip())
except UnicodeDecodeError: except UnicodeDecodeError:
result = str(stdout.decode('gbk').strip()) \ result = str(stdout.decode("gbk").strip()) + str(stderr.decode("gbk").strip())
+ str(stderr.decode('gbk').strip())
else: else:
try: try:
result = str(stdout.decode().strip()) result = str(stdout.decode().strip())
except UnicodeDecodeError: except UnicodeDecodeError:
result = str(stdout.decode('gbk').strip()) result = str(stdout.decode("gbk").strip())
return result return result

View File

@ -4,7 +4,7 @@ from pathlib import Path
from types import TracebackType from types import TracebackType
from typing import AnyStr, IO, Iterable, Iterator, List, Optional, Type from typing import AnyStr, IO, Iterable, Iterator, List, Optional, Type
__all__ = ['FileIO'] __all__ = ["FileIO"]
# noinspection SpellCheckingInspection # noinspection SpellCheckingInspection
@ -18,12 +18,12 @@ class FileIO(IO[str]):
today = date.today() today = date.today()
if self.file.exists(): if self.file.exists():
if not self.file.is_file(): if not self.file.is_file():
raise RuntimeError(f'日志文件冲突, 请删除文件夹 \"{str(self.file.resolve())}\"') raise RuntimeError(f'日志文件冲突, 请删除文件夹 "{str(self.file.resolve())}"')
if self.file_stream is None or self.file_stream.closed: if self.file_stream is None or self.file_stream.closed:
self.file_stream = self.file.open(mode='a+', encoding='utf-8') self.file_stream = self.file.open(mode="a+", encoding="utf-8")
modify_date = date.fromtimestamp(os.stat(self.file).st_mtime) modify_date = date.fromtimestamp(os.stat(self.file).st_mtime)
else: else:
self.file_stream = self.file.open(mode='a+', encoding='utf-8') self.file_stream = self.file.open(mode="a+", encoding="utf-8")
modify_date = today modify_date = today
if modify_date < today: if modify_date < today:
if self.file_stream is not None and not self.file_stream.closed: if self.file_stream is not None and not self.file_stream.closed:
@ -31,13 +31,13 @@ class FileIO(IO[str]):
log_path = self.path.joinpath(f'{modify_date.strftime("%Y-%m-%d")}.log') log_path = self.path.joinpath(f'{modify_date.strftime("%Y-%m-%d")}.log')
if log_path.exists(): if log_path.exists():
# 转存日志 # 转存日志
with open(log_path, mode='a+', encoding='utf-8') as file: with open(log_path, mode="a+", encoding="utf-8") as file:
file.write('\n') file.write("\n")
with open(self.file, mode='r+', encoding='utf-8') as f: with open(self.file, mode="r+", encoding="utf-8") as f:
file.writelines(f.readlines()) file.writelines(f.readlines())
else: else:
self.file.rename(self.path.joinpath(f'{modify_date.strftime("%Y-%m-%d")}.log')) self.file.rename(self.path.joinpath(f'{modify_date.strftime("%Y-%m-%d")}.log'))
self.file_stream = self.file.open(mode='a+', encoding='utf-8') self.file_stream = self.file.open(mode="a+", encoding="utf-8")
return self.file_stream return self.file_stream
def close(self) -> None: def close(self) -> None:
@ -94,6 +94,7 @@ class FileIO(IO[str]):
def __enter__(self) -> IO[AnyStr]: def __enter__(self) -> IO[AnyStr]:
return self._get_file().__enter__() return self._get_file().__enter__()
def __exit__(self, __t: Optional[Type[BaseException]], __value: Optional[BaseException], def __exit__(
__traceback: Optional[TracebackType]) -> None: self, __t: Optional[Type[BaseException]], __value: Optional[BaseException], __traceback: Optional[TracebackType]
) -> None:
return self._get_file().__exit__(__t, __value, __traceback) return self._get_file().__exit__(__t, __value, __traceback)

View File

@ -7,7 +7,7 @@ import traceback as traceback_
from datetime import datetime from datetime import datetime
from multiprocessing import RLock as Lock from multiprocessing import RLock as Lock
from pathlib import Path from pathlib import Path
from typing import (Any, Callable, Dict, Iterable, List, Literal, Mapping, Optional, TYPE_CHECKING, Tuple, Union) from typing import Any, Callable, Dict, Iterable, List, Literal, Mapping, Optional, TYPE_CHECKING, Tuple, Union
import ujson as json import ujson as json
from rich.columns import Columns from rich.columns import Columns
@ -65,22 +65,20 @@ __initialized__ = False
FormatTimeCallable = Callable[[datetime], Text] FormatTimeCallable = Callable[[datetime], Text]
config = BotConfig() config = BotConfig()
logging.addLevelName(5, 'TRACE') logging.addLevelName(5, "TRACE")
logging.addLevelName(25, 'SUCCESS') logging.addLevelName(25, "SUCCESS")
color_system: Literal['windows', 'truecolor'] color_system: Literal["windows", "truecolor"]
if sys.platform == 'win32': if sys.platform == "win32":
color_system = 'windows' color_system = "windows"
else: else:
color_system = 'truecolor' color_system = "truecolor"
# noinspection SpellCheckingInspection # noinspection SpellCheckingInspection
log_console = Console( log_console = Console(color_system=color_system, theme=Theme(DEFAULT_STYLE), width=config.logger.width)
color_system=color_system, theme=Theme(DEFAULT_STYLE), width=config.logger.width
)
class Traceback(BaseTraceback): class Traceback(BaseTraceback):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs.update({'show_locals': True, 'max_frames': config.logger.traceback_max_frames}) kwargs.update({"show_locals": True, "max_frames": config.logger.traceback_max_frames})
super(Traceback, self).__init__(*args, **kwargs) super(Traceback, self).__init__(*args, **kwargs)
self.theme = PygmentsSyntaxTheme(MonokaiProStyle) self.theme = PygmentsSyntaxTheme(MonokaiProStyle)
@ -99,9 +97,7 @@ class Traceback(BaseTraceback):
def read_code(filename: str) -> str: def read_code(filename: str) -> str:
code = code_cache.get(filename) code = code_cache.get(filename)
if code is None: if code is None:
with open( with open(filename, "rt", encoding="utf-8", errors="replace") as code_file:
filename, "rt", encoding="utf-8", errors="replace"
) as code_file:
code = code_file.read() code = code_file.read()
code_cache[filename] = code code_cache[filename] = code
return code return code
@ -110,6 +106,7 @@ class Traceback(BaseTraceback):
def render_locals(frame: Frame) -> Iterable["ConsoleRenderable"]: def render_locals(frame: Frame) -> Iterable["ConsoleRenderable"]:
if frame.locals: if frame.locals:
from rich.scope import render_scope from rich.scope import render_scope
yield render_scope( yield render_scope(
frame.locals, frame.locals,
title="locals", title="locals",
@ -144,8 +141,7 @@ class Traceback(BaseTraceback):
first = frame_index == 0 first = frame_index == 0
frame_filename = frame.filename frame_filename = frame.filename
suppressed = any( suppressed = any(frame_filename.startswith(path) for path in self.suppress)
frame_filename.startswith(path) for path in self.suppress)
text = Text.assemble( text = Text.assemble(
path_highlighter(Text(frame.filename, style="pygments.string")), path_highlighter(Text(frame.filename, style="pygments.string")),
@ -218,15 +214,15 @@ class LogRender(DefaultLogRender):
self.time_format = config.logger.time_format self.time_format = config.logger.time_format
def __call__( def __call__(
self, self,
console: "Console", console: "Console",
renderables: Iterable["ConsoleRenderable"], renderables: Iterable["ConsoleRenderable"],
log_time: Optional[datetime] = None, log_time: Optional[datetime] = None,
time_format: Optional[Union[str, FormatTimeCallable]] = None, time_format: Optional[Union[str, FormatTimeCallable]] = None,
level: TextType = "", level: TextType = "",
path: Optional[str] = None, path: Optional[str] = None,
line_no: Optional[int] = None, line_no: Optional[int] = None,
link_path: Optional[str] = None, link_path: Optional[str] = None,
) -> "Table": ) -> "Table":
from rich.containers import Renderables from rich.containers import Renderables
from rich.table import Table from rich.table import Table
@ -239,7 +235,7 @@ class LogRender(DefaultLogRender):
if path: if path:
output.add_column(style="log.path") output.add_column(style="log.path")
if line_no: if line_no:
output.add_column(style='log.line_no', width=4) output.add_column(style="log.line_no", width=4)
row: List["RenderableType"] = [] row: List["RenderableType"] = []
if self.show_time: if self.show_time:
log_time = log_time or log_console.get_datetime() log_time = log_time or log_console.get_datetime()
@ -259,15 +255,11 @@ class LogRender(DefaultLogRender):
row.append(Renderables(renderables)) row.append(Renderables(renderables))
if path: if path:
path_text = Text() path_text = Text()
path_text.append( path_text.append(path, style=f"link file://{link_path}" if link_path else "")
path, style=f"link file://{link_path}" if link_path else ""
)
row.append(path_text) row.append(path_text)
line_no_text = Text() line_no_text = Text()
line_no_text.append( line_no_text.append(str(line_no), style=f"link file://{link_path}#{line_no}" if link_path else "")
str(line_no), style=f"link file://{link_path}#{line_no}" if link_path else ""
)
row.append(line_no_text) row.append(line_no_text)
output.add_row(*row) output.add_row(*row)
@ -284,18 +276,19 @@ class Handler(DefaultRichHandler):
self.keywords = self.KEYWORDS + config.logger.render_keywords self.keywords = self.KEYWORDS + config.logger.render_keywords
def render( def render(
self, self,
*, *,
record: "LogRecord", record: "LogRecord",
traceback: Optional[Traceback], traceback: Optional[Traceback],
message_renderable: Optional["ConsoleRenderable"], message_renderable: Optional["ConsoleRenderable"],
) -> "ConsoleRenderable": ) -> "ConsoleRenderable":
if record.pathname != '<input>': if record.pathname != "<input>":
try: try:
path = str(Path(record.pathname).relative_to(PROJECT_ROOT)) path = str(Path(record.pathname).relative_to(PROJECT_ROOT))
path = path.split('.')[0].replace(os.sep, '.') path = path.split(".")[0].replace(os.sep, ".")
except ValueError: except ValueError:
import site import site
path = None path = None
for s in site.getsitepackages(): for s in site.getsitepackages():
try: try:
@ -306,10 +299,10 @@ class Handler(DefaultRichHandler):
if path is None: if path is None:
path = "<SITE>" path = "<SITE>"
else: else:
path = path.split('.')[0].replace(os.sep, '.') path = path.split(".")[0].replace(os.sep, ".")
else: else:
path = '<INPUT>' path = "<INPUT>"
path = path.replace('lib.site-packages.', '') path = path.replace("lib.site-packages.", "")
_level = self.get_level_text(record) _level = self.get_level_text(record)
time_format = None if self.formatter is None else self.formatter.datefmt time_format = None if self.formatter is None else self.formatter.datefmt
log_time = datetime.fromtimestamp(record.created) log_time = datetime.fromtimestamp(record.created)
@ -318,12 +311,8 @@ class Handler(DefaultRichHandler):
self.console, self.console,
( (
[message_renderable] [message_renderable]
if not traceback else if not traceback
( else ([message_renderable, traceback] if message_renderable is not None else [traceback])
[message_renderable, traceback]
if message_renderable is not None else
[traceback]
)
), ),
log_time=log_time, log_time=log_time,
time_format=time_format, time_format=time_format,
@ -334,18 +323,15 @@ class Handler(DefaultRichHandler):
) )
return log_renderable return log_renderable
def render_message( def render_message(self, record: "LogRecord", message: Any) -> "ConsoleRenderable":
self, record: "LogRecord", message: Any
) -> "ConsoleRenderable":
use_markup = getattr(record, "markup", self.markup) use_markup = getattr(record, "markup", self.markup)
if isinstance(message, str): if isinstance(message, str):
message_text = ( message_text = Text.from_markup(message) if use_markup else Text(message)
Text.from_markup(message) if use_markup else Text(message)
)
highlighter = getattr(record, "highlighter", self.highlighter) highlighter = getattr(record, "highlighter", self.highlighter)
else: else:
from rich.highlighter import JSONHighlighter from rich.highlighter import JSONHighlighter
from rich.json import JSON from rich.json import JSON
highlighter = JSONHighlighter() highlighter = JSONHighlighter()
message_text = JSON.from_data(message, indent=4).text message_text = JSON.from_data(message, indent=4).text
@ -364,11 +350,7 @@ class Handler(DefaultRichHandler):
def emit(self, record: "LogRecord") -> None: def emit(self, record: "LogRecord") -> None:
message = self.format(record) message = self.format(record)
_traceback = None _traceback = None
if ( if self.rich_tracebacks and record.exc_info and record.exc_info != (None, None, None):
self.rich_tracebacks
and record.exc_info
and record.exc_info != (None, None, None)
):
exc_type, exc_value, exc_traceback = record.exc_info exc_type, exc_value, exc_traceback = record.exc_info
if exc_type is None or exc_value is None: if exc_type is None or exc_value is None:
raise ValueError(record) raise ValueError(record)
@ -430,28 +412,33 @@ class FileHandler(Handler):
class Logger(logging.Logger): class Logger(logging.Logger):
def success( def success(
self, self,
msg: Any, *args: Any, msg: Any,
exc_info: Optional[ExceptionInfoType] = None, *args: Any,
stack_info: bool = False, exc_info: Optional[ExceptionInfoType] = None,
stacklevel: int = 1, stack_info: bool = False,
extra: Optional[Mapping[str, Any]] = None stacklevel: int = 1,
extra: Optional[Mapping[str, Any]] = None,
) -> None: ) -> None:
return self.log(25, msg, *args, exc_info=exc_info, stack_info=stack_info, stacklevel=stacklevel, extra=extra) return self.log(25, msg, *args, exc_info=exc_info, stack_info=stack_info, stacklevel=stacklevel, extra=extra)
def exception( def exception(
self, self,
msg: Any = NOT_SET, msg: Any = NOT_SET,
*args: Any, *args: Any,
exc_info: Optional[ExceptionInfoType] = True, exc_info: Optional[ExceptionInfoType] = True,
stack_info: bool = False, stack_info: bool = False,
stacklevel: int = 1, stacklevel: int = 1,
extra: Optional[Mapping[str, Any]] = None, extra: Optional[Mapping[str, Any]] = None,
**kwargs **kwargs,
) -> None: ) -> None:
super(Logger, self).exception( super(Logger, self).exception(
"" if msg is NOT_SET else msg, *args, "" if msg is NOT_SET else msg,
exc_info=exc_info, stack_info=stack_info, stacklevel=stacklevel, extra=extra *args,
exc_info=exc_info,
stack_info=stack_info,
stacklevel=stacklevel,
extra=extra,
) )
def findCaller(self, stack_info: bool = False, stacklevel: int = 1) -> Tuple[str, int, str, Optional[str]]: def findCaller(self, stack_info: bool = False, stacklevel: int = 1) -> Tuple[str, int, str, Optional[str]]:
@ -470,17 +457,17 @@ class Logger(logging.Logger):
filename = os.path.normcase(code.co_filename) filename = os.path.normcase(code.co_filename)
if filename in [ if filename in [
os.path.normcase(Path(__file__).resolve()), os.path.normcase(Path(__file__).resolve()),
os.path.normcase(logging.addLevelName.__code__.co_filename) os.path.normcase(logging.addLevelName.__code__.co_filename),
]: ]:
frame = frame.f_back frame = frame.f_back
continue continue
sinfo = None sinfo = None
if stack_info: if stack_info:
sio = io.StringIO() sio = io.StringIO()
sio.write('Stack (most recent call last):\n') sio.write("Stack (most recent call last):\n")
traceback_.print_stack(frame, file=sio) traceback_.print_stack(frame, file=sio)
sinfo = sio.getvalue() sinfo = sio.getvalue()
if sinfo[-1] == '\n': if sinfo[-1] == "\n":
sinfo = sinfo[:-1] sinfo = sinfo[:-1]
sio.close() sio.close()
rv = (code.co_filename, frame.f_lineno, code.co_name, sinfo) rv = (code.co_filename, frame.f_lineno, code.co_name, sinfo)
@ -496,10 +483,10 @@ with _lock:
handler, debug_handler, error_handler = ( handler, debug_handler, error_handler = (
Handler(locals_max_length=4), Handler(locals_max_length=4),
FileHandler(level=10, path=config.logger.path.joinpath("debug/debug.log")), FileHandler(level=10, path=config.logger.path.joinpath("debug/debug.log")),
FileHandler(level=40, path=config.logger.path.joinpath("error/error.log")) FileHandler(level=40, path=config.logger.path.joinpath("error/error.log")),
) )
log_filter = logging.Filter('TGPaimon') log_filter = logging.Filter("TGPaimon")
handler.addFilter(log_filter) handler.addFilter(log_filter)
debug_handler.addFilter(log_filter) debug_handler.addFilter(log_filter)
@ -514,7 +501,7 @@ with _lock:
warnings_logger.addHandler(handler) warnings_logger.addHandler(handler)
warnings_logger.addHandler(debug_handler) warnings_logger.addHandler(debug_handler)
logger = Logger('TGPaimon', level_) logger = Logger("TGPaimon", level_)
logger.addHandler(handler) logger.addHandler(handler)
logger.addHandler(debug_handler) logger.addHandler(debug_handler)
logger.addHandler(error_handler) logger.addHandler(error_handler)

View File

@ -17,10 +17,23 @@ from pygments.token import (
from rich.style import Style from rich.style import Style
__all__ = [ __all__ = [
'MonokaiProStyle', 'DEFAULT_STYLE', "MonokaiProStyle",
'BACKGROUND', 'FOREGROUND', "DEFAULT_STYLE",
'BLACK', 'DARK_GREY', 'LIGHT_GREY', 'GREY', 'RED', 'MAGENTA', 'GREEN', "BACKGROUND",
'YELLOW', 'ORANGE', 'PURPLE', 'BLUE', 'CYAN', 'WHITE' "FOREGROUND",
"BLACK",
"DARK_GREY",
"LIGHT_GREY",
"GREY",
"RED",
"MAGENTA",
"GREEN",
"YELLOW",
"ORANGE",
"PURPLE",
"BLUE",
"CYAN",
"WHITE",
] ]
BACKGROUND = "#272822" BACKGROUND = "#272822"
@ -49,17 +62,12 @@ class MonokaiProStyle(PyStyle):
# No corresponding class for the following: # No corresponding class for the following:
Text: WHITE, # class: '' Text: WHITE, # class: ''
Error: "#fc618d bg:#1e0010", # class: 'err' Error: "#fc618d bg:#1e0010", # class: 'err'
Comment: LIGHT_GREY, # class: 'c' Comment: LIGHT_GREY, # class: 'c'
Comment.Multiline: YELLOW, # class: 'cm' Comment.Multiline: YELLOW, # class: 'cm'
Keyword: RED, # class: 'k' Keyword: RED, # class: 'k'
Keyword.Namespace: GREEN, # class: 'kn' Keyword.Namespace: GREEN, # class: 'kn'
Operator: RED, # class: 'o' Operator: RED, # class: 'o'
Punctuation: WHITE, # class: 'p' Punctuation: WHITE, # class: 'p'
Name: WHITE, # class: 'n' Name: WHITE, # class: 'n'
Name.Attribute: GREEN, # class: 'na' - to be revised Name.Attribute: GREEN, # class: 'na' - to be revised
Name.Builtin: CYAN, # class: 'nb' Name.Builtin: CYAN, # class: 'nb'
@ -69,15 +77,11 @@ class MonokaiProStyle(PyStyle):
Name.Exception: GREEN, # class: 'ne' Name.Exception: GREEN, # class: 'ne'
Name.Function: GREEN, # class: 'nf' Name.Function: GREEN, # class: 'nf'
Name.Property: ORANGE, # class: 'py' Name.Property: ORANGE, # class: 'py'
Number: PURPLE, # class: 'm' Number: PURPLE, # class: 'm'
Literal: PURPLE, # class: 'l' Literal: PURPLE, # class: 'l'
Literal.Date: ORANGE, # class: 'ld' Literal.Date: ORANGE, # class: 'ld'
String: YELLOW, # class: 's' String: YELLOW, # class: 's'
String.Regex: ORANGE, # class: 'sr' String.Regex: ORANGE, # class: 'sr'
Generic.Deleted: YELLOW, # class: 'gd', Generic.Deleted: YELLOW, # class: 'gd',
Generic.Emph: "italic", # class: 'ge' Generic.Emph: "italic", # class: 'ge'
Generic.Inserted: GREEN, # class: 'gi' Generic.Inserted: GREEN, # class: 'gi'
@ -122,7 +126,6 @@ DEFAULT_STYLE: Dict[str, Style] = {
"blue": Style(color=BLUE), "blue": Style(color=BLUE),
"cyan": Style(color=CYAN), "cyan": Style(color=CYAN),
"white": Style(color=WHITE), "white": Style(color=WHITE),
# inspect # inspect
"inspect.attr": Style(color=YELLOW, italic=True), "inspect.attr": Style(color=YELLOW, italic=True),
"inspect.attr.dunder": Style(color=YELLOW, italic=True, dim=True), "inspect.attr.dunder": Style(color=YELLOW, italic=True, dim=True),
@ -134,31 +137,27 @@ DEFAULT_STYLE: Dict[str, Style] = {
"inspect.help": Style(color=CYAN), "inspect.help": Style(color=CYAN),
"inspect.doc": Style(dim=True), "inspect.doc": Style(dim=True),
"inspect.value.border": Style(color=GREEN), "inspect.value.border": Style(color=GREEN),
# live # live
"live.ellipsis": Style(bold=True, color=RED), "live.ellipsis": Style(bold=True, color=RED),
# layout # layout
"layout.tree.row": Style(dim=False, color=RED), "layout.tree.row": Style(dim=False, color=RED),
"layout.tree.column": Style(dim=False, color=BLUE), "layout.tree.column": Style(dim=False, color=BLUE),
# log # log
"logging.keyword": Style(bold=True, color=ORANGE), "logging.keyword": Style(bold=True, color=ORANGE),
"logging.level.notset": Style(color=DARK_GREY, dim=True), "logging.level.notset": Style(color=DARK_GREY, dim=True),
"logging.level.trace": Style(color=GREY), "logging.level.trace": Style(color=GREY),
"logging.level.debug": Style(color=LIGHT_GREY, bold=True), "logging.level.debug": Style(color=LIGHT_GREY, bold=True),
"logging.level.info": Style(color='white'), "logging.level.info": Style(color="white"),
"logging.level.plugin": Style(color='cyan'), "logging.level.plugin": Style(color="cyan"),
"logging.level.success": Style(color='green'), "logging.level.success": Style(color="green"),
"logging.level.warning": Style(color='yellow'), "logging.level.warning": Style(color="yellow"),
"logging.level.error": Style(color='red'), "logging.level.error": Style(color="red"),
"logging.level.critical": Style(color='red', bgcolor='#1e0010', bold=True), "logging.level.critical": Style(color="red", bgcolor="#1e0010", bold=True),
"log.level": Style.null(), "log.level": Style.null(),
"log.time": Style(color=CYAN, dim=True), "log.time": Style(color=CYAN, dim=True),
"log.message": Style.null(), "log.message": Style.null(),
"log.path": Style(dim=True), "log.path": Style(dim=True),
"log.line_no": Style(color=CYAN, bold=True, italic=False, dim=True), "log.line_no": Style(color=CYAN, bold=True, italic=False, dim=True),
# repr # repr
"repr.ellipsis": Style(color=YELLOW), "repr.ellipsis": Style(color=YELLOW),
"repr.indent": Style(color=GREEN, dim=True), "repr.indent": Style(color=GREEN, dim=True),
@ -182,16 +181,13 @@ DEFAULT_STYLE: Dict[str, Style] = {
"repr.bool_true": Style(color="bright_green", italic=True), "repr.bool_true": Style(color="bright_green", italic=True),
"repr.bool_false": Style(color="bright_red", italic=True), "repr.bool_false": Style(color="bright_red", italic=True),
"repr.none": Style(color=MAGENTA, italic=True), "repr.none": Style(color=MAGENTA, italic=True),
"repr.url": Style( "repr.url": Style(underline=True, color="bright_blue", italic=False, bold=False),
underline=True, color="bright_blue", italic=False, bold=False
),
"repr.uuid": Style(color="bright_yellow", bold=False), "repr.uuid": Style(color="bright_yellow", bold=False),
"repr.call": Style(color=MAGENTA, bold=True), "repr.call": Style(color=MAGENTA, bold=True),
"repr.path": Style(color=MAGENTA), "repr.path": Style(color=MAGENTA),
"repr.filename": Style(color="bright_magenta"), "repr.filename": Style(color="bright_magenta"),
"rule.line": Style(color="bright_green"), "rule.line": Style(color="bright_green"),
"rule.text": Style.null(), "rule.text": Style.null(),
# json # json
"json.brace": Style(bold=True), "json.brace": Style(bold=True),
"json.bool_true": Style(color="bright_green", italic=True), "json.bool_true": Style(color="bright_green", italic=True),
@ -200,30 +196,25 @@ DEFAULT_STYLE: Dict[str, Style] = {
"json.number": Style(color=CYAN, bold=True, italic=False), "json.number": Style(color=CYAN, bold=True, italic=False),
"json.str": Style(color=GREEN, italic=False, bold=False), "json.str": Style(color=GREEN, italic=False, bold=False),
"json.key": Style(color=BLUE, bold=True), "json.key": Style(color=BLUE, bold=True),
# prompt # prompt
"prompt": Style.null(), "prompt": Style.null(),
"prompt.choices": Style(color=MAGENTA, bold=True), "prompt.choices": Style(color=MAGENTA, bold=True),
"prompt.default": Style(color=CYAN, bold=True), "prompt.default": Style(color=CYAN, bold=True),
"prompt.invalid": Style(color=RED), "prompt.invalid": Style(color=RED),
"prompt.invalid.choice": Style(color=RED), "prompt.invalid.choice": Style(color=RED),
# pretty # pretty
"pretty": Style.null(), "pretty": Style.null(),
# scope # scope
"scope.border": Style(color=BLUE), "scope.border": Style(color=BLUE),
"scope.key": Style(color=YELLOW, italic=True), "scope.key": Style(color=YELLOW, italic=True),
"scope.key.special": Style(color=YELLOW, italic=True, dim=True), "scope.key.special": Style(color=YELLOW, italic=True, dim=True),
"scope.equals": Style(color=RED), "scope.equals": Style(color=RED),
# table # table
"table.header": Style(bold=True), "table.header": Style(bold=True),
"table.footer": Style(bold=True), "table.footer": Style(bold=True),
"table.cell": Style.null(), "table.cell": Style.null(),
"table.title": Style(italic=True), "table.title": Style(italic=True),
"table.caption": Style(italic=True, dim=True), "table.caption": Style(italic=True, dim=True),
# traceback # traceback
"traceback.error": Style(color=RED, italic=True), "traceback.error": Style(color=RED, italic=True),
"traceback.border.syntax_error": Style(color="bright_red"), "traceback.border.syntax_error": Style(color="bright_red"),
@ -233,13 +224,11 @@ DEFAULT_STYLE: Dict[str, Style] = {
"traceback.exc_type": Style(color="bright_red", bold=True), "traceback.exc_type": Style(color="bright_red", bold=True),
"traceback.exc_value": Style.null(), "traceback.exc_value": Style.null(),
"traceback.offset": Style(color="bright_red", bold=True), "traceback.offset": Style(color="bright_red", bold=True),
# bar # bar
"bar.back": Style(color="grey23"), "bar.back": Style(color="grey23"),
"bar.complete": Style(color="rgb(249,38,114)"), "bar.complete": Style(color="rgb(249,38,114)"),
"bar.finished": Style(color="rgb(114,156,31)"), "bar.finished": Style(color="rgb(114,156,31)"),
"bar.pulse": Style(color="rgb(249,38,114)"), "bar.pulse": Style(color="rgb(249,38,114)"),
# progress # progress
"progress.description": Style.null(), "progress.description": Style.null(),
"progress.filesize": Style(color=GREEN), "progress.filesize": Style(color=GREEN),
@ -251,11 +240,9 @@ DEFAULT_STYLE: Dict[str, Style] = {
"progress.data.speed": Style(color=RED), "progress.data.speed": Style(color=RED),
"progress.spinner": Style(color=GREEN), "progress.spinner": Style(color=GREEN),
"status.spinner": Style(color=GREEN), "status.spinner": Style(color=GREEN),
# tree # tree
"tree": Style(), "tree": Style(),
"tree.line": Style(), "tree.line": Style(),
# markdown # markdown
"markdown.paragraph": Style(), "markdown.paragraph": Style(),
"markdown.text": Style(), "markdown.text": Style(),

View File

@ -7,8 +7,9 @@ from utils.baseobject import BaseObject
class Stat: class Stat:
def __init__(self, view_num: int = 0, reply_num: int = 0, like_num: int = 0, bookmark_num: int = 0, def __init__(
forward_num: int = 0): self, view_num: int = 0, reply_num: int = 0, like_num: int = 0, bookmark_num: int = 0, forward_num: int = 0
):
self.forward_num = forward_num # 关注数 self.forward_num = forward_num # 关注数
self.bookmark_num = bookmark_num # 收藏数 self.bookmark_num = bookmark_num # 收藏数
self.like_num = like_num # 喜欢数 self.like_num = like_num # 喜欢数
@ -17,7 +18,6 @@ class Stat:
class ArtworkInfo: class ArtworkInfo:
def __init__(self): def __init__(self):
self.user_id: int = 0 self.user_id: int = 0
self.artwork_id: int = 0 # 作品ID self.artwork_id: int = 0 # 作品ID
@ -32,7 +32,6 @@ class ArtworkInfo:
class ArtworkImage: class ArtworkImage:
def __init__(self, art_id: int, page: int = 0, is_error: bool = False, data: bytes = b""): def __init__(self, art_id: int, page: int = 0, is_error: bool = False, data: bytes = b""):
self.art_id = art_id self.art_id = art_id
self.data = data self.data = data
@ -50,14 +49,20 @@ class RegionEnum(Enum):
查了一下确实有点意思 考虑到大部分重要的功能确实是在移动端实现了 查了一下确实有点意思 考虑到大部分重要的功能确实是在移动端实现了
干脆用这个还好听 """ 干脆用这个还好听 """
NULL = None NULL = None
HYPERION = 1 # 米忽悠国服 hyperion HYPERION = 1 # 米忽悠国服 hyperion
HOYOLAB = 2 # 米忽悠国际服 hoyolab HOYOLAB = 2 # 米忽悠国际服 hoyolab
class GameItem(BaseObject): class GameItem(BaseObject):
def __init__(self, item_id: int = 0, name: str = "", item_type: Union[Enum, str, int] = "", def __init__(
value: Union[Enum, str, int, bool, float] = 0): self,
item_id: int = 0,
name: str = "",
item_type: Union[Enum, str, int] = "",
value: Union[Enum, str, int, bool, float] = 0,
):
self.item_id = item_id self.item_id = item_id
self.name = name # 名称 self.name = name # 名称
self.type = item_type # 类型 self.type = item_type # 类型
@ -67,9 +72,9 @@ class GameItem(BaseObject):
class ModuleInfo: class ModuleInfo:
def __init__(
def __init__(self, file_name: Optional[str] = None, plugin_name: Optional[str] = None, self, file_name: Optional[str] = None, plugin_name: Optional[str] = None, relative_path: Optional[str] = None
relative_path: Optional[str] = None): ):
self.relative_path = relative_path self.relative_path = relative_path
self.module_name = plugin_name self.module_name = plugin_name
self.file_name = file_name self.file_name = file_name

View File

@ -1,11 +1,11 @@
def patch(obj): def patch(obj):
def is_patchable(item): def is_patchable(item):
return getattr(item[1], 'patchable', False) return getattr(item[1], "patchable", False)
def wrapper(container): def wrapper(container):
for name, func in filter(is_patchable, container.__dict__.items()): for name, func in filter(is_patchable, container.__dict__.items()):
old = getattr(obj, name, None) old = getattr(obj, name, None)
setattr(obj, f'old_{name}', old) setattr(obj, f"old_{name}", old)
setattr(obj, name, func) setattr(obj, name, func)
return container return container

View File

@ -4,20 +4,13 @@ from typing import Any, Dict, Optional, Tuple, Type, Union
from httpx import URL from httpx import URL
__all__ = [ __all__ = ["StrOrPath", "StrOrURL", "StrOrInt", "SysExcInfoType", "ExceptionInfoType", "JSONDict", "JSONType"]
'StrOrPath', 'StrOrURL', 'StrOrInt',
'SysExcInfoType', 'ExceptionInfoType',
'JSONDict', 'JSONType'
]
StrOrPath = Union[str, Path] StrOrPath = Union[str, Path]
StrOrURL = Union[str, URL] StrOrURL = Union[str, URL]
StrOrInt = Union[str, int] StrOrInt = Union[str, int]
SysExcInfoType = Union[ SysExcInfoType = Union[Tuple[Type[BaseException], BaseException, Optional[TracebackType]], Tuple[None, None, None]]
Tuple[Type[BaseException], BaseException, Optional[TracebackType]],
Tuple[None, None, None]
]
ExceptionInfoType = Union[bool, SysExcInfoType, BaseException] ExceptionInfoType = Union[bool, SysExcInfoType, BaseException]
JSONDict = Dict[str, Any] JSONDict = Dict[str, Any]
JSONType = Union[JSONDict, list] JSONType = Union[JSONDict, list]