🎨 使用 black 格式化所有代码

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

View File

@ -42,9 +42,7 @@ def import_models():
try:
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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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):

View File

@ -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"

View File

@ -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__}'")

View File

@ -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

View File

@ -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)
@ -192,9 +180,9 @@ class _Handler:
class _CallbackQuery(_Handler):
def __init__(
self,
pattern: Union[str, Pattern, type, Callable[[object], Optional[bool]]] = None,
block: DVInput[bool] = DEFAULT_TRUE,
self,
pattern: Union[str, Pattern, type, Callable[[object], Optional[bool]]] = None,
block: DVInput[bool] = DEFAULT_TRUE,
):
super(_CallbackQuery, self).__init__(pattern=pattern, block=block)
@ -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
@ -271,11 +260,11 @@ class _PreCheckoutQuery(_Handler):
class _Prefix(_Handler):
def __init__(
self,
prefix: str,
command: str,
filters: BaseFilter = None,
block: DVInput[bool] = DEFAULT_TRUE,
self,
prefix: str,
command: str,
filters: BaseFilter = None,
block: DVInput[bool] = DEFAULT_TRUE,
):
super(_Prefix, self).__init__(prefix=prefix, command=command, filters=filters, block=block)
@ -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)

View File

@ -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)))

View File

@ -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()

View File

@ -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

View File

@ -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")

View File

@ -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: 模板文件名

View File

@ -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)

View File

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

View File

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

View File

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

View File

@ -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"}]

View File

@ -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",
},
]

View File

@ -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",
},
]

View File

@ -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

View File

@ -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,25 +41,27 @@ 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'],
"name": name,
"rank": rank,
"icon": icon,
"navbar": navbar,
"profile": banner,
"description": description,
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,
"navbar": navbar,
"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

View File

@ -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)

View File

@ -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):

View File

@ -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]

View File

@ -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:
@ -58,6 +58,6 @@ def get_recognize_server(uid: int) -> str:
def get_headers():
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36",
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36",
}
return headers

View File

@ -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) " \
"Chrome/90.0.4430.72 Safari/537.36"
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," \
f"{auto_orient}/interlace,{interlace}/format,{images_format}"
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) " \
"Chrome/90.0.4430.72 Safari/537.36"
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) " \
"AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15"
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):

View File

@ -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:

View File

@ -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()

View File

@ -15,29 +15,33 @@ 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:
"""圣遗物副词条评分
Args:
sub_stats: 圣遗物对象
Returns:
返回得分
Args:
sub_stats: 圣遗物对象
Returns:
返回得分
"""
score: float = 0
if sub_stats.prop_id in map(lambda x: x.name, self.main_prop):

View File

@ -16,7 +16,7 @@ from pydantic import (
)
from typing_extensions import Self
__all__ = ['Model', 'WikiModel', 'HONEY_HOST']
__all__ = ["Model", "WikiModel", "HONEY_HOST"]
HONEY_HOST = URL("https://genshin.honeyhunterworld.com/")
@ -39,13 +39,13 @@ class WikiModel(Model):
# noinspection PyUnresolvedReferences
"""wiki所用到的基类
Attributes:
id (:obj:`int`): ID
name (:obj:`str`): 名称
rarity (:obj:`int`): 星级
Attributes:
id (:obj:`int`): ID
name (:obj:`str`): 名称
rarity (:obj:`int`): 星级
_client (:class:`httpx.AsyncClient`): 发起 http 请求的 client
"""
_client (:class:`httpx.AsyncClient`): 发起 http 请求的 client
"""
_client: ClassVar[AsyncClient] = AsyncClient()
id: str
@ -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)

View File

@ -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")),
)

View File

@ -6,21 +6,21 @@ 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):
# noinspection PyUnresolvedReferences
"""武器、角色培养素材
Attributes:
type: 类型
weekdays: 每周开放的时间
source: 获取方式
description: 描述
"""
Attributes:
type: 类型
weekdays: 每周开放的时间
source: 获取方式
description: 描述
"""
type: str
source: Optional[List[str]] = None
weekdays: Optional[List[int]] = None
@ -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"))

View File

@ -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]:

View File

@ -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")),
)

View File

@ -21,11 +21,13 @@ from utils.log import logger
class AbyssUnlocked(Exception):
"""根本没动"""
pass
class NoMostKills(Exception):
"""挑战了但是数据没刷新"""
pass
@ -33,11 +35,11 @@ class Abyss(Plugin, BasePlugin):
"""深渊数据查询"""
def __init__(
self,
user_service: UserService = None,
cookies_service: CookiesService = None,
template_service: TemplateService = None,
assets_service: AssetsService = None
self,
user_service: UserService = None,
cookies_service: CookiesService = None,
template_service: TemplateService = 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)

View File

@ -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" \
"_评分、识图均来自 genshin\\.pub_"
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)

View File

@ -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(" \
"'将Cookie复制到剪贴板?')&&copy(document.cookie)})(); "
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复制到剪贴板?')&&copy(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" \
f"`{escape_markdown(javascript_android, version=2, entity_type='code')}`"
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" \
f"服务器名称:`{user_info.server_name}`\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

View File

@ -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: (
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))
))
.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([
InputMediaDocument(character_img_data, filename="可培养角色.png"),
InputMediaDocument(weapon_img_data, filename="可培养武器.png")
])
await update.message.reply_media_group(
[
InputMediaDocument(character_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] = [] # 可培养的角色或武器

View File

@ -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)

View File

@ -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):

View File

@ -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,
"name": i.name,
"color": color[i.id % len(color)],
"amount": i.amount,
"percentage": i.percentage} for i in diary_info.month_data.categories]
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
]
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)

View File

@ -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)

View File

@ -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)

View File

@ -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,
)

