mirror of
https://github.com/PaiGramTeam/PaiGram.git
synced 2024-11-21 22:58:05 +00:00
🎨 使用 black 格式化所有代码
This commit is contained in:
parent
c44b785118
commit
345edb9fe5
@ -42,9 +42,7 @@ def import_models():
|
||||
try:
|
||||
import_module(pkg) # 导入 models
|
||||
except Exception as e: # pylint: disable=W0703
|
||||
logger.error(
|
||||
f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]'
|
||||
)
|
||||
logger.error(f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]')
|
||||
|
||||
|
||||
# register our models for alembic to auto-generate migrations
|
||||
|
@ -1,9 +1,8 @@
|
||||
|
||||
from sqlmodel import SQLModel, Field
|
||||
|
||||
|
||||
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)
|
||||
user_id: int = Field(foreign_key="user.user_id")
|
||||
|
@ -7,7 +7,6 @@ from utils.log import logger
|
||||
|
||||
|
||||
class AioBrowser(Service):
|
||||
|
||||
def __init__(self, loop=None):
|
||||
self.browser: Optional[Browser] = None
|
||||
self._playwright: Optional[Playwright] = None
|
||||
@ -15,16 +14,16 @@ class AioBrowser(Service):
|
||||
|
||||
async def start(self):
|
||||
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()
|
||||
logger.success("[blue]Playwright[/] 启动成功", extra={'markup': True})
|
||||
logger.success("[blue]Playwright[/] 启动成功", extra={"markup": True})
|
||||
if self.browser is None:
|
||||
logger.info("正在尝试启动 [blue]Browser[/]", extra={'markup': True})
|
||||
logger.info("正在尝试启动 [blue]Browser[/]", extra={"markup": True})
|
||||
try:
|
||||
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:
|
||||
logger.warning("[blue]Browser[/] 启动失败", extra={'markup': True})
|
||||
logger.warning("[blue]Browser[/] 启动失败", extra={"markup": True})
|
||||
raise err
|
||||
|
||||
return self.browser
|
||||
|
@ -20,13 +20,12 @@ from core.service import Service
|
||||
|
||||
|
||||
class MTProto(Service):
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
def session_exists(self):
|
||||
@ -53,8 +52,13 @@ class MTProto(Service):
|
||||
if bot.config.mtproto.api_hash is None:
|
||||
logger.info("MTProto 服务需要的 api_hash 未配置 本次服务 client 为 None")
|
||||
return
|
||||
self.client = Client(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)
|
||||
self.client = Client(
|
||||
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()
|
||||
|
||||
async def stop(self): # pylint: disable=W0221
|
||||
|
@ -12,8 +12,7 @@ class MySQL(Service):
|
||||
def from_config(cls, config: BotConfig) -> Self:
|
||||
return cls(**config.mysql.dict())
|
||||
|
||||
def __init__(self, host: str = "127.0.0.1", port: int = 3306, username: str = "root", # nosec B107
|
||||
password: str = "", database: str = ""): # nosec B107
|
||||
def __init__(self, host: str, port: int, username: str, password: str, database: str):
|
||||
self.database = database
|
||||
self.password = password
|
||||
self.user = username
|
||||
|
@ -22,21 +22,21 @@ class RedisDB(Service):
|
||||
|
||||
async def ping(self):
|
||||
if await self.client.ping():
|
||||
logger.info("连接 [red]Redis[/] 成功", extra={'markup': True})
|
||||
logger.info("连接 [red]Redis[/] 成功", extra={"markup": True})
|
||||
else:
|
||||
logger.info("连接 [red]Redis[/] 失败", extra={'markup': True})
|
||||
logger.info("连接 [red]Redis[/] 失败", extra={"markup": True})
|
||||
raise RuntimeError("连接 Redis 失败")
|
||||
|
||||
async def start(self): # pylint: disable=W0221
|
||||
if self._loop is None:
|
||||
self._loop = asyncio.get_running_loop()
|
||||
logger.info("正在尝试建立与 [red]Redis[/] 连接", extra={'markup': True})
|
||||
logger.info("正在尝试建立与 [red]Redis[/] 连接", extra={"markup": True})
|
||||
try:
|
||||
await self.ping()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
pass
|
||||
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()
|
||||
await self.ping()
|
||||
|
||||
|
@ -16,8 +16,7 @@ async def clean_message(context: CallbackContext):
|
||||
if "not found" in str(exc):
|
||||
logger.warning(f"删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败 消息不存在")
|
||||
elif "Message can't be deleted" 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}]失败 消息无法删除 可能是没有授权")
|
||||
else:
|
||||
logger.error(f"删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败")
|
||||
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):
|
||||
context.job_queue.run_once(callback=clean_message, when=delete_seconds, 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"})
|
||||
context.job_queue.run_once(
|
||||
callback=clean_message,
|
||||
when=delete_seconds,
|
||||
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:
|
||||
@ -43,9 +46,8 @@ class _BasePlugin:
|
||||
|
||||
|
||||
class _Conversation(_BasePlugin):
|
||||
|
||||
@conversation.fallback
|
||||
@handler.command(command='cancel', block=True)
|
||||
@handler.command(command="cancel", block=True)
|
||||
async def cancel(self, update: Update, _: CallbackContext) -> int:
|
||||
await update.effective_message.reply_text("退出命令", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
|
82
core/bot.py
82
core/bot.py
@ -23,6 +23,7 @@ from telegram.ext.filters import StatusUpdate
|
||||
|
||||
from core.config import BotConfig, config # pylint: disable=W0611
|
||||
from core.error import ServiceNotFoundError
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
from core.plugin import Plugin, _Plugin
|
||||
from core.service import Service
|
||||
@ -32,10 +33,10 @@ from utils.log import logger
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Update
|
||||
|
||||
__all__ = ['bot']
|
||||
__all__ = ["bot"]
|
||||
|
||||
T = TypeVar('T')
|
||||
PluginType = TypeVar('PluginType', bound=_Plugin)
|
||||
T = TypeVar("T")
|
||||
PluginType = TypeVar("PluginType", bound=_Plugin)
|
||||
|
||||
|
||||
class Bot:
|
||||
@ -57,7 +58,7 @@ class Bot:
|
||||
def _inject(self, signature: inspect.Signature, target: Callable[..., T]) -> T:
|
||||
kwargs = {}
|
||||
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):
|
||||
kwargs[name] = value
|
||||
return target(**kwargs)
|
||||
@ -76,11 +77,11 @@ class Bot:
|
||||
def _gen_pkg(self, root: Path) -> Iterator[str]:
|
||||
"""生成可以用于 import_module 导入的字符串"""
|
||||
for path in root.iterdir():
|
||||
if not path.name.startswith('_'):
|
||||
if not path.name.startswith("_"):
|
||||
if path.is_dir():
|
||||
yield from self._gen_pkg(path)
|
||||
elif path.suffix == '.py':
|
||||
yield str(path.relative_to(PROJECT_ROOT).with_suffix('')).replace(os.sep, '.')
|
||||
elif path.suffix == ".py":
|
||||
yield str(path.relative_to(PROJECT_ROOT).with_suffix("")).replace(os.sep, ".")
|
||||
|
||||
async def install_plugins(self):
|
||||
"""安装插件"""
|
||||
@ -89,8 +90,7 @@ class Bot:
|
||||
import_module(pkg) # 导入插件
|
||||
except Exception as e: # pylint: disable=W0703
|
||||
logger.exception(
|
||||
f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]',
|
||||
extra={'markup': True}
|
||||
f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]', extra={"markup": True}
|
||||
)
|
||||
continue # 如有错误则继续
|
||||
callback_dict: Dict[int, List[Callable]] = {}
|
||||
@ -98,7 +98,7 @@ class Bot:
|
||||
path = f"{plugin_cls.__module__}.{plugin_cls.__name__}"
|
||||
try:
|
||||
plugin: PluginType = self.init_inject(plugin_cls)
|
||||
if hasattr(plugin, '__async_init__'):
|
||||
if hasattr(plugin, "__async_init__"):
|
||||
await self.async_inject(plugin.__async_init__)
|
||||
handlers = plugin.handlers
|
||||
self.app.add_handlers(handlers)
|
||||
@ -115,46 +115,44 @@ class Bot:
|
||||
for callback, block in error_handlers.items():
|
||||
self.app.add_error_handler(callback, block)
|
||||
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:
|
||||
logger.debug(f'插件 "{path}" 添加了 {len(jobs)} 个任务')
|
||||
logger.success(f'插件 "{path}" 载入成功')
|
||||
except Exception as e: # pylint: disable=W0703
|
||||
logger.exception(
|
||||
f'在安装插件 \"{path}\" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]',
|
||||
extra={'markup': True}
|
||||
f'在安装插件 "{path}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]', extra={"markup": True}
|
||||
)
|
||||
if 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
|
||||
for _, value in callback_dict.items():
|
||||
for callback in value:
|
||||
await callback(update, context)
|
||||
|
||||
self.app.add_handler(MessageHandler(
|
||||
callback=_new_chat_member_callback, filters=StatusUpdate.NEW_CHAT_MEMBERS, block=False
|
||||
))
|
||||
self.app.add_handler(
|
||||
MessageHandler(callback=_new_chat_member_callback, filters=StatusUpdate.NEW_CHAT_MEMBERS, block=False)
|
||||
)
|
||||
logger.success(
|
||||
f'成功添加了 {num} 个针对 [blue]{StatusUpdate.NEW_CHAT_MEMBERS}[/] 的 [blue]MessageHandler[/]',
|
||||
extra={'markup': True}
|
||||
f"成功添加了 {num} 个针对 [blue]{StatusUpdate.NEW_CHAT_MEMBERS}[/] 的 [blue]MessageHandler[/]",
|
||||
extra={"markup": True},
|
||||
)
|
||||
|
||||
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:
|
||||
import_module(pkg)
|
||||
except Exception as e: # pylint: disable=W0703
|
||||
logger.exception(
|
||||
f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]',
|
||||
extra={'markup': True}
|
||||
f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]', extra={"markup": True}
|
||||
)
|
||||
continue
|
||||
for base_service_cls in Service.__subclasses__():
|
||||
try:
|
||||
if hasattr(base_service_cls, 'from_config'):
|
||||
if hasattr(base_service_cls, "from_config"):
|
||||
instance = base_service_cls.from_config(self._config)
|
||||
else:
|
||||
instance = self.init_inject(base_service_cls)
|
||||
@ -168,15 +166,14 @@ class Bot:
|
||||
async def start_services(self):
|
||||
"""启动服务"""
|
||||
await self._start_base_services()
|
||||
for path in (PROJECT_ROOT / 'core').iterdir():
|
||||
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, '.')
|
||||
for path in (PROJECT_ROOT / "core").iterdir():
|
||||
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, ".")
|
||||
try:
|
||||
import_module(pkg)
|
||||
except Exception as e: # pylint: disable=W0703
|
||||
logger.exception(
|
||||
f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]',
|
||||
extra={'markup': True}
|
||||
f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]', extra={"markup": True}
|
||||
)
|
||||
continue
|
||||
|
||||
@ -184,11 +181,11 @@ class Bot:
|
||||
"""关闭服务"""
|
||||
if not self._services:
|
||||
return
|
||||
logger.info('正在关闭服务')
|
||||
logger.info("正在关闭服务")
|
||||
for _, service in filter(lambda x: not isinstance(x[1], TgApplication), self._services.items()):
|
||||
async with timeout(5):
|
||||
try:
|
||||
if hasattr(service, 'stop'):
|
||||
if hasattr(service, "stop"):
|
||||
if inspect.iscoroutinefunction(service.stop):
|
||||
await service.stop()
|
||||
else:
|
||||
@ -197,16 +194,19 @@ class Bot:
|
||||
except CancelledError:
|
||||
logger.warning(f'服务 "{service.__class__.__name__}" 关闭超时')
|
||||
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:
|
||||
logger.info('开始初始化 genshin.py 相关资源')
|
||||
logger.info("开始初始化 genshin.py 相关资源")
|
||||
try:
|
||||
# 替换为 fastgit 镜像源
|
||||
for i in dir(genshin.utility.extdb):
|
||||
if "_URL" in i:
|
||||
setattr(genshin.utility.extdb, i,
|
||||
getattr(genshin.utility.extdb, i).replace("githubusercontent.com", "fastgit.org"))
|
||||
setattr(
|
||||
genshin.utility.extdb,
|
||||
i,
|
||||
getattr(genshin.utility.extdb, i).replace("githubusercontent.com", "fastgit.org"),
|
||||
)
|
||||
await genshin.utility.update_characters_enka()
|
||||
except Exception as exc:
|
||||
logger.error("初始化 genshin.py 相关资源失败")
|
||||
@ -214,16 +214,16 @@ class Bot:
|
||||
else:
|
||||
logger.success("初始化 genshin.py 相关资源成功")
|
||||
self._services.update({CallbackContext: context})
|
||||
logger.info('开始初始化服务')
|
||||
logger.info("开始初始化服务")
|
||||
await self.start_services()
|
||||
logger.info('开始安装插件')
|
||||
logger.info("开始安装插件")
|
||||
await self.install_plugins()
|
||||
logger.info('BOT 初始化成功')
|
||||
logger.info("BOT 初始化成功")
|
||||
|
||||
def launch(self) -> NoReturn:
|
||||
"""启动机器人"""
|
||||
self._running = True
|
||||
logger.info('正在初始化BOT')
|
||||
logger.info("正在初始化BOT")
|
||||
self.app = (
|
||||
TgApplication.builder()
|
||||
.rate_limiter(AIORateLimiter())
|
||||
@ -238,11 +238,11 @@ class Bot:
|
||||
self.app.run_polling(close_loop=False)
|
||||
break
|
||||
except TimedOut:
|
||||
logger.warning("连接至 [blue]telegram[/] 服务器失败,正在重试", extra={'markup': True})
|
||||
logger.warning("连接至 [blue]telegram[/] 服务器失败,正在重试", extra={"markup": True})
|
||||
continue
|
||||
except NetworkError as e:
|
||||
logger.exception()
|
||||
if 'SSLZeroReturnError' in str(e):
|
||||
if "SSLZeroReturnError" in str(e):
|
||||
logger.error("代理服务出现异常, 请检查您的代理服务是否配置成功.")
|
||||
else:
|
||||
logger.error("网络连接出现问题, 请检查您的网络状况.")
|
||||
@ -267,7 +267,7 @@ class Bot:
|
||||
def add_service(self, service: T) -> NoReturn:
|
||||
"""添加服务。若已经有同类型的服务,则会抛出异常"""
|
||||
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)
|
||||
|
||||
def update_service(self, service: T):
|
||||
|
@ -11,7 +11,7 @@ class CookiesStatusEnum(int, enum.Enum):
|
||||
|
||||
|
||||
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)
|
||||
user_id: Optional[int] = Field(foreign_key="user.user_id")
|
||||
@ -20,8 +20,8 @@ class Cookies(SQLModel):
|
||||
|
||||
|
||||
class HyperionCookie(Cookies, table=True):
|
||||
__tablename__ = 'mihoyo_cookies'
|
||||
__tablename__ = "mihoyo_cookies"
|
||||
|
||||
|
||||
class HoyolabCookie(Cookies, table=True):
|
||||
__tablename__ = 'hoyoverse_cookies'
|
||||
__tablename__ = "hoyoverse_cookies"
|
||||
|
@ -3,6 +3,5 @@ from typing import Union
|
||||
|
||||
|
||||
class ServiceNotFoundError(Exception):
|
||||
|
||||
def __init__(self, name: Union[str, type]):
|
||||
super().__init__(f"No service named '{name if isinstance(name, str) else name.__name__}'")
|
||||
|
@ -48,7 +48,7 @@ class GameMaterialService:
|
||||
self._cache = cache
|
||||
self._hyperion = Hyperion()
|
||||
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:
|
||||
post_id: int = -1
|
||||
|
189
core/plugin.py
189
core/plugin.py
@ -7,43 +7,42 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypeVar, Un
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
from telegram._utils.defaultvalue import DEFAULT_TRUE
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
from telegram._utils.types import DVInput, JSONDict
|
||||
from telegram.ext import BaseHandler, ConversationHandler, Job
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
from telegram.ext._utils.types import JobCallback
|
||||
from telegram.ext.filters import BaseFilter
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
__all__ = [
|
||||
'Plugin', 'handler', 'conversation', 'job', 'error_handler'
|
||||
]
|
||||
__all__ = ["Plugin", "handler", "conversation", "job", "error_handler"]
|
||||
|
||||
P = ParamSpec('P')
|
||||
T = TypeVar('T')
|
||||
HandlerType = TypeVar('HandlerType', bound=BaseHandler)
|
||||
P = ParamSpec("P")
|
||||
T = TypeVar("T")
|
||||
HandlerType = TypeVar("HandlerType", bound=BaseHandler)
|
||||
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"
|
||||
_CONVERSATION_HANDLER_ATTR_NAME = "_conversation_data"
|
||||
_JOB_ATTR_NAME = "_job_data"
|
||||
|
||||
_EXCLUDE_ATTRS = ['handlers', 'jobs', 'error_handlers']
|
||||
_EXCLUDE_ATTRS = ["handlers", "jobs", "error_handlers"]
|
||||
|
||||
|
||||
class _Plugin:
|
||||
|
||||
def _make_handler(self, datas: Union[List[Dict], Dict]) -> List[HandlerType]:
|
||||
result = []
|
||||
if isinstance(datas, list):
|
||||
for data in filter(lambda x: x, datas):
|
||||
func = getattr(self, data.pop('func'))
|
||||
result.append(data.pop('type')(callback=func, **data.pop('kwargs')))
|
||||
func = getattr(self, data.pop("func"))
|
||||
result.append(data.pop("type")(callback=func, **data.pop("kwargs")))
|
||||
else:
|
||||
func = getattr(self, datas.pop('func'))
|
||||
result.append(datas.pop('type')(callback=func, **datas.pop('kwargs')))
|
||||
func = getattr(self, datas.pop("func"))
|
||||
result.append(datas.pop("type")(callback=func, **datas.pop("kwargs")))
|
||||
return result
|
||||
|
||||
@property
|
||||
@ -52,14 +51,12 @@ class _Plugin:
|
||||
for attr in dir(self):
|
||||
# noinspection PyUnboundLocalVariable
|
||||
if (
|
||||
not (attr.startswith('_') or attr in _EXCLUDE_ATTRS)
|
||||
and
|
||||
isinstance(func := getattr(self, attr), MethodType)
|
||||
and
|
||||
(datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
|
||||
not (attr.startswith("_") or attr in _EXCLUDE_ATTRS)
|
||||
and isinstance(func := getattr(self, attr), MethodType)
|
||||
and (datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
|
||||
):
|
||||
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))
|
||||
return result
|
||||
|
||||
@ -69,15 +66,13 @@ class _Plugin:
|
||||
for attr in dir(self):
|
||||
# noinspection PyUnboundLocalVariable
|
||||
if (
|
||||
not (attr.startswith('_') or attr in _EXCLUDE_ATTRS)
|
||||
and
|
||||
isinstance(func := getattr(self, attr), MethodType)
|
||||
and
|
||||
(datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
|
||||
not (attr.startswith("_") or attr in _EXCLUDE_ATTRS)
|
||||
and isinstance(func := getattr(self, attr), MethodType)
|
||||
and (datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
|
||||
):
|
||||
for data in datas:
|
||||
if data and data['type'] == 'new_chat_member':
|
||||
result.append((data['priority'], func))
|
||||
if data and data["type"] == "new_chat_member":
|
||||
result.append((data["priority"], func))
|
||||
|
||||
return result
|
||||
|
||||
@ -87,34 +82,30 @@ class _Plugin:
|
||||
for attr in dir(self):
|
||||
# noinspection PyUnboundLocalVariable
|
||||
if (
|
||||
not (attr.startswith('_') or attr in _EXCLUDE_ATTRS)
|
||||
and
|
||||
isinstance(func := getattr(self, attr), MethodType)
|
||||
and
|
||||
(datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
|
||||
not (attr.startswith("_") or attr in _EXCLUDE_ATTRS)
|
||||
and isinstance(func := getattr(self, attr), MethodType)
|
||||
and (datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
|
||||
):
|
||||
for data in datas:
|
||||
if data and data['type'] == 'error':
|
||||
result.update({func: data['block']})
|
||||
if data and data["type"] == "error":
|
||||
result.update({func: data["block"]})
|
||||
return result
|
||||
|
||||
@property
|
||||
def jobs(self) -> List[Job]:
|
||||
from core.bot import bot
|
||||
|
||||
result = []
|
||||
for attr in dir(self):
|
||||
# noinspection PyUnboundLocalVariable
|
||||
if (
|
||||
not (attr.startswith('_') or attr in _EXCLUDE_ATTRS)
|
||||
and
|
||||
isinstance(func := getattr(self, attr), MethodType)
|
||||
and
|
||||
(datas := getattr(func, _JOB_ATTR_NAME, None))
|
||||
not (attr.startswith("_") or attr in _EXCLUDE_ATTRS)
|
||||
and isinstance(func := getattr(self, attr), MethodType)
|
||||
and (datas := getattr(func, _JOB_ATTR_NAME, None))
|
||||
):
|
||||
for data in datas:
|
||||
_job = getattr(bot.job_queue, data.pop('type'))(
|
||||
callback=func, **data.pop('kwargs'),
|
||||
**{key: data.pop(key) for key in list(data.keys())}
|
||||
_job = getattr(bot.job_queue, data.pop("type"))(
|
||||
callback=func, **data.pop("kwargs"), **{key: data.pop(key) for key in list(data.keys())}
|
||||
)
|
||||
result.append(_job)
|
||||
return result
|
||||
@ -138,30 +129,27 @@ class _Conversation(_Plugin):
|
||||
for attr in dir(self):
|
||||
# noinspection PyUnboundLocalVariable
|
||||
if (
|
||||
not (attr.startswith('_') or attr == 'handlers')
|
||||
and
|
||||
isinstance(func := getattr(self, attr), Callable)
|
||||
and
|
||||
(handler_datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
|
||||
not (attr.startswith("_") or attr == "handlers")
|
||||
and isinstance(func := getattr(self, attr), Callable)
|
||||
and (handler_datas := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
|
||||
):
|
||||
_handlers = self._make_handler(handler_datas)
|
||||
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)
|
||||
elif _type == 'state':
|
||||
if (key := conversation_data.pop('state')) in states:
|
||||
elif _type == "state":
|
||||
if (key := conversation_data.pop("state")) in states:
|
||||
states[key].extend(_handlers)
|
||||
else:
|
||||
states[key] = _handlers
|
||||
elif _type == 'fallback':
|
||||
elif _type == "fallback":
|
||||
fallbacks.extend(_handlers)
|
||||
else:
|
||||
result.extend(_handlers)
|
||||
if entry_points or states or fallbacks:
|
||||
result.append(
|
||||
ConversationHandler(
|
||||
entry_points, states, fallbacks,
|
||||
**self.__class__._conversation_kwargs # pylint: disable=W0212
|
||||
entry_points, states, fallbacks, **self.__class__._conversation_kwargs # pylint: disable=W0212
|
||||
)
|
||||
)
|
||||
return result
|
||||
@ -180,7 +168,7 @@ class _Handler:
|
||||
return getattr(_Module, f"{self.__class__.__name__.strip('_')}Handler")
|
||||
|
||||
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):
|
||||
handler_datas = getattr(func, _NORMAL_HANDLER_ATTR_NAME)
|
||||
handler_datas.append(data)
|
||||
@ -221,10 +209,7 @@ class _Command(_Handler):
|
||||
|
||||
class _InlineQuery(_Handler):
|
||||
def __init__(
|
||||
self,
|
||||
pattern: Union[str, Pattern] = None,
|
||||
block: DVInput[bool] = DEFAULT_TRUE,
|
||||
chat_types: List[str] = None
|
||||
self, 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)
|
||||
|
||||
@ -237,7 +222,7 @@ class _MessageNewChatMembers(_Handler):
|
||||
|
||||
def __call__(self, func: Callable[P, T] = None) -> Callable[P, T]:
|
||||
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):
|
||||
handler_datas = getattr(func, _NORMAL_HANDLER_ATTR_NAME)
|
||||
handler_datas.append(data)
|
||||
@ -248,7 +233,11 @@ class _MessageNewChatMembers(_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)
|
||||
|
||||
new_chat_members = _MessageNewChatMembers
|
||||
@ -298,10 +287,7 @@ class _StringRegex(_Handler):
|
||||
class _Type(_Handler):
|
||||
# noinspection PyShadowingBuiltins
|
||||
def __init__(
|
||||
self,
|
||||
type: Type, # pylint: disable=redefined-builtin
|
||||
strict: bool = False,
|
||||
block: DVInput[bool] = DEFAULT_TRUE
|
||||
self, type: Type, strict: bool = False, block: DVInput[bool] = DEFAULT_TRUE # pylint: disable=redefined-builtin
|
||||
):
|
||||
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]:
|
||||
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):
|
||||
handler_datas = getattr(func, _NORMAL_HANDLER_ATTR_NAME)
|
||||
handler_datas.append(data)
|
||||
@ -353,7 +339,7 @@ class error_handler:
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -362,12 +348,12 @@ class _State:
|
||||
self.state = state
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -382,8 +368,13 @@ class _Job:
|
||||
kwargs: Dict = {}
|
||||
|
||||
def __init__(
|
||||
self, name: str = None, data: object = None, chat_id: int = None,
|
||||
user_id: int = None, job_kwargs: JSONDict = None, **kwargs
|
||||
self,
|
||||
name: str = None,
|
||||
data: object = None,
|
||||
chat_id: int = None,
|
||||
user_id: int = None,
|
||||
job_kwargs: JSONDict = None,
|
||||
**kwargs,
|
||||
):
|
||||
self.name = name
|
||||
self.data = data
|
||||
@ -394,9 +385,13 @@ class _Job:
|
||||
|
||||
def __call__(self, func: JobCallback) -> JobCallback:
|
||||
data = {
|
||||
'name': self.name, 'data': self.data, '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('_')
|
||||
"name": self.name,
|
||||
"data": self.data,
|
||||
"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):
|
||||
job_datas = getattr(func, _JOB_ATTR_NAME)
|
||||
@ -409,39 +404,69 @@ class _Job:
|
||||
|
||||
class _RunOnce(_Job):
|
||||
def __init__(
|
||||
self, when: TimeType,
|
||||
data: object = None, name: str = None, chat_id: int = None, user_id: int = None, job_kwargs: JSONDict = None
|
||||
self,
|
||||
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)
|
||||
|
||||
|
||||
class _RunRepeating(_Job):
|
||||
def __init__(
|
||||
self, 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
|
||||
self,
|
||||
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)
|
||||
|
||||
|
||||
class _RunMonthly(_Job):
|
||||
def __init__(
|
||||
self, when: datetime.time, day: int,
|
||||
data: object = None, name: str = None, chat_id: int = None, user_id: int = None, job_kwargs: JSONDict = None
|
||||
self,
|
||||
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)
|
||||
|
||||
|
||||
class _RunDaily(_Job):
|
||||
def __init__(
|
||||
self, 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
|
||||
self,
|
||||
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)
|
||||
|
||||
|
||||
class _RunCustom(_Job):
|
||||
def __init__(self, data: object = None, name: str = None, chat_id: int = None, user_id: int = None,
|
||||
job_kwargs: JSONDict = None):
|
||||
def __init__(
|
||||
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)
|
||||
|
||||
|
||||
|
@ -7,7 +7,6 @@ from .models import Answer, Question
|
||||
|
||||
|
||||
class QuizCache:
|
||||
|
||||
def __init__(self, redis: RedisDB):
|
||||
self.client = redis.client
|
||||
self.question_qname = "quiz:question"
|
||||
@ -16,8 +15,7 @@ class QuizCache:
|
||||
async def get_all_question(self) -> List[Question]:
|
||||
temp_list = []
|
||||
qname = self.question_qname + "id_list"
|
||||
data_list = [self.question_qname + f":{question_id}" for question_id in
|
||||
await self.client.lrange(qname, 0, -1)]
|
||||
data_list = [self.question_qname + f":{question_id}" for question_id in await self.client.lrange(qname, 0, -1)]
|
||||
data = await self.client.mget(data_list)
|
||||
for i in data:
|
||||
temp_list.append(Question.de_json(ujson.loads(i)))
|
||||
|
@ -7,23 +7,20 @@ from utils.typedefs import JSONDict
|
||||
|
||||
|
||||
class AnswerDB(SQLModel, table=True):
|
||||
__tablename__ = 'answer'
|
||||
__table_args__ = dict(mysql_charset='utf8mb4', mysql_collate="utf8mb4_general_ci")
|
||||
__tablename__ = "answer"
|
||||
__table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci")
|
||||
|
||||
id: int = Field(primary_key=True)
|
||||
question_id: Optional[int] = Field(
|
||||
sa_column=Column(
|
||||
Integer,
|
||||
ForeignKey("question.id", ondelete="RESTRICT", onupdate="RESTRICT")
|
||||
)
|
||||
sa_column=Column(Integer, ForeignKey("question.id", ondelete="RESTRICT", onupdate="RESTRICT"))
|
||||
)
|
||||
is_correct: Optional[bool] = Field()
|
||||
text: Optional[str] = Field()
|
||||
|
||||
|
||||
class QuestionDB(SQLModel, table=True):
|
||||
__tablename__ = 'question'
|
||||
__table_args__ = dict(mysql_charset='utf8mb4', mysql_collate="utf8mb4_general_ci")
|
||||
__tablename__ = "question"
|
||||
__table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci")
|
||||
|
||||
id: int = Field(primary_key=True)
|
||||
text: Optional[str] = Field()
|
||||
|
@ -3,7 +3,7 @@ from types import FunctionType
|
||||
|
||||
from utils.log import logger
|
||||
|
||||
__all__ = ['Service', 'init_service']
|
||||
__all__ = ["Service", "init_service"]
|
||||
|
||||
|
||||
class Service(ABC):
|
||||
@ -20,11 +20,12 @@ class Service(ABC):
|
||||
|
||||
def init_service(func: FunctionType):
|
||||
from core.bot import bot
|
||||
|
||||
if bot.is_running:
|
||||
try:
|
||||
service = bot.init_inject(func)
|
||||
logger.success(f'服务 "{service.__class__.__name__}" 初始化成功')
|
||||
bot.add_service(service)
|
||||
except Exception as e: # pylint: disable=W0703
|
||||
logger.exception(f'来自{func.__module__}的服务初始化失败:{e}')
|
||||
logger.exception(f"来自{func.__module__}的服务初始化失败:{e}")
|
||||
return func
|
||||
|
@ -18,7 +18,7 @@ class SignStatusEnum(int, enum.Enum):
|
||||
|
||||
|
||||
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)
|
||||
user_id: int = Field(foreign_key="user.user_id")
|
||||
|
@ -53,9 +53,16 @@ class TemplateService:
|
||||
logger.debug(f"{template_name} 模板渲染使用了 {str(time.time() - start_time)}")
|
||||
return html
|
||||
|
||||
async def render(self, 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:
|
||||
async def render(
|
||||
self,
|
||||
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_name: 模板文件名
|
||||
|
@ -6,7 +6,7 @@ from utils.models.base import RegionEnum
|
||||
|
||||
|
||||
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)
|
||||
user_id: int = Field(unique=True)
|
||||
|
@ -3,7 +3,6 @@ from .repositories import UserRepository
|
||||
|
||||
|
||||
class UserService:
|
||||
|
||||
def __init__(self, user_repository: UserRepository) -> None:
|
||||
self._repository: UserRepository = user_repository
|
||||
|
||||
|
@ -7,7 +7,6 @@ from utils.log import logger
|
||||
|
||||
|
||||
class WikiService:
|
||||
|
||||
def __init__(self, cache: WikiCache):
|
||||
self._cache = cache
|
||||
"""Redis 在这里的作用是作为持久化"""
|
||||
|
@ -1,11 +1 @@
|
||||
POOL_200 = [
|
||||
{
|
||||
"five": [
|
||||
"常驻池"
|
||||
],
|
||||
"four": [],
|
||||
"from": "2020-09-15 06:00:00",
|
||||
"name": "常驻池",
|
||||
"to": "2050-09-15 17:59:59"
|
||||
}
|
||||
]
|
||||
POOL_200 = [{"five": ["常驻池"], "four": [], "from": "2020-09-15 06:00:00", "name": "常驻池", "to": "2050-09-15 17:59:59"}]
|
||||
|
@ -1,480 +1,254 @@
|
||||
POOL_301 = [
|
||||
{
|
||||
"five": [
|
||||
"赛诺",
|
||||
"温迪"
|
||||
],
|
||||
"four": [
|
||||
"久岐忍",
|
||||
"早柚",
|
||||
"坎蒂丝"
|
||||
],
|
||||
"five": ["赛诺", "温迪"],
|
||||
"four": ["久岐忍", "早柚", "坎蒂丝"],
|
||||
"from": "2022-09-28 06:00:00",
|
||||
"name": "劈裁冥昭|杯装之诗",
|
||||
"to": "2022-10-14 17:59:59"
|
||||
"to": "2022-10-14 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"甘雨",
|
||||
"心海"
|
||||
],
|
||||
"four": [
|
||||
"行秋",
|
||||
"砂糖",
|
||||
"多莉"
|
||||
],
|
||||
"five": ["甘雨", "心海"],
|
||||
"four": ["行秋", "砂糖", "多莉"],
|
||||
"from": "2022-09-09 18:00:00",
|
||||
"name": "浮生孰来|浮岳虹珠",
|
||||
"to": "2022-09-27 14:59:59"
|
||||
"to": "2022-09-27 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"提纳里",
|
||||
"钟离"
|
||||
],
|
||||
"four": [
|
||||
"云堇",
|
||||
"辛焱",
|
||||
"班尼特"
|
||||
],
|
||||
"five": ["提纳里", "钟离"],
|
||||
"four": ["云堇", "辛焱", "班尼特"],
|
||||
"from": "2022-08-24 06:00:00",
|
||||
"name": "巡御蘙荟|陵薮市朝",
|
||||
"to": "2022-09-09 17:59:59"
|
||||
"to": "2022-09-09 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"宵宫"
|
||||
],
|
||||
"four": [
|
||||
"云堇",
|
||||
"辛焱",
|
||||
"班尼特"
|
||||
],
|
||||
"five": ["宵宫"],
|
||||
"four": ["云堇", "辛焱", "班尼特"],
|
||||
"from": "2022-08-02 18:00:00",
|
||||
"name": "焰色天河",
|
||||
"to": "2022-08-23 14:59:59"
|
||||
"to": "2022-08-23 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"枫原万叶",
|
||||
"可莉"
|
||||
],
|
||||
"four": [
|
||||
"凝光",
|
||||
"鹿野院平藏",
|
||||
"托马"
|
||||
],
|
||||
"five": ["枫原万叶", "可莉"],
|
||||
"four": ["凝光", "鹿野院平藏", "托马"],
|
||||
"from": "2022-07-13 06:00:00",
|
||||
"name": "红叶逐荒波",
|
||||
"to": "2022-08-02 17:59:59"
|
||||
"to": "2022-08-02 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"荒泷一斗"
|
||||
],
|
||||
"four": [
|
||||
"烟绯",
|
||||
"芭芭拉",
|
||||
"诺艾尔"
|
||||
],
|
||||
"five": ["荒泷一斗"],
|
||||
"four": ["烟绯", "芭芭拉", "诺艾尔"],
|
||||
"from": "2022-06-21 18:00:00",
|
||||
"name": "鬼门斗宴",
|
||||
"to": "2022-07-12 14:59:59"
|
||||
"to": "2022-07-12 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"夜兰",
|
||||
"魈"
|
||||
],
|
||||
"four": [
|
||||
"烟绯",
|
||||
"芭芭拉",
|
||||
"诺艾尔"
|
||||
],
|
||||
"five": ["夜兰", "魈"],
|
||||
"four": ["烟绯", "芭芭拉", "诺艾尔"],
|
||||
"from": "2022-05-31 06:00:00",
|
||||
"name": "素霓伣天|烟火之邀",
|
||||
"to": "2022-06-21 17:59:59"
|
||||
"to": "2022-06-21 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"神里绫华"
|
||||
],
|
||||
"four": [
|
||||
"罗莎莉亚",
|
||||
"早柚",
|
||||
"雷泽"
|
||||
],
|
||||
"five": ["神里绫华"],
|
||||
"four": ["罗莎莉亚", "早柚", "雷泽"],
|
||||
"from": "2022-04-19 17:59:59",
|
||||
"name": "白鹭之庭",
|
||||
"to": "2022-05-31 05:59:59"
|
||||
"to": "2022-05-31 05:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"神里绫人",
|
||||
"温迪"
|
||||
],
|
||||
"four": [
|
||||
"香菱",
|
||||
"砂糖",
|
||||
"云堇"
|
||||
],
|
||||
"five": ["神里绫人", "温迪"],
|
||||
"four": ["香菱", "砂糖", "云堇"],
|
||||
"from": "2022-03-30 06:00:00",
|
||||
"name": "苍流踏花|杯装之诗",
|
||||
"to": "2022-04-19 17:59:59"
|
||||
"to": "2022-04-19 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"雷电将军",
|
||||
"珊瑚宫心海"
|
||||
],
|
||||
"four": [
|
||||
"辛焱",
|
||||
"九条裟罗",
|
||||
"班尼特"
|
||||
],
|
||||
"five": ["雷电将军", "珊瑚宫心海"],
|
||||
"four": ["辛焱", "九条裟罗", "班尼特"],
|
||||
"from": "2022-03-08 18:00:00",
|
||||
"name": "影寂天下人|浮岳虹珠",
|
||||
"to": "2022-03-29 14:59:59"
|
||||
"to": "2022-03-29 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"八重神子"
|
||||
],
|
||||
"four": [
|
||||
"菲谢尔",
|
||||
"迪奥娜",
|
||||
"托马"
|
||||
],
|
||||
"five": ["八重神子"],
|
||||
"four": ["菲谢尔", "迪奥娜", "托马"],
|
||||
"from": "2022-02-16 06:00:00",
|
||||
"name": "华紫樱绯",
|
||||
"to": "2022-03-08 17:59:59"
|
||||
"to": "2022-03-08 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"甘雨",
|
||||
"钟离"
|
||||
],
|
||||
"four": [
|
||||
"行秋",
|
||||
"北斗",
|
||||
"烟绯"
|
||||
],
|
||||
"five": ["甘雨", "钟离"],
|
||||
"four": ["行秋", "北斗", "烟绯"],
|
||||
"from": "2022-01-25 18:00:00",
|
||||
"name": "浮生孰来|陵薮市朝",
|
||||
"to": "2022-02-15 14:59:59"
|
||||
"to": "2022-02-15 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"申鹤",
|
||||
"魈"
|
||||
],
|
||||
"four": [
|
||||
"云堇",
|
||||
"凝光",
|
||||
"重云"
|
||||
],
|
||||
"five": ["申鹤", "魈"],
|
||||
"four": ["云堇", "凝光", "重云"],
|
||||
"from": "2022-01-05 06:00:00",
|
||||
"name": "出尘入世|烟火之邀",
|
||||
"to": "2022-01-25 17:59:59"
|
||||
"to": "2022-01-25 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"荒泷一斗"
|
||||
],
|
||||
"four": [
|
||||
"五郎",
|
||||
"芭芭拉",
|
||||
"香菱"
|
||||
],
|
||||
"five": ["荒泷一斗"],
|
||||
"four": ["五郎", "芭芭拉", "香菱"],
|
||||
"from": "2021-12-14 18:00:00",
|
||||
"name": "鬼门斗宴",
|
||||
"to": "2022-01-04 14:59:59"
|
||||
"to": "2022-01-04 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"阿贝多",
|
||||
"优菈"
|
||||
],
|
||||
"four": [
|
||||
"班尼特",
|
||||
"诺艾尔",
|
||||
"罗莎莉亚"
|
||||
],
|
||||
"five": ["阿贝多", "优菈"],
|
||||
"four": ["班尼特", "诺艾尔", "罗莎莉亚"],
|
||||
"from": "2021-11-24 06:00:00",
|
||||
"name": "深秘之息|浪涌之瞬",
|
||||
"to": "2021-12-14 17:59:59"
|
||||
"to": "2021-12-14 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"胡桃"
|
||||
],
|
||||
"four": [
|
||||
"托马",
|
||||
"迪奥娜",
|
||||
"早柚"
|
||||
],
|
||||
"five": ["胡桃"],
|
||||
"four": ["托马", "迪奥娜", "早柚"],
|
||||
"from": "2021-11-02 18:00:00",
|
||||
"name": "赤团开时",
|
||||
"to": "2021-11-23 14:59:59"
|
||||
"to": "2021-11-23 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"达达利亚"
|
||||
],
|
||||
"four": [
|
||||
"凝光",
|
||||
"重云",
|
||||
"烟绯"
|
||||
],
|
||||
"five": ["达达利亚"],
|
||||
"four": ["凝光", "重云", "烟绯"],
|
||||
"from": "2021-10-13 06:00:00",
|
||||
"name": "暂别冬都",
|
||||
"to": "2021-11-02 17:59:59"
|
||||
"to": "2021-11-02 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"珊瑚宫心海"
|
||||
],
|
||||
"four": [
|
||||
"罗莎莉亚",
|
||||
"北斗",
|
||||
"行秋"
|
||||
],
|
||||
"five": ["珊瑚宫心海"],
|
||||
"four": ["罗莎莉亚", "北斗", "行秋"],
|
||||
"from": "2021-09-21 18:00:00",
|
||||
"name": "浮岳虹珠",
|
||||
"to": "2021-10-12 14:59:59"
|
||||
"to": "2021-10-12 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"雷电将军"
|
||||
],
|
||||
"four": [
|
||||
"九条裟罗",
|
||||
"香菱",
|
||||
"砂糖"
|
||||
],
|
||||
"five": ["雷电将军"],
|
||||
"four": ["九条裟罗", "香菱", "砂糖"],
|
||||
"from": "2021-09-01 06:00:00",
|
||||
"name": "影寂天下人",
|
||||
"to": "2021-09-21 17:59:59"
|
||||
"to": "2021-09-21 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"宵宫"
|
||||
],
|
||||
"four": [
|
||||
"早柚",
|
||||
"迪奥娜",
|
||||
"辛焱"
|
||||
],
|
||||
"five": ["宵宫"],
|
||||
"four": ["早柚", "迪奥娜", "辛焱"],
|
||||
"from": "2021-08-10 18:00:00",
|
||||
"name": "焰色天河",
|
||||
"to": "2021-08-31 14:59:59"
|
||||
"to": "2021-08-31 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"神里绫华"
|
||||
],
|
||||
"four": [
|
||||
"凝光",
|
||||
"重云",
|
||||
"烟绯"
|
||||
],
|
||||
"five": ["神里绫华"],
|
||||
"four": ["凝光", "重云", "烟绯"],
|
||||
"from": "2021-07-21 06:00:00",
|
||||
"name": "白鹭之庭",
|
||||
"to": "2021-08-10 17:59:59"
|
||||
"to": "2021-08-10 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"枫原万叶"
|
||||
],
|
||||
"four": [
|
||||
"罗莎莉亚",
|
||||
"班尼特",
|
||||
"雷泽"
|
||||
],
|
||||
"five": ["枫原万叶"],
|
||||
"four": ["罗莎莉亚", "班尼特", "雷泽"],
|
||||
"from": "2021-06-29 18:00:00",
|
||||
"name": "红叶逐荒波",
|
||||
"to": "2021-07-20 14:59:59"
|
||||
"to": "2021-07-20 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"可莉"
|
||||
],
|
||||
"four": [
|
||||
"芭芭拉",
|
||||
"砂糖",
|
||||
"菲谢尔"
|
||||
],
|
||||
"five": ["可莉"],
|
||||
"four": ["芭芭拉", "砂糖", "菲谢尔"],
|
||||
"from": "2021-06-09 06:00:00",
|
||||
"name": "逃跑的太阳",
|
||||
"to": "2021-06-29 17:59:59"
|
||||
"to": "2021-06-29 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"优菈"
|
||||
],
|
||||
"four": [
|
||||
"辛焱",
|
||||
"行秋",
|
||||
"北斗"
|
||||
],
|
||||
"five": ["优菈"],
|
||||
"four": ["辛焱", "行秋", "北斗"],
|
||||
"from": "2021-05-18 18:00:00",
|
||||
"name": "浪沫的旋舞",
|
||||
"to": "2021-06-08 14:59:59"
|
||||
"to": "2021-06-08 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"钟离"
|
||||
],
|
||||
"four": [
|
||||
"烟绯",
|
||||
"诺艾尔",
|
||||
"迪奥娜"
|
||||
],
|
||||
"five": ["钟离"],
|
||||
"four": ["烟绯", "诺艾尔", "迪奥娜"],
|
||||
"from": "2021-04-28 06:00:00",
|
||||
"name": "陵薮市朝",
|
||||
"to": "2021-05-18 17:59:59"
|
||||
"to": "2021-05-18 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"达达利亚"
|
||||
],
|
||||
"four": [
|
||||
"罗莎莉亚",
|
||||
"芭芭拉",
|
||||
"菲谢尔"
|
||||
],
|
||||
"five": ["达达利亚"],
|
||||
"four": ["罗莎莉亚", "芭芭拉", "菲谢尔"],
|
||||
"from": "2021-04-06 18:00:00",
|
||||
"name": "暂别冬都",
|
||||
"to": "2021-04-27 14:59:59"
|
||||
"to": "2021-04-27 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"温迪"
|
||||
],
|
||||
"four": [
|
||||
"砂糖",
|
||||
"雷泽",
|
||||
"诺艾尔"
|
||||
],
|
||||
"five": ["温迪"],
|
||||
"four": ["砂糖", "雷泽", "诺艾尔"],
|
||||
"from": "2021-03-17 06:00:00",
|
||||
"name": "杯装之诗",
|
||||
"to": "2021-04-06 15:59:59"
|
||||
"to": "2021-04-06 15:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"胡桃"
|
||||
],
|
||||
"four": [
|
||||
"行秋",
|
||||
"香菱",
|
||||
"重云"
|
||||
],
|
||||
"five": ["胡桃"],
|
||||
"four": ["行秋", "香菱", "重云"],
|
||||
"from": "2021-03-02 18:00:00",
|
||||
"name": "赤团开时",
|
||||
"to": "2021-03-16 14:59:59"
|
||||
"to": "2021-03-16 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"刻晴"
|
||||
],
|
||||
"four": [
|
||||
"凝光",
|
||||
"班尼特",
|
||||
"芭芭拉"
|
||||
],
|
||||
"five": ["刻晴"],
|
||||
"four": ["凝光", "班尼特", "芭芭拉"],
|
||||
"from": "2021-02-17 18:00:00",
|
||||
"name": "鱼龙灯昼",
|
||||
"to": "2021-03-02 15:59:59"
|
||||
"to": "2021-03-02 15:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"魈"
|
||||
],
|
||||
"four": [
|
||||
"迪奥娜",
|
||||
"北斗",
|
||||
"辛焱"
|
||||
],
|
||||
"five": ["魈"],
|
||||
"four": ["迪奥娜", "北斗", "辛焱"],
|
||||
"from": "2021-02-03 06:00:00",
|
||||
"name": "烟火之邀",
|
||||
"to": "2021-02-17 15:59:59"
|
||||
"to": "2021-02-17 15:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"甘雨"
|
||||
],
|
||||
"four": [
|
||||
"香菱",
|
||||
"行秋",
|
||||
"诺艾尔"
|
||||
],
|
||||
"five": ["甘雨"],
|
||||
"four": ["香菱", "行秋", "诺艾尔"],
|
||||
"from": "2021-01-12 18:00:00",
|
||||
"name": "浮生孰来",
|
||||
"to": "2021-02-02 14:59:59"
|
||||
"to": "2021-02-02 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"阿贝多"
|
||||
],
|
||||
"four": [
|
||||
"菲谢尔",
|
||||
"砂糖",
|
||||
"班尼特"
|
||||
],
|
||||
"five": ["阿贝多"],
|
||||
"four": ["菲谢尔", "砂糖", "班尼特"],
|
||||
"from": "2020-12-23 06:00:00",
|
||||
"name": "深秘之息",
|
||||
"to": "2021-01-12 15:59:59"
|
||||
"to": "2021-01-12 15:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"钟离"
|
||||
],
|
||||
"four": [
|
||||
"辛焱",
|
||||
"雷泽",
|
||||
"重云"
|
||||
],
|
||||
"five": ["钟离"],
|
||||
"four": ["辛焱", "雷泽", "重云"],
|
||||
"from": "2020-12-01 18:00:00",
|
||||
"name": "陵薮市朝",
|
||||
"to": "2020-12-22 14:59:59"
|
||||
"to": "2020-12-22 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"达达利亚"
|
||||
],
|
||||
"four": [
|
||||
"迪奥娜",
|
||||
"北斗",
|
||||
"凝光"
|
||||
],
|
||||
"five": ["达达利亚"],
|
||||
"four": ["迪奥娜", "北斗", "凝光"],
|
||||
"from": "2020-11-11 06:00:00",
|
||||
"name": "暂别冬都",
|
||||
"to": "2020-12-01 15:59:59"
|
||||
"to": "2020-12-01 15:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"可莉"
|
||||
],
|
||||
"four": [
|
||||
"行秋",
|
||||
"诺艾尔",
|
||||
"砂糖"
|
||||
],
|
||||
"five": ["可莉"],
|
||||
"four": ["行秋", "诺艾尔", "砂糖"],
|
||||
"from": "2020-10-20 18:00:00",
|
||||
"name": "闪焰的驻足",
|
||||
"to": "2020-11-10 14:59:59"
|
||||
"to": "2020-11-10 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"温迪"
|
||||
],
|
||||
"four": [
|
||||
"芭芭拉",
|
||||
"菲谢尔",
|
||||
"香菱"
|
||||
],
|
||||
"five": ["温迪"],
|
||||
"four": ["芭芭拉", "菲谢尔", "香菱"],
|
||||
"from": "2020-9-28 06:00:00",
|
||||
"name": "杯装之诗",
|
||||
"to": "2020-10-18 17:59:59"
|
||||
}
|
||||
"to": "2020-10-18 17:59:59",
|
||||
},
|
||||
]
|
@ -1,562 +1,247 @@
|
||||
POOL_302 = [
|
||||
{
|
||||
"five": [
|
||||
"赤沙之杖",
|
||||
"终末嗟叹之诗"
|
||||
],
|
||||
"four": [
|
||||
"匣里龙吟",
|
||||
"玛海菈的水色",
|
||||
"西风长枪",
|
||||
"祭礼残章",
|
||||
"西风猎弓"
|
||||
],
|
||||
"five": ["赤沙之杖", "终末嗟叹之诗"],
|
||||
"four": ["匣里龙吟", "玛海菈的水色", "西风长枪", "祭礼残章", "西风猎弓"],
|
||||
"from": "2022-09-28 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2022-10-14 17:59:59"
|
||||
"to": "2022-10-14 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"阿莫斯之弓",
|
||||
"不灭月华"
|
||||
],
|
||||
"four": [
|
||||
"祭礼剑",
|
||||
"西风大剑",
|
||||
"匣里灭辰",
|
||||
"昭心",
|
||||
"弓藏"
|
||||
],
|
||||
"five": ["阿莫斯之弓", "不灭月华"],
|
||||
"four": ["祭礼剑", "西风大剑", "匣里灭辰", "昭心", "弓藏"],
|
||||
"from": "2022-09-09 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2022-09-27 14:59:59"
|
||||
"to": "2022-09-27 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"猎人之径",
|
||||
"贯虹之槊"
|
||||
],
|
||||
"four": [
|
||||
"西风剑",
|
||||
"钟剑",
|
||||
"西风长枪",
|
||||
"西风秘典",
|
||||
"绝弦"
|
||||
],
|
||||
"five": ["猎人之径", "贯虹之槊"],
|
||||
"four": ["西风剑", "钟剑", "西风长枪", "西风秘典", "绝弦"],
|
||||
"from": "2022-08-24 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2022-09-09 17:59:59"
|
||||
"to": "2022-09-09 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"飞雷之弦振",
|
||||
"斫峰之刃"
|
||||
],
|
||||
"four": [
|
||||
"暗巷的酒与诗",
|
||||
"暗巷猎手",
|
||||
"笛剑",
|
||||
"祭礼大剑",
|
||||
"匣里灭辰"
|
||||
],
|
||||
"five": ["飞雷之弦振", "斫峰之刃"],
|
||||
"four": ["暗巷的酒与诗", "暗巷猎手", "笛剑", "祭礼大剑", "匣里灭辰"],
|
||||
"from": "2022-08-02 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2022-08-23 14:59:59"
|
||||
"to": "2022-08-23 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"苍古自由之誓",
|
||||
"四风原典"
|
||||
],
|
||||
"four": [
|
||||
"千岩古剑",
|
||||
"匣里龙吟",
|
||||
"匣里灭辰",
|
||||
"祭礼残章",
|
||||
"绝弦"
|
||||
],
|
||||
"five": ["苍古自由之誓", "四风原典"],
|
||||
"four": ["千岩古剑", "匣里龙吟", "匣里灭辰", "祭礼残章", "绝弦"],
|
||||
"from": "2022-07-13 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2022-08-02 17:59:59"
|
||||
"to": "2022-08-02 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"赤角石溃杵",
|
||||
"尘世之锁"
|
||||
],
|
||||
"four": [
|
||||
"千岩古剑",
|
||||
"匣里龙吟",
|
||||
"匣里灭辰",
|
||||
"祭礼残章",
|
||||
"绝弦"
|
||||
],
|
||||
"five": ["赤角石溃杵", "尘世之锁"],
|
||||
"four": ["千岩古剑", "匣里龙吟", "匣里灭辰", "祭礼残章", "绝弦"],
|
||||
"from": "2022-06-21 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2022-07-12 14:59:59"
|
||||
"to": "2022-07-12 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"若水",
|
||||
"和璞鸢"
|
||||
],
|
||||
"four": [
|
||||
"千岩长枪",
|
||||
"祭礼剑",
|
||||
"西风大剑",
|
||||
"昭心",
|
||||
"祭礼弓"
|
||||
],
|
||||
"five": ["若水", "和璞鸢"],
|
||||
"four": ["千岩长枪", "祭礼剑", "西风大剑", "昭心", "祭礼弓"],
|
||||
"from": "2022-05-31 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2022-06-21 17:59:59"
|
||||
"to": "2022-06-21 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"雾切之回光",
|
||||
"无工之剑"
|
||||
],
|
||||
"four": [
|
||||
"西风剑",
|
||||
"钟剑",
|
||||
"西风长枪",
|
||||
"西风秘典",
|
||||
"西风猎弓"
|
||||
],
|
||||
"five": ["雾切之回光", "无工之剑"],
|
||||
"four": ["西风剑", "钟剑", "西风长枪", "西风秘典", "西风猎弓"],
|
||||
"from": "2022-04-19 17:59:59",
|
||||
"name": "神铸赋形",
|
||||
"to": "2022-05-31 05:59:59"
|
||||
"to": "2022-05-31 05:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"波乱月白经津",
|
||||
"终末嗟叹之诗"
|
||||
],
|
||||
"four": [
|
||||
"弓藏",
|
||||
"笛剑",
|
||||
"流浪乐章",
|
||||
"匣里灭辰",
|
||||
"祭礼大剑"
|
||||
],
|
||||
"five": ["波乱月白经津", "终末嗟叹之诗"],
|
||||
"four": ["弓藏", "笛剑", "流浪乐章", "匣里灭辰", "祭礼大剑"],
|
||||
"from": "2022-03-30 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2022-04-19 17:59:59"
|
||||
"to": "2022-04-19 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"薙草之稻光",
|
||||
"不灭月华"
|
||||
],
|
||||
"four": [
|
||||
"恶王丸",
|
||||
"曚云之月",
|
||||
"匣里龙吟",
|
||||
"西风长枪",
|
||||
"祭礼残章"
|
||||
],
|
||||
"five": ["薙草之稻光", "不灭月华"],
|
||||
"four": ["恶王丸", "曚云之月", "匣里龙吟", "西风长枪", "祭礼残章"],
|
||||
"from": "2022-03-08 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2022-03-29 14:59:59"
|
||||
"to": "2022-03-29 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"神乐之真意",
|
||||
"磐岩结绿"
|
||||
],
|
||||
"four": [
|
||||
"祭礼剑",
|
||||
"雨裁",
|
||||
"断浪长鳍",
|
||||
"昭心",
|
||||
"绝弦"
|
||||
],
|
||||
"five": ["神乐之真意", "磐岩结绿"],
|
||||
"four": ["祭礼剑", "雨裁", "断浪长鳍", "昭心", "绝弦"],
|
||||
"from": "2022-02-16 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2022-03-08 17:59:59"
|
||||
"to": "2022-03-08 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"贯虹之槊",
|
||||
"阿莫斯之弓"
|
||||
],
|
||||
"four": [
|
||||
"西风剑",
|
||||
"千岩古剑",
|
||||
"匣里灭辰",
|
||||
"西风秘典",
|
||||
"祭礼弓"
|
||||
],
|
||||
"five": ["贯虹之槊", "阿莫斯之弓"],
|
||||
"four": ["西风剑", "千岩古剑", "匣里灭辰", "西风秘典", "祭礼弓"],
|
||||
"from": "2022-01-25 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2022-02-15 14:59:59"
|
||||
"to": "2022-02-15 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"息灾",
|
||||
"和璞鸢"
|
||||
],
|
||||
"four": [
|
||||
"笛剑",
|
||||
"西风大剑",
|
||||
"千岩长枪",
|
||||
"流浪乐章",
|
||||
"西风猎弓"
|
||||
],
|
||||
"five": ["息灾", "和璞鸢"],
|
||||
"four": ["笛剑", "西风大剑", "千岩长枪", "流浪乐章", "西风猎弓"],
|
||||
"from": "2022-01-05 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2022-01-25 17:59:59"
|
||||
"to": "2022-01-25 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"赤角石溃杵",
|
||||
"天空之翼"
|
||||
],
|
||||
"four": [
|
||||
"暗巷闪光",
|
||||
"钟剑",
|
||||
"西风长枪",
|
||||
"祭礼残章",
|
||||
"幽夜华尔兹"
|
||||
],
|
||||
"five": ["赤角石溃杵", "天空之翼"],
|
||||
"four": ["暗巷闪光", "钟剑", "西风长枪", "祭礼残章", "幽夜华尔兹"],
|
||||
"from": "2021-12-14 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2022-01-04 14:59:59"
|
||||
"to": "2022-01-04 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"苍古自由之誓",
|
||||
"松籁响起之时"
|
||||
],
|
||||
"four": [
|
||||
"匣里龙吟",
|
||||
"祭礼大剑",
|
||||
"匣里灭辰",
|
||||
"暗巷的酒与诗",
|
||||
"暗巷猎手"
|
||||
],
|
||||
"five": ["苍古自由之誓", "松籁响起之时"],
|
||||
"four": ["匣里龙吟", "祭礼大剑", "匣里灭辰", "暗巷的酒与诗", "暗巷猎手"],
|
||||
"from": "2021-11-24 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-12-14 17:59:59"
|
||||
"to": "2021-12-14 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"护摩之杖",
|
||||
"终末嗟叹之诗"
|
||||
],
|
||||
"four": [
|
||||
"祭礼剑",
|
||||
"雨裁",
|
||||
"断浪长鳍",
|
||||
"流浪乐章",
|
||||
"曚云之月"
|
||||
],
|
||||
"five": ["护摩之杖", "终末嗟叹之诗"],
|
||||
"four": ["祭礼剑", "雨裁", "断浪长鳍", "流浪乐章", "曚云之月"],
|
||||
"from": "2021-11-02 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-11-23 14:59:59"
|
||||
"to": "2021-11-23 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"冬极白星",
|
||||
"尘世之锁"
|
||||
],
|
||||
"four": [
|
||||
"西风剑",
|
||||
"恶王丸",
|
||||
"西风长枪",
|
||||
"昭心",
|
||||
"弓藏"
|
||||
],
|
||||
"five": ["冬极白星", "尘世之锁"],
|
||||
"four": ["西风剑", "恶王丸", "西风长枪", "昭心", "弓藏"],
|
||||
"from": "2021-10-13 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-11-02 17:59:59"
|
||||
"to": "2021-11-02 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"不灭月华",
|
||||
"磐岩结绿"
|
||||
],
|
||||
"four": [
|
||||
"笛剑",
|
||||
"西风大剑",
|
||||
"匣里灭辰",
|
||||
"西风秘典",
|
||||
"绝弦"
|
||||
],
|
||||
"five": ["不灭月华", "磐岩结绿"],
|
||||
"four": ["笛剑", "西风大剑", "匣里灭辰", "西风秘典", "绝弦"],
|
||||
"from": "2021-09-21 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-10-12 14:59:59"
|
||||
"to": "2021-10-12 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"薙草之稻光",
|
||||
"无工之剑"
|
||||
],
|
||||
"four": [
|
||||
"匣里龙吟",
|
||||
"钟剑",
|
||||
"西风长枪",
|
||||
"流浪乐章",
|
||||
"祭礼弓"
|
||||
],
|
||||
"five": ["薙草之稻光", "无工之剑"],
|
||||
"four": ["匣里龙吟", "钟剑", "西风长枪", "流浪乐章", "祭礼弓"],
|
||||
"from": "2021-09-01 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-09-21 17:59:59"
|
||||
"to": "2021-09-21 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"飞雷之弦振",
|
||||
"天空之刃"
|
||||
],
|
||||
"four": [
|
||||
"祭礼剑",
|
||||
"雨裁",
|
||||
"匣里灭辰",
|
||||
"祭礼残章",
|
||||
"西风猎弓"
|
||||
],
|
||||
"five": ["飞雷之弦振", "天空之刃"],
|
||||
"four": ["祭礼剑", "雨裁", "匣里灭辰", "祭礼残章", "西风猎弓"],
|
||||
"from": "2021-08-10 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-08-31 14:59:59"
|
||||
"to": "2021-08-31 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"雾切之回光",
|
||||
"天空之脊"
|
||||
],
|
||||
"four": [
|
||||
"西风剑",
|
||||
"祭礼大剑",
|
||||
"西风长枪",
|
||||
"西风秘典",
|
||||
"绝弦"
|
||||
],
|
||||
"five": ["雾切之回光", "天空之脊"],
|
||||
"four": ["西风剑", "祭礼大剑", "西风长枪", "西风秘典", "绝弦"],
|
||||
"from": "2021-07-21 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-08-10 17:59:59"
|
||||
"to": "2021-08-10 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"苍古自由之誓",
|
||||
"天空之卷"
|
||||
],
|
||||
"four": [
|
||||
"暗巷闪光",
|
||||
"西风大剑",
|
||||
"匣里灭辰",
|
||||
"暗巷的酒与诗",
|
||||
"暗巷猎手"
|
||||
],
|
||||
"five": ["苍古自由之誓", "天空之卷"],
|
||||
"four": ["暗巷闪光", "西风大剑", "匣里灭辰", "暗巷的酒与诗", "暗巷猎手"],
|
||||
"from": "2021-06-29 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-07-20 14:59:59"
|
||||
"to": "2021-07-20 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"天空之傲",
|
||||
"四风原典"
|
||||
],
|
||||
"four": [
|
||||
"匣里龙吟",
|
||||
"钟剑",
|
||||
"西风长枪",
|
||||
"流浪乐章",
|
||||
"幽夜华尔兹"
|
||||
],
|
||||
"five": ["天空之傲", "四风原典"],
|
||||
"four": ["匣里龙吟", "钟剑", "西风长枪", "流浪乐章", "幽夜华尔兹"],
|
||||
"from": "2021-06-09 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-06-29 17:59:59"
|
||||
"to": "2021-06-29 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"松籁响起之时",
|
||||
"风鹰剑"
|
||||
],
|
||||
"four": [
|
||||
"祭礼剑",
|
||||
"雨裁",
|
||||
"匣里灭辰",
|
||||
"祭礼残章",
|
||||
"弓藏"
|
||||
],
|
||||
"five": ["松籁响起之时", "风鹰剑"],
|
||||
"four": ["祭礼剑", "雨裁", "匣里灭辰", "祭礼残章", "弓藏"],
|
||||
"from": "2021-05-18 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-06-08 14:59:59"
|
||||
"to": "2021-06-08 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"斫峰之刃",
|
||||
"尘世之锁"
|
||||
],
|
||||
"four": [
|
||||
"笛剑",
|
||||
"千岩古剑",
|
||||
"祭礼弓",
|
||||
"昭心",
|
||||
"千岩长枪"
|
||||
],
|
||||
"five": ["斫峰之刃", "尘世之锁"],
|
||||
"four": ["笛剑", "千岩古剑", "祭礼弓", "昭心", "千岩长枪"],
|
||||
"from": "2021-04-28 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-05-18 17:59:59"
|
||||
"to": "2021-05-18 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"天空之翼",
|
||||
"四风原典"
|
||||
],
|
||||
"four": [
|
||||
"西风剑",
|
||||
"祭礼大剑",
|
||||
"暗巷猎手",
|
||||
"西风秘典",
|
||||
"西风长枪"
|
||||
],
|
||||
"five": ["天空之翼", "四风原典"],
|
||||
"four": ["西风剑", "祭礼大剑", "暗巷猎手", "西风秘典", "西风长枪"],
|
||||
"from": "2021-04-06 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-04-27 14:59:59"
|
||||
"to": "2021-04-27 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"终末嗟叹之诗",
|
||||
"天空之刃"
|
||||
],
|
||||
"four": [
|
||||
"暗巷闪光",
|
||||
"西风大剑",
|
||||
"西风猎弓",
|
||||
"暗巷的酒与诗",
|
||||
"匣里灭辰"
|
||||
],
|
||||
"five": ["终末嗟叹之诗", "天空之刃"],
|
||||
"four": ["暗巷闪光", "西风大剑", "西风猎弓", "暗巷的酒与诗", "匣里灭辰"],
|
||||
"from": "2021-03-17 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-04-06 15:59:59"
|
||||
"to": "2021-04-06 15:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"护摩之杖",
|
||||
"狼的末路"
|
||||
],
|
||||
"four": [
|
||||
"匣里龙吟",
|
||||
"千岩古剑",
|
||||
"祭礼弓",
|
||||
"流浪乐章",
|
||||
"千岩长枪"
|
||||
],
|
||||
"five": ["护摩之杖", "狼的末路"],
|
||||
"four": ["匣里龙吟", "千岩古剑", "祭礼弓", "流浪乐章", "千岩长枪"],
|
||||
"from": "2021-02-23 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-03-16 14:59:59"
|
||||
"to": "2021-03-16 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"磐岩结绿",
|
||||
"和璞鸢"
|
||||
],
|
||||
"four": [
|
||||
"笛剑",
|
||||
"祭礼大剑",
|
||||
"弓藏",
|
||||
"昭心",
|
||||
"西风长枪"
|
||||
],
|
||||
"five": ["磐岩结绿", "和璞鸢"],
|
||||
"four": ["笛剑", "祭礼大剑", "弓藏", "昭心", "西风长枪"],
|
||||
"from": "2021-02-03 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-02-23 15:59:59"
|
||||
"to": "2021-02-23 15:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"阿莫斯之弓",
|
||||
"天空之傲"
|
||||
],
|
||||
"four": [
|
||||
"祭礼剑",
|
||||
"钟剑",
|
||||
"匣里灭辰",
|
||||
"昭心",
|
||||
"西风猎弓"
|
||||
],
|
||||
"five": ["阿莫斯之弓", "天空之傲"],
|
||||
"four": ["祭礼剑", "钟剑", "匣里灭辰", "昭心", "西风猎弓"],
|
||||
"from": "2021-01-12 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-02-02 14:59:59"
|
||||
"to": "2021-02-02 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"斫峰之刃",
|
||||
"天空之卷"
|
||||
],
|
||||
"four": [
|
||||
"西风剑",
|
||||
"西风大剑",
|
||||
"西风长枪",
|
||||
"祭礼残章",
|
||||
"绝弦"
|
||||
],
|
||||
"five": ["斫峰之刃", "天空之卷"],
|
||||
"four": ["西风剑", "西风大剑", "西风长枪", "祭礼残章", "绝弦"],
|
||||
"from": "2020-12-23 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2021-01-12 15:59:59"
|
||||
"to": "2021-01-12 15:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"贯虹之槊",
|
||||
"无工之剑"
|
||||
],
|
||||
"four": [
|
||||
"匣里龙吟",
|
||||
"钟剑",
|
||||
"西风秘典",
|
||||
"西风猎弓",
|
||||
"匣里灭辰"
|
||||
],
|
||||
"five": ["贯虹之槊", "无工之剑"],
|
||||
"four": ["匣里龙吟", "钟剑", "西风秘典", "西风猎弓", "匣里灭辰"],
|
||||
"from": "2020-12-01 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2020-12-22 14:59:59"
|
||||
"to": "2020-12-22 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"天空之翼",
|
||||
"尘世之锁"
|
||||
],
|
||||
"four": [
|
||||
"笛剑",
|
||||
"雨裁",
|
||||
"昭心",
|
||||
"弓藏",
|
||||
"西风长枪"
|
||||
],
|
||||
"five": ["天空之翼", "尘世之锁"],
|
||||
"four": ["笛剑", "雨裁", "昭心", "弓藏", "西风长枪"],
|
||||
"from": "2020-11-11 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2020-12-01 15:59:59"
|
||||
"to": "2020-12-01 15:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"四风原典",
|
||||
"狼的末路"
|
||||
],
|
||||
"four": [
|
||||
"祭礼剑",
|
||||
"祭礼大剑",
|
||||
"祭礼残章",
|
||||
"祭礼弓",
|
||||
"匣里灭辰"
|
||||
],
|
||||
"five": ["四风原典", "狼的末路"],
|
||||
"four": ["祭礼剑", "祭礼大剑", "祭礼残章", "祭礼弓", "匣里灭辰"],
|
||||
"from": "2020-10-20 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2020-11-10 14:59:59"
|
||||
"to": "2020-11-10 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": [
|
||||
"风鹰剑",
|
||||
"阿莫斯之弓"
|
||||
],
|
||||
"four": [
|
||||
"祭礼剑",
|
||||
"祭礼大剑",
|
||||
"祭礼残章",
|
||||
"祭礼弓",
|
||||
"匣里灭辰"
|
||||
],
|
||||
"five": ["风鹰剑", "阿莫斯之弓"],
|
||||
"four": ["祭礼剑", "祭礼大剑", "祭礼残章", "祭礼弓", "匣里灭辰"],
|
||||
"from": "2020-09-28 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2020-10-18 17:59:59"
|
||||
}
|
||||
"to": "2020-10-18 17:59:59",
|
||||
},
|
||||
]
|
@ -14,8 +14,12 @@ from utils.log import logger
|
||||
from utils.typedefs import StrOrInt
|
||||
|
||||
__all__ = [
|
||||
'get_avatar_data', 'get_artifact_data', 'get_material_data', 'get_namecard_data', 'get_weapon_data',
|
||||
'update_honey_metadata',
|
||||
"get_avatar_data",
|
||||
"get_artifact_data",
|
||||
"get_material_data",
|
||||
"get_namecard_data",
|
||||
"get_weapon_data",
|
||||
"update_honey_metadata",
|
||||
]
|
||||
|
||||
DATA_TYPE = Dict[StrOrInt, List[str]]
|
||||
@ -41,12 +45,12 @@ async def get_avatar_data() -> DATA_TYPE:
|
||||
result = {}
|
||||
url = "https://genshin.honeyhunterworld.com/fam_chars/?lang=CHS"
|
||||
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
|
||||
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]
|
||||
name = re.findall(r'>(.*)<', data[1])[0]
|
||||
name = re.findall(r">(.*)<", data[1])[0]
|
||||
rarity = int(re.findall(r">(\d)<", data[2])[0])
|
||||
result[cid] = [honey_id, name, rarity]
|
||||
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__]
|
||||
for url in urls:
|
||||
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
|
||||
for data in json_data:
|
||||
name = re.findall(r'>(.*)<', data[1])[0]
|
||||
if name in ['「一心传」名刀', '石英大剑', '琥珀玥', '黑檀弓']: # 跳过特殊的武器
|
||||
name = re.findall(r">(.*)<", data[1])[0]
|
||||
if name in ["「一心传」名刀", "石英大剑", "琥珀玥", "黑檀弓"]: # 跳过特殊的武器
|
||||
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]
|
||||
rarity = int(re.findall(r">(\d)<", data[2])[0])
|
||||
result[wid] = [honey_id, name, rarity]
|
||||
@ -75,27 +79,27 @@ async def get_weapon_data() -> DATA_TYPE:
|
||||
async def get_material_data() -> DATA_TYPE:
|
||||
result = {}
|
||||
|
||||
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']]
|
||||
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"]]
|
||||
namecard = [HONEY_HOST.join("fam_nameplate/?lang=CHS")]
|
||||
urls = weapon + talent + namecard
|
||||
|
||||
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:
|
||||
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
|
||||
for data in json_data:
|
||||
honey_id = re.findall(r'/(.*?)/', data[1])[0]
|
||||
name = re.findall(r'>(.*)<', data[1])[0]
|
||||
honey_id = re.findall(r"/(.*?)/", data[1])[0]
|
||||
name = re.findall(r">(.*)<", data[1])[0]
|
||||
rarity = int(re.findall(r">(\d)<", data[2])[0])
|
||||
mid = None
|
||||
for mid, item in ambr_data.items():
|
||||
if name == item['name']:
|
||||
if name == item["name"]:
|
||||
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]
|
||||
return result
|
||||
|
||||
@ -103,7 +107,7 @@ async def get_material_data() -> DATA_TYPE:
|
||||
async def get_artifact_data() -> DATA_TYPE:
|
||||
async def get_first_id(_link) -> str:
|
||||
_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)
|
||||
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"
|
||||
|
||||
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)
|
||||
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
|
||||
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]
|
||||
link = HONEY_HOST.join(re.findall(r'href="(.*?)"', data[0])[0])
|
||||
first_id = await get_first_id(link)
|
||||
aid = None
|
||||
for aid, item in ambr_data.items():
|
||||
if name == item['name']:
|
||||
if name == item["name"]:
|
||||
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]
|
||||
|
||||
return result
|
||||
@ -138,20 +142,21 @@ async def get_namecard_data() -> DATA_TYPE:
|
||||
# noinspection PyProtectedMember
|
||||
from metadata.genshin import Data
|
||||
from metadata.scripts.metadatas import update_metadata_from_github
|
||||
|
||||
await update_metadata_from_github()
|
||||
# noinspection PyPep8Naming
|
||||
NAMECARD_DATA = Data('namecard')
|
||||
NAMECARD_DATA = Data("namecard")
|
||||
url = HONEY_HOST.join("fam_nameplate/?lang=CHS")
|
||||
result = {}
|
||||
|
||||
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)
|
||||
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]
|
||||
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 的名片
|
||||
continue
|
||||
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:
|
||||
path = PROJECT_ROOT.joinpath('metadata/data/honey.json')
|
||||
path = PROJECT_ROOT.joinpath("metadata/data/honey.json")
|
||||
if not overwrite and path.exists():
|
||||
return
|
||||
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.")
|
||||
|
||||
result = {
|
||||
'avatar': avatar_data,
|
||||
'weapon': weapon_data,
|
||||
'material': material_data,
|
||||
'artifact': artifact_data,
|
||||
'namecard': namecard_data,
|
||||
"avatar": avatar_data,
|
||||
"weapon": weapon_data,
|
||||
"material": material_data,
|
||||
"artifact": artifact_data,
|
||||
"namecard": namecard_data,
|
||||
}
|
||||
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))
|
||||
return result
|
||||
|
@ -4,23 +4,23 @@ from httpx import AsyncClient, URL
|
||||
|
||||
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()
|
||||
|
||||
|
||||
async def update_metadata_from_ambr(overwrite: bool = True):
|
||||
result = []
|
||||
targets = ['material', 'weapon', 'avatar', 'reliquary']
|
||||
targets = ["material", "weapon", "avatar", "reliquary"]
|
||||
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():
|
||||
continue
|
||||
url = AMBR_HOST.join(f"v2/chs/{target}")
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
response = await client.get(url)
|
||||
json_data = json.loads(response.text)['data']['items']
|
||||
async with async_open(path, mode='w', encoding='utf-8') as file:
|
||||
json_data = json.loads(response.text)["data"]["items"]
|
||||
async with async_open(path, mode="w", encoding="utf-8") as file:
|
||||
data = json.dumps(json_data, ensure_ascii=False)
|
||||
await file.write(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):
|
||||
path = PROJECT_ROOT.joinpath('metadata/data/namecard.json')
|
||||
path = PROJECT_ROOT.joinpath("metadata/data/namecard.json")
|
||||
if not overwrite and path.exists():
|
||||
return
|
||||
|
||||
@ -41,16 +41,17 @@ async def update_metadata_from_github(overwrite: bool = True):
|
||||
material_json_data = json.loads((await client.get(material_url)).text)
|
||||
|
||||
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'])]
|
||||
icon = namecard_data['icon']
|
||||
navbar = namecard_data['picPath'][0]
|
||||
banner = namecard_data['picPath'][1]
|
||||
rank = namecard_data['rankLevel']
|
||||
description = text_map_json_data[str(namecard_data['descTextMapHash'])].replace('\\n', '\n')
|
||||
data.update({
|
||||
str(namecard_data['id']): {
|
||||
"id": namecard_data['id'],
|
||||
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"])]
|
||||
icon = namecard_data["icon"]
|
||||
navbar = namecard_data["picPath"][0]
|
||||
banner = namecard_data["picPath"][1]
|
||||
rank = namecard_data["rankLevel"]
|
||||
description = text_map_json_data[str(namecard_data["descTextMapHash"])].replace("\\n", "\n")
|
||||
data.update(
|
||||
{
|
||||
str(namecard_data["id"]): {
|
||||
"id": namecard_data["id"],
|
||||
"name": name,
|
||||
"rank": rank,
|
||||
"icon": icon,
|
||||
@ -58,8 +59,9 @@ async def update_metadata_from_github(overwrite: bool = True):
|
||||
"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)
|
||||
await file.write(data)
|
||||
return data
|
||||
|
@ -4,103 +4,274 @@ import functools
|
||||
|
||||
from metadata.genshin import WEAPON_DATA
|
||||
|
||||
__all__ = [
|
||||
'roles', 'weapons',
|
||||
'roleToId', 'roleToName', 'weaponToName', 'weaponToId'
|
||||
]
|
||||
__all__ = ["roles", "weapons", "roleToId", "roleToName", "weaponToName", "weaponToId"]
|
||||
|
||||
# noinspection SpellCheckingInspection
|
||||
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', '神里', '绫华', '神里凌华', '凌华', '白鹭公主',
|
||||
'神里大小 姐'],
|
||||
10000003: ['琴', 'Jean', 'jean', '团长', '代理团长', '琴团长', '蒲公英骑士'],
|
||||
10000005: ['空', 'Aether', 'aether', '男主', '男主角', '龙哥', '空哥'],
|
||||
10000006: ['丽莎', 'Lisa', 'lisa', '图书管理员', '图书馆管理员', '蔷薇魔女'],
|
||||
10000007: ['荧', 'Lumine', 'lumine', '女主', '女主角', '莹', '萤', '黄毛阿姨', '荧妹'],
|
||||
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', '杏仁豆腐', '打桩机', '插秧', '三眼五显仙人', '三眼五显真人', '降魔大圣',
|
||||
'护法夜叉', '快乐风男', '无聊', '靖妖傩舞', '矮子仙人', '三点五尺仙人', '跳跳虎'],
|
||||
10000027: ['凝光', 'Ningguang', 'ningguang', '富婆', '天权星'],
|
||||
10000029: ['可莉', 'Klee', 'klee', '嘟嘟可', '火花骑士', '蹦蹦炸弹', '炸鱼', '放火烧山', '放火烧山真君',
|
||||
'蒙德最强战力', '逃跑的太阳', '啦啦啦', '哒哒哒', '炸弹人', '禁闭室'],
|
||||
10000030: ['钟离', 'Zhongli', 'zhongli', '摩拉克斯', '岩王爷', '岩神', '钟师傅', '天动万象', '岩王帝君', '未来可期',
|
||||
'帝君', '拒收病婿'],
|
||||
10000031: ['菲谢尔', 'Fischl', 'fischl', '皇女', '小艾米', '小艾咪', '奥兹', '断罪皇女', '中二病', '中二少女',
|
||||
'中二皇女', '奥兹发射器'],
|
||||
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', '刻情', '氪晴', '刻师傅', '刻师父', '牛杂', '牛杂师傅', '斩尽牛杂', '免疫',
|
||||
'免疫免疫', '屁斜剑法', '玉衡星', '阿晴', ' 啊晴'],
|
||||
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', '拉一拉'],
|
||||
10000020: ["雷泽", "Razor", "razor", "狼少年", "狼崽子", "狼崽", "卢皮卡", "小狼", "小狼狗"],
|
||||
10000021: ["安柏", "Amber", "amber", "安伯", "兔兔伯爵", "飞行冠军", "侦查骑士", "点火姬", "点火机", "打火机", "打火姬"],
|
||||
10000022: [
|
||||
"温迪",
|
||||
"Venti",
|
||||
"venti",
|
||||
"温蒂",
|
||||
"风神",
|
||||
"卖唱的",
|
||||
"巴巴托斯",
|
||||
"巴巴脱丝",
|
||||
"芭芭托斯",
|
||||
"芭芭脱丝",
|
||||
"干点正事",
|
||||
"不干正事",
|
||||
"吟游诗人",
|
||||
"诶嘿",
|
||||
"唉嘿",
|
||||
"摸鱼",
|
||||
],
|
||||
10000023: ["香菱", "Xiangling", "xiangling", "香玲", "锅巴", "厨师", "万民堂厨师", "香师傅"],
|
||||
10000024: ["北斗", "Beidou", "beidou", "大姐头", "大姐", "无冕的龙王", "龙王"],
|
||||
10000025: ["行秋", "Xingqiu", "xingqiu", "秋秋人", "秋妹妹", "书呆子", "水神", "飞云商会二少爷"],
|
||||
10000026: [
|
||||
"魈",
|
||||
"Xiao",
|
||||
"xiao",
|
||||
"杏仁豆腐",
|
||||
"打桩机",
|
||||
"插秧",
|
||||
"三眼五显仙人",
|
||||
"三眼五显真人",
|
||||
"降魔大圣",
|
||||
"护法夜叉",
|
||||
"快乐风男",
|
||||
"无聊",
|
||||
"靖妖傩舞",
|
||||
"矮子仙人",
|
||||
"三点五尺仙人",
|
||||
"跳跳虎",
|
||||
],
|
||||
10000027: ["凝光", "Ningguang", "ningguang", "富婆", "天权星"],
|
||||
10000029: [
|
||||
"可莉",
|
||||
"Klee",
|
||||
"klee",
|
||||
"嘟嘟可",
|
||||
"火花骑士",
|
||||
"蹦蹦炸弹",
|
||||
"炸鱼",
|
||||
"放火烧山",
|
||||
"放火烧山真君",
|
||||
"蒙德最强战力",
|
||||
"逃跑的太阳",
|
||||
"啦啦啦",
|
||||
"哒哒哒",
|
||||
"炸弹人",
|
||||
"禁闭室",
|
||||
],
|
||||
10000030: ["钟离", "Zhongli", "zhongli", "摩拉克斯", "岩王爷", "岩神", "钟师傅", "天动万象", "岩王帝君", "未来可期", "帝君", "拒收病婿"],
|
||||
10000031: ["菲谢尔", "Fischl", "fischl", "皇女", "小艾米", "小艾咪", "奥兹", "断罪皇女", "中二病", "中二少女", "中二皇女", "奥兹发射器"],
|
||||
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",
|
||||
"刻情",
|
||||
"氪晴",
|
||||
"刻师傅",
|
||||
"刻师父",
|
||||
"牛杂",
|
||||
"牛杂师傅",
|
||||
"斩尽牛杂",
|
||||
"免疫",
|
||||
"免疫免疫",
|
||||
"屁斜剑法",
|
||||
"玉衡星",
|
||||
"阿晴",
|
||||
" 啊晴",
|
||||
],
|
||||
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]
|
||||
weapons = {
|
||||
@ -110,37 +281,29 @@ weapons = {
|
||||
"贯虹之槊": ["贯虹", "岩枪", "盾枪"],
|
||||
"赤角石溃杵": ["赤角", "石溃杵"],
|
||||
"尘世之锁": ["尘世锁", "尘世", "盾书", "锁"],
|
||||
|
||||
"终末嗟叹之诗": ["终末", "终末弓", "叹气弓", "乐团弓"],
|
||||
"松籁响起之时": ["松籁", "乐团大剑", "松剑"],
|
||||
"苍古自由之誓": ["苍古", "乐团剑"],
|
||||
|
||||
"「渔获」": ["鱼叉", "渔叉"],
|
||||
"衔珠海皇": ["海皇", "咸鱼剑", "咸鱼大剑"],
|
||||
|
||||
"匣里日月": ["日月"],
|
||||
"匣里灭辰": ["灭辰"],
|
||||
"匣里龙吟": ["龙吟"],
|
||||
|
||||
"天空之翼": ["天空弓"],
|
||||
"天空之刃": ["天空剑"],
|
||||
"天空之卷": ["天空书", "厕纸"],
|
||||
"天空之脊": ["天空枪", "薄荷枪"],
|
||||
"天空之傲": ["天空大剑"],
|
||||
"四风原典": ["四风"],
|
||||
|
||||
"试作斩岩": ["斩岩"],
|
||||
"试作星镰": ["星镰"],
|
||||
"试作金珀": ["金珀"],
|
||||
"试作古华": ["古华"],
|
||||
"试作澹月": ["澹月"],
|
||||
|
||||
"千岩长枪": ["千岩枪"],
|
||||
"千岩古剑": ["千岩剑", "千岩大剑"],
|
||||
|
||||
"暗巷闪光": ["暗巷剑"],
|
||||
"暗巷猎手": ["暗巷弓"],
|
||||
|
||||
"阿莫斯之弓": ["阿莫斯", "ams", "痛苦弓"],
|
||||
"雾切之回光": ["雾切"],
|
||||
"飞雷之弦振": ["飞雷", "飞雷弓"],
|
||||
@ -154,7 +317,6 @@ weapons = {
|
||||
"不灭月华": ["月华"],
|
||||
"波乱月白经津": ["波乱", "月白", "波乱月白", "经津", "波波津"],
|
||||
"若水": ["麒麟弓", "Aqua", "aqua"],
|
||||
|
||||
"昭心": ["糟心"],
|
||||
"幽夜华尔兹": ["幽夜", "幽夜弓", "华尔兹", "皇女弓"],
|
||||
"雪葬的星银": ["雪葬", "星银", "雪葬星银", "雪山大剑"],
|
||||
@ -172,15 +334,13 @@ weapons = {
|
||||
"嘟嘟可故事集": ["嘟嘟可"],
|
||||
"辰砂之纺锤": ["辰砂", "辰砂纺锤", "纺锤"],
|
||||
"白辰之环": ["白辰", "白辰环"],
|
||||
|
||||
"决斗之枪": ["决斗枪", "决斗", "月卡枪"],
|
||||
"螭骨剑": ["螭骨", "丈育剑", "离骨剑", "月卡大剑"],
|
||||
"黑剑": ["月卡剑"],
|
||||
"苍翠猎弓": ["绿弓", "月卡弓"],
|
||||
|
||||
"讨龙英杰谭": ["讨龙"],
|
||||
"神射手之誓": ["脚气弓", "神射手"],
|
||||
"黑缨枪": ["史莱姆枪"]
|
||||
"黑缨枪": ["史莱姆枪"],
|
||||
}
|
||||
|
||||
|
||||
@ -209,4 +369,4 @@ def weaponToName(shortname: str) -> str:
|
||||
@functools.lru_cache()
|
||||
def weaponToId(name: str) -> int | None:
|
||||
"""获取武器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)
|
||||
|
@ -9,20 +9,54 @@ def get_format_sub_item(artifact_attr: dict):
|
||||
|
||||
|
||||
def get_comment(get_rate_num):
|
||||
data = {"1": ["破玩意谁能用啊,谁都用不了吧", "喂了吧,这东西做狗粮还能有点用", "抽卡有保底,圣遗物没有下限",
|
||||
"未来可期呢(笑)", "你出门一定很安全", "你是不是得罪米哈游了?", "……宁就是班尼特本特?",
|
||||
"丢人!你给我退出提瓦特(", "不能说很糟糕,只能说特别不好"],
|
||||
"2": ["淡如清泉镇的圣水,莫得提升", "你怎么不强化啊?", "嗯嗯嗯好好好可以可以可以挺好挺好(敷衍)",
|
||||
"这就是日常,下一个", "洗洗还能吃(bushi)", "下次一定行……?", "派蒙平静地点了个赞",
|
||||
"不知道该说什么,就当留个纪念吧"],
|
||||
"3": ["不能说有质变,只能说有提升", "过渡用的话没啥问题,大概", "再努努力吧", "嗯,差不多能用",
|
||||
"这很合理", "达成成就“合格圣遗物”", "嗯,及格了,过渡用挺好的", "中规中矩,有待提升"],
|
||||
"4": ["以普遍理性而论,很好", "算是个很不戳的圣遗物了!", "很好,很有精神!", "再努努力,超越一下自己",
|
||||
"感觉可以戴着它大杀四方了", "这就是大佬背包里的平均水平吧", "先锁上呗,这波不亏", "达成成就“高分圣遗物”",
|
||||
"这波对输出有很大提升啊(认真)", "我也想拥有这种分数的圣遗物(切实)"],
|
||||
"5": ["多吃点好的,出门注意安全", "晒吧,欧不可耻,只是可恨", "没啥好说的,让我自闭一会", "达成成就“高分圣遗物”",
|
||||
"怕不是以后开宝箱只能开出卷心菜", "吃了吗?没吃的话,吃我一拳", "我觉得这个游戏有问题", "这合理吗",
|
||||
"这东西没啥用,给我吧(柠檬)", "??? ????"]}
|
||||
data = {
|
||||
"1": [
|
||||
"破玩意谁能用啊,谁都用不了吧",
|
||||
"喂了吧,这东西做狗粮还能有点用",
|
||||
"抽卡有保底,圣遗物没有下限",
|
||||
"未来可期呢(笑)",
|
||||
"你出门一定很安全",
|
||||
"你是不是得罪米哈游了?",
|
||||
"……宁就是班尼特本特?",
|
||||
"丢人!你给我退出提瓦特(",
|
||||
"不能说很糟糕,只能说特别不好",
|
||||
],
|
||||
"2": [
|
||||
"淡如清泉镇的圣水,莫得提升",
|
||||
"你怎么不强化啊?",
|
||||
"嗯嗯嗯好好好可以可以可以挺好挺好(敷衍)",
|
||||
"这就是日常,下一个",
|
||||
"洗洗还能吃(bushi)",
|
||||
"下次一定行……?",
|
||||
"派蒙平静地点了个赞",
|
||||
"不知道该说什么,就当留个纪念吧",
|
||||
],
|
||||
"3": ["不能说有质变,只能说有提升", "过渡用的话没啥问题,大概", "再努努力吧", "嗯,差不多能用", "这很合理", "达成成就“合格圣遗物”", "嗯,及格了,过渡用挺好的", "中规中矩,有待提升"],
|
||||
"4": [
|
||||
"以普遍理性而论,很好",
|
||||
"算是个很不戳的圣遗物了!",
|
||||
"很好,很有精神!",
|
||||
"再努努力,超越一下自己",
|
||||
"感觉可以戴着它大杀四方了",
|
||||
"这就是大佬背包里的平均水平吧",
|
||||
"先锁上呗,这波不亏",
|
||||
"达成成就“高分圣遗物”",
|
||||
"这波对输出有很大提升啊(认真)",
|
||||
"我也想拥有这种分数的圣遗物(切实)",
|
||||
],
|
||||
"5": [
|
||||
"多吃点好的,出门注意安全",
|
||||
"晒吧,欧不可耻,只是可恨",
|
||||
"没啥好说的,让我自闭一会",
|
||||
"达成成就“高分圣遗物”",
|
||||
"怕不是以后开宝箱只能开出卷心菜",
|
||||
"吃了吗?没吃的话,吃我一拳",
|
||||
"我觉得这个游戏有问题",
|
||||
"这合理吗",
|
||||
"这东西没啥用,给我吧(柠檬)",
|
||||
"??? ????",
|
||||
],
|
||||
}
|
||||
try:
|
||||
data_ = int(float(get_rate_num))
|
||||
except ValueError:
|
||||
@ -36,22 +70,22 @@ class ArtifactOcrRate:
|
||||
OCR_URL = "https://api.genshin.pub/api/v1/app/ocr"
|
||||
RATE_URL = "https://api.genshin.pub/api/v1/relic/rate"
|
||||
HEADERS = {
|
||||
'authority': 'api.genshin.pub',
|
||||
'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',
|
||||
'content-type': 'application/json;charset=UTF-8',
|
||||
'dnt': '1',
|
||||
'origin': 'https://genshin.pub',
|
||||
'referer': 'https://genshin.pub/',
|
||||
'sec-ch-ua': '"Chromium";v="104", " Not A;Brand";v="99"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-site',
|
||||
'sec-gpc': '1',
|
||||
'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',
|
||||
"authority": "api.genshin.pub",
|
||||
"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",
|
||||
"content-type": "application/json;charset=UTF-8",
|
||||
"dnt": "1",
|
||||
"origin": "https://genshin.pub",
|
||||
"referer": "https://genshin.pub/",
|
||||
"sec-ch-ua": '"Chromium";v="104", " Not A;Brand";v="99"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"sec-gpc": "1",
|
||||
"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",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
|
@ -43,8 +43,14 @@ class PostInfo(BaseModel):
|
||||
created_at = post["created_at"]
|
||||
user = _data_post["user"] # 用户数据
|
||||
user_uid = user["uid"] # 用户ID
|
||||
return PostInfo(_data=data, post_id=post_id, user_uid=user_uid, subject=subject, image_urls=image_urls,
|
||||
created_at=created_at)
|
||||
return PostInfo(
|
||||
_data=data,
|
||||
post_id=post_id,
|
||||
user_uid=user_uid,
|
||||
subject=subject,
|
||||
image_urls=image_urls,
|
||||
created_at=created_at,
|
||||
)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self._data[item]
|
||||
|
@ -16,7 +16,7 @@ RECOGNIZE_SERVER = {
|
||||
|
||||
|
||||
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:
|
||||
@ -26,7 +26,7 @@ def md5(text: str) -> 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:
|
||||
|
@ -20,8 +20,10 @@ class Hyperion:
|
||||
POST_FULL_URL = "https://bbs-api.mihoyo.com/post/wapi/getPostFull"
|
||||
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"
|
||||
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/90.0.4430.72 Safari/537.36"
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.client = HOYORequest(headers=self.get_headers())
|
||||
@ -42,34 +44,31 @@ class Hyperion:
|
||||
if entries is None:
|
||||
return -1
|
||||
try:
|
||||
art_id = int(entries.get('article_id'))
|
||||
art_id = int(entries.get("article_id"))
|
||||
except (IndexError, ValueError, TypeError):
|
||||
return -1
|
||||
return art_id
|
||||
|
||||
def get_headers(self, referer: str = "https://bbs.mihoyo.com/"):
|
||||
return {
|
||||
"User-Agent": self.USER_AGENT,
|
||||
"Referer": referer
|
||||
}
|
||||
return {"User-Agent": self.USER_AGENT, "Referer": referer}
|
||||
|
||||
@staticmethod
|
||||
def get_list_url_params(forum_id: int, is_good: bool = False, is_hot: bool = False,
|
||||
page_size: int = 20) -> dict:
|
||||
def get_list_url_params(forum_id: int, is_good: bool = False, is_hot: bool = False, page_size: int = 20) -> dict:
|
||||
params = {
|
||||
"forum_id": forum_id,
|
||||
"gids": 2,
|
||||
"is_good": is_good,
|
||||
"is_hot": is_hot,
|
||||
"page_size": page_size,
|
||||
"sort_type": 1
|
||||
"sort_type": 1,
|
||||
}
|
||||
|
||||
return params
|
||||
|
||||
@staticmethod
|
||||
def get_images_params(resize: int = 600, quality: int = 80, auto_orient: int = 0, interlace: int = 1,
|
||||
images_format: str = "jpg"):
|
||||
def get_images_params(
|
||||
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
|
||||
:param resize: 图片大小
|
||||
@ -79,25 +78,19 @@ class Hyperion:
|
||||
:param images_format: 图片格式
|
||||
:return:
|
||||
"""
|
||||
params = f"image/resize,s_{resize}/quality,q_{quality}/auto-orient," \
|
||||
params = (
|
||||
f"image/resize,s_{resize}/quality,q_{quality}/auto-orient,"
|
||||
f"{auto_orient}/interlace,{interlace}/format,{images_format}"
|
||||
)
|
||||
return {"x-oss-process": params}
|
||||
|
||||
async def get_post_full_in_collection(self, collection_id: int, gids: int = 2, order_type=1) -> JSONDict:
|
||||
params = {
|
||||
"collection_id": collection_id,
|
||||
"gids": gids,
|
||||
"order_type": order_type
|
||||
}
|
||||
params = {"collection_id": collection_id, "gids": gids, "order_type": order_type}
|
||||
response = await self.client.get(url=self.POST_FULL_IN_COLLECTION_URL, params=params)
|
||||
return response
|
||||
|
||||
async def get_post_info(self, gids: int, post_id: int, read: int = 1) -> PostInfo:
|
||||
params = {
|
||||
"gids": gids,
|
||||
"post_id": post_id,
|
||||
"read": read
|
||||
}
|
||||
params = {"gids": gids, "post_id": post_id, "read": read}
|
||||
response = await self.client.get(self.POST_FULL_URL, params=params)
|
||||
return PostInfo.paste_data(response)
|
||||
|
||||
@ -128,11 +121,7 @@ class Hyperion:
|
||||
?gids=2&page_size=20&type=3
|
||||
:return:
|
||||
"""
|
||||
params = {
|
||||
"gids": gids,
|
||||
"page_size": page_size,
|
||||
"type": type_id
|
||||
}
|
||||
params = {"gids": gids, "page_size": page_size, "type": type_id}
|
||||
response = await self.client.get(url=self.GET_NEW_LIST_URL, params=params)
|
||||
return response
|
||||
|
||||
@ -144,12 +133,14 @@ class GachaInfo:
|
||||
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"
|
||||
|
||||
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/90.0.4430.72 Safari/537.36"
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.headers = {
|
||||
'User-Agent': self.USER_AGENT,
|
||||
"User-Agent": self.USER_AGENT,
|
||||
}
|
||||
self.client = HOYORequest(headers=self.headers)
|
||||
self.cache = {}
|
||||
@ -177,32 +168,35 @@ class GachaInfo:
|
||||
|
||||
class SignIn:
|
||||
LOGIN_URL = "https://webapi.account.mihoyo.com/Api/login_by_mobilecaptcha"
|
||||
S_TOKEN_URL = "https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?" \
|
||||
"login_ticket={0}&token_types=3&uid={1}"
|
||||
S_TOKEN_URL = (
|
||||
"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"
|
||||
USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " \
|
||||
USER_AGENT = (
|
||||
"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 = {
|
||||
"Host": "webapi.account.mihoyo.com",
|
||||
"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",
|
||||
"x-rpc-device_model": "OS X 10.15.7",
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"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, */*",
|
||||
"x-rpc-device_name": "Microsoft Edge 103.0.1264.62",
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"x-rpc-client_type": "4",
|
||||
"sec-ch-ua-platform": "\"macOS\"",
|
||||
"sec-ch-ua-platform": '"macOS"',
|
||||
"Origin": "https://user.mihoyo.com",
|
||||
"Sec-Fetch-Site": "same-site",
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
"Sec-Fetch-Dest": "empty",
|
||||
"Referer": "https://user.mihoyo.com/",
|
||||
"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 = {
|
||||
"Host": "api-takumi.mihoyo.com",
|
||||
@ -213,7 +207,7 @@ class SignIn:
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"User-Agent": USER_AGENT,
|
||||
"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):
|
||||
@ -251,7 +245,7 @@ class SignIn:
|
||||
data = await self.client.post(
|
||||
self.LOGIN_URL,
|
||||
data={"mobile": str(self.phone), "mobile_captcha": str(captcha), "source": "user.mihoyo.com"},
|
||||
headers=self.HEADERS
|
||||
headers=self.HEADERS,
|
||||
)
|
||||
res_json = data.json()
|
||||
if self.check_error(res_json):
|
||||
@ -265,8 +259,7 @@ class SignIn:
|
||||
|
||||
async def get_s_token(self):
|
||||
data = await self.client.get(
|
||||
self.S_TOKEN_URL.format(self.cookie["login_ticket"], self.uid),
|
||||
headers={"User-Agent": self.USER_AGENT}
|
||||
self.S_TOKEN_URL.format(self.cookie["login_ticket"], self.uid), headers={"User-Agent": self.USER_AGENT}
|
||||
)
|
||||
res_json = data.json()
|
||||
res_data = res_json.get("data", {}).get("list", [])
|
||||
@ -283,8 +276,8 @@ class SignIn:
|
||||
"mobile": str(self.phone),
|
||||
"captcha": str(captcha),
|
||||
"action_type": "login",
|
||||
"token_type": 6
|
||||
}
|
||||
"token_type": 6,
|
||||
},
|
||||
)
|
||||
res_json = data.json()
|
||||
if self.check_error(res_json):
|
||||
|
@ -8,9 +8,9 @@ from modules.apihelper.typedefs import POST_DATA, JSON_DATA
|
||||
|
||||
|
||||
class HOYORequest(HTTPXRequest):
|
||||
|
||||
async def get(self, url: str, *args, de_json: bool = True, re_json_data: bool = False, **kwargs) \
|
||||
-> Union[POST_DATA, JSON_DATA, bytes]:
|
||||
async def get(
|
||||
self, url: str, *args, de_json: bool = True, re_json_data: bool = False, **kwargs
|
||||
) -> Union[POST_DATA, JSON_DATA, bytes]:
|
||||
try:
|
||||
response = await self._client.get(url=url, *args, **kwargs)
|
||||
except httpx.TimeoutException as err:
|
||||
|
@ -6,7 +6,6 @@ import httpx
|
||||
|
||||
|
||||
class HTTPXRequest(AbstractAsyncContextManager):
|
||||
|
||||
def __init__(self, *args, headers=None, **kwargs):
|
||||
self._client = httpx.AsyncClient(headers=headers, *args, **kwargs)
|
||||
|
||||
@ -18,8 +17,9 @@ class HTTPXRequest(AbstractAsyncContextManager):
|
||||
await self.shutdown()
|
||||
raise exc
|
||||
|
||||
async def __aexit__(self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType]) -> None:
|
||||
async def __aexit__(
|
||||
self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
|
||||
) -> None:
|
||||
|
||||
await self.initialize()
|
||||
|
||||
|
@ -15,21 +15,25 @@ with open(_fix_skills_level_file, "r", encoding="utf-8") as f:
|
||||
|
||||
|
||||
class ArtifactStatsTheory:
|
||||
|
||||
def __init__(self, character_name: str):
|
||||
self.character_name = 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]
|
||||
if not self.main_prop:
|
||||
self.main_prop = [FightProp.FIGHT_PROP_CRITICAL, FightProp.FIGHT_PROP_CRITICAL_HURT,
|
||||
FightProp.FIGHT_PROP_ATTACK_PERCENT]
|
||||
self.main_prop = [
|
||||
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:
|
||||
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:
|
||||
self.main_prop.append(FightProp.FIGHT_PROP_HP)
|
||||
if FightProp.FIGHT_PROP_DEFENSE_PERCENT in self.main_prop and \
|
||||
FightProp.FIGHT_PROP_DEFENSE not in self.main_prop:
|
||||
if (
|
||||
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)
|
||||
|
||||
def theory(self, sub_stats: EquipmentsStats) -> float:
|
||||
|
@ -16,7 +16,7 @@ from pydantic import (
|
||||
)
|
||||
from typing_extensions import Self
|
||||
|
||||
__all__ = ['Model', 'WikiModel', 'HONEY_HOST']
|
||||
__all__ = ["Model", "WikiModel", "HONEY_HOST"]
|
||||
|
||||
HONEY_HOST = URL("https://genshin.honeyhunterworld.com/")
|
||||
|
||||
@ -107,7 +107,7 @@ class WikiModel(Model):
|
||||
返回对应的 WikiModel
|
||||
"""
|
||||
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
|
||||
async def get_by_id(cls, id_: str) -> Self:
|
||||
@ -152,7 +152,7 @@ class WikiModel(Model):
|
||||
返回能爬到的所有的 WikiModel 所组成的 List
|
||||
"""
|
||||
queue: Queue[Self] = Queue() # 存放 Model 的队列
|
||||
signal = Value('i', 0) # 一个用于异步任务同步的信号
|
||||
signal = Value("i", 0) # 一个用于异步任务同步的信号
|
||||
|
||||
async def task(u):
|
||||
# 包装的爬虫任务
|
||||
@ -196,18 +196,18 @@ class WikiModel(Model):
|
||||
"""
|
||||
urls = cls.scrape_urls()
|
||||
queue: Queue[Union[str, Tuple[str, URL]]] = Queue() # 存放 Model 的队列
|
||||
signal = Value('i', len(urls)) # 一个用于异步任务同步的信号,初始值为存放所需要爬取的页面数
|
||||
signal = Value("i", len(urls)) # 一个用于异步任务同步的信号,初始值为存放所需要爬取的页面数
|
||||
|
||||
async def task(page: URL):
|
||||
"""包装的爬虫任务"""
|
||||
response = await cls._client_get(page)
|
||||
# 从页面中获取对应的 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
|
||||
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
|
||||
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))
|
||||
else:
|
||||
await queue.put(data_name)
|
||||
|
@ -15,6 +15,7 @@ class Birth(Model):
|
||||
day: 天
|
||||
month: 月
|
||||
"""
|
||||
|
||||
day: int
|
||||
month: int
|
||||
|
||||
@ -26,6 +27,7 @@ class CharacterAscension(Model):
|
||||
level: 等级突破材料
|
||||
skill: 技能/天赋培养材料
|
||||
"""
|
||||
|
||||
level: List[str] = []
|
||||
skill: List[str] = []
|
||||
|
||||
@ -42,6 +44,7 @@ class CharacterState(Model):
|
||||
CD: 暴击伤害
|
||||
bonus: 突破属性
|
||||
"""
|
||||
|
||||
level: str
|
||||
HP: int
|
||||
ATK: float
|
||||
@ -97,23 +100,23 @@ class Character(WikiModel):
|
||||
return [HONEY_HOST.join("fam_chars/?lang=CHS")]
|
||||
|
||||
@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]
|
||||
tables = soup.find_all('table')
|
||||
table_rows = tables[0].find_all('tr')
|
||||
soup = soup.select(".wp-block-post-content")[0]
|
||||
tables = soup.find_all("table")
|
||||
table_rows = tables[0].find_all("tr")
|
||||
|
||||
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)
|
||||
if name != '旅行者': # 如果角色名不是 旅行者
|
||||
if name != "旅行者": # 如果角色名不是 旅行者
|
||||
title = get_table_text(1)
|
||||
occupation = get_table_text(2)
|
||||
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)]
|
||||
element = Element[get_table_text(6)]
|
||||
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)
|
||||
kr_cv = get_table_text(14)
|
||||
else:
|
||||
name = '空' if id_.endswith('5') else '荧'
|
||||
name = "空" if id_.endswith("5") else "荧"
|
||||
title = get_table_text(0)
|
||||
occupation = get_table_text(1)
|
||||
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)]
|
||||
element = Element[get_table_text(5)]
|
||||
birth = None
|
||||
@ -139,30 +142,50 @@ class Character(WikiModel):
|
||||
description = get_table_text(-3)
|
||||
ascension = CharacterAscension(
|
||||
level=[
|
||||
target[0] for i in table_rows[-2].find_all('a')
|
||||
if (target := re.findall(r'/(.*)/', i.attrs['href'])) # 过滤掉错误的材料(honey网页的bug)
|
||||
target[0]
|
||||
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 = []
|
||||
for row in tables[2].find_all('tr')[1:]:
|
||||
cells = row.find_all('td')
|
||||
for row in tables[2].find_all("tr")[1:]:
|
||||
cells = row.find_all("td")
|
||||
stats.append(
|
||||
CharacterState(
|
||||
level=cells[0].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
|
||||
level=cells[0].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(
|
||||
id=id_, name=name, 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
|
||||
id=id_,
|
||||
name=name,
|
||||
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
|
||||
async def get_url_by_name(cls, name: str) -> Optional[URL]:
|
||||
# 重写此函数的目的是处理主角名字的 ID
|
||||
_map = {'荧': "playergirl_007", '空': "playerboy_005"}
|
||||
_map = {"荧": "playergirl_007", "空": "playerboy_005"}
|
||||
if (id_ := _map.get(name)) is not None:
|
||||
return await cls.get_url_by_id(id_)
|
||||
return await super(Character, cls).get_url_by_name(name)
|
||||
@ -170,8 +193,8 @@ class Character(WikiModel):
|
||||
@property
|
||||
def icon(self) -> CharacterIcon:
|
||||
return CharacterIcon(
|
||||
icon=str(HONEY_HOST.join(f'/img/{self.id}_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')),
|
||||
splash=str(HONEY_HOST.join(f'/img/{self.id}_gacha_splash.webp'))
|
||||
icon=str(HONEY_HOST.join(f"/img/{self.id}_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")),
|
||||
splash=str(HONEY_HOST.join(f"/img/{self.id}_gacha_splash.webp")),
|
||||
)
|
||||
|
@ -6,9 +6,9 @@ from httpx import URL
|
||||
|
||||
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):
|
||||
@ -28,8 +28,8 @@ class Material(WikiModel):
|
||||
|
||||
@staticmethod
|
||||
def scrape_urls() -> List[URL]:
|
||||
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']]
|
||||
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"]]
|
||||
return weapon + talent
|
||||
|
||||
@classmethod
|
||||
@ -39,37 +39,37 @@ class Material(WikiModel):
|
||||
@classmethod
|
||||
async def _parse_soup(cls, soup: BeautifulSoup) -> "Material":
|
||||
"""解析突破素材页"""
|
||||
soup = soup.select('.wp-block-post-content')[0]
|
||||
tables = soup.find_all('table')
|
||||
table_rows = tables[0].find_all('tr')
|
||||
soup = soup.select(".wp-block-post-content")[0]
|
||||
tables = soup.find_all("table")
|
||||
table_rows = tables[0].find_all("tr")
|
||||
|
||||
def get_table_row(target: str):
|
||||
"""一个便捷函数,用于返回对应表格头的对应行的最后一个单元格中的文本"""
|
||||
for row in table_rows:
|
||||
if target in row.find('td').text:
|
||||
return row.find_all('td')[-1]
|
||||
if target in row.find("td").text:
|
||||
return row.find_all("td")[-1]
|
||||
|
||||
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)
|
||||
rarity = len(table_rows[3].find_all('img'))
|
||||
rarity = len(table_rows[3].find_all("img"))
|
||||
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(
|
||||
# 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(
|
||||
# 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 []))))
|
||||
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]
|
||||
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]
|
||||
description = get_table_text(-1)
|
||||
return Material(
|
||||
id=id_, name=name, rarity=rarity, type=type_, description=description, source=source, weekdays=weekdays
|
||||
@ -77,4 +77,4 @@ class Material(WikiModel):
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
return str(HONEY_HOST.join(f'/img/{self.id}.webp'))
|
||||
return str(HONEY_HOST.join(f"/img/{self.id}.webp"))
|
||||
|
@ -6,41 +6,43 @@ from typing_extensions import Self
|
||||
from modules.wiki.base import HONEY_HOST
|
||||
|
||||
__all__ = [
|
||||
'Element',
|
||||
'WeaponType',
|
||||
'AttributeType',
|
||||
'Association',
|
||||
"Element",
|
||||
"WeaponType",
|
||||
"AttributeType",
|
||||
"Association",
|
||||
]
|
||||
|
||||
|
||||
class Element(Enum):
|
||||
"""元素"""
|
||||
Pyro = '火'
|
||||
Hydro = '水'
|
||||
Electro = '雷'
|
||||
Cryo = '冰'
|
||||
Dendro = '草'
|
||||
Anemo = '风'
|
||||
Geo = '岩'
|
||||
Multi = '无' # 主角
|
||||
|
||||
Pyro = "火"
|
||||
Hydro = "水"
|
||||
Electro = "雷"
|
||||
Cryo = "冰"
|
||||
Dendro = "草"
|
||||
Anemo = "风"
|
||||
Geo = "岩"
|
||||
Multi = "无" # 主角
|
||||
|
||||
|
||||
_WEAPON_ICON_MAP = {
|
||||
'Sword': HONEY_HOST.join('img/s_23101.png'),
|
||||
'Claymore': HONEY_HOST.join('img/s_163101.png'),
|
||||
'Polearm': HONEY_HOST.join('img/s_233101.png'),
|
||||
'Catalyst': HONEY_HOST.join('img/s_43101.png'),
|
||||
'Bow': HONEY_HOST.join('img/s_213101.png'),
|
||||
"Sword": HONEY_HOST.join("img/s_23101.png"),
|
||||
"Claymore": HONEY_HOST.join("img/s_163101.png"),
|
||||
"Polearm": HONEY_HOST.join("img/s_233101.png"),
|
||||
"Catalyst": HONEY_HOST.join("img/s_43101.png"),
|
||||
"Bow": HONEY_HOST.join("img/s_213101.png"),
|
||||
}
|
||||
|
||||
|
||||
class WeaponType(Enum):
|
||||
"""武器类型"""
|
||||
Sword = '单手剑'
|
||||
Claymore = '双手剑'
|
||||
Polearm = '长柄武器'
|
||||
Catalyst = '法器'
|
||||
Bow = '弓'
|
||||
|
||||
Sword = "单手剑"
|
||||
Claymore = "双手剑"
|
||||
Polearm = "长柄武器"
|
||||
Catalyst = "法器"
|
||||
Bow = "弓"
|
||||
|
||||
def icon_url(self) -> str:
|
||||
return str(_WEAPON_ICON_MAP.get(self.name))
|
||||
@ -49,17 +51,17 @@ class WeaponType(Enum):
|
||||
_ATTR_TYPE_MAP = {
|
||||
# 这个字典用于将 Honey 页面中遇到的 属性的缩写的字符 转为 AttributeType 的字符
|
||||
# 例如 Honey 页面上写的 HP% 则对应 HP_p
|
||||
"HP": ['Health'],
|
||||
"HP_p": ['HP%', 'Health %'],
|
||||
"ATK": ['Attack'],
|
||||
"ATK_p": ['Atk%', 'Attack %'],
|
||||
"DEF": ['Defense'],
|
||||
"DEF_p": ['Def%', 'Defense %'],
|
||||
"EM": ['Elemental Mastery'],
|
||||
"ER": ['ER%', 'Energy Recharge %'],
|
||||
"CR": ['CrR%', 'Critical Rate %', 'CritRate%'],
|
||||
"CD": ['Crd%', 'Critical Damage %', 'CritDMG%'],
|
||||
"PD": ['Phys%', 'Physical Damage %'],
|
||||
"HP": ["Health"],
|
||||
"HP_p": ["HP%", "Health %"],
|
||||
"ATK": ["Attack"],
|
||||
"ATK_p": ["Atk%", "Attack %"],
|
||||
"DEF": ["Defense"],
|
||||
"DEF_p": ["Def%", "Defense %"],
|
||||
"EM": ["Elemental Mastery"],
|
||||
"ER": ["ER%", "Energy Recharge %"],
|
||||
"CR": ["CrR%", "Critical Rate %", "CritRate%"],
|
||||
"CD": ["Crd%", "Critical Damage %", "CritDMG%"],
|
||||
"PD": ["Phys%", "Physical Damage %"],
|
||||
"HB": [],
|
||||
"Pyro": [],
|
||||
"Hydro": [],
|
||||
@ -73,6 +75,7 @@ _ATTR_TYPE_MAP = {
|
||||
|
||||
class AttributeType(Enum):
|
||||
"""属性枚举类。包含了武器和圣遗物的属性。"""
|
||||
|
||||
HP = "生命"
|
||||
HP_p = "生命%"
|
||||
ATK = "攻击力"
|
||||
@ -85,13 +88,13 @@ class AttributeType(Enum):
|
||||
CD = "暴击伤害"
|
||||
PD = "物理伤害加成"
|
||||
HB = "治疗加成"
|
||||
Pyro = '火元素伤害加成'
|
||||
Hydro = '水元素伤害加成'
|
||||
Electro = '雷元素伤害加成'
|
||||
Cryo = '冰元素伤害加成'
|
||||
Dendro = '草元素伤害加成'
|
||||
Anemo = '风元素伤害加成'
|
||||
Geo = '岩元素伤害加成'
|
||||
Pyro = "火元素伤害加成"
|
||||
Hydro = "水元素伤害加成"
|
||||
Electro = "雷元素伤害加成"
|
||||
Cryo = "冰元素伤害加成"
|
||||
Dendro = "草元素伤害加成"
|
||||
Anemo = "风元素伤害加成"
|
||||
Geo = "岩元素伤害加成"
|
||||
|
||||
@classmethod
|
||||
def convert(cls, string: str) -> Optional[Self]:
|
||||
@ -102,23 +105,24 @@ class AttributeType(Enum):
|
||||
|
||||
|
||||
_ASSOCIATION_MAP = {
|
||||
'Other': ['Mainactor', 'Ranger', 'Fatui'],
|
||||
'Snezhnaya': [],
|
||||
'Sumeru': [],
|
||||
'Inazuma': [],
|
||||
'Liyue': [],
|
||||
'Mondstadt': [],
|
||||
"Other": ["Mainactor", "Ranger", "Fatui"],
|
||||
"Snezhnaya": [],
|
||||
"Sumeru": [],
|
||||
"Inazuma": [],
|
||||
"Liyue": [],
|
||||
"Mondstadt": [],
|
||||
}
|
||||
|
||||
|
||||
class Association(Enum):
|
||||
"""角色所属地区"""
|
||||
Other = '其它'
|
||||
Snezhnaya = '至冬'
|
||||
Sumeru = '须弥'
|
||||
Inazuma = '稻妻'
|
||||
Liyue = '璃月'
|
||||
Mondstadt = '蒙德'
|
||||
|
||||
Other = "其它"
|
||||
Snezhnaya = "至冬"
|
||||
Sumeru = "须弥"
|
||||
Inazuma = "稻妻"
|
||||
Liyue = "璃月"
|
||||
Mondstadt = "蒙德"
|
||||
|
||||
@classmethod
|
||||
def convert(cls, string: str) -> Optional[Self]:
|
||||
|
@ -8,11 +8,12 @@ from httpx import URL
|
||||
from modules.wiki.base import Model, HONEY_HOST, WikiModel
|
||||
from modules.wiki.other import AttributeType, WeaponType
|
||||
|
||||
__all__ = ['Weapon', 'WeaponAffix', 'WeaponAttribute']
|
||||
__all__ = ["Weapon", "WeaponAffix", "WeaponAttribute"]
|
||||
|
||||
|
||||
class WeaponAttribute(Model):
|
||||
"""武器词条"""
|
||||
|
||||
type: AttributeType
|
||||
value: str
|
||||
|
||||
@ -25,6 +26,7 @@ class WeaponAffix(Model):
|
||||
description: 技能描述
|
||||
|
||||
"""
|
||||
|
||||
name: 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__]
|
||||
|
||||
@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]
|
||||
tables = soup.find_all('table')
|
||||
table_rows = tables[0].find_all('tr')
|
||||
soup = soup.select(".wp-block-post-content")[0]
|
||||
tables = soup.find_all("table")
|
||||
table_rows = tables[0].find_all("tr")
|
||||
|
||||
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):
|
||||
"""一个快捷函数,用于寻找对应表格头的表格"""
|
||||
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]
|
||||
weapon_type = WeaponType[get_table_text(1).split(',')[-1].strip()]
|
||||
id_ = re.findall(r"/img/(.*?)_gacha", str(table_rows[0]))[0]
|
||||
weapon_type = WeaponType[get_table_text(1).split(",")[-1].strip()]
|
||||
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))
|
||||
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 星及其以上的武器
|
||||
attribute = WeaponAttribute(
|
||||
type=AttributeType.convert(
|
||||
tables[2].find('thead').find('tr').find_all('td')[2].text.split(' ')[1]
|
||||
),
|
||||
value=get_table_text(6)
|
||||
type=AttributeType.convert(tables[2].find("thead").find("tr").find_all("td")[2].text.split(" ")[1]),
|
||||
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)
|
||||
if story_table := find_table('quotes'):
|
||||
if story_table := find_table("quotes"):
|
||||
story = story_table[0].text.strip()
|
||||
else:
|
||||
story = None
|
||||
@ -109,15 +109,24 @@ class Weapon(WikiModel):
|
||||
description = get_table_text(5)
|
||||
story = tables[-1].text.strip()
|
||||
stats = []
|
||||
for row in tables[2].find_all('tr')[1:]:
|
||||
cells = row.find_all('td')
|
||||
for row in tables[2].find_all("tr")[1:]:
|
||||
cells = row.find_all("td")
|
||||
if rarity > 2:
|
||||
stats.append(WeaponState(level=cells[0].text, ATK=cells[1].text, bonus=cells[2].text))
|
||||
else:
|
||||
stats.append(WeaponState(level=cells[0].text, ATK=cells[1].text))
|
||||
return Weapon(
|
||||
id=id_, name=name, rarity=rarity, attack=attack, attribute=attribute, affix=affix, weapon_type=weapon_type,
|
||||
story=story, stats=stats, description=description, ascension=ascension
|
||||
id=id_,
|
||||
name=name,
|
||||
rarity=rarity,
|
||||
attack=attack,
|
||||
attribute=attribute,
|
||||
affix=affix,
|
||||
weapon_type=weapon_type,
|
||||
story=story,
|
||||
stats=stats,
|
||||
description=description,
|
||||
ascension=ascension,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@ -125,16 +134,14 @@ class Weapon(WikiModel):
|
||||
# 重写此函数的目的是名字去重,例如单手剑页面中有三个 “「一心传」名刀”
|
||||
name_list = [i async for i in cls._name_list_generator(with_url=with_url)]
|
||||
if with_url:
|
||||
return [
|
||||
(i[0], list(i[1])[0][1]) for i in itertools.groupby(name_list, lambda x: x[0])
|
||||
]
|
||||
return [(i[0], list(i[1])[0][1]) for i in itertools.groupby(name_list, lambda x: x[0])]
|
||||
else:
|
||||
return [i[0] for i in itertools.groupby(name_list, lambda x: x)]
|
||||
|
||||
@property
|
||||
def icon(self) -> WeaponIcon:
|
||||
return WeaponIcon(
|
||||
icon=str(HONEY_HOST.join(f'/img/{self.id}.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')),
|
||||
icon=str(HONEY_HOST.join(f"/img/{self.id}.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")),
|
||||
)
|
||||
|
@ -21,11 +21,13 @@ from utils.log import logger
|
||||
|
||||
class AbyssUnlocked(Exception):
|
||||
"""根本没动"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NoMostKills(Exception):
|
||||
"""挑战了但是数据没刷新"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -37,7 +39,7 @@ class Abyss(Plugin, BasePlugin):
|
||||
user_service: UserService = None,
|
||||
cookies_service: CookiesService = None,
|
||||
template_service: TemplateService = None,
|
||||
assets_service: AssetsService = None
|
||||
assets_service: AssetsService = None,
|
||||
):
|
||||
self.template_service = template_service
|
||||
self.cookies_service = cookies_service
|
||||
@ -77,20 +79,20 @@ class Abyss(Plugin, BasePlugin):
|
||||
},
|
||||
"strongest_strike": {
|
||||
"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": {
|
||||
"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": {
|
||||
"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": {
|
||||
"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_played_list = ranks.most_played
|
||||
@ -98,7 +100,7 @@ class Abyss(Plugin, BasePlugin):
|
||||
temp = {
|
||||
"icon": await self.assets_service.avatar(most_played.id).icon(),
|
||||
"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)
|
||||
return abyss_data
|
||||
@ -132,7 +134,7 @@ class Abyss(Plugin, BasePlugin):
|
||||
await message.reply_text("本次深渊旅行者还没挑战呢,咕咕咕~~~")
|
||||
return
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
png_data = await self.template_service.render('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)
|
||||
png_data = await self.template_service.render(
|
||||
"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)
|
||||
|
@ -18,15 +18,17 @@ COMMAND_RESULT = 1
|
||||
class ArtifactRate(Plugin.Conversation, BasePlugin.Conversation):
|
||||
"""圣遗物评分"""
|
||||
|
||||
STAR_KEYBOARD = [[
|
||||
InlineKeyboardButton(
|
||||
f"{i}", callback_data=f"artifact_ocr_rate_data|star|{i}") for i in range(1, 6)
|
||||
]]
|
||||
STAR_KEYBOARD = [
|
||||
[InlineKeyboardButton(f"{i}", callback_data=f"artifact_ocr_rate_data|star|{i}") for i in range(1, 6)]
|
||||
]
|
||||
|
||||
LEVEL_KEYBOARD = [[
|
||||
InlineKeyboardButton(
|
||||
f"{i * 5 + j}", callback_data=f"artifact_ocr_rate_data|level|{i * 5 + j}") for j in range(1, 6)
|
||||
] for i in range(0, 4)]
|
||||
LEVEL_KEYBOARD = [
|
||||
[
|
||||
InlineKeyboardButton(f"{i * 5 + j}", callback_data=f"artifact_ocr_rate_data|level|{i * 5 + j}")
|
||||
for j in range(1, 6)
|
||||
]
|
||||
for i in range(0, 4)
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self.artifact_rate = ArtifactOcrRate()
|
||||
@ -39,19 +41,21 @@ class ArtifactRate(Plugin.Conversation, BasePlugin.Conversation):
|
||||
return artifact_attr.get("message", "API请求错误")
|
||||
return "API请求错误"
|
||||
rate_result = rate_result_req.json()
|
||||
return "*圣遗物评分结果*\n" \
|
||||
f"主属性:{escape_markdown(artifact_attr['main_item']['name'], version=2)}\n" \
|
||||
f"{escape_markdown(get_format_sub_item(artifact_attr), version=2)}" \
|
||||
f'`--------------------`\n' \
|
||||
f"总分:{escape_markdown(rate_result['total_percent'], version=2)}\n" \
|
||||
f"主词条:{escape_markdown(rate_result['main_percent'], version=2)}\n" \
|
||||
f"副词条:{escape_markdown(rate_result['sub_percent'], version=2)}\n" \
|
||||
f'`--------------------`\n' \
|
||||
f"{escape_markdown(get_comment(rate_result['total_percent']), version=2)}\n" \
|
||||
return (
|
||||
"*圣遗物评分结果*\n"
|
||||
f"主属性:{escape_markdown(artifact_attr['main_item']['name'], version=2)}\n"
|
||||
f"{escape_markdown(get_format_sub_item(artifact_attr), version=2)}"
|
||||
f"`--------------------`\n"
|
||||
f"总分:{escape_markdown(rate_result['total_percent'], version=2)}\n"
|
||||
f"主词条:{escape_markdown(rate_result['main_percent'], version=2)}\n"
|
||||
f"副词条:{escape_markdown(rate_result['sub_percent'], version=2)}\n"
|
||||
f"`--------------------`\n"
|
||||
f"{escape_markdown(get_comment(rate_result['total_percent']), version=2)}\n"
|
||||
"_评分、识图均来自 genshin\\.pub_"
|
||||
)
|
||||
|
||||
@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.CaptionRegex(r"^圣遗物评分(.*)"), block=True)
|
||||
@error_callable
|
||||
@ -95,15 +99,12 @@ class ArtifactRate(Plugin.Conversation, BasePlugin.Conversation):
|
||||
artifact_attr = artifact_attr_req.json()
|
||||
context.user_data["artifact_attr"] = artifact_attr
|
||||
if artifact_attr.get("star") is None:
|
||||
await message.reply_text("无法识别圣遗物星级,请选择圣遗物星级",
|
||||
reply_markup=InlineKeyboardMarkup(self.STAR_KEYBOARD))
|
||||
await message.reply_text("无法识别圣遗物星级,请选择圣遗物星级", reply_markup=InlineKeyboardMarkup(self.STAR_KEYBOARD))
|
||||
return COMMAND_RESULT
|
||||
if artifact_attr.get("level") is None:
|
||||
await message.reply_text("无法识别圣遗物等级,请选择圣遗物等级",
|
||||
reply_markup=InlineKeyboardMarkup(self.LEVEL_KEYBOARD))
|
||||
await message.reply_text("无法识别圣遗物等级,请选择圣遗物等级", reply_markup=InlineKeyboardMarkup(self.LEVEL_KEYBOARD))
|
||||
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)
|
||||
await reply_message.edit_text(rate_text, parse_mode=ParseMode.MARKDOWN_V2)
|
||||
return ConversationHandler.END
|
||||
@ -138,12 +139,10 @@ class ArtifactRate(Plugin.Conversation, BasePlugin.Conversation):
|
||||
await query.edit_message_text("数据错误")
|
||||
return ConversationHandler.END
|
||||
if artifact_attr.get("level") is None:
|
||||
await query.edit_message_text("无法识别圣遗物等级,请选择圣遗物等级",
|
||||
reply_markup=InlineKeyboardMarkup(self.LEVEL_KEYBOARD))
|
||||
await query.edit_message_text("无法识别圣遗物等级,请选择圣遗物等级", reply_markup=InlineKeyboardMarkup(self.LEVEL_KEYBOARD))
|
||||
return COMMAND_RESULT
|
||||
if artifact_attr.get("star") is None:
|
||||
await query.edit_message_text("无法识别圣遗物星级,请选择圣遗物星级",
|
||||
reply_markup=InlineKeyboardMarkup(self.STAR_KEYBOARD))
|
||||
await query.edit_message_text("无法识别圣遗物星级,请选择圣遗物星级", reply_markup=InlineKeyboardMarkup(self.STAR_KEYBOARD))
|
||||
return COMMAND_RESULT
|
||||
await query.edit_message_text("正在评分中...")
|
||||
rate_text = await self.get_rate(artifact_attr)
|
||||
|
@ -43,7 +43,7 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
|
||||
self.user_service = user_service
|
||||
|
||||
@conversation.entry_point
|
||||
@handler.command(command='setcookies', filters=filters.ChatType.PRIVATE, block=True)
|
||||
@handler.command(command="setcookies", filters=filters.ChatType.PRIVATE, block=True)
|
||||
@restricts()
|
||||
@error_callable
|
||||
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
|
||||
|
||||
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))
|
||||
return CHECK_SERVER
|
||||
|
||||
@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
|
||||
async def choose_method(self, update: Update, context: CallbackContext) -> int:
|
||||
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.region = region
|
||||
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[" \
|
||||
"0].trim()==n)return a[1]}};c=_('account_id')||alert('无效的Cookie,请重新登录!');c&&confirm(" \
|
||||
javascript = (
|
||||
"javascript:(()=>{_=(n)=>{for(i in(r=document.cookie.split(';'))){var a=r[i].split('=');if(a["
|
||||
"0].trim()==n)return a[1]}};c=_('account_id')||alert('无效的Cookie,请重新登录!');c&&confirm("
|
||||
"'将Cookie复制到剪贴板?')&©(document.cookie)})(); "
|
||||
)
|
||||
javascript_android = "javascript:(()=>{prompt('',document.cookie)})();"
|
||||
help_message = f"*关于如何获取Cookies*\n\n" \
|
||||
f"PC:\n" \
|
||||
f"[1、打开{bbs_name}并登录]({bbs_url})\n" \
|
||||
f"2、按F12打开开发者工具\n" \
|
||||
f"3、{escape_markdown('将开发者工具切换至控制台(Console)页签', version=2)}\n" \
|
||||
f"4、复制下方的代码,并将其粘贴在控制台中,按下回车\n" \
|
||||
f"`{escape_markdown(javascript, version=2, entity_type='code')}`\n\n" \
|
||||
f"Android:\n" \
|
||||
f"[1、通过 Via 浏览器打开{bbs_name}并登录]({bbs_url})\n" \
|
||||
f"2、复制下方的代码,并将其粘贴在地址栏中,点击右侧箭头\n" \
|
||||
help_message = (
|
||||
f"*关于如何获取Cookies*\n\n"
|
||||
f"PC:\n"
|
||||
f"[1、打开{bbs_name}并登录]({bbs_url})\n"
|
||||
f"2、按F12打开开发者工具\n"
|
||||
f"3、{escape_markdown('将开发者工具切换至控制台(Console)页签', version=2)}\n"
|
||||
f"4、复制下方的代码,并将其粘贴在控制台中,按下回车\n"
|
||||
f"`{escape_markdown(javascript, version=2, entity_type='code')}`\n\n"
|
||||
f"Android:\n"
|
||||
f"[1、通过 Via 浏览器打开{bbs_name}并登录]({bbs_url})\n"
|
||||
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)
|
||||
return INPUT_COOKIES
|
||||
|
||||
@ -149,8 +153,9 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
|
||||
return CHECK_PHONE
|
||||
add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data")
|
||||
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
|
||||
|
||||
@conversation.state(state=CHECK_CAPTCHA)
|
||||
@ -175,8 +180,7 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
|
||||
try:
|
||||
success = await client.login(captcha)
|
||||
if not success:
|
||||
await message.reply_text(
|
||||
"登录失败:可能是验证码错误,注意不要在登录页面使用掉验证码,如果验证码已经使用,请重新获取验证码!")
|
||||
await message.reply_text("登录失败:可能是验证码错误,注意不要在登录页面使用掉验证码,如果验证码已经使用,请重新获取验证码!")
|
||||
return ConversationHandler.END
|
||||
await client.get_s_token()
|
||||
except Exception:
|
||||
@ -184,16 +188,15 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
|
||||
return ConversationHandler.END
|
||||
add_user_command_data.sign_in_client = client
|
||||
await message.reply_text(
|
||||
"请再次打开 https://user.mihoyo.com/#/login/captcha ,输入手机号并获取验证码(需要等待一分钟),"
|
||||
"然后将收到的验证码发送给我(请不要在网页上进行登录)")
|
||||
"请再次打开 https://user.mihoyo.com/#/login/captcha ,输入手机号并获取验证码(需要等待一分钟)," "然后将收到的验证码发送给我(请不要在网页上进行登录)"
|
||||
)
|
||||
return CHECK_CAPTCHA
|
||||
else:
|
||||
client = add_user_command_data.sign_in_client
|
||||
try:
|
||||
success = await client.get_token(captcha)
|
||||
if not success:
|
||||
await message.reply_text(
|
||||
"登录失败:可能是验证码错误,注意不要在登录页面使用掉验证码,如果验证码已经使用,请重新获取验证码!")
|
||||
await message.reply_text("登录失败:可能是验证码错误,注意不要在登录页面使用掉验证码,如果验证码已经使用,请重新获取验证码!")
|
||||
return ConversationHandler.END
|
||||
except Exception:
|
||||
await message.reply_text("登录失败:米游社返回了错误的数据,请稍后再试!")
|
||||
@ -249,21 +252,24 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
|
||||
await message.reply_text("Cookies已经过期,请检查是否正确", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
except GenshinException as exc:
|
||||
await message.reply_text(f"获取账号信息发生错误,错误信息为 {str(exc)},请检查Cookie或者账号是否正常",
|
||||
reply_markup=ReplyKeyboardRemove())
|
||||
await message.reply_text(
|
||||
f"获取账号信息发生错误,错误信息为 {str(exc)},请检查Cookie或者账号是否正常", reply_markup=ReplyKeyboardRemove()
|
||||
)
|
||||
return ConversationHandler.END
|
||||
except (AttributeError, ValueError):
|
||||
await message.reply_text("Cookies错误,请检查是否正确", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
add_user_command_data.game_uid = user_info.uid
|
||||
reply_keyboard = [['确认', '退出']]
|
||||
reply_keyboard = [["确认", "退出"]]
|
||||
await message.reply_text("获取角色基础信息成功,请检查是否正确!")
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 获取账号 {user_info.nickname}[{user_info.uid}] 信息成功")
|
||||
text = f"*角色信息*\n" \
|
||||
f"角色名称:{escape_markdown(user_info.nickname, version=2)}\n" \
|
||||
f"角色等级:{user_info.level}\n" \
|
||||
f"UID:`{user_info.uid}`\n" \
|
||||
text = (
|
||||
f"*角色信息*\n"
|
||||
f"角色名称:{escape_markdown(user_info.nickname, version=2)}\n"
|
||||
f"角色等级:{user_info.level}\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))
|
||||
return COMMAND_RESULT
|
||||
|
||||
@ -280,11 +286,15 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
|
||||
elif message.text == "确认":
|
||||
if add_user_command_data.user is None:
|
||||
if add_user_command_data.region == RegionEnum.HYPERION:
|
||||
user_db = User(user_id=user.id, yuanshen_uid=add_user_command_data.game_uid,
|
||||
region=add_user_command_data.region)
|
||||
user_db = User(
|
||||
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:
|
||||
user_db = User(user_id=user.id, genshin_uid=add_user_command_data.game_uid,
|
||||
region=add_user_command_data.region)
|
||||
user_db = User(
|
||||
user_id=user.id, genshin_uid=add_user_command_data.game_uid, region=add_user_command_data.region
|
||||
)
|
||||
else:
|
||||
await message.reply_text("数据错误")
|
||||
return ConversationHandler.END
|
||||
@ -301,11 +311,13 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
|
||||
return ConversationHandler.END
|
||||
await self.user_service.update_user(user_db)
|
||||
if add_user_command_data.cookies_database_data is None:
|
||||
await self.cookies_service.add_cookies(user.id, add_user_command_data.cookies,
|
||||
add_user_command_data.region)
|
||||
await self.cookies_service.add_cookies(
|
||||
user.id, add_user_command_data.cookies, add_user_command_data.region
|
||||
)
|
||||
else:
|
||||
await self.cookies_service.update_cookies(user.id, add_user_command_data.cookies,
|
||||
add_user_command_data.region)
|
||||
await self.cookies_service.update_cookies(
|
||||
user.id, add_user_command_data.cookies, add_user_command_data.region
|
||||
)
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 绑定账号成功")
|
||||
await message.reply_text("保存成功", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
|
@ -39,14 +39,14 @@ from utils.log import logger
|
||||
INTERVAL = 1
|
||||
|
||||
DATA_TYPE = Dict[str, List[List[str]]]
|
||||
DATA_FILE_PATH = Path(__file__).joinpath('../daily.json').resolve()
|
||||
DOMAINS = ['忘却之峡', '太山府', '菫色之庭', '昏识塔', '塞西莉亚苗圃', '震雷连山密宫', '砂流之庭', '有顶塔']
|
||||
DOMAIN_AREA_MAP = dict(zip(DOMAINS, ['蒙德', '璃月', '稻妻', '须弥'] * 2))
|
||||
DATA_FILE_PATH = Path(__file__).joinpath("../daily.json").resolve()
|
||||
DOMAINS = ["忘却之峡", "太山府", "菫色之庭", "昏识塔", "塞西莉亚苗圃", "震雷连山密宫", "砂流之庭", "有顶塔"]
|
||||
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])
|
||||
.sort(lambda y: y.rarity, reverse=True)
|
||||
.groupby(lambda y: y.rarity) # 根据星级分组并排序
|
||||
.map(lambda y: (
|
||||
.map(
|
||||
lambda y: (
|
||||
ArkoWrapper(y[1])
|
||||
.sort(lambda z: z.refinement or z.constellation or -1, reverse=True)
|
||||
.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)):
|
||||
result.append(sub_string)
|
||||
result = ArkoWrapper(result).sort(len, reverse=True)[0]
|
||||
chars = {'的': 0, '之': 0}
|
||||
chars = {"的": 0, "之": 0}
|
||||
for char, k in chars.items():
|
||||
result = result.split(char)[k]
|
||||
return result
|
||||
@ -95,6 +98,7 @@ def get_material_serial_name(names: Iterable[str]) -> str:
|
||||
|
||||
class DailyMaterial(Plugin, BasePlugin):
|
||||
"""每日素材表"""
|
||||
|
||||
data: DATA_TYPE
|
||||
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]]]:
|
||||
"""获取已经绑定的账号的角色、武器信息"""
|
||||
client = None
|
||||
user_data = {'avatar': [], 'weapon': []}
|
||||
user_data = {"avatar": [], "weapon": []}
|
||||
try:
|
||||
logger.debug("尝试获取已绑定的原神账号")
|
||||
client = await get_genshin_client(user.id)
|
||||
logger.debug(f"获取账号数据成功: UID={client.uid}")
|
||||
characters = await client.get_genshin_characters(client.uid)
|
||||
for character in characters:
|
||||
if character.name == '旅行者': # 跳过主角
|
||||
if character.name == "旅行者": # 跳过主角
|
||||
continue
|
||||
cid = AVATAR_DATA[str(character.id)]['id']
|
||||
cid = AVATAR_DATA[str(character.id)]["id"]
|
||||
weapon = character.weapon
|
||||
user_data['avatar'].append(
|
||||
user_data["avatar"].append(
|
||||
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,
|
||||
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(
|
||||
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,
|
||||
icon=(await getattr( # 判定武器的突破次数是否大于 2 ;若是, 则将图标替换为 awakened (觉醒) 的图标
|
||||
self.assets_service.weapon(weapon.id), 'icon' if weapon.ascension < 2 else 'awaken'
|
||||
)()).as_uri(),
|
||||
c_path=(await self.assets_service.avatar(cid).side()).as_uri()
|
||||
icon=(
|
||||
await getattr( # 判定武器的突破次数是否大于 2 ;若是, 则将图标替换为 awakened (觉醒) 的图标
|
||||
self.assets_service.weapon(weapon.id), "icon" if weapon.ascension < 2 else "awaken"
|
||||
)()
|
||||
).as_uri(),
|
||||
c_path=(await self.assets_service.avatar(cid).side()).as_uri(),
|
||||
)
|
||||
)
|
||||
except (UserNotFoundError, CookiesNotFoundError):
|
||||
logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息")
|
||||
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)
|
||||
@error_callable
|
||||
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 = 6 if weekday < 0 else 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(
|
||||
f"用户 {user.full_name}[{user.id}] 每日素材命令请求 || 参数 weekday=\"{WEEK_MAP[weekday]}\" full={full}")
|
||||
logger.info(f'用户 {user.full_name}[{user.id}] 每日素材命令请求 || 参数 weekday="{WEEK_MAP[weekday]}" full={full}')
|
||||
|
||||
if weekday == 6:
|
||||
await update.message.reply_text(
|
||||
("今天" if title == '今日' else '这天') + "是星期天, <b>全部素材都可以</b>刷哦~",
|
||||
parse_mode=ParseMode.HTML
|
||||
("今天" if title == "今日" else "这天") + "是星期天, <b>全部素材都可以</b>刷哦~", parse_mode=ParseMode.HTML
|
||||
)
|
||||
return
|
||||
|
||||
@ -196,26 +206,26 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
await update.message.reply_chat_action(ChatAction.TYPING)
|
||||
|
||||
# 获取已经缓存的秘境素材信息
|
||||
local_data = {'avatar': [], 'weapon': []}
|
||||
local_data = {"avatar": [], "weapon": []}
|
||||
if not self.data: # 若没有缓存每日素材表的数据
|
||||
logger.info("正在获取每日素材缓存")
|
||||
self.data = await self._refresh_data()
|
||||
for domain, sche in self.data.items():
|
||||
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[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)
|
||||
|
||||
await update.message.reply_chat_action(ChatAction.TYPING)
|
||||
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 = []
|
||||
for area_data in local_data[type_]: # 遍历每个区域的信息:蒙德、璃月、稻妻、须弥
|
||||
items = []
|
||||
for id_ in area_data['items']: # 遍历所有该区域下,当天(weekday)可以培养的角色、武器
|
||||
for id_ in area_data["items"]: # 遍历所有该区域下,当天(weekday)可以培养的角色、武器
|
||||
added = False
|
||||
for i in user_data[type_]: # 从已经获取的角色数据中查找对应角色、武器
|
||||
if id_ == str(i.id):
|
||||
@ -231,33 +241,42 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
continue
|
||||
if item[2] < 4: # 跳过 3 星及以下的武器
|
||||
continue
|
||||
items.append(ItemData( # 添加角色数据中未找到的
|
||||
id=id_, name=item[1], rarity=item[2],
|
||||
icon=(await getattr(self.assets_service, type_)(id_).icon()).as_uri()
|
||||
))
|
||||
items.append(
|
||||
ItemData( # 添加角色数据中未找到的
|
||||
id=id_,
|
||||
name=item[1],
|
||||
rarity=item[2],
|
||||
icon=(await getattr(self.assets_service, type_)(id_).icon()).as_uri(),
|
||||
)
|
||||
)
|
||||
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()
|
||||
material = HONEY_DATA['material'][mid]
|
||||
material = HONEY_DATA["material"][mid]
|
||||
materials.append(ItemData(id=mid, icon=path, name=material[1], rarity=material[2]))
|
||||
areas.append(AreaData(
|
||||
name=area_data['name'], materials=materials, 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)
|
||||
areas.append(
|
||||
AreaData(
|
||||
name=area_data["name"],
|
||||
materials=materials,
|
||||
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)
|
||||
render_tasks = [
|
||||
asyncio.create_task(
|
||||
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(
|
||||
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)):
|
||||
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)
|
||||
await update.message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
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(weapon_img_data, filename="可培养武器.png"),
|
||||
]
|
||||
)
|
||||
else:
|
||||
await update.message.reply_media_group(
|
||||
[InputMediaPhoto(character_img_data), InputMediaPhoto(weapon_img_data)]
|
||||
)
|
||||
logger.debug("角色、武器培养素材图发送成功")
|
||||
|
||||
@handler.command('refresh_daily_material', block=False)
|
||||
@handler.command("refresh_daily_material", block=False)
|
||||
@bot_admins_rights_check
|
||||
async def refresh(self, update: Update, context: CallbackContext):
|
||||
user = update.effective_user
|
||||
message = update.effective_message
|
||||
|
||||
logger.info(
|
||||
f"用户 {user.full_name}[{user.id}] 刷新[bold]每日素材[/]缓存命令", extra={'markup': True}
|
||||
)
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 刷新[bold]每日素材[/]缓存命令", extra={"markup": True})
|
||||
if self.locks[0].locked():
|
||||
notice = await message.reply_text("派蒙还在抄每日素材表呢,我有在好好工作哦~")
|
||||
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]: # 锁住第一把锁
|
||||
data = await self._refresh_data()
|
||||
notice = await notice.edit_text(
|
||||
"每日素材表" +
|
||||
("摘抄<b>完成!</b>" if data else "坏掉了!等会它再长好了之后我再抄。。。") +
|
||||
'\n正搬运每日素材的图标中。。。',
|
||||
parse_mode=ParseMode.HTML
|
||||
"每日素材表" + ("摘抄<b>完成!</b>" if data else "坏掉了!等会它再长好了之后我再抄。。。") + "\n正搬运每日素材的图标中。。。",
|
||||
parse_mode=ParseMode.HTML,
|
||||
)
|
||||
self.data = data or self.data
|
||||
time = await self._download_icon(notice)
|
||||
|
||||
async def job(_, n):
|
||||
await n.edit_text(
|
||||
n.text_html.split('\n')[0] + "\n每日素材图标搬运<b>完成!</b>",
|
||||
parse_mode=ParseMode.HTML
|
||||
)
|
||||
await n.edit_text(n.text_html.split("\n")[0] + "\n每日素材图标搬运<b>完成!</b>", parse_mode=ParseMode.HTML)
|
||||
await asyncio.sleep(INTERVAL)
|
||||
await notice.delete()
|
||||
|
||||
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:
|
||||
"""刷新来自 honey impact 的每日素材表"""
|
||||
from bs4 import Tag
|
||||
|
||||
result = {}
|
||||
for i in range(retry): # 重复尝试 retry 次
|
||||
try:
|
||||
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]
|
||||
key: str = ''
|
||||
key: str = ""
|
||||
for tag in calendar:
|
||||
tag: Tag
|
||||
if tag.name == 'span': # 如果是秘境
|
||||
key = tag.find('a').text
|
||||
if tag.name == "span": # 如果是秘境
|
||||
key = tag.find("a").text
|
||||
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] = []
|
||||
for a in div.find_all('a'):
|
||||
honey_id = re.findall(r"/(.*)?/", a['href'])[0]
|
||||
mid: str = [
|
||||
i[0]
|
||||
for i in HONEY_DATA['material'].items()
|
||||
if i[1][0] == honey_id
|
||||
][0]
|
||||
for a in div.find_all("a"):
|
||||
honey_id = re.findall(r"/(.*)?/", a["href"])[0]
|
||||
mid: str = [i[0] for i in HONEY_DATA["material"].items() if i[1][0] == honey_id][0]
|
||||
result[key][day][0].append(mid)
|
||||
else: # 如果是角色或武器
|
||||
id_ = re.findall(r"/(.*)?/", tag['href'])[0]
|
||||
if tag.text.strip() == '旅行者': # 忽略主角
|
||||
id_ = re.findall(r"/(.*)?/", tag["href"])[0]
|
||||
if tag.text.strip() == "旅行者": # 忽略主角
|
||||
continue
|
||||
id_ = ("" if id_.startswith('i_n') else "10000") + re.findall(r'\d+', id_)[0]
|
||||
for day in map(int, tag.find('div')['data-days']): # 获取该角色/武器的可培养天
|
||||
id_ = ("" if id_.startswith("i_n") else "10000") + re.findall(r"\d+", id_)[0]
|
||||
for day in map(int, tag.find("div")["data-days"]): # 获取该角色/武器的可培养天
|
||||
result[key][day][1].append(id_)
|
||||
for stage, schedules in result.items():
|
||||
for day, _ in enumerate(schedules):
|
||||
# noinspection PyUnresolvedReferences
|
||||
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
|
||||
logger.info("每日素材刷新成功")
|
||||
break
|
||||
except (HTTPError, SSLZeroReturnError):
|
||||
from asyncio import sleep
|
||||
|
||||
await sleep(1)
|
||||
if i <= retry - 1:
|
||||
logger.warning("每日素材刷新失败, 正在重试")
|
||||
@ -375,6 +387,7 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
asset_list = []
|
||||
|
||||
from time import time as time_
|
||||
|
||||
lock = asyncio.Lock()
|
||||
|
||||
the_time = Value(c_double, time_() - INTERVAL)
|
||||
@ -382,20 +395,15 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
async def edit_message(text):
|
||||
"""修改提示消息"""
|
||||
async with lock:
|
||||
if (
|
||||
message is not None
|
||||
and
|
||||
time_() >= (the_time.value + INTERVAL)
|
||||
):
|
||||
if message is not None and time_() >= (the_time.value + INTERVAL):
|
||||
with contextlib.suppress(TimedOut, RetryAfter):
|
||||
await message.edit_text(
|
||||
'\n'.join(message.text_html.split('\n')[:2] + [text]),
|
||||
parse_mode=ParseMode.HTML
|
||||
"\n".join(message.text_html.split("\n")[:2] + [text]), parse_mode=ParseMode.HTML
|
||||
)
|
||||
the_time.value = time_()
|
||||
|
||||
async def task(item_id, name, item_type):
|
||||
logger.debug(f"正在开始下载 \"{name}\" 的图标素材")
|
||||
logger.debug(f'正在开始下载 "{name}" 的图标素材')
|
||||
await edit_message(f"正在搬运 <b>{name}</b> 的图标素材。。。")
|
||||
asset: AssetsServiceType = getattr(self.assets_service, item_type)(item_id) # 获取素材对象
|
||||
asset_list.append(asset.honey_id)
|
||||
@ -403,7 +411,7 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
# 并根据图标类型找到下载对应图标的函数
|
||||
for icon_type in asset.icon_types:
|
||||
await getattr(asset, icon_type)(True) # 执行下载函数
|
||||
logger.debug(f"\"{name}\" 的图标素材下载成功")
|
||||
logger.debug(f'"{name}" 的图标素材下载成功')
|
||||
await edit_message(f"正在搬运 <b>{name}</b> 的图标素材。。。<b>成功!</b>")
|
||||
|
||||
for TYPE, ITEMS in HONEY_DATA.items(): # 遍历每个对象
|
||||
@ -431,7 +439,7 @@ class ItemData(BaseModel):
|
||||
|
||||
|
||||
class AreaData(BaseModel):
|
||||
name: Literal['蒙德', '璃月', '稻妻', '须弥'] # 区域名
|
||||
name: Literal["蒙德", "璃月", "稻妻", "须弥"] # 区域名
|
||||
material_name: str # 区域的材料系列名
|
||||
materials: List[ItemData] = [] # 区域材料
|
||||
items: Iterable[ItemData] = [] # 可培养的角色或武器
|
||||
|
@ -5,8 +5,7 @@ from typing import Optional
|
||||
from genshin import DataNotPublic
|
||||
from telegram import Update
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.ext import CommandHandler, MessageHandler, ConversationHandler, filters, \
|
||||
CallbackContext
|
||||
from telegram.ext import CommandHandler, MessageHandler, ConversationHandler, filters, CallbackContext
|
||||
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.cookies.error import CookiesNotFoundError
|
||||
@ -24,8 +23,12 @@ from utils.log import logger
|
||||
class DailyNote(Plugin, BasePlugin):
|
||||
"""每日便签"""
|
||||
|
||||
def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None,
|
||||
template_service: TemplateService = None):
|
||||
def __init__(
|
||||
self,
|
||||
user_service: UserService = None,
|
||||
cookies_service: CookiesService = None,
|
||||
template_service: TemplateService = None,
|
||||
):
|
||||
self.template_service = template_service
|
||||
self.cookies_service = cookies_service
|
||||
self.user_service = user_service
|
||||
@ -34,11 +37,18 @@ class DailyNote(Plugin, BasePlugin):
|
||||
async def _get_daily_note(self, client) -> bytes:
|
||||
daily_info = await client.get_genshin_notes(client.uid)
|
||||
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 \
|
||||
daily_info.max_resin - daily_info.current_resin 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
|
||||
resin_recovery_time = (
|
||||
daily_info.resin_recovery_time.strftime("%m-%d %H:%M")
|
||||
if daily_info.max_resin - daily_info.current_resin
|
||||
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
|
||||
for i in daily_info.expeditions:
|
||||
if remained_time:
|
||||
@ -73,10 +83,11 @@ class DailyNote(Plugin, BasePlugin):
|
||||
"max_resin_discounts": daily_info.max_resin_discounts,
|
||||
"transformer": transformer,
|
||||
"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,
|
||||
{"width": 600, "height": 548}, full_page=False)
|
||||
png_data = await self.template_service.render(
|
||||
"genshin/daily_note", "daily_note.html", daily_data, {"width": 600, "height": 548}, full_page=False
|
||||
)
|
||||
return png_data
|
||||
|
||||
@handler(CommandHandler, command="dailynote", block=False)
|
||||
|
@ -125,8 +125,9 @@ class Gacha(Plugin, BasePlugin):
|
||||
data["items"].sort(key=take_rang, reverse=True)
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
# 因为 gacha_info["title"] 返回的是 HTML 标签 尝试关闭自动转义
|
||||
png_data = await self.template_service.render('genshin/gacha', "gacha.html", data,
|
||||
{"width": 1157, "height": 603}, False)
|
||||
png_data = await self.template_service.render(
|
||||
"genshin/gacha", "gacha.html", data, {"width": 1157, "height": 603}, False
|
||||
)
|
||||
|
||||
reply_message = await message.reply_photo(png_data)
|
||||
if filters.ChatType.GROUPS.filter(message):
|
||||
|
@ -38,8 +38,7 @@ def check_ledger_month(context: CallbackContext) -> int:
|
||||
elif re_data := re.findall(r"\d+", str(month)):
|
||||
month = int(re_data[0])
|
||||
else:
|
||||
num_dict = {"一": 1, "二": 2, "三": 3, "四": 4, "五": 5,
|
||||
"六": 6, "七": 7, "八": 8, "九": 9, "十": 10}
|
||||
num_dict = {"一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6, "七": 7, "八": 8, "九": 9, "十": 10}
|
||||
month = sum(num_dict.get(i, 0) for i in str(month))
|
||||
# check right
|
||||
allow_month = [now_time.month]
|
||||
@ -58,8 +57,12 @@ def check_ledger_month(context: CallbackContext) -> int:
|
||||
class Ledger(Plugin, BasePlugin):
|
||||
"""旅行札记"""
|
||||
|
||||
def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None,
|
||||
template_service: TemplateService = None):
|
||||
def __init__(
|
||||
self,
|
||||
user_service: UserService = None,
|
||||
cookies_service: CookiesService = None,
|
||||
template_service: TemplateService = None,
|
||||
):
|
||||
self.template_service = template_service
|
||||
self.cookies_service = cookies_service
|
||||
self.user_service = user_service
|
||||
@ -71,18 +74,26 @@ class Ledger(Plugin, BasePlugin):
|
||||
except GenshinException as error:
|
||||
raise error
|
||||
color = ["#73a9c6", "#d56565", "#70b2b4", "#bd9a5a", "#739970", "#7a6da7", "#597ea0"]
|
||||
categories = [{"id": i.id,
|
||||
categories = [
|
||||
{
|
||||
"id": i.id,
|
||||
"name": i.name,
|
||||
"color": color[i.id % len(color)],
|
||||
"amount": i.amount,
|
||||
"percentage": i.percentage} for i in diary_info.month_data.categories]
|
||||
"percentage": i.percentage,
|
||||
}
|
||||
for i in diary_info.month_data.categories
|
||||
]
|
||||
color = [i["color"] for i in categories]
|
||||
|
||||
def format_amount(amount: int) -> str:
|
||||
return f"{round(amount / 10000, 2)}w" if amount >= 10000 else amount
|
||||
|
||||
evaluate = """const { Pie } = G2Plot;
|
||||
const data = JSON.parse(`""" + json.dumps(categories) + """`);
|
||||
evaluate = (
|
||||
"""const { Pie } = G2Plot;
|
||||
const data = JSON.parse(`"""
|
||||
+ json.dumps(categories)
|
||||
+ """`);
|
||||
const piePlot = new Pie("chartContainer", {
|
||||
renderer: "svg",
|
||||
animation: false,
|
||||
@ -92,7 +103,9 @@ class Ledger(Plugin, BasePlugin):
|
||||
colorField: "name",
|
||||
radius: 1,
|
||||
innerRadius: 0.7,
|
||||
color: JSON.parse(`""" + json.dumps(color) + """`),
|
||||
color: JSON.parse(`"""
|
||||
+ json.dumps(color)
|
||||
+ """`),
|
||||
meta: {},
|
||||
label: {
|
||||
type: "inner",
|
||||
@ -121,6 +134,7 @@ class Ledger(Plugin, BasePlugin):
|
||||
legend:false,
|
||||
});
|
||||
piePlot.render();"""
|
||||
)
|
||||
ledger_data = {
|
||||
"uid": client.uid,
|
||||
"day": diary_info.month,
|
||||
@ -132,9 +146,9 @@ class Ledger(Plugin, BasePlugin):
|
||||
"last_mora": format_amount(diary_info.month_data.last_mora),
|
||||
"categories": categories,
|
||||
}
|
||||
png_data = await self.template_service.render('genshin/ledger', "ledger.html", ledger_data,
|
||||
{"width": 580, "height": 610},
|
||||
evaluate=evaluate)
|
||||
png_data = await self.template_service.render(
|
||||
"genshin/ledger", "ledger.html", ledger_data, {"width": 580, "height": 610}, evaluate=evaluate
|
||||
)
|
||||
return png_data
|
||||
|
||||
@handler(CommandHandler, command="ledger", block=False)
|
||||
|
@ -60,6 +60,6 @@ class Map(Plugin, BasePlugin):
|
||||
return
|
||||
img = Image.open(f"cache{sep}map.jpg")
|
||||
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:
|
||||
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)
|
||||
|
@ -16,9 +16,9 @@ RESOURCE_ICON_OFFSET = (-int(150 * 0.5 * ZOOM), -int(150 * ZOOM))
|
||||
|
||||
|
||||
class MapHelper:
|
||||
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'
|
||||
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'
|
||||
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"
|
||||
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"):
|
||||
self._current_dir = os.getcwd()
|
||||
@ -109,11 +109,11 @@ class MapHelper:
|
||||
map_info = map_info["data"]["info"]["detail"]
|
||||
map_info = ujson.loads(map_info)
|
||||
|
||||
map_url_list = map_info['slices'][0]
|
||||
map_url_list = map_info["slices"][0]
|
||||
origin = map_info["origin"]
|
||||
|
||||
x_start = map_info['total_size'][1]
|
||||
y_start = map_info['total_size'][1]
|
||||
x_start = map_info["total_size"][1]
|
||||
y_start = map_info["total_size"][1]
|
||||
x_end = 0
|
||||
y_end = 0
|
||||
for resource_point in self.all_resource_point_list:
|
||||
@ -223,7 +223,6 @@ class MapHelper:
|
||||
|
||||
|
||||
class ResourceMap:
|
||||
|
||||
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.resource_id = resource_id
|
||||
@ -263,8 +262,9 @@ class ResourceMap:
|
||||
# 这时地图已经裁切过了,要以裁切后的地图左上角为中心再转换一次坐标
|
||||
x -= self.x_start
|
||||
y -= self.y_start
|
||||
self.map_image.paste(self.resource_icon, (x + RESOURCE_ICON_OFFSET[0], y + RESOURCE_ICON_OFFSET[1]),
|
||||
self.resource_icon)
|
||||
self.map_image.paste(
|
||||
self.resource_icon, (x + RESOURCE_ICON_OFFSET[0], y + RESOURCE_ICON_OFFSET[1]), self.resource_icon
|
||||
)
|
||||
|
||||
def crop(self):
|
||||
# 把大地图裁切到只保留资源图标位置
|
||||
@ -291,8 +291,7 @@ class ResourceMap:
|
||||
self.y_start = center - 500
|
||||
self.y_end = center + 500
|
||||
|
||||
self.map_image = self.map_image.crop((self.x_start, self.y_start,
|
||||
self.x_end, self.y_end))
|
||||
self.map_image = self.map_image.crop((self.x_start, self.y_start, self.x_end, self.y_end))
|
||||
|
||||
def gen_jpg(self):
|
||||
if not self.resource_xy_list:
|
||||
@ -301,7 +300,7 @@ class ResourceMap:
|
||||
os.mkdir("cache") # 查找 cache 目录 (缓存目录) 是否存在,如果不存在则创建
|
||||
self.crop()
|
||||
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):
|
||||
return len(self.resource_xy_list)
|
||||
|
@ -16,9 +16,7 @@ from utils.log import logger
|
||||
class Material(Plugin, BasePlugin):
|
||||
"""角色培养素材查询"""
|
||||
|
||||
KEYBOARD = [[InlineKeyboardButton(
|
||||
text="查看角色培养素材列表并查询",
|
||||
switch_inline_query_current_chat="查看角色培养素材列表并查询")]]
|
||||
KEYBOARD = [[InlineKeyboardButton(text="查看角色培养素材列表并查询", switch_inline_query_current_chat="查看角色培养素材列表并查询")]]
|
||||
|
||||
def __init__(self, game_material_service: GameMaterialService = None):
|
||||
self.game_material_service = game_material_service
|
||||
@ -34,8 +32,9 @@ class Material(Plugin, BasePlugin):
|
||||
if len(args) >= 1:
|
||||
character_name = args[0]
|
||||
else:
|
||||
reply_message = await message.reply_text("请回复你要查询的培养素材的角色名",
|
||||
reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
|
||||
reply_message = await message.reply_text(
|
||||
"请回复你要查询的培养素材的角色名", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
|
||||
)
|
||||
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, reply_message.chat_id, reply_message.message_id)
|
||||
@ -43,8 +42,9 @@ class Material(Plugin, BasePlugin):
|
||||
character_name = roleToName(character_name)
|
||||
url = await self.game_material_service.get_material(character_name)
|
||||
if not url:
|
||||
reply_message = await message.reply_text(f"没有找到 {character_name} 的培养素材",
|
||||
reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
|
||||
reply_message = await message.reply_text(
|
||||
f"没有找到 {character_name} 的培养素材", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
|
||||
)
|
||||
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, 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}")
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
file_path = await url_to_file(url, return_path=True)
|
||||
caption = "From 米游社 " \
|
||||
f"查看 [原图]({url})"
|
||||
await message.reply_photo(photo=open(file_path, "rb"), caption=caption, filename=f"{character_name}.png",
|
||||
allow_sending_without_reply=True, parse_mode=ParseMode.MARKDOWN_V2)
|
||||
caption = "From 米游社 " f"查看 [原图]({url})"
|
||||
await message.reply_photo(
|
||||
photo=open(file_path, "rb"),
|
||||
caption=caption,
|
||||
filename=f"{character_name}.png",
|
||||
allow_sending_without_reply=True,
|
||||
parse_mode=ParseMode.MARKDOWN_V2,
|
||||
)
|
||||
|
@ -50,8 +50,13 @@ class QuizPlugin(Plugin, BasePlugin):
|
||||
return None
|
||||
random.shuffle(_options)
|
||||
index = _options.index(correct_option)
|
||||
poll_message = await update.effective_message.reply_poll(question.text, _options,
|
||||
correct_option_id=index, is_anonymous=False,
|
||||
open_period=self.time_out, type=Poll.QUIZ)
|
||||
poll_message = await update.effective_message.reply_poll(
|
||||
question.text,
|
||||
_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, poll_message.chat_id, poll_message.message_id, 300)
|
||||
|
@ -35,8 +35,7 @@ class StrategyPlugin(Plugin, BasePlugin):
|
||||
if len(args) >= 1:
|
||||
character_name = args[0]
|
||||
else:
|
||||
reply_message = await message.reply_text("请回复你要查询的攻略的角色名",
|
||||
reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
|
||||
reply_message = await message.reply_text("请回复你要查询的攻略的角色名", reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
|
||||
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, reply_message.chat_id, reply_message.message_id)
|
||||
@ -44,8 +43,9 @@ class StrategyPlugin(Plugin, BasePlugin):
|
||||
character_name = roleToName(character_name)
|
||||
url = await self.game_strategy_service.get_strategy(character_name)
|
||||
if url == "":
|
||||
reply_message = await message.reply_text(f"没有找到 {character_name} 的攻略",
|
||||
reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
|
||||
reply_message = await message.reply_text(
|
||||
f"没有找到 {character_name} 的攻略", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
|
||||
)
|
||||
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, 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}")
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
file_path = await url_to_file(url, return_path=True)
|
||||
caption = "From 米游社 西风驿站 " \
|
||||
f"查看 [原图]({url})"
|
||||
await message.reply_photo(photo=open(file_path, "rb"), caption=caption, filename=f"{character_name}.png",
|
||||
allow_sending_without_reply=True, parse_mode=ParseMode.MARKDOWN_V2)
|
||||
caption = "From 米游社 西风驿站 " f"查看 [原图]({url})"
|
||||
await message.reply_photo(
|
||||
photo=open(file_path, "rb"),
|
||||
caption=caption,
|
||||
filename=f"{character_name}.png",
|
||||
allow_sending_without_reply=True,
|
||||
parse_mode=ParseMode.MARKDOWN_V2,
|
||||
)
|
||||
|
@ -31,14 +31,18 @@ CHECK_SERVER, CHECK_UID, COMMAND_RESULT = range(10100, 10103)
|
||||
class SetUserUid(Plugin.Conversation, BasePlugin.Conversation):
|
||||
"""UID用户绑定"""
|
||||
|
||||
def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None,
|
||||
public_cookies_service: PublicCookiesService = None):
|
||||
def __init__(
|
||||
self,
|
||||
user_service: UserService = None,
|
||||
cookies_service: CookiesService = None,
|
||||
public_cookies_service: PublicCookiesService = None,
|
||||
):
|
||||
self.public_cookies_service = public_cookies_service
|
||||
self.cookies_service = cookies_service
|
||||
self.user_service = user_service
|
||||
|
||||
@conversation.entry_point
|
||||
@handler.command(command='setuid', filters=filters.ChatType.PRIVATE, block=True)
|
||||
@handler.command(command="setuid", filters=filters.ChatType.PRIVATE, block=True)
|
||||
@restricts()
|
||||
@error_callable
|
||||
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:
|
||||
cookies_command_data = AddUserCommandData()
|
||||
context.chat_data["add_uid_command_data"] = cookies_command_data
|
||||
text = f'你好 {user.mention_markdown_v2()} ' \
|
||||
text = (
|
||||
f"你好 {user.mention_markdown_v2()} "
|
||||
f'{escape_markdown("!请输入通行证UID(非游戏UID),BOT将会通过通行证UID查找游戏UID。请选择要绑定的服务器!或回复退出取消操作")}'
|
||||
reply_keyboard = [['米游社', 'HoYoLab'], ["退出"]]
|
||||
)
|
||||
reply_keyboard = [["米游社", "HoYoLab"], ["退出"]]
|
||||
await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
|
||||
return CHECK_SERVER
|
||||
|
||||
@ -112,8 +118,9 @@ class SetUserUid(Plugin.Conversation, BasePlugin.Conversation):
|
||||
if region == RegionEnum.HYPERION:
|
||||
client = genshin.Client(cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.CHINESE)
|
||||
elif region == RegionEnum.HOYOLAB:
|
||||
client = genshin.Client(cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.OVERSEAS,
|
||||
lang="zh-cn")
|
||||
client = genshin.Client(
|
||||
cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.OVERSEAS, lang="zh-cn"
|
||||
)
|
||||
else:
|
||||
return ConversationHandler.END
|
||||
try:
|
||||
@ -128,22 +135,20 @@ class SetUserUid(Plugin.Conversation, BasePlugin.Conversation):
|
||||
logger.exception(exc)
|
||||
return ConversationHandler.END
|
||||
if user_info.game != types.Game.GENSHIN:
|
||||
await message.reply_text("角色信息查询返回非原神游戏信息,"
|
||||
"请设置展示主界面为原神", reply_markup=ReplyKeyboardRemove())
|
||||
await message.reply_text("角色信息查询返回非原神游戏信息," "请设置展示主界面为原神", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
reply_keyboard = [['确认', '退出']]
|
||||
reply_keyboard = [["确认", "退出"]]
|
||||
await message.reply_text("获取角色基础信息成功,请检查是否正确!")
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 获取账号 {user_info.nickname}[{user_info.uid}] 信息成功")
|
||||
text = f"*角色信息*\n" \
|
||||
f"角色名称:{escape_markdown(user_info.nickname, version=2)}\n" \
|
||||
f"角色等级:{user_info.level}\n" \
|
||||
f"UID:`{user_info.uid}`\n" \
|
||||
text = (
|
||||
f"*角色信息*\n"
|
||||
f"角色名称:{escape_markdown(user_info.nickname, version=2)}\n"
|
||||
f"角色等级:{user_info.level}\n"
|
||||
f"UID:`{user_info.uid}`\n"
|
||||
f"服务器名称:`{user_info.server_name}`\n"
|
||||
add_user_command_data.game_uid = user_info.uid
|
||||
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
|
||||
|
||||
@conversation.state(state=COMMAND_RESULT)
|
||||
@ -159,11 +164,15 @@ class SetUserUid(Plugin.Conversation, BasePlugin.Conversation):
|
||||
elif message.text == "确认":
|
||||
if add_user_command_data.user is None:
|
||||
if add_user_command_data.region == RegionEnum.HYPERION:
|
||||
user_db = User(user_id=user.id, yuanshen_uid=add_user_command_data.game_uid,
|
||||
region=add_user_command_data.region)
|
||||
user_db = User(
|
||||
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:
|
||||
user_db = User(user_id=user.id, genshin_uid=add_user_command_data.game_uid,
|
||||
region=add_user_command_data.region)
|
||||
user_db = User(
|
||||
user_id=user.id, genshin_uid=add_user_command_data.game_uid, region=add_user_command_data.region
|
||||
)
|
||||
else:
|
||||
await message.reply_text("数据错误")
|
||||
return ConversationHandler.END
|
||||
|
@ -55,13 +55,9 @@ class UserStatsPlugins(Plugin, BasePlugin):
|
||||
except UserNotFoundError:
|
||||
reply_message = await message.reply_text("未查询到账号信息,请先私聊派蒙绑定账号")
|
||||
if filters.ChatType.GROUPS.filter(message):
|
||||
self._add_delete_message_job(
|
||||
context, reply_message.chat_id, reply_message.message_id, 30
|
||||
)
|
||||
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
|
||||
|
||||
self._add_delete_message_job(
|
||||
context, message.chat_id, message.message_id, 30
|
||||
)
|
||||
self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
|
||||
return
|
||||
except TooManyRequestPublicCookies:
|
||||
await message.reply_text("用户查询次数过多 请稍后重试")
|
||||
@ -72,9 +68,7 @@ class UserStatsPlugins(Plugin, BasePlugin):
|
||||
await message.reply_text("角色数据有误 估计是派蒙晕了")
|
||||
return
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
await message.reply_photo(
|
||||
png_data, filename=f"{client.uid}.png", allow_sending_without_reply=True
|
||||
)
|
||||
await message.reply_photo(png_data, filename=f"{client.uid}.png", allow_sending_without_reply=True)
|
||||
|
||||
async def render(self, client: Client, uid: Optional[int] = None) -> bytes:
|
||||
if uid is None:
|
||||
@ -109,7 +103,7 @@ class UserStatsPlugins(Plugin, BasePlugin):
|
||||
("雷神瞳", "electroculi"),
|
||||
("草神瞳", "dendroculi"),
|
||||
],
|
||||
"style": secrets.choice(["mondstadt", "liyue"])
|
||||
"style": secrets.choice(["mondstadt", "liyue"]),
|
||||
}
|
||||
|
||||
# html = await self.template_service.render_async(
|
||||
|
@ -20,15 +20,13 @@ from utils.log import logger
|
||||
class WeaponPlugin(Plugin, BasePlugin):
|
||||
"""武器查询"""
|
||||
|
||||
KEYBOARD = [[
|
||||
InlineKeyboardButton(text="查看武器列表并查询", switch_inline_query_current_chat="查看武器列表并查询")
|
||||
]]
|
||||
KEYBOARD = [[InlineKeyboardButton(text="查看武器列表并查询", switch_inline_query_current_chat="查看武器列表并查询")]]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
template_service: TemplateService = None,
|
||||
wiki_service: WikiService = None,
|
||||
assets_service: AssetsService = None
|
||||
assets_service: AssetsService = None,
|
||||
):
|
||||
self.wiki_service = wiki_service
|
||||
self.template_service = template_service
|
||||
@ -45,8 +43,7 @@ class WeaponPlugin(Plugin, BasePlugin):
|
||||
if len(args) >= 1:
|
||||
weapon_name = args[0]
|
||||
else:
|
||||
reply_message = await message.reply_text("请回复你要查询的武器",
|
||||
reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
|
||||
reply_message = await message.reply_text("请回复你要查询的武器", reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
|
||||
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, reply_message.chat_id, reply_message.message_id)
|
||||
@ -59,8 +56,9 @@ class WeaponPlugin(Plugin, BasePlugin):
|
||||
weapon_data = weapon
|
||||
break
|
||||
else:
|
||||
reply_message = await message.reply_text(f"没有找到 {weapon_name}",
|
||||
reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
|
||||
reply_message = await message.reply_text(
|
||||
f"没有找到 {weapon_name}", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
|
||||
)
|
||||
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, reply_message.chat_id, reply_message.message_id)
|
||||
@ -70,8 +68,8 @@ class WeaponPlugin(Plugin, BasePlugin):
|
||||
async def input_template_data(_weapon_data: Weapon):
|
||||
if weapon.rarity > 2:
|
||||
bonus = _weapon_data.stats[-1].bonus
|
||||
if '%' in bonus:
|
||||
bonus = str(round(float(bonus.rstrip('%')))) + '%'
|
||||
if "%" in bonus:
|
||||
bonus = str(round(float(bonus.rstrip("%")))) + "%"
|
||||
else:
|
||||
bonus = str(round(float(bonus)))
|
||||
_template_data = {
|
||||
@ -80,12 +78,12 @@ class WeaponPlugin(Plugin, BasePlugin):
|
||||
"progression_secondary_stat_value": bonus,
|
||||
"progression_secondary_stat_name": _weapon_data.attribute.type.value,
|
||||
"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(),
|
||||
"weapon_info_max_level": _weapon_data.stats[-1].level,
|
||||
"progression_base_atk": round(_weapon_data.stats[-1].ATK),
|
||||
"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:]
|
||||
],
|
||||
"special_ability_name": _weapon_data.affix.name,
|
||||
@ -95,25 +93,27 @@ class WeaponPlugin(Plugin, BasePlugin):
|
||||
_template_data = {
|
||||
"weapon_name": _weapon_data.name,
|
||||
"weapon_info_type_img": await url_to_file(_weapon_data.weapon_type.icon_url()),
|
||||
"progression_secondary_stat_value": ' ',
|
||||
"progression_secondary_stat_name": '无其它属性加成',
|
||||
"progression_secondary_stat_value": " ",
|
||||
"progression_secondary_stat_name": "无其它属性加成",
|
||||
"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(),
|
||||
"weapon_info_max_level": _weapon_data.stats[-1].level,
|
||||
"progression_base_atk": round(_weapon_data.stats[-1].ATK),
|
||||
"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:]
|
||||
],
|
||||
"special_ability_name": '',
|
||||
"special_ability_name": "",
|
||||
"special_ability_info": _weapon_data.description,
|
||||
}
|
||||
return _template_data
|
||||
|
||||
template_data = await input_template_data(weapon_data)
|
||||
png_data = await self.template_service.render('genshin/weapon', "weapon.html", template_data,
|
||||
{"width": 540, "height": 540})
|
||||
png_data = await self.template_service.render(
|
||||
"genshin/weapon", "weapon.html", template_data, {"width": 540, "height": 540}
|
||||
)
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
await message.reply_photo(png_data, filename=f"{template_data['weapon_name']}.png",
|
||||
allow_sending_without_reply=True)
|
||||
await message.reply_photo(
|
||||
png_data, filename=f"{template_data['weapon_name']}.png", allow_sending_without_reply=True
|
||||
)
|
||||
|
@ -8,7 +8,6 @@ from utils.log import logger
|
||||
|
||||
|
||||
class PublicCookies(Plugin):
|
||||
|
||||
def __init__(self, public_cookies_service: PublicCookiesService = None):
|
||||
self.public_cookies_service = public_cookies_service
|
||||
|
||||
|
@ -19,7 +19,6 @@ from utils.log import logger
|
||||
|
||||
|
||||
class PostHandlerData:
|
||||
|
||||
def __init__(self):
|
||||
self.post_text: str = ""
|
||||
self.post_images: Optional[List[ArtworkImage]] = None
|
||||
@ -41,7 +40,7 @@ class Post(Plugin.Conversation, BasePlugin):
|
||||
self.bbs = Hyperion()
|
||||
|
||||
@conversation.entry_point
|
||||
@handler.command(command='post', filters=filters.ChatType.PRIVATE, block=True)
|
||||
@handler.command(command="post", filters=filters.ChatType.PRIVATE, block=True)
|
||||
@restricts()
|
||||
@bot_admins_rights_check
|
||||
@error_callable
|
||||
@ -53,10 +52,8 @@ class Post(Plugin.Conversation, BasePlugin):
|
||||
if post_handler_data is None:
|
||||
post_handler_data = PostHandlerData()
|
||||
context.chat_data["post_handler_data"] = post_handler_data
|
||||
text = f"✿✿ヽ(°▽°)ノ✿ 你好! {user.username} ,\n" \
|
||||
"只需复制URL回复即可 \n" \
|
||||
"退出投稿只需回复退出"
|
||||
reply_keyboard = [['退出']]
|
||||
text = f"✿✿ヽ(°▽°)ノ✿ 你好! {user.username} ,\n" "只需复制URL回复即可 \n" "退出投稿只需回复退出"
|
||||
reply_keyboard = [["退出"]]
|
||||
await message.reply_text(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, True, True))
|
||||
return CHECK_POST
|
||||
|
||||
@ -77,11 +74,10 @@ class Post(Plugin.Conversation, BasePlugin):
|
||||
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_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_p = post_soup.find_all('p')
|
||||
post_text = f"*{escape_markdown(post_subject, version=2)}*\n" \
|
||||
f"\n"
|
||||
post_p = post_soup.find_all("p")
|
||||
post_text = f"*{escape_markdown(post_subject, version=2)}*\n" f"\n"
|
||||
for p in post_p:
|
||||
post_text += f"{escape_markdown(p.get_text(), version=2)}\n"
|
||||
post_text += f"[source](https://bbs.mihoyo.com/ys/article/{post_id})"
|
||||
@ -138,8 +134,7 @@ class Post(Plugin.Conversation, BasePlugin):
|
||||
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
|
||||
photo_len = len(post_handler_data.post_images)
|
||||
message = update.effective_message
|
||||
await message.reply_text("请回复你要删除的图片的序列,从1开始,如果删除多张图片回复的序列请以空格作为分隔符,"
|
||||
f"当前一共有 {photo_len} 张图片")
|
||||
await message.reply_text("请回复你要删除的图片的序列,从1开始,如果删除多张图片回复的序列请以空格作为分隔符," f"当前一共有 {photo_len} 张图片")
|
||||
return GTE_DELETE_PHOTO
|
||||
|
||||
@conversation.state(state=GTE_DELETE_PHOTO)
|
||||
@ -175,8 +170,7 @@ class Post(Plugin.Conversation, BasePlugin):
|
||||
logger.error("从配置文件获取频道信息发生错误,退出任务", error)
|
||||
await message.reply_text("从配置文件获取频道信息发生错误,退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
await message.reply_text("请选择你要推送的频道",
|
||||
reply_markup=ReplyKeyboardMarkup(reply_keyboard, True, True))
|
||||
await message.reply_text("请选择你要推送的频道", reply_markup=ReplyKeyboardMarkup(reply_keyboard, True, True))
|
||||
return GET_POST_CHANNEL
|
||||
|
||||
@conversation.state(state=GET_POST_CHANNEL)
|
||||
@ -200,8 +194,7 @@ class Post(Plugin.Conversation, BasePlugin):
|
||||
return ConversationHandler.END
|
||||
post_handler_data.channel_id = channel_id
|
||||
reply_keyboard = [["确认", "退出"]]
|
||||
await message.reply_text("请核对你修改的信息",
|
||||
reply_markup=ReplyKeyboardMarkup(reply_keyboard, True, True))
|
||||
await message.reply_text("请核对你修改的信息", reply_markup=ReplyKeyboardMarkup(reply_keyboard, True, True))
|
||||
return SEND_POST
|
||||
|
||||
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)
|
||||
elif len(post_images) == 1:
|
||||
image = post_images[0]
|
||||
await context.bot.send_photo(channel_id, photo=image.data, caption=post_text,
|
||||
parse_mode=ParseMode.MARKDOWN_V2)
|
||||
await context.bot.send_photo(
|
||||
channel_id, photo=image.data, caption=post_text, parse_mode=ParseMode.MARKDOWN_V2
|
||||
)
|
||||
elif len(post_images) == 0:
|
||||
await context.bot.send_message(channel_id, post_text, parse_mode=ParseMode.MARKDOWN_V2)
|
||||
else:
|
||||
@ -287,4 +281,3 @@ class Post(Plugin.Conversation, BasePlugin):
|
||||
return ConversationHandler.END
|
||||
await message.reply_text("推送成功", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
|
||||
|
@ -72,8 +72,9 @@ class GroupJoiningVerification(Plugin):
|
||||
job = context.job
|
||||
logger.info(f"踢出用户 user_id[{job.user_id}] 在 chat_id[{job.chat_id}]")
|
||||
try:
|
||||
await context.bot.ban_chat_member(chat_id=job.chat_id, user_id=job.user_id,
|
||||
until_date=int(time.time()) + self.kick_time)
|
||||
await context.bot.ban_chat_member(
|
||||
chat_id=job.chat_id, user_id=job.user_id, until_date=int(time.time()) + self.kick_time
|
||||
)
|
||||
except BadRequest as exc:
|
||||
logger.error(f"Auth模块在 chat_id[{job.chat_id}] user_id[{job.user_id}] 执行kick失败")
|
||||
logger.exception(exc)
|
||||
@ -88,8 +89,7 @@ class GroupJoiningVerification(Plugin):
|
||||
if "not found" in str(exc):
|
||||
logger.warning(f"Auth模块删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败 消息不存在")
|
||||
elif "Message can't be deleted" 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}]失败 消息无法删除 可能是没有授权")
|
||||
else:
|
||||
logger.error(f"Auth模块删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败")
|
||||
logger.exception(exc)
|
||||
@ -106,7 +106,6 @@ class GroupJoiningVerification(Plugin):
|
||||
@handler(CallbackQueryHandler, pattern=r"^auth_admin\|", block=False)
|
||||
@restricts(without_overlapping=True)
|
||||
async def admin(self, update: Update, context: CallbackContext) -> None:
|
||||
|
||||
async def admin_callback(callback_query_data: str) -> Tuple[str, int]:
|
||||
_data = callback_query_data.split("|")
|
||||
_result = _data[1]
|
||||
@ -122,8 +121,7 @@ class GroupJoiningVerification(Plugin):
|
||||
chat_administrators = await self.get_chat_administrators(context, chat_id=chat.id)
|
||||
if not self.is_admin(chat_administrators, user.id):
|
||||
logger.debug(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 非群管理")
|
||||
await callback_query.answer(text="你不是管理!\n"
|
||||
"再乱点我叫西风骑士团、千岩军和天领奉行了!", show_alert=True)
|
||||
await callback_query.answer(text="你不是管理!\n" "再乱点我叫西风骑士团、千岩军和天领奉行了!", show_alert=True)
|
||||
return
|
||||
result, user_id = await admin_callback(callback_query.data)
|
||||
try:
|
||||
@ -139,22 +137,21 @@ class GroupJoiningVerification(Plugin):
|
||||
await self.restore_member(context, chat.id, user_id)
|
||||
if schedule := context.job_queue.scheduler.get_job(f"{chat.id}|{user_id}|auth_kick"):
|
||||
schedule.remove()
|
||||
await message.edit_text(f"{user_info} 被 {user.mention_markdown_v2()} 放行",
|
||||
parse_mode=ParseMode.MARKDOWN_V2)
|
||||
await message.edit_text(f"{user_info} 被 {user.mention_markdown_v2()} 放行", parse_mode=ParseMode.MARKDOWN_V2)
|
||||
logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 被管理放行")
|
||||
elif result == "kick":
|
||||
await callback_query.answer(text="驱离", show_alert=False)
|
||||
await context.bot.ban_chat_member(chat.id, user_id)
|
||||
await message.edit_text(f"{user_info} 被 {user.mention_markdown_v2()} 驱离",
|
||||
parse_mode=ParseMode.MARKDOWN_V2)
|
||||
await message.edit_text(f"{user_info} 被 {user.mention_markdown_v2()} 驱离", parse_mode=ParseMode.MARKDOWN_V2)
|
||||
logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 被管理踢出")
|
||||
elif result == "unban":
|
||||
await callback_query.answer(text="解除驱离", show_alert=False)
|
||||
await self.restore_member(context, chat.id, user_id)
|
||||
if schedule := context.job_queue.scheduler.get_job(f"{chat.id}|{user_id}|auth_kick"):
|
||||
schedule.remove()
|
||||
await message.edit_text(f"{user_info} 被 {user.mention_markdown_v2()} 解除驱离",
|
||||
parse_mode=ParseMode.MARKDOWN_V2)
|
||||
await message.edit_text(
|
||||
f"{user_info} 被 {user.mention_markdown_v2()} 解除驱离", parse_mode=ParseMode.MARKDOWN_V2
|
||||
)
|
||||
logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 被管理解除封禁")
|
||||
else:
|
||||
logger.warning(f"auth 模块 admin 函数 发现未知命令 result[{result}]")
|
||||
@ -165,7 +162,6 @@ class GroupJoiningVerification(Plugin):
|
||||
@handler(CallbackQueryHandler, pattern=r"^auth_challenge\|", block=False)
|
||||
@restricts(without_overlapping=True)
|
||||
async def query(self, update: Update, context: CallbackContext) -> None:
|
||||
|
||||
async def query_callback(callback_query_data: str) -> Tuple[int, bool, str, str]:
|
||||
_data = callback_query_data.split("|")
|
||||
_user_id = int(_data[1])
|
||||
@ -176,8 +172,10 @@ class GroupJoiningVerification(Plugin):
|
||||
_result = _answer.is_correct
|
||||
_answer_encode = _answer.text
|
||||
_question_encode = _question.text
|
||||
logger.debug(f"query_callback函数返回 user_id[{_user_id}] result[{_result}] \n"
|
||||
f"question_encode[{_question_encode}] answer_encode[{_answer_encode}]")
|
||||
logger.debug(
|
||||
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
|
||||
|
||||
callback_query = update.callback_query
|
||||
@ -187,36 +185,43 @@ class GroupJoiningVerification(Plugin):
|
||||
user_id, result, question, answer = await query_callback(callback_query.data)
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 点击Auth认证命令 ")
|
||||
if user.id != user_id:
|
||||
await callback_query.answer(text="这不是你的验证!\n"
|
||||
"再乱点再按我叫西风骑士团、千岩军和天领奉行了!", show_alert=True)
|
||||
await callback_query.answer(text="这不是你的验证!\n" "再乱点再按我叫西风骑士团、千岩军和天领奉行了!", show_alert=True)
|
||||
return
|
||||
logger.info(
|
||||
f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 认证结果为 {'通过' if result else '失败'}")
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 认证结果为 {'通过' if result else '失败'}")
|
||||
if result:
|
||||
buttons = [[InlineKeyboardButton("驱离", callback_data=f"auth_admin|kick|{user.id}")]]
|
||||
await callback_query.answer(text="验证成功", show_alert=False)
|
||||
await self.restore_member(context, chat.id, user_id)
|
||||
if schedule := context.job_queue.scheduler.get_job(f"{chat.id}|{user.id}|auth_kick"):
|
||||
schedule.remove()
|
||||
text = f"{user.mention_markdown_v2()} 验证成功,向着星辰与深渊!\n" \
|
||||
f"问题:{escape_markdown(question, version=2)} \n" \
|
||||
text = (
|
||||
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}] 验证成功")
|
||||
else:
|
||||
buttons = [[InlineKeyboardButton("驱离", callback_data=f"auth_admin|kick|{user.id}"),
|
||||
InlineKeyboardButton("撤回驱离", callback_data=f"auth_admin|unban|{user.id}")]]
|
||||
buttons = [
|
||||
[
|
||||
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 asyncio.sleep(3)
|
||||
await context.bot.ban_chat_member(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" \
|
||||
await context.bot.ban_chat_member(
|
||||
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"
|
||||
f"回答:{escape_markdown(answer, version=2)}"
|
||||
)
|
||||
logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 验证失败")
|
||||
try:
|
||||
await message.edit_text(text, reply_markup=InlineKeyboardMarkup(buttons), parse_mode=ParseMode.MARKDOWN_V2)
|
||||
except BadRequest as exc:
|
||||
if 'are exactly the same as ' in str(exc):
|
||||
if "are exactly the same as " in str(exc):
|
||||
logger.warning("编辑消息发生异常,可能为用户点按多次键盘导致")
|
||||
else:
|
||||
raise exc
|
||||
@ -254,8 +259,9 @@ class GroupJoiningVerification(Plugin):
|
||||
await message.reply_text("旅行者!!!派蒙的问题清单你还没给我!!快去私聊我给我问题!")
|
||||
return
|
||||
try:
|
||||
await context.bot.restrict_chat_member(chat_id=message.chat.id, user_id=user.id,
|
||||
permissions=ChatPermissions(can_send_messages=False))
|
||||
await context.bot.restrict_chat_member(
|
||||
chat_id=message.chat.id, user_id=user.id, permissions=ChatPermissions(can_send_messages=False)
|
||||
)
|
||||
except BadRequest as err:
|
||||
if "Not enough rights" in str(err):
|
||||
logger.warning(f"权限不够 chat_id[{message.chat_id}]")
|
||||
@ -290,45 +296,59 @@ class GroupJoiningVerification(Plugin):
|
||||
),
|
||||
]
|
||||
)
|
||||
reply_message = f"*欢迎来到「提瓦特」世界!* \n" \
|
||||
f"问题: {escape_markdown(question.text, version=2)} \n" \
|
||||
f"请在 {self.time_out}S 内回答问题"
|
||||
logger.debug(f"发送入群验证问题 question_id[{question.question_id}] question[{question.text}] \n"
|
||||
f"给{user.full_name}[{user.id}] 在 {chat.title}[{chat.id}]")
|
||||
reply_message = (
|
||||
f"*欢迎来到「提瓦特」世界!* \n" f"问题: {escape_markdown(question.text, version=2)} \n" f"请在 {self.time_out}S 内回答问题"
|
||||
)
|
||||
logger.debug(
|
||||
f"发送入群验证问题 question_id[{question.question_id}] question[{question.text}] \n"
|
||||
f"给{user.full_name}[{user.id}] 在 {chat.title}[{chat.id}]"
|
||||
)
|
||||
try:
|
||||
question_message = await message.reply_markdown_v2(reply_message,
|
||||
reply_markup=InlineKeyboardMarkup(buttons))
|
||||
question_message = await message.reply_markdown_v2(
|
||||
reply_message, reply_markup=InlineKeyboardMarkup(buttons)
|
||||
)
|
||||
except BadRequest as exc:
|
||||
await message.reply_text("派蒙分心了一下,不小心忘记你了,你只能先退出群再重新进来吧。")
|
||||
raise exc
|
||||
context.job_queue.run_once(callback=self.kick_member_job, when=self.time_out,
|
||||
name=f"{chat.id}|{user.id}|auth_kick", chat_id=chat.id, user_id=user.id,
|
||||
job_kwargs={"replace_existing": True, "id": f"{chat.id}|{user.id}|auth_kick"})
|
||||
context.job_queue.run_once(callback=self.clean_message_job, when=self.time_out, data=message.message_id,
|
||||
context.job_queue.run_once(
|
||||
callback=self.kick_member_job,
|
||||
when=self.time_out,
|
||||
name=f"{chat.id}|{user.id}|auth_kick",
|
||||
chat_id=chat.id,
|
||||
user_id=user.id,
|
||||
job_kwargs={"replace_existing": True, "id": f"{chat.id}|{user.id}|auth_kick"},
|
||||
)
|
||||
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_clean_join_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,
|
||||
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"})
|
||||
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):
|
||||
from pyrogram.errors import BadRequest as MTPBadRequest, FloodWait as MTPFloodWait
|
||||
|
||||
try:
|
||||
messages_list = await self.mtp.get_messages(
|
||||
chat.id,
|
||||
message_ids=list(range(message.id + 1, question_message.id))
|
||||
chat.id, message_ids=list(range(message.id + 1, question_message.id))
|
||||
)
|
||||
for find_message in messages_list:
|
||||
if find_message.empty:
|
||||
continue
|
||||
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)
|
||||
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:
|
||||
logger.warning("调用 mtp 触发洪水限制")
|
||||
continue
|
||||
|
@ -24,7 +24,6 @@ if not os.path.exists(report_dir):
|
||||
|
||||
|
||||
class ErrorHandler(Plugin):
|
||||
|
||||
@error_handler(block=False) # pylint: disable=E1123, E1120
|
||||
async def error_handler(self, update: object, context: CallbackContext) -> None:
|
||||
"""记录错误并发送消息通知开发人员。 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"
|
||||
log_file = os.path.join(report_dir, file_name)
|
||||
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)
|
||||
except Exception as exc:
|
||||
logger.error("保存日记失败")
|
||||
logger.exception(exc)
|
||||
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("其他机器人在运行,请停止!")
|
||||
return
|
||||
if 'Message is not modified' in tb_string:
|
||||
if "Message is not modified" in tb_string:
|
||||
logger.error("消息未修改")
|
||||
return
|
||||
await context.bot.send_document(chat_id=notice_chat_id, document=open(log_file, "rb"),
|
||||
caption=f"Error: \"{context.error.__class__.__name__}\"")
|
||||
await context.bot.send_document(
|
||||
chat_id=notice_chat_id,
|
||||
document=open(log_file, "rb"),
|
||||
caption=f'Error: "{context.error.__class__.__name__}"',
|
||||
)
|
||||
except (BadRequest, Forbidden) as exc:
|
||||
logger.error("发送日记失败")
|
||||
logger.exception(exc)
|
||||
@ -76,12 +78,15 @@ class ErrorHandler(Plugin):
|
||||
try:
|
||||
if effective_message is not None:
|
||||
chat = effective_message.chat
|
||||
logger.info(f"尝试通知用户 {effective_user.full_name}[{effective_user.id}] "
|
||||
logger.info(
|
||||
f"尝试通知用户 {effective_user.full_name}[{effective_user.id}] "
|
||||
f"在 {chat.full_name}[{chat.id}]"
|
||||
f"的 update_id[{update.update_id}] 错误信息")
|
||||
f"的 update_id[{update.update_id}] 错误信息"
|
||||
)
|
||||
text = "出错了呜呜呜 ~ 派蒙这边发生了点问题无法处理!"
|
||||
await context.bot.send_message(effective_message.chat_id, text, reply_markup=ReplyKeyboardRemove(),
|
||||
parse_mode=ParseMode.HTML)
|
||||
await context.bot.send_message(
|
||||
effective_message.chat_id, text, reply_markup=ReplyKeyboardRemove(), parse_mode=ParseMode.HTML
|
||||
)
|
||||
except (BadRequest, Forbidden) as exc:
|
||||
logger.error(f"发送 update_id[{update.update_id}] 错误信息失败 错误信息为")
|
||||
logger.exception(exc)
|
||||
|
@ -25,15 +25,14 @@ class GetChat(Plugin):
|
||||
|
||||
@staticmethod
|
||||
def parse_group_chat(chat: Chat, admins: List[ChatMember]) -> str:
|
||||
text = f"群 ID:<code>{chat.id}</code>\n" \
|
||||
f"群名称:<code>{chat.title}</code>\n"
|
||||
text = f"群 ID:<code>{chat.id}</code>\n" f"群名称:<code>{chat.title}</code>\n"
|
||||
if chat.username:
|
||||
text += f"群用户名:<code>{chat.username}</code>\n"
|
||||
if chat.description:
|
||||
text += f"群简介:<code>{chat.description}</code>\n"
|
||||
if 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):
|
||||
text += "C" if admin.can_change_info else "_"
|
||||
text += "D" if admin.can_delete_messages else "_"
|
||||
@ -49,9 +48,11 @@ class GetChat(Plugin):
|
||||
return text
|
||||
|
||||
async def parse_private_chat(self, chat: Chat) -> str:
|
||||
text = f"<a href=\"tg://user?id={chat.id}\">MENTION</a>\n" \
|
||||
f"用户 ID:<code>{chat.id}</code>\n" \
|
||||
text = (
|
||||
f'<a href="tg://user?id={chat.id}">MENTION</a>\n'
|
||||
f"用户 ID:<code>{chat.id}</code>\n"
|
||||
f"用户名称:<code>{chat.full_name}</code>\n"
|
||||
)
|
||||
if chat.username:
|
||||
text += f"用户名:@{chat.username}\n"
|
||||
try:
|
||||
@ -70,8 +71,7 @@ class GetChat(Plugin):
|
||||
await get_genshin_client(chat.id)
|
||||
except CookiesNotFoundError:
|
||||
temp = "UID 绑定"
|
||||
text += f"<code>{temp}</code>\n" \
|
||||
f"游戏 ID:<code>{uid}</code>"
|
||||
text += f"<code>{temp}</code>\n" f"游戏 ID:<code>{uid}</code>"
|
||||
with contextlib.suppress(Exception):
|
||||
gacha_log, status = await GachaLog.load_history_info(str(chat.id), str(uid))
|
||||
if status:
|
||||
|
@ -36,9 +36,11 @@ class Inline(Plugin):
|
||||
id=str(uuid4()),
|
||||
title=weapons_name,
|
||||
description=f"查看武器列表并查询 {weapons_name}",
|
||||
input_message_content=InputTextMessageContent(f"武器查询{weapons_name}",
|
||||
parse_mode=ParseMode.MARKDOWN_V2)
|
||||
))
|
||||
input_message_content=InputTextMessageContent(
|
||||
f"武器查询{weapons_name}", parse_mode=ParseMode.MARKDOWN_V2
|
||||
),
|
||||
)
|
||||
)
|
||||
elif "查看角色攻略列表并查询" == args[0]:
|
||||
characters_list = await self.wiki_service.get_characters_name_list()
|
||||
for role_name in characters_list:
|
||||
@ -47,9 +49,11 @@ class Inline(Plugin):
|
||||
id=str(uuid4()),
|
||||
title=role_name,
|
||||
description=f"查看角色攻略列表并查询 {role_name}",
|
||||
input_message_content=InputTextMessageContent(f"角色攻略查询{role_name}",
|
||||
parse_mode=ParseMode.MARKDOWN_V2)
|
||||
))
|
||||
input_message_content=InputTextMessageContent(
|
||||
f"角色攻略查询{role_name}", parse_mode=ParseMode.MARKDOWN_V2
|
||||
),
|
||||
)
|
||||
)
|
||||
elif "查看角色培养素材列表并查询" == args[0]:
|
||||
characters_list = await self.wiki_service.get_characters_name_list()
|
||||
for role_name in characters_list:
|
||||
@ -58,9 +62,11 @@ class Inline(Plugin):
|
||||
id=str(uuid4()),
|
||||
title=role_name,
|
||||
description=f"查看角色培养素材列表并查询 {role_name}",
|
||||
input_message_content=InputTextMessageContent(f"角色培养素材查询{role_name}",
|
||||
parse_mode=ParseMode.MARKDOWN_V2)
|
||||
))
|
||||
input_message_content=InputTextMessageContent(
|
||||
f"角色培养素材查询{role_name}", parse_mode=ParseMode.MARKDOWN_V2
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
if not results_list:
|
||||
results_list.append(
|
||||
@ -69,7 +75,8 @@ class Inline(Plugin):
|
||||
title="好像找不到问题呢",
|
||||
description="这个问题我也不知道,因为我就是个应急食品。",
|
||||
input_message_content=InputTextMessageContent("这个问题我也不知道,因为我就是个应急食品。"),
|
||||
))
|
||||
)
|
||||
)
|
||||
try:
|
||||
await ilq.answer(
|
||||
results=results_list,
|
||||
|
@ -13,7 +13,6 @@ debug_log = os.path.join(current_dir, "logs", "debug", "debug.log")
|
||||
|
||||
|
||||
class Log(Plugin):
|
||||
|
||||
@handler(CommandHandler, command="send_log", block=False)
|
||||
@bot_admins_rights_check
|
||||
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 命令请求")
|
||||
message = update.effective_message
|
||||
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:
|
||||
await message.reply_text("错误日记未找到")
|
||||
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:
|
||||
await message.reply_text("调试日记未找到")
|
||||
|
@ -7,7 +7,6 @@ from utils.log import logger
|
||||
|
||||
|
||||
class BotJoiningGroupsVerification(Plugin):
|
||||
|
||||
def __init__(self, bot_admin_service: BotAdminService = None):
|
||||
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")
|
||||
admin_list = await self.bot_admin_service.get_admin_list()
|
||||
if from_user.id in admin_list:
|
||||
await context.bot.send_message(message.chat_id,
|
||||
'感谢邀请小派蒙到本群!请使用 /help 查看咱已经学会的功能。')
|
||||
await context.bot.send_message(message.chat_id, "感谢邀请小派蒙到本群!请使用 /help 查看咱已经学会的功能。")
|
||||
quit_status = False
|
||||
else:
|
||||
logger.info(f"未知用户 在群 {chat.title}[{chat.id}] 邀请BOT")
|
||||
|
@ -8,16 +8,13 @@ from utils.log import logger
|
||||
|
||||
|
||||
class MetadataPlugin(Plugin):
|
||||
|
||||
@handler.command('refresh_metadata')
|
||||
@handler.command("refresh_metadata")
|
||||
@bot_admins_rights_check
|
||||
async def refresh(self, update: Update, _) -> None:
|
||||
user = update.effective_user
|
||||
message = update.effective_message
|
||||
|
||||
logger.info(
|
||||
f"用户 {user.full_name}[{user.id}] 刷新[bold]metadata[/]缓存命令", extra={'markup': True}
|
||||
)
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 刷新[bold]metadata[/]缓存命令", extra={"markup": True})
|
||||
|
||||
msg = await message.reply_text("正在刷新元数据,请耐心等待...")
|
||||
logger.info("正在从 github 上获取元数据")
|
||||
|
@ -23,7 +23,7 @@ from utils.log import logger
|
||||
GET_NEW_CORRECT_ANSWER,
|
||||
GET_NEW_WRONG_ANSWER,
|
||||
QUESTION_EDIT,
|
||||
SAVE_QUESTION
|
||||
SAVE_QUESTION,
|
||||
) = range(10300, 10308)
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
|
||||
self.time_out = 120
|
||||
|
||||
@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()
|
||||
@bot_admins_rights_check
|
||||
@error_callable
|
||||
@ -56,32 +56,20 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
|
||||
quiz_command_data = QuizCommandData()
|
||||
context.chat_data["quiz_command_data"] = quiz_command_data
|
||||
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))
|
||||
return CHECK_COMMAND
|
||||
|
||||
async def view_command(self, update: Update, _: CallbackContext) -> int:
|
||||
_ = self
|
||||
keyboard = [
|
||||
[
|
||||
InlineKeyboardButton(text="选择问题", switch_inline_query_current_chat="查看问题 ")
|
||||
]
|
||||
]
|
||||
await update.message.reply_text("请回复你要查看的问题",
|
||||
reply_markup=InlineKeyboardMarkup(keyboard))
|
||||
keyboard = [[InlineKeyboardButton(text="选择问题", switch_inline_query_current_chat="查看问题 ")]]
|
||||
await update.message.reply_text("请回复你要查看的问题", reply_markup=InlineKeyboardMarkup(keyboard))
|
||||
return CHECK_COMMAND
|
||||
|
||||
@conversation.state(state=CHECK_QUESTION)
|
||||
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
|
||||
async def check_question(self, update: Update, _: CallbackContext) -> int:
|
||||
reply_keyboard = [
|
||||
["删除问题"],
|
||||
["退出"]
|
||||
]
|
||||
reply_keyboard = [["删除问题"], ["退出"]]
|
||||
await update.message.reply_text("请选择你的操作", reply_markup=ReplyKeyboardMarkup(reply_keyboard))
|
||||
return CHECK_COMMAND
|
||||
|
||||
@ -124,8 +112,7 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
|
||||
return ConversationHandler.END
|
||||
except ResponseError as error:
|
||||
logger.error("重载问题失败", error)
|
||||
await update.message.reply_text("重载问题失败,异常抛出Redis请求错误异常,详情错误请看日记",
|
||||
reply_markup=ReplyKeyboardRemove())
|
||||
await update.message.reply_text("重载问题失败,异常抛出Redis请求错误异常,详情错误请看日记", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
await update.message.reply_text("重载成功", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
@ -137,8 +124,7 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
|
||||
quiz_command_data.new_question = ""
|
||||
quiz_command_data.new_correct_answer = ""
|
||||
quiz_command_data.status = 1
|
||||
await update.message.reply_text("请回复你要添加的问题,或发送 /cancel 取消操作",
|
||||
reply_markup=ReplyKeyboardRemove())
|
||||
await update.message.reply_text("请回复你要添加的问题,或发送 /cancel 取消操作", reply_markup=ReplyKeyboardRemove())
|
||||
return 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:
|
||||
message = update.effective_message
|
||||
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
|
||||
reply_text = f"问题:`{escape_markdown(update.message.text, version=2)}`\n" \
|
||||
f"请填写正确答案:"
|
||||
reply_text = f"问题:`{escape_markdown(update.message.text, version=2)}`\n" f"请填写正确答案:"
|
||||
quiz_command_data.new_question = message.text
|
||||
await update.message.reply_markdown_v2(reply_text)
|
||||
return GET_NEW_CORRECT_ANSWER
|
||||
@ -156,19 +141,20 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
|
||||
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
|
||||
async def get_new_correct_answer(self, update: Update, context: CallbackContext) -> int:
|
||||
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
|
||||
reply_text = f"正确答案:`{escape_markdown(update.message.text, version=2)}`\n" \
|
||||
f"请填写错误答案:"
|
||||
reply_text = f"正确答案:`{escape_markdown(update.message.text, version=2)}`\n" f"请填写错误答案:"
|
||||
await update.message.reply_markdown_v2(reply_text)
|
||||
quiz_command_data.new_correct_answer = update.message.text
|
||||
return GET_NEW_WRONG_ANSWER
|
||||
|
||||
@conversation.state(state=GET_NEW_WRONG_ANSWER)
|
||||
@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:
|
||||
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"可继续填写,并使用 {escape_markdown('/finish', version=2)} 结束。"
|
||||
)
|
||||
await update.message.reply_markdown_v2(reply_text)
|
||||
quiz_command_data.new_wrong_answer.append(update.message.text)
|
||||
return GET_NEW_WRONG_ANSWER
|
||||
@ -176,13 +162,14 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
|
||||
async def finish_edit(self, update: Update, context: CallbackContext):
|
||||
_ = self
|
||||
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
|
||||
reply_text = f"问题:`{escape_markdown(quiz_command_data.new_question, version=2)}`\n" \
|
||||
f"正确答案:`{escape_markdown(quiz_command_data.new_correct_answer, version=2)}`\n" \
|
||||
reply_text = (
|
||||
f"问题:`{escape_markdown(quiz_command_data.new_question, version=2)}`\n"
|
||||
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)
|
||||
reply_keyboard = [["保存并重载配置", "抛弃修改并退出"]]
|
||||
await update.message.reply_text("请核对问题,并选择下一步操作。",
|
||||
reply_markup=ReplyKeyboardMarkup(reply_keyboard))
|
||||
await update.message.reply_text("请核对问题,并选择下一步操作。", reply_markup=ReplyKeyboardMarkup(reply_keyboard))
|
||||
return SAVE_QUESTION
|
||||
|
||||
@conversation.state(state=SAVE_QUESTION)
|
||||
@ -195,19 +182,18 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
|
||||
elif update.message.text == "保存并重载配置":
|
||||
if quiz_command_data.status == 1:
|
||||
answer = [
|
||||
Answer(text=wrong_answer, is_correct=False) for wrong_answer in
|
||||
quiz_command_data.new_wrong_answer
|
||||
Answer(text=wrong_answer, is_correct=False) for wrong_answer in quiz_command_data.new_wrong_answer
|
||||
]
|
||||
answer.append(Answer(text=quiz_command_data.new_correct_answer, is_correct=True))
|
||||
await self.quiz_service.save_quiz(
|
||||
Question(text=quiz_command_data.new_question))
|
||||
await self.quiz_service.save_quiz(Question(text=quiz_command_data.new_question))
|
||||
await update.message.reply_text("保存成功", reply_markup=ReplyKeyboardRemove())
|
||||
try:
|
||||
await self.quiz_service.refresh_quiz()
|
||||
except ResponseError as error:
|
||||
logger.error("重载问题失败", error)
|
||||
await update.message.reply_text("重载问题失败,异常抛出Redis请求错误异常,详情错误请看日记",
|
||||
reply_markup=ReplyKeyboardRemove())
|
||||
await update.message.reply_text(
|
||||
"重载问题失败,异常抛出Redis请求错误异常,详情错误请看日记", reply_markup=ReplyKeyboardRemove()
|
||||
)
|
||||
return ConversationHandler.END
|
||||
await update.message.reply_text("重载配置成功", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
@ -239,8 +225,7 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
|
||||
await self.quiz_service.refresh_quiz()
|
||||
except ResponseError as error:
|
||||
logger.error("重载问题失败", error)
|
||||
await update.message.reply_text("重载问题失败,异常抛出Redis请求错误异常,详情错误请看日记",
|
||||
reply_markup=ReplyKeyboardRemove())
|
||||
await update.message.reply_text("重载问题失败,异常抛出Redis请求错误异常,详情错误请看日记", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
await update.message.reply_text("重载配置成功", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
|
@ -7,7 +7,6 @@ from utils.decorators.restricts import restricts
|
||||
|
||||
|
||||
class StartPlugin(Plugin):
|
||||
|
||||
@handler(CommandHandler, command="start", block=False)
|
||||
@restricts()
|
||||
async def start(self, update: Update, context: CallbackContext) -> None:
|
||||
@ -15,8 +14,10 @@ class StartPlugin(Plugin):
|
||||
message = update.effective_message
|
||||
args = context.args
|
||||
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"
|
||||
f"{escape_markdown('发送 /help 命令即可查看命令帮助')}")
|
||||
await message.reply_markdown_v2(
|
||||
f"你好 {user.mention_markdown_v2()} {escape_markdown('!我是派蒙 !')}\n"
|
||||
f"{escape_markdown('发送 /help 命令即可查看命令帮助')}"
|
||||
)
|
||||
return
|
||||
await message.reply_markdown_v2(f"你好 {user.mention_markdown_v2()} {escape_markdown('!我是派蒙 !')}")
|
||||
|
||||
|
@ -21,7 +21,6 @@ UPDATE_DATA = os.path.join(current_dir, "data", "update.json")
|
||||
|
||||
|
||||
class UpdatePlugin(Plugin):
|
||||
|
||||
def __init__(self):
|
||||
self._lock = asyncio.Lock()
|
||||
|
||||
@ -59,6 +58,6 @@ class UpdatePlugin(Plugin):
|
||||
await execute(f"{executable} -m poetry install --extras all")
|
||||
logger.info(f"更新成功 正在重启")
|
||||
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())
|
||||
raise SystemExit
|
||||
|
477
resources/genshin/abyss_team/example2.html
Normal file
477
resources/genshin/abyss_team/example2.html
Normal 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
2
run.py
@ -5,5 +5,5 @@ def main():
|
||||
bot.launch()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
@ -20,17 +20,22 @@ async def artifact_rate():
|
||||
# noinspection PyShadowingNames
|
||||
@pytest.mark.asyncio
|
||||
class TestArtifactOcrRate:
|
||||
|
||||
@staticmethod
|
||||
@flaky(3, 1)
|
||||
async def test_rate_artifact(artifact_rate):
|
||||
artifact_attr = {
|
||||
'name': '翠绿的猎人之冠', 'pos': '理之冠', 'star': 5, 'level': 20,
|
||||
'main_item': {'type': 'cr', 'name': '暴击率', 'value': '31.1%'},
|
||||
'sub_item': [{'type': 'hp', 'name': '生命值', 'value': '9.3%'},
|
||||
{'type': 'df', 'name': '防御力', 'value': '46'},
|
||||
{'type': 'atk', 'name': '攻击力', 'value': '49'},
|
||||
{'type': 'cd', 'name': '暴击伤害', 'value': '10.9%'}]}
|
||||
"name": "翠绿的猎人之冠",
|
||||
"pos": "理之冠",
|
||||
"star": 5,
|
||||
"level": 20,
|
||||
"main_item": {"type": "cr", "name": "暴击率", "value": "31.1%"},
|
||||
"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)
|
||||
|
||||
@staticmethod
|
||||
|
@ -28,13 +28,13 @@ async def test_get_post_info(hyperion):
|
||||
post_info = await hyperion.get_post_info(2, 29023709)
|
||||
assert post_info
|
||||
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"]["post"]["subject"] == "《原神》长期项目启动·概念PV"
|
||||
assert post_info.subject == "《原神》长期项目启动·概念PV"
|
||||
assert len(post_info["post"]["post"]["images"]) == 1
|
||||
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
|
||||
|
@ -24,32 +24,32 @@ def event_loop():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestWeapon:
|
||||
|
||||
@staticmethod
|
||||
@flaky(3, 1)
|
||||
async def test_get_by_id():
|
||||
weapon = await Weapon.get_by_id('i_n11417')
|
||||
assert weapon.name == '原木刀'
|
||||
weapon = await Weapon.get_by_id("i_n11417")
|
||||
assert weapon.name == "原木刀"
|
||||
assert weapon.rarity == 4
|
||||
assert weapon.attack == 43.73
|
||||
assert weapon.attribute.type.value == '元素充能效率'
|
||||
assert weapon.affix.name == '森林的瑞佑'
|
||||
assert weapon.attribute.type.value == "元素充能效率"
|
||||
assert weapon.affix.name == "森林的瑞佑"
|
||||
|
||||
@staticmethod
|
||||
@flaky(3, 1)
|
||||
async def test_get_by_name():
|
||||
weapon = await Weapon.get_by_name('风鹰剑')
|
||||
assert weapon.id == 'i_n11501'
|
||||
weapon = await Weapon.get_by_name("风鹰剑")
|
||||
assert weapon.id == "i_n11501"
|
||||
assert weapon.rarity == 5
|
||||
assert weapon.attack == 47.54
|
||||
assert weapon.attribute.type.value == '物理伤害加成'
|
||||
assert weapon.affix.name == '西风之鹰的抗争'
|
||||
assert '听凭风引,便是正义与自由之风' in weapon.story
|
||||
assert weapon.attribute.type.value == "物理伤害加成"
|
||||
assert weapon.affix.name == "西风之鹰的抗争"
|
||||
assert "听凭风引,便是正义与自由之风" in weapon.story
|
||||
|
||||
@staticmethod
|
||||
@flaky(3, 1)
|
||||
async def test_name_list():
|
||||
from httpx import URL
|
||||
|
||||
async for name in Weapon._name_list_generator(with_url=True):
|
||||
assert isinstance(name[0], str)
|
||||
assert isinstance(name[1], URL)
|
||||
@ -57,34 +57,34 @@ class TestWeapon:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestCharacter:
|
||||
|
||||
@staticmethod
|
||||
@flaky(3, 1)
|
||||
async def test_get_by_id():
|
||||
character = await Character.get_by_id('ayaka_002')
|
||||
assert character.name == '神里绫华'
|
||||
assert character.title == '白鹭霜华'
|
||||
assert character.occupation == '社奉行'
|
||||
assert character.association.value == '稻妻'
|
||||
assert character.cn_cv == '小N'
|
||||
character = await Character.get_by_id("ayaka_002")
|
||||
assert character.name == "神里绫华"
|
||||
assert character.title == "白鹭霜华"
|
||||
assert character.occupation == "社奉行"
|
||||
assert character.association.value == "稻妻"
|
||||
assert character.cn_cv == "小N"
|
||||
|
||||
@staticmethod
|
||||
@flaky(3, 1)
|
||||
async def test_get_by_name():
|
||||
character = await Character.get_by_name('神里绫华')
|
||||
assert character.name == '神里绫华'
|
||||
assert character.title == '白鹭霜华'
|
||||
assert character.occupation == '社奉行'
|
||||
assert character.association.value == '稻妻'
|
||||
assert character.cn_cv == '小N'
|
||||
main_character = await Character.get_by_name('荧')
|
||||
assert main_character.constellation == '旅人座'
|
||||
assert main_character.cn_cv == '宴宁&多多poi'
|
||||
character = await Character.get_by_name("神里绫华")
|
||||
assert character.name == "神里绫华"
|
||||
assert character.title == "白鹭霜华"
|
||||
assert character.occupation == "社奉行"
|
||||
assert character.association.value == "稻妻"
|
||||
assert character.cn_cv == "小N"
|
||||
main_character = await Character.get_by_name("荧")
|
||||
assert main_character.constellation == "旅人座"
|
||||
assert main_character.cn_cv == "宴宁&多多poi"
|
||||
|
||||
@staticmethod
|
||||
@flaky(3, 1)
|
||||
async def test_name_list():
|
||||
from httpx import URL
|
||||
|
||||
async for name in Character._name_list_generator(with_url=True):
|
||||
assert isinstance(name[0], str)
|
||||
assert isinstance(name[1], URL)
|
||||
@ -92,41 +92,41 @@ class TestCharacter:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestMaterial:
|
||||
|
||||
@staticmethod
|
||||
@flaky(3, 1)
|
||||
async def test_get_by_id():
|
||||
material = await Material.get_by_id('i_504')
|
||||
assert material.name == '高塔孤王的碎梦'
|
||||
assert material.type == '武器突破素材'
|
||||
assert '合成获得' in material.source
|
||||
assert '巴巴托斯' in material.description
|
||||
material = await Material.get_by_id("i_504")
|
||||
assert material.name == "高塔孤王的碎梦"
|
||||
assert material.type == "武器突破素材"
|
||||
assert "合成获得" in material.source
|
||||
assert "巴巴托斯" in material.description
|
||||
|
||||
material = await Material.get_by_id('i_483')
|
||||
assert material.name == '凶将之手眼'
|
||||
assert material.type == '角色培养素材'
|
||||
assert '70级以上永恒的守护者挑战奖励' in material.source
|
||||
assert '所见即所为' in material.description
|
||||
material = await Material.get_by_id("i_483")
|
||||
assert material.name == "凶将之手眼"
|
||||
assert material.type == "角色培养素材"
|
||||
assert "70级以上永恒的守护者挑战奖励" in material.source
|
||||
assert "所见即所为" in material.description
|
||||
|
||||
@staticmethod
|
||||
@flaky(3, 1)
|
||||
async def test_get_by_name():
|
||||
material = await Material.get_by_name('地脉的新芽')
|
||||
assert material.id == 'i_73'
|
||||
assert material.type == '角色培养素材'
|
||||
assert '60级以上深渊法师掉落' in material.source
|
||||
assert '勃发' in material.description
|
||||
material = await Material.get_by_name("地脉的新芽")
|
||||
assert material.id == "i_73"
|
||||
assert material.type == "角色培养素材"
|
||||
assert "60级以上深渊法师掉落" in material.source
|
||||
assert "勃发" in material.description
|
||||
|
||||
material = await Material.get_by_name('「黄金」的教导')
|
||||
assert material.id == 'i_431'
|
||||
assert material.type == '天赋培养素材'
|
||||
material = await Material.get_by_name("「黄金」的教导")
|
||||
assert material.id == "i_431"
|
||||
assert material.type == "天赋培养素材"
|
||||
assert 2 in material.weekdays
|
||||
assert '土的象' in material.description
|
||||
assert "土的象" in material.description
|
||||
|
||||
@staticmethod
|
||||
@flaky(3, 1)
|
||||
async def test_name_list():
|
||||
from httpx import URL
|
||||
|
||||
async for name in Material._name_list_generator(with_url=True):
|
||||
assert isinstance(name[0], str)
|
||||
assert isinstance(name[1], URL)
|
||||
@ -134,11 +134,11 @@ class TestMaterial:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestAll:
|
||||
|
||||
@staticmethod
|
||||
@flaky(3, 1)
|
||||
async def make_test(target: Type[WikiModel]):
|
||||
from httpx import URL
|
||||
|
||||
name_list = await target.get_name_list(with_url=True)
|
||||
name_len = len(name_list)
|
||||
assert name_len != 0
|
||||
|
@ -22,8 +22,7 @@ class BaseObject:
|
||||
return getattr(self, item)
|
||||
except AttributeError as exc:
|
||||
raise KeyError(
|
||||
f"Objects of type {self.__class__.__name__} don't have an attribute called "
|
||||
f"`{item}`."
|
||||
f"Objects of type {self.__class__.__name__} don't have an attribute called " f"`{item}`."
|
||||
) from exc
|
||||
|
||||
def __getstate__(self) -> Dict[str, Union[str, object]]:
|
||||
@ -47,7 +46,11 @@ class BaseObject:
|
||||
# 添加插槽可减少内存使用,并允许更快的属性访问
|
||||
__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 = {}
|
||||
if not recursive:
|
||||
try:
|
||||
|
@ -4,19 +4,24 @@ from pathlib import Path
|
||||
from httpx import URL
|
||||
|
||||
__all__ = [
|
||||
'PROJECT_ROOT', 'PLUGIN_DIR', 'RESOURCE_DIR',
|
||||
'NOT_SET',
|
||||
'HONEY_HOST', 'ENKA_HOST', 'AMBR_HOST', 'CELESTIA_HOST',
|
||||
"PROJECT_ROOT",
|
||||
"PLUGIN_DIR",
|
||||
"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_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()
|
||||
|
||||
|
@ -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))
|
||||
return
|
||||
chat = message.chat
|
||||
logger.info(f"尝试通知用户 {effective_user.full_name}[{effective_user.id}] "
|
||||
f"在 {chat.full_name}[{chat.id}]"
|
||||
f"的 错误信息[{text}]")
|
||||
logger.info(
|
||||
f"尝试通知用户 {effective_user.full_name}[{effective_user.id}] " f"在 {chat.full_name}[{chat.id}]" f"的 错误信息[{text}]"
|
||||
)
|
||||
try:
|
||||
await message.reply_text(text, reply_markup=ReplyKeyboardRemove(), allow_sending_without_reply=True)
|
||||
except BadRequest as exc:
|
||||
@ -81,8 +81,7 @@ def error_callable(func: Callable) -> Callable:
|
||||
if exc.retcode in (10001, -100):
|
||||
await send_user_notification(update, context, "出错了呜呜呜 ~ Cookies无效,请尝试重新绑定账户")
|
||||
elif exc.retcode == 10103:
|
||||
await send_user_notification(update, context, "出错了呜呜呜 ~ Cookie有效,但没有绑定到游戏帐户,"
|
||||
"请尝试重新绑定邮游戏账户")
|
||||
await send_user_notification(update, context, "出错了呜呜呜 ~ Cookie有效,但没有绑定到游戏帐户," "请尝试重新绑定邮游戏账户")
|
||||
else:
|
||||
logger.warning("Cookie错误")
|
||||
logger.exception(exc)
|
||||
|
@ -11,8 +11,12 @@ from utils.log import logger
|
||||
_lock = asyncio.Lock()
|
||||
|
||||
|
||||
def restricts(restricts_time: int = 9, restricts_time_of_groups: Optional[int] = None, return_data: Any = None,
|
||||
without_overlapping: bool = False):
|
||||
def restricts(
|
||||
restricts_time: int = 9,
|
||||
restricts_time_of_groups: Optional[int] = None,
|
||||
return_data: Any = None,
|
||||
without_overlapping: bool = False,
|
||||
):
|
||||
"""用于装饰在指定函数预防洪水攻击的装饰器
|
||||
|
||||
被修饰的函数生声明必须为
|
||||
|
@ -20,12 +20,14 @@ from utils.error import UrlResourcesNotFoundError
|
||||
from utils.log import logger
|
||||
from utils.models.base import RegionEnum
|
||||
|
||||
T = TypeVar('T')
|
||||
P = ParamSpec('P')
|
||||
T = TypeVar("T")
|
||||
P = ParamSpec("P")
|
||||
|
||||
USER_AGENT: str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " \
|
||||
USER_AGENT: str = (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/90.0.4430.72 Safari/537.36"
|
||||
REQUEST_HEADERS: dict = {'User-Agent': USER_AGENT}
|
||||
)
|
||||
REQUEST_HEADERS: dict = {"User-Agent": USER_AGENT}
|
||||
current_dir = os.getcwd()
|
||||
cache_dir = os.path.join(current_dir, "cache")
|
||||
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:
|
||||
logger.error(f"url_to_file 获取url[{url}] 错误 status_code[f{data.status_code}]")
|
||||
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)
|
||||
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)
|
||||
elif region == RegionEnum.HOYOLAB:
|
||||
uid = user.genshin_uid
|
||||
client = genshin.Client(cookies=cookies,
|
||||
game=types.Game.GENSHIN, region=types.Region.OVERSEAS, lang="zh-cn", uid=uid)
|
||||
client = genshin.Client(
|
||||
cookies=cookies, game=types.Game.GENSHIN, region=types.Region.OVERSEAS, lang="zh-cn", uid=uid
|
||||
)
|
||||
else:
|
||||
raise TypeError("region is not RegionEnum.NULL")
|
||||
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)
|
||||
elif region == RegionEnum.HOYOLAB:
|
||||
uid = user.genshin_uid
|
||||
client = genshin.Client(cookies=cookies.cookies,
|
||||
game=types.Game.GENSHIN, region=types.Region.OVERSEAS, lang="zh-cn")
|
||||
client = genshin.Client(
|
||||
cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.OVERSEAS, lang="zh-cn"
|
||||
)
|
||||
else:
|
||||
raise TypeError("region is not RegionEnum.NULL")
|
||||
return client, uid
|
||||
@ -143,24 +147,17 @@ def region_server(uid: Union[int, str]) -> RegionEnum:
|
||||
|
||||
async def execute(command, pass_error=True):
|
||||
"""Executes command and returns output, with the option of enabling stderr."""
|
||||
executor = await create_subprocess_shell(
|
||||
command,
|
||||
stdout=PIPE,
|
||||
stderr=PIPE,
|
||||
stdin=PIPE
|
||||
)
|
||||
executor = await create_subprocess_shell(command, stdout=PIPE, stderr=PIPE, stdin=PIPE)
|
||||
|
||||
stdout, stderr = await executor.communicate()
|
||||
if pass_error:
|
||||
try:
|
||||
result = str(stdout.decode().strip()) \
|
||||
+ str(stderr.decode().strip())
|
||||
result = str(stdout.decode().strip()) + str(stderr.decode().strip())
|
||||
except UnicodeDecodeError:
|
||||
result = str(stdout.decode('gbk').strip()) \
|
||||
+ str(stderr.decode('gbk').strip())
|
||||
result = str(stdout.decode("gbk").strip()) + str(stderr.decode("gbk").strip())
|
||||
else:
|
||||
try:
|
||||
result = str(stdout.decode().strip())
|
||||
except UnicodeDecodeError:
|
||||
result = str(stdout.decode('gbk').strip())
|
||||
result = str(stdout.decode("gbk").strip())
|
||||
return result
|
||||
|
@ -4,7 +4,7 @@ from pathlib import Path
|
||||
from types import TracebackType
|
||||
from typing import AnyStr, IO, Iterable, Iterator, List, Optional, Type
|
||||
|
||||
__all__ = ['FileIO']
|
||||
__all__ = ["FileIO"]
|
||||
|
||||
|
||||
# noinspection SpellCheckingInspection
|
||||
@ -18,12 +18,12 @@ class FileIO(IO[str]):
|
||||
today = date.today()
|
||||
if self.file.exists():
|
||||
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:
|
||||
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)
|
||||
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
|
||||
if modify_date < today:
|
||||
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')
|
||||
if log_path.exists():
|
||||
# 转存日志
|
||||
with open(log_path, mode='a+', encoding='utf-8') as file:
|
||||
file.write('\n')
|
||||
with open(self.file, mode='r+', encoding='utf-8') as f:
|
||||
with open(log_path, mode="a+", encoding="utf-8") as file:
|
||||
file.write("\n")
|
||||
with open(self.file, mode="r+", encoding="utf-8") as f:
|
||||
file.writelines(f.readlines())
|
||||
else:
|
||||
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
|
||||
|
||||
def close(self) -> None:
|
||||
@ -94,6 +94,7 @@ class FileIO(IO[str]):
|
||||
def __enter__(self) -> IO[AnyStr]:
|
||||
return self._get_file().__enter__()
|
||||
|
||||
def __exit__(self, __t: Optional[Type[BaseException]], __value: Optional[BaseException],
|
||||
__traceback: Optional[TracebackType]) -> None:
|
||||
def __exit__(
|
||||
self, __t: Optional[Type[BaseException]], __value: Optional[BaseException], __traceback: Optional[TracebackType]
|
||||
) -> None:
|
||||
return self._get_file().__exit__(__t, __value, __traceback)
|
||||
|
@ -7,7 +7,7 @@ import traceback as traceback_
|
||||
from datetime import datetime
|
||||
from multiprocessing import RLock as Lock
|
||||
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
|
||||
from rich.columns import Columns
|
||||
@ -65,22 +65,20 @@ __initialized__ = False
|
||||
FormatTimeCallable = Callable[[datetime], Text]
|
||||
|
||||
config = BotConfig()
|
||||
logging.addLevelName(5, 'TRACE')
|
||||
logging.addLevelName(25, 'SUCCESS')
|
||||
color_system: Literal['windows', 'truecolor']
|
||||
if sys.platform == 'win32':
|
||||
color_system = 'windows'
|
||||
logging.addLevelName(5, "TRACE")
|
||||
logging.addLevelName(25, "SUCCESS")
|
||||
color_system: Literal["windows", "truecolor"]
|
||||
if sys.platform == "win32":
|
||||
color_system = "windows"
|
||||
else:
|
||||
color_system = 'truecolor'
|
||||
color_system = "truecolor"
|
||||
# noinspection SpellCheckingInspection
|
||||
log_console = Console(
|
||||
color_system=color_system, theme=Theme(DEFAULT_STYLE), width=config.logger.width
|
||||
)
|
||||
log_console = Console(color_system=color_system, theme=Theme(DEFAULT_STYLE), width=config.logger.width)
|
||||
|
||||
|
||||
class Traceback(BaseTraceback):
|
||||
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)
|
||||
self.theme = PygmentsSyntaxTheme(MonokaiProStyle)
|
||||
|
||||
@ -99,9 +97,7 @@ class Traceback(BaseTraceback):
|
||||
def read_code(filename: str) -> str:
|
||||
code = code_cache.get(filename)
|
||||
if code is None:
|
||||
with open(
|
||||
filename, "rt", encoding="utf-8", errors="replace"
|
||||
) as code_file:
|
||||
with open(filename, "rt", encoding="utf-8", errors="replace") as code_file:
|
||||
code = code_file.read()
|
||||
code_cache[filename] = code
|
||||
return code
|
||||
@ -110,6 +106,7 @@ class Traceback(BaseTraceback):
|
||||
def render_locals(frame: Frame) -> Iterable["ConsoleRenderable"]:
|
||||
if frame.locals:
|
||||
from rich.scope import render_scope
|
||||
|
||||
yield render_scope(
|
||||
frame.locals,
|
||||
title="locals",
|
||||
@ -144,8 +141,7 @@ class Traceback(BaseTraceback):
|
||||
|
||||
first = frame_index == 0
|
||||
frame_filename = frame.filename
|
||||
suppressed = any(
|
||||
frame_filename.startswith(path) for path in self.suppress)
|
||||
suppressed = any(frame_filename.startswith(path) for path in self.suppress)
|
||||
|
||||
text = Text.assemble(
|
||||
path_highlighter(Text(frame.filename, style="pygments.string")),
|
||||
@ -239,7 +235,7 @@ class LogRender(DefaultLogRender):
|
||||
if path:
|
||||
output.add_column(style="log.path")
|
||||
if line_no:
|
||||
output.add_column(style='log.line_no', width=4)
|
||||
output.add_column(style="log.line_no", width=4)
|
||||
row: List["RenderableType"] = []
|
||||
if self.show_time:
|
||||
log_time = log_time or log_console.get_datetime()
|
||||
@ -259,15 +255,11 @@ class LogRender(DefaultLogRender):
|
||||
row.append(Renderables(renderables))
|
||||
if path:
|
||||
path_text = Text()
|
||||
path_text.append(
|
||||
path, style=f"link file://{link_path}" if link_path else ""
|
||||
)
|
||||
path_text.append(path, style=f"link file://{link_path}" if link_path else "")
|
||||
row.append(path_text)
|
||||
|
||||
line_no_text = Text()
|
||||
line_no_text.append(
|
||||
str(line_no), style=f"link file://{link_path}#{line_no}" if link_path else ""
|
||||
)
|
||||
line_no_text.append(str(line_no), style=f"link file://{link_path}#{line_no}" if link_path else "")
|
||||
row.append(line_no_text)
|
||||
|
||||
output.add_row(*row)
|
||||
@ -290,12 +282,13 @@ class Handler(DefaultRichHandler):
|
||||
traceback: Optional[Traceback],
|
||||
message_renderable: Optional["ConsoleRenderable"],
|
||||
) -> "ConsoleRenderable":
|
||||
if record.pathname != '<input>':
|
||||
if record.pathname != "<input>":
|
||||
try:
|
||||
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:
|
||||
import site
|
||||
|
||||
path = None
|
||||
for s in site.getsitepackages():
|
||||
try:
|
||||
@ -306,10 +299,10 @@ class Handler(DefaultRichHandler):
|
||||
if path is None:
|
||||
path = "<SITE>"
|
||||
else:
|
||||
path = path.split('.')[0].replace(os.sep, '.')
|
||||
path = path.split(".")[0].replace(os.sep, ".")
|
||||
else:
|
||||
path = '<INPUT>'
|
||||
path = path.replace('lib.site-packages.', '')
|
||||
path = "<INPUT>"
|
||||
path = path.replace("lib.site-packages.", "")
|
||||
_level = self.get_level_text(record)
|
||||
time_format = None if self.formatter is None else self.formatter.datefmt
|
||||
log_time = datetime.fromtimestamp(record.created)
|
||||
@ -318,12 +311,8 @@ class Handler(DefaultRichHandler):
|
||||
self.console,
|
||||
(
|
||||
[message_renderable]
|
||||
if not traceback else
|
||||
(
|
||||
[message_renderable, traceback]
|
||||
if message_renderable is not None else
|
||||
[traceback]
|
||||
)
|
||||
if not traceback
|
||||
else ([message_renderable, traceback] if message_renderable is not None else [traceback])
|
||||
),
|
||||
log_time=log_time,
|
||||
time_format=time_format,
|
||||
@ -334,18 +323,15 @@ class Handler(DefaultRichHandler):
|
||||
)
|
||||
return log_renderable
|
||||
|
||||
def render_message(
|
||||
self, record: "LogRecord", message: Any
|
||||
) -> "ConsoleRenderable":
|
||||
def render_message(self, record: "LogRecord", message: Any) -> "ConsoleRenderable":
|
||||
use_markup = getattr(record, "markup", self.markup)
|
||||
if isinstance(message, str):
|
||||
message_text = (
|
||||
Text.from_markup(message) if use_markup else Text(message)
|
||||
)
|
||||
message_text = Text.from_markup(message) if use_markup else Text(message)
|
||||
highlighter = getattr(record, "highlighter", self.highlighter)
|
||||
else:
|
||||
from rich.highlighter import JSONHighlighter
|
||||
from rich.json import JSON
|
||||
|
||||
highlighter = JSONHighlighter()
|
||||
message_text = JSON.from_data(message, indent=4).text
|
||||
|
||||
@ -364,11 +350,7 @@ class Handler(DefaultRichHandler):
|
||||
def emit(self, record: "LogRecord") -> None:
|
||||
message = self.format(record)
|
||||
_traceback = None
|
||||
if (
|
||||
self.rich_tracebacks
|
||||
and record.exc_info
|
||||
and record.exc_info != (None, None, None)
|
||||
):
|
||||
if self.rich_tracebacks and record.exc_info and record.exc_info != (None, None, None):
|
||||
exc_type, exc_value, exc_traceback = record.exc_info
|
||||
if exc_type is None or exc_value is None:
|
||||
raise ValueError(record)
|
||||
@ -431,11 +413,12 @@ class FileHandler(Handler):
|
||||
class Logger(logging.Logger):
|
||||
def success(
|
||||
self,
|
||||
msg: Any, *args: Any,
|
||||
msg: Any,
|
||||
*args: Any,
|
||||
exc_info: Optional[ExceptionInfoType] = None,
|
||||
stack_info: bool = False,
|
||||
stacklevel: int = 1,
|
||||
extra: Optional[Mapping[str, Any]] = None
|
||||
extra: Optional[Mapping[str, Any]] = None,
|
||||
) -> None:
|
||||
return self.log(25, msg, *args, exc_info=exc_info, stack_info=stack_info, stacklevel=stacklevel, extra=extra)
|
||||
|
||||
@ -447,11 +430,15 @@ class Logger(logging.Logger):
|
||||
stack_info: bool = False,
|
||||
stacklevel: int = 1,
|
||||
extra: Optional[Mapping[str, Any]] = None,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
) -> None:
|
||||
super(Logger, self).exception(
|
||||
"" if msg is NOT_SET else msg, *args,
|
||||
exc_info=exc_info, stack_info=stack_info, stacklevel=stacklevel, extra=extra
|
||||
"" if msg is NOT_SET else msg,
|
||||
*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]]:
|
||||
@ -470,17 +457,17 @@ class Logger(logging.Logger):
|
||||
filename = os.path.normcase(code.co_filename)
|
||||
if filename in [
|
||||
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
|
||||
continue
|
||||
sinfo = None
|
||||
if stack_info:
|
||||
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)
|
||||
sinfo = sio.getvalue()
|
||||
if sinfo[-1] == '\n':
|
||||
if sinfo[-1] == "\n":
|
||||
sinfo = sinfo[:-1]
|
||||
sio.close()
|
||||
rv = (code.co_filename, frame.f_lineno, code.co_name, sinfo)
|
||||
@ -496,10 +483,10 @@ with _lock:
|
||||
handler, debug_handler, error_handler = (
|
||||
Handler(locals_max_length=4),
|
||||
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)
|
||||
debug_handler.addFilter(log_filter)
|
||||
|
||||
@ -514,7 +501,7 @@ with _lock:
|
||||
warnings_logger.addHandler(handler)
|
||||
warnings_logger.addHandler(debug_handler)
|
||||
|
||||
logger = Logger('TGPaimon', level_)
|
||||
logger = Logger("TGPaimon", level_)
|
||||
logger.addHandler(handler)
|
||||
logger.addHandler(debug_handler)
|
||||
logger.addHandler(error_handler)
|
||||
|
@ -17,10 +17,23 @@ from pygments.token import (
|
||||
from rich.style import Style
|
||||
|
||||
__all__ = [
|
||||
'MonokaiProStyle', 'DEFAULT_STYLE',
|
||||
'BACKGROUND', 'FOREGROUND',
|
||||
'BLACK', 'DARK_GREY', 'LIGHT_GREY', 'GREY', 'RED', 'MAGENTA', 'GREEN',
|
||||
'YELLOW', 'ORANGE', 'PURPLE', 'BLUE', 'CYAN', 'WHITE'
|
||||
"MonokaiProStyle",
|
||||
"DEFAULT_STYLE",
|
||||
"BACKGROUND",
|
||||
"FOREGROUND",
|
||||
"BLACK",
|
||||
"DARK_GREY",
|
||||
"LIGHT_GREY",
|
||||
"GREY",
|
||||
"RED",
|
||||
"MAGENTA",
|
||||
"GREEN",
|
||||
"YELLOW",
|
||||
"ORANGE",
|
||||
"PURPLE",
|
||||
"BLUE",
|
||||
"CYAN",
|
||||
"WHITE",
|
||||
]
|
||||
|
||||
BACKGROUND = "#272822"
|
||||
@ -49,17 +62,12 @@ class MonokaiProStyle(PyStyle):
|
||||
# No corresponding class for the following:
|
||||
Text: WHITE, # class: ''
|
||||
Error: "#fc618d bg:#1e0010", # class: 'err'
|
||||
|
||||
Comment: LIGHT_GREY, # class: 'c'
|
||||
Comment.Multiline: YELLOW, # class: 'cm'
|
||||
|
||||
Keyword: RED, # class: 'k'
|
||||
Keyword.Namespace: GREEN, # class: 'kn'
|
||||
|
||||
Operator: RED, # class: 'o'
|
||||
|
||||
Punctuation: WHITE, # class: 'p'
|
||||
|
||||
Name: WHITE, # class: 'n'
|
||||
Name.Attribute: GREEN, # class: 'na' - to be revised
|
||||
Name.Builtin: CYAN, # class: 'nb'
|
||||
@ -69,15 +77,11 @@ class MonokaiProStyle(PyStyle):
|
||||
Name.Exception: GREEN, # class: 'ne'
|
||||
Name.Function: GREEN, # class: 'nf'
|
||||
Name.Property: ORANGE, # class: 'py'
|
||||
|
||||
Number: PURPLE, # class: 'm'
|
||||
|
||||
Literal: PURPLE, # class: 'l'
|
||||
Literal.Date: ORANGE, # class: 'ld'
|
||||
|
||||
String: YELLOW, # class: 's'
|
||||
String.Regex: ORANGE, # class: 'sr'
|
||||
|
||||
Generic.Deleted: YELLOW, # class: 'gd',
|
||||
Generic.Emph: "italic", # class: 'ge'
|
||||
Generic.Inserted: GREEN, # class: 'gi'
|
||||
@ -122,7 +126,6 @@ DEFAULT_STYLE: Dict[str, Style] = {
|
||||
"blue": Style(color=BLUE),
|
||||
"cyan": Style(color=CYAN),
|
||||
"white": Style(color=WHITE),
|
||||
|
||||
# inspect
|
||||
"inspect.attr": Style(color=YELLOW, italic=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.doc": Style(dim=True),
|
||||
"inspect.value.border": Style(color=GREEN),
|
||||
|
||||
# live
|
||||
"live.ellipsis": Style(bold=True, color=RED),
|
||||
|
||||
# layout
|
||||
"layout.tree.row": Style(dim=False, color=RED),
|
||||
"layout.tree.column": Style(dim=False, color=BLUE),
|
||||
|
||||
# log
|
||||
"logging.keyword": Style(bold=True, color=ORANGE),
|
||||
"logging.level.notset": Style(color=DARK_GREY, dim=True),
|
||||
"logging.level.trace": Style(color=GREY),
|
||||
"logging.level.debug": Style(color=LIGHT_GREY, bold=True),
|
||||
"logging.level.info": Style(color='white'),
|
||||
"logging.level.plugin": Style(color='cyan'),
|
||||
"logging.level.success": Style(color='green'),
|
||||
"logging.level.warning": Style(color='yellow'),
|
||||
"logging.level.error": Style(color='red'),
|
||||
"logging.level.critical": Style(color='red', bgcolor='#1e0010', bold=True),
|
||||
"logging.level.info": Style(color="white"),
|
||||
"logging.level.plugin": Style(color="cyan"),
|
||||
"logging.level.success": Style(color="green"),
|
||||
"logging.level.warning": Style(color="yellow"),
|
||||
"logging.level.error": Style(color="red"),
|
||||
"logging.level.critical": Style(color="red", bgcolor="#1e0010", bold=True),
|
||||
"log.level": Style.null(),
|
||||
"log.time": Style(color=CYAN, dim=True),
|
||||
"log.message": Style.null(),
|
||||
"log.path": Style(dim=True),
|
||||
"log.line_no": Style(color=CYAN, bold=True, italic=False, dim=True),
|
||||
|
||||
# repr
|
||||
"repr.ellipsis": Style(color=YELLOW),
|
||||
"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_false": Style(color="bright_red", italic=True),
|
||||
"repr.none": Style(color=MAGENTA, italic=True),
|
||||
"repr.url": Style(
|
||||
underline=True, color="bright_blue", italic=False, bold=False
|
||||
),
|
||||
"repr.url": Style(underline=True, color="bright_blue", italic=False, bold=False),
|
||||
"repr.uuid": Style(color="bright_yellow", bold=False),
|
||||
"repr.call": Style(color=MAGENTA, bold=True),
|
||||
"repr.path": Style(color=MAGENTA),
|
||||
"repr.filename": Style(color="bright_magenta"),
|
||||
"rule.line": Style(color="bright_green"),
|
||||
"rule.text": Style.null(),
|
||||
|
||||
# json
|
||||
"json.brace": Style(bold=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.str": Style(color=GREEN, italic=False, bold=False),
|
||||
"json.key": Style(color=BLUE, bold=True),
|
||||
|
||||
# prompt
|
||||
"prompt": Style.null(),
|
||||
"prompt.choices": Style(color=MAGENTA, bold=True),
|
||||
"prompt.default": Style(color=CYAN, bold=True),
|
||||
"prompt.invalid": Style(color=RED),
|
||||
"prompt.invalid.choice": Style(color=RED),
|
||||
|
||||
# pretty
|
||||
"pretty": Style.null(),
|
||||
|
||||
# scope
|
||||
"scope.border": Style(color=BLUE),
|
||||
"scope.key": Style(color=YELLOW, italic=True),
|
||||
"scope.key.special": Style(color=YELLOW, italic=True, dim=True),
|
||||
"scope.equals": Style(color=RED),
|
||||
|
||||
# table
|
||||
"table.header": Style(bold=True),
|
||||
"table.footer": Style(bold=True),
|
||||
"table.cell": Style.null(),
|
||||
"table.title": Style(italic=True),
|
||||
"table.caption": Style(italic=True, dim=True),
|
||||
|
||||
# traceback
|
||||
"traceback.error": Style(color=RED, italic=True),
|
||||
"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_value": Style.null(),
|
||||
"traceback.offset": Style(color="bright_red", bold=True),
|
||||
|
||||
# bar
|
||||
"bar.back": Style(color="grey23"),
|
||||
"bar.complete": Style(color="rgb(249,38,114)"),
|
||||
"bar.finished": Style(color="rgb(114,156,31)"),
|
||||
"bar.pulse": Style(color="rgb(249,38,114)"),
|
||||
|
||||
# progress
|
||||
"progress.description": Style.null(),
|
||||
"progress.filesize": Style(color=GREEN),
|
||||
@ -251,11 +240,9 @@ DEFAULT_STYLE: Dict[str, Style] = {
|
||||
"progress.data.speed": Style(color=RED),
|
||||
"progress.spinner": Style(color=GREEN),
|
||||
"status.spinner": Style(color=GREEN),
|
||||
|
||||
# tree
|
||||
"tree": Style(),
|
||||
"tree.line": Style(),
|
||||
|
||||
# markdown
|
||||
"markdown.paragraph": Style(),
|
||||
"markdown.text": Style(),
|
||||
|
@ -7,8 +7,9 @@ from utils.baseobject import BaseObject
|
||||
|
||||
|
||||
class Stat:
|
||||
def __init__(self, view_num: int = 0, reply_num: int = 0, like_num: int = 0, bookmark_num: int = 0,
|
||||
forward_num: int = 0):
|
||||
def __init__(
|
||||
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.bookmark_num = bookmark_num # 收藏数
|
||||
self.like_num = like_num # 喜欢数
|
||||
@ -17,7 +18,6 @@ class Stat:
|
||||
|
||||
|
||||
class ArtworkInfo:
|
||||
|
||||
def __init__(self):
|
||||
self.user_id: int = 0
|
||||
self.artwork_id: int = 0 # 作品ID
|
||||
@ -32,7 +32,6 @@ class ArtworkInfo:
|
||||
|
||||
|
||||
class ArtworkImage:
|
||||
|
||||
def __init__(self, art_id: int, page: int = 0, is_error: bool = False, data: bytes = b""):
|
||||
self.art_id = art_id
|
||||
self.data = data
|
||||
@ -50,14 +49,20 @@ class RegionEnum(Enum):
|
||||
查了一下确实有点意思 考虑到大部分重要的功能确实是在移动端实现了
|
||||
|
||||
干脆用这个还好听 )"""
|
||||
|
||||
NULL = None
|
||||
HYPERION = 1 # 米忽悠国服 hyperion
|
||||
HOYOLAB = 2 # 米忽悠国际服 hoyolab
|
||||
|
||||
|
||||
class GameItem(BaseObject):
|
||||
def __init__(self, item_id: int = 0, name: str = "", item_type: Union[Enum, str, int] = "",
|
||||
value: Union[Enum, str, int, bool, float] = 0):
|
||||
def __init__(
|
||||
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.name = name # 名称
|
||||
self.type = item_type # 类型
|
||||
@ -67,9 +72,9 @@ class GameItem(BaseObject):
|
||||
|
||||
|
||||
class ModuleInfo:
|
||||
|
||||
def __init__(self, file_name: Optional[str] = None, plugin_name: Optional[str] = None,
|
||||
relative_path: Optional[str] = None):
|
||||
def __init__(
|
||||
self, file_name: Optional[str] = None, plugin_name: Optional[str] = None, relative_path: Optional[str] = None
|
||||
):
|
||||
self.relative_path = relative_path
|
||||
self.module_name = plugin_name
|
||||
self.file_name = file_name
|
||||
|
@ -1,11 +1,11 @@
|
||||
def patch(obj):
|
||||
def is_patchable(item):
|
||||
return getattr(item[1], 'patchable', False)
|
||||
return getattr(item[1], "patchable", False)
|
||||
|
||||
def wrapper(container):
|
||||
for name, func in filter(is_patchable, container.__dict__.items()):
|
||||
old = getattr(obj, name, None)
|
||||
setattr(obj, f'old_{name}', old)
|
||||
setattr(obj, f"old_{name}", old)
|
||||
setattr(obj, name, func)
|
||||
return container
|
||||
|
||||
|
@ -4,20 +4,13 @@ from typing import Any, Dict, Optional, Tuple, Type, Union
|
||||
|
||||
from httpx import URL
|
||||
|
||||
__all__ = [
|
||||
'StrOrPath', 'StrOrURL', 'StrOrInt',
|
||||
'SysExcInfoType', 'ExceptionInfoType',
|
||||
'JSONDict', 'JSONType'
|
||||
]
|
||||
__all__ = ["StrOrPath", "StrOrURL", "StrOrInt", "SysExcInfoType", "ExceptionInfoType", "JSONDict", "JSONType"]
|
||||
|
||||
StrOrPath = Union[str, Path]
|
||||
StrOrURL = Union[str, URL]
|
||||
StrOrInt = Union[str, int]
|
||||
|
||||
SysExcInfoType = Union[
|
||||
Tuple[Type[BaseException], BaseException, Optional[TracebackType]],
|
||||
Tuple[None, None, None]
|
||||
]
|
||||
SysExcInfoType = Union[Tuple[Type[BaseException], BaseException, Optional[TracebackType]], Tuple[None, None, None]]
|
||||
ExceptionInfoType = Union[bool, SysExcInfoType, BaseException]
|
||||
JSONDict = Dict[str, Any]
|
||||
JSONType = Union[JSONDict, list]
|
||||
|
Loading…
Reference in New Issue
Block a user