View File

@ -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)

View File

@ -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,
)

View File

@ -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()} ' \
f'{escape_markdown("请输入通行证UID非游戏UIDBOT将会通过通行证UID查找游戏UID。请选择要绑定的服务器或回复退出取消操作")}'
reply_keyboard = [['米游社', 'HoYoLab'], ["退出"]]
text = (
f"你好 {user.mention_markdown_v2()} "
f'{escape_markdown("请输入通行证UID非游戏UIDBOT将会通过通行证UID查找游戏UID。请选择要绑定的服务器或回复退出取消操作")}'
)
reply_keyboard = [["米游社", "HoYoLab"], ["退出"]]
await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
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" \
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)
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))
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

View File

@ -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(

View File

@ -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
self,
template_service: TemplateService = None,
wiki_service: WikiService = 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
)

View File

@ -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

View File

@ -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,17 +74,16 @@ 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})"
if len(post_text) >= MessageLimit.CAPTION_LENGTH:
await message.reply_markdown_v2(post_text)
post_text = post_text[0:MessageLimit.CAPTION_LENGTH]
post_text = post_text[0 : MessageLimit.CAPTION_LENGTH]
await message.reply_text(f"警告!图片字符描述已经超过 {MessageLimit.CAPTION_LENGTH} 个字,已经切割并发送原文本")
try:
if len(post_images) > 1:
@ -138,8 +134,7 @@ class Post(Plugin.Conversation, BasePlugin):
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
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

View File

@ -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" \
f"回答:{escape_markdown(answer, version=2)}"
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" \
f"回答:{escape_markdown(answer, version=2)}"
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,
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,
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"})
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,
data=question_message.message_id,
name=f"{chat.id}|{user.id}|auth_clean_question_message",
chat_id=chat.id,
user_id=user.id,
job_kwargs={"replace_existing": True, "id": f"{chat.id}|{user.id}|auth_clean_question_message"},
)
if self.mtp and (question_message.id - message.id - 1):
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

View File

@ -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}] "
f"{chat.full_name}[{chat.id}]"
f"的 update_id[{update.update_id}] 错误信息")
logger.info(
f"尝试通知用户 {effective_user.full_name}[{effective_user.id}] "
f"{chat.full_name}[{chat.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)

View File

@ -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" \
f"用户名称:<code>{chat.full_name}</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:

View File

@ -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,

View File

@ -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("调试日记未找到")

View File

@ -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")

View File

@ -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 上获取元数据")

View File

@ -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" \
f"可继续填写,并使用 {escape_markdown('/finish', version=2)} 结束。"
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" \
f"错误答案:`{escape_markdown(' '.join(quiz_command_data.new_wrong_answer), version=2)}`"
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

View File

@ -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('!我是派蒙 ')}")

View File

@ -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

View File

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

2
run.py
View File

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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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()

View File

@ -22,9 +22,9 @@ async def send_user_notification(update: Update, _: CallbackContext, text: str):
logger.warning("错误的消息类型\n" + json.dumps(update_str, indent=2, ensure_ascii=False))
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)

View File

@ -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,
):
"""用于装饰在指定函数预防洪水攻击的装饰器
被修饰的函数生声明必须为

View File

@ -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) " \
"Chrome/90.0.4430.72 Safari/537.36"
REQUEST_HEADERS: dict = {'User-Agent': USER_AGENT}
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}
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
@ -142,25 +146,18 @@ 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
)
"""Executes command and returns output, with the option of enabling stderr."""
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

View File

@ -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)

View File

@ -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")),
@ -218,15 +214,15 @@ class LogRender(DefaultLogRender):
self.time_format = config.logger.time_format
def __call__(
self,
console: "Console",
renderables: Iterable["ConsoleRenderable"],
log_time: Optional[datetime] = None,
time_format: Optional[Union[str, FormatTimeCallable]] = None,
level: TextType = "",
path: Optional[str] = None,
line_no: Optional[int] = None,
link_path: Optional[str] = None,
self,
console: "Console",
renderables: Iterable["ConsoleRenderable"],
log_time: Optional[datetime] = None,
time_format: Optional[Union[str, FormatTimeCallable]] = None,
level: TextType = "",
path: Optional[str] = None,
line_no: Optional[int] = None,
link_path: Optional[str] = None,
) -> "Table":
from rich.containers import Renderables
from rich.table import Table
@ -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)
@ -284,18 +276,19 @@ class Handler(DefaultRichHandler):
self.keywords = self.KEYWORDS + config.logger.render_keywords
def render(
self,
*,
record: "LogRecord",
traceback: Optional[Traceback],
message_renderable: Optional["ConsoleRenderable"],
self,
*,
record: "LogRecord",
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)
@ -430,28 +412,33 @@ class FileHandler(Handler):
class Logger(logging.Logger):
def success(
self,
msg: Any, *args: Any,
exc_info: Optional[ExceptionInfoType] = None,
stack_info: bool = False,
stacklevel: int = 1,
extra: Optional[Mapping[str, Any]] = None
self,
msg: Any,
*args: Any,
exc_info: Optional[ExceptionInfoType] = None,
stack_info: bool = False,
stacklevel: int = 1,
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)
def exception(
self,
msg: Any = NOT_SET,
*args: Any,
exc_info: Optional[ExceptionInfoType] = True,
stack_info: bool = False,
stacklevel: int = 1,
extra: Optional[Mapping[str, Any]] = None,
**kwargs
self,
msg: Any = NOT_SET,
*args: Any,
exc_info: Optional[ExceptionInfoType] = True,
stack_info: bool = False,
stacklevel: int = 1,
extra: Optional[Mapping[str, Any]] = None,
**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)

View File

@ -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(),

View File

@ -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

View File

@ -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

View File

@ -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]