mirror of
https://github.com/PaiGramTeam/PamGram.git
synced 2024-11-23 06:49:35 +00:00
♻ 更新V3版本
♻️ 重构插件系统 ⚙️ 重写插件 🎨 改进代码结构 📝 完善文档 Co-authored-by: zhxy-CN <admin@owo.cab> Co-authored-by: 洛水居室 <luoshuijs@outlook.com> Co-authored-by: xtaodada <xtao@xtaolink.cn> Co-authored-by: Li Chuangbo <im@chuangbo.li>
This commit is contained in:
parent
86503671ed
commit
8f424bf0d4
4
.gitignore
vendored
4
.gitignore
vendored
@ -17,6 +17,7 @@ out/
|
||||
env/
|
||||
venv/
|
||||
cache/
|
||||
temp/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
@ -35,3 +36,6 @@ logs/
|
||||
### DotEnv ###
|
||||
.env
|
||||
|
||||
|
||||
### private plugins ###
|
||||
plugins/private
|
@ -31,6 +31,7 @@ pip install --upgrade poetry
|
||||
|
||||
```bash
|
||||
poetry install
|
||||
playwright install chromium
|
||||
```
|
||||
|
||||
### 3. 修改配置
|
||||
@ -50,7 +51,7 @@ alembic upgrade head
|
||||
### 5. 运行
|
||||
|
||||
```bash
|
||||
python ./main.py
|
||||
python ./run.py
|
||||
```
|
||||
|
||||
## 其他说明
|
||||
|
@ -1,5 +1,8 @@
|
||||
import os
|
||||
import asyncio
|
||||
from importlib import import_module
|
||||
from logging.config import fileConfig
|
||||
from typing import Iterator
|
||||
|
||||
from sqlalchemy import engine_from_config
|
||||
from sqlalchemy import pool
|
||||
@ -10,6 +13,8 @@ from sqlmodel import SQLModel
|
||||
|
||||
from alembic import context
|
||||
|
||||
from utils.const import PROJECT_ROOT
|
||||
from utils.log import logger
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
@ -20,15 +25,28 @@ config = context.config
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# register our models for alembic to auto-generate migrations
|
||||
from utils.manager import ModulesManager
|
||||
|
||||
manager = ModulesManager()
|
||||
manager.refresh_list("core/*")
|
||||
manager.refresh_list("jobs/*")
|
||||
manager.refresh_list("plugins/genshin/*")
|
||||
manager.refresh_list("plugins/system/*")
|
||||
manager.import_module()
|
||||
def scan_models() -> Iterator[str]:
|
||||
"""扫描所有 models.py 模块。
|
||||
我们规定所有插件的 model 都需要放在名为 models.py 的文件里。"""
|
||||
|
||||
for path in PROJECT_ROOT.glob("**/models.py"):
|
||||
yield str(path.relative_to(PROJECT_ROOT).with_suffix("")).replace(os.sep, ".")
|
||||
|
||||
|
||||
def import_models():
|
||||
"""导入我们所有的 models,使 alembic 可以自动对比 db scheme 创建 migration revision"""
|
||||
for pkg in scan_models():
|
||||
try:
|
||||
import_module(pkg) # 导入 models
|
||||
except Exception as e: # pylint: disable=W0703
|
||||
logger.error(
|
||||
f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]'
|
||||
)
|
||||
|
||||
|
||||
# register our models for alembic to auto-generate migrations
|
||||
import_models()
|
||||
|
||||
target_metadata = SQLModel.metadata
|
||||
|
||||
@ -39,14 +57,14 @@ target_metadata = SQLModel.metadata
|
||||
|
||||
# here we allow ourselves to pass interpolation vars to alembic.ini
|
||||
# from the application config module
|
||||
from config import config as appConfig
|
||||
from core.config import config as botConfig
|
||||
|
||||
section = config.config_ini_section
|
||||
config.set_section_option(section, "DB_HOST", appConfig.mysql["host"])
|
||||
config.set_section_option(section, "DB_PORT", str(appConfig.mysql["port"]))
|
||||
config.set_section_option(section, "DB_USERNAME", appConfig.mysql["user"])
|
||||
config.set_section_option(section, "DB_PASSWORD", appConfig.mysql["password"])
|
||||
config.set_section_option(section, "DB_DATABASE", appConfig.mysql["database"])
|
||||
config.set_section_option(section, "DB_HOST", botConfig.mysql.host)
|
||||
config.set_section_option(section, "DB_PORT", str(botConfig.mysql.port))
|
||||
config.set_section_option(section, "DB_USERNAME", botConfig.mysql.username)
|
||||
config.set_section_option(section, "DB_PASSWORD", botConfig.mysql.password)
|
||||
config.set_section_option(section, "DB_DATABASE", botConfig.mysql.database)
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
|
64
config.py
64
config.py
@ -1,64 +0,0 @@
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import ujson
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from utils.storage import Storage
|
||||
|
||||
# take environment variables from .env.
|
||||
load_dotenv()
|
||||
|
||||
env = os.getenv
|
||||
|
||||
|
||||
def str_to_bool(value: Any) -> bool:
|
||||
"""Return whether the provided string (or any value really) represents true. Otherwise false.
|
||||
Just like plugin server stringToBoolean.
|
||||
"""
|
||||
if not value:
|
||||
return False
|
||||
return str(value).lower() in ("y", "yes", "t", "true", "on", "1")
|
||||
|
||||
|
||||
_config = {
|
||||
"debug": str_to_bool(os.getenv('DEBUG', 'False')),
|
||||
|
||||
"mysql": {
|
||||
"host": env("DB_HOST", "127.0.0.1"),
|
||||
"port": int(env("DB_PORT", "3306")),
|
||||
"user": env("DB_USERNAME"),
|
||||
"password": env("DB_PASSWORD"),
|
||||
"database": env("DB_DATABASE"),
|
||||
},
|
||||
|
||||
"redis": {
|
||||
"host": env("REDIS_HOST", "127.0.0.1"),
|
||||
"port": int(env("REDIS_PORT", "6369")),
|
||||
"database": int(env("REDIS_DB", "0")),
|
||||
},
|
||||
|
||||
# 联系 https://t.me/BotFather 使用 /newbot 命令创建机器人并获取 token
|
||||
"bot_token": env("BOT_TOKEN"),
|
||||
|
||||
# 记录错误并发送消息通知开发人员
|
||||
"error_notification_chat_id": env("ERROR_NOTIFICATION_CHAT_ID"),
|
||||
|
||||
# 文章推送群组
|
||||
"channels": [
|
||||
# {"name": "", "chat_id": 1},
|
||||
# 在环境变量里的格式是 json: [{"name": "", "chat_id": 1}]
|
||||
*ujson.loads(env('CHANNELS', '[]'))
|
||||
],
|
||||
|
||||
# bot 管理员
|
||||
"admins": [
|
||||
# {"username": "", "user_id": 123},
|
||||
# 在环境变量里的格式是 json: [{"username": "", "user_id": 1}]
|
||||
*ujson.loads(env('ADMINS', '[]'))
|
||||
],
|
||||
|
||||
"joining_verification_groups": ujson.loads(env('JOINING_VERIFICATION_GROUPS', '[]')),
|
||||
}
|
||||
|
||||
config = Storage(_config)
|
22
core/README.md
Normal file
22
core/README.md
Normal file
@ -0,0 +1,22 @@
|
||||
# core 目录说明
|
||||
|
||||
## 关于 `Service`
|
||||
|
||||
服务 `Service` 需定义在 `services` 文件夹下, 并继承 `core.service.Service`
|
||||
|
||||
每个 `Service` 都应包含 `start` 和 `stop` 方法, 且这两个方法都为异步方法
|
||||
|
||||
```python
|
||||
from core.service import Service
|
||||
|
||||
|
||||
class TestService(Service):
|
||||
def __init__(self):
|
||||
"""do something"""
|
||||
|
||||
async def start(self, *args, **kwargs):
|
||||
"""do something"""
|
||||
|
||||
async def stop(self, *args, **kwargs):
|
||||
"""do something"""
|
||||
```
|
@ -1,12 +1,12 @@
|
||||
from utils.mysql import MySQL
|
||||
from utils.redisdb import RedisDB
|
||||
from utils.service.manager import listener_service
|
||||
from .cache import BotAdminCache
|
||||
from .repositories import BotAdminRepository
|
||||
from .services import BotAdminService
|
||||
from core.service import init_service
|
||||
from core.base.mysql import MySQL
|
||||
from core.base.redisdb import RedisDB
|
||||
from core.admin.cache import BotAdminCache
|
||||
from core.admin.repositories import BotAdminRepository
|
||||
from core.admin.services import BotAdminService
|
||||
|
||||
|
||||
@listener_service()
|
||||
@init_service
|
||||
def create_bot_admin_service(mysql: MySQL, redis: RedisDB):
|
||||
_cache = BotAdminCache(redis)
|
||||
_repository = BotAdminRepository(mysql)
|
||||
|
@ -1,6 +1,6 @@
|
||||
from typing import List
|
||||
|
||||
from utils.redisdb import RedisDB
|
||||
from core.base.redisdb import RedisDB
|
||||
|
||||
|
||||
class BotAdminCache:
|
||||
|
@ -3,8 +3,8 @@ from typing import List, cast
|
||||
from sqlalchemy import select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from utils.mysql import MySQL
|
||||
from .models import Admin
|
||||
from core.admin.models import Admin
|
||||
from core.base.mysql import MySQL
|
||||
|
||||
|
||||
class BotAdminRepository:
|
||||
|
@ -3,10 +3,10 @@ from typing import List
|
||||
from pymysql import IntegrityError
|
||||
from telegram import Bot
|
||||
|
||||
from config import config
|
||||
from logger import Log
|
||||
from .cache import BotAdminCache, GroupAdminCache
|
||||
from .repositories import BotAdminRepository
|
||||
from core.admin.cache import BotAdminCache, GroupAdminCache
|
||||
from core.admin.repositories import BotAdminRepository
|
||||
from core.config import config
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
class BotAdminService:
|
||||
@ -27,7 +27,7 @@ class BotAdminService:
|
||||
try:
|
||||
await self._repository.add_by_user_id(user_id)
|
||||
except IntegrityError as error:
|
||||
Log.warning(f"{user_id} 已经存在数据库 \n", error)
|
||||
logger.warning(f"{user_id} 已经存在数据库 \n", error)
|
||||
admin_list = await self._repository.get_all_user_id()
|
||||
for config_admin in config.admins:
|
||||
admin_list.append(config_admin["user_id"])
|
||||
|
0
core/base/__init__.py
Normal file
0
core/base/__init__.py
Normal file
41
core/base/aiobrowser.py
Normal file
41
core/base/aiobrowser.py
Normal file
@ -0,0 +1,41 @@
|
||||
from typing import Optional
|
||||
|
||||
from playwright.async_api import Browser, Playwright, async_playwright
|
||||
|
||||
from core.service import Service
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
class AioBrowser(Service):
|
||||
|
||||
def __init__(self, loop=None):
|
||||
self.browser: Optional[Browser] = None
|
||||
self._playwright: Optional[Playwright] = None
|
||||
self._loop = loop
|
||||
|
||||
async def start(self):
|
||||
if self._playwright is None:
|
||||
logger.info("正在尝试启动 [blue]Playwright[/]")
|
||||
self._playwright = await async_playwright().start()
|
||||
logger.success("[blue]Playwright[/] 启动成功")
|
||||
if self.browser is None:
|
||||
logger.info("正在尝试启动 [blue]Browser[/]")
|
||||
try:
|
||||
self.browser = await self._playwright.chromium.launch(timeout=5000)
|
||||
logger.success("[blue]Browser[/] 启动成功")
|
||||
except TimeoutError as err:
|
||||
logger.warning("[blue]Browser[/] 启动失败")
|
||||
raise err
|
||||
|
||||
return self.browser
|
||||
|
||||
async def stop(self):
|
||||
if self.browser is not None:
|
||||
await self.browser.close()
|
||||
if self._playwright is not None:
|
||||
await self._playwright.stop()
|
||||
|
||||
async def get_browser(self) -> Browser:
|
||||
if self.browser is None:
|
||||
await self.start()
|
||||
return self.browser
|
30
core/base/mysql.py
Normal file
30
core/base/mysql.py
Normal file
@ -0,0 +1,30 @@
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from typing_extensions import Self
|
||||
|
||||
from core.config import BotConfig
|
||||
from core.service import Service
|
||||
|
||||
|
||||
class MySQL(Service):
|
||||
@classmethod
|
||||
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
|
||||
self.database = database
|
||||
self.password = password
|
||||
self.user = username
|
||||
self.port = port
|
||||
self.host = host
|
||||
self.engine = create_async_engine(
|
||||
f"mysql+asyncmy://{self.user}:{self.password}@{self.host}:{self.port}/{self.database}"
|
||||
)
|
||||
self.Session = sessionmaker(bind=self.engine, class_=AsyncSession)
|
||||
|
||||
async def get_session(self):
|
||||
"""获取会话"""
|
||||
async with self.Session() as session:
|
||||
yield session
|
44
core/base/redisdb.py
Normal file
44
core/base/redisdb.py
Normal file
@ -0,0 +1,44 @@
|
||||
import asyncio
|
||||
|
||||
import fakeredis.aioredis
|
||||
from redis import asyncio as aioredis
|
||||
from typing_extensions import Self
|
||||
|
||||
from core.config import BotConfig
|
||||
from core.service import Service
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
class RedisDB(Service):
|
||||
@classmethod
|
||||
def from_config(cls, config: BotConfig) -> Self:
|
||||
return cls(**config.redis.dict())
|
||||
|
||||
def __init__(self, host="127.0.0.1", port=6379, database=0, loop=None):
|
||||
self.client = aioredis.Redis(host=host, port=port, db=database)
|
||||
self.ttl = 600
|
||||
self.key_prefix = "paimon_bot"
|
||||
self._loop = loop
|
||||
|
||||
async def ping(self):
|
||||
if await self.client.ping():
|
||||
logger.info("连接 [red]Redis[/] 成功")
|
||||
else:
|
||||
logger.info("连接 [red]Redis[/] 失败")
|
||||
raise RuntimeError("连接 [red]Redis[/] 失败")
|
||||
|
||||
async def start(self): # pylint: disable=W0221
|
||||
if self._loop is None:
|
||||
self._loop = asyncio.get_running_loop()
|
||||
logger.info("正在尝试建立与 [red]Redis[/] 连接")
|
||||
try:
|
||||
await self.ping()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
pass
|
||||
except BaseException as exc:
|
||||
logger.warning("尝试连接 [red]Redis[/] 失败,使用 [red]fakeredis[/] 模拟", exc)
|
||||
self.client = fakeredis.aioredis.FakeRedis()
|
||||
await self.ping()
|
||||
|
||||
async def stop(self): # pylint: disable=W0221
|
||||
await self.client.close()
|
48
core/baseplugin.py
Normal file
48
core/baseplugin.py
Normal file
@ -0,0 +1,48 @@
|
||||
from telegram import Update, ReplyKeyboardRemove
|
||||
from telegram.error import BadRequest
|
||||
from telegram.ext import CallbackContext, ConversationHandler
|
||||
|
||||
from core.plugin import handler, conversation
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
async def clean_message(context: CallbackContext):
|
||||
job = context.job
|
||||
logger.debug(f"删除消息 chat_id[{job.chat_id}] 的 message_id[{job.data}]")
|
||||
try:
|
||||
# noinspection PyTypeChecker
|
||||
await context.bot.delete_message(chat_id=job.chat_id, message_id=job.data)
|
||||
except BadRequest as error:
|
||||
if "not found" in str(error):
|
||||
logger.warning(f"Auth模块删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败 消息不存在")
|
||||
elif "Message can't be deleted" in str(error):
|
||||
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}]失败", error)
|
||||
|
||||
|
||||
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"})
|
||||
|
||||
|
||||
class _BasePlugin:
|
||||
@staticmethod
|
||||
def _add_delete_message_job(context: CallbackContext, chat_id: int, message_id: int, delete_seconds: int = 60):
|
||||
return add_delete_message_job(context, chat_id, message_id, delete_seconds)
|
||||
|
||||
|
||||
class _Conversation(_BasePlugin):
|
||||
|
||||
@conversation.fallback
|
||||
@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
|
||||
|
||||
|
||||
class BasePlugin(_BasePlugin):
|
||||
Conversation = _Conversation
|
256
core/bot.py
Normal file
256
core/bot.py
Normal file
@ -0,0 +1,256 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
import os
|
||||
from importlib import import_module
|
||||
from multiprocessing import RLock as Lock
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, ClassVar, Dict, Iterator, List, NoReturn, Optional, TYPE_CHECKING, Type, TypeVar
|
||||
|
||||
import pytz
|
||||
from telegram.error import NetworkError, TimedOut
|
||||
from telegram.ext import AIORateLimiter, Application as TgApplication, Defaults, JobQueue, MessageHandler
|
||||
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
|
||||
from utils.const import PLUGIN_DIR, PROJECT_ROOT
|
||||
from utils.log import logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Update
|
||||
from telegram.ext import CallbackContext
|
||||
|
||||
__all__ = ['bot']
|
||||
|
||||
T = TypeVar('T')
|
||||
PluginType = TypeVar('PluginType', bound=_Plugin)
|
||||
|
||||
|
||||
class Bot:
|
||||
_lock: ClassVar[Lock] = Lock()
|
||||
_instance: ClassVar[Optional["Bot"]] = None
|
||||
|
||||
def __new__(cls, *args, **kwargs) -> "Bot":
|
||||
"""实现单例"""
|
||||
with cls._lock: # 使线程、进程安全
|
||||
if cls._instance is None:
|
||||
cls._instance = object.__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
app: Optional[TgApplication] = None
|
||||
_config: BotConfig = config
|
||||
_services: Dict[Type[T], T] = {}
|
||||
_running: bool = False
|
||||
|
||||
def init_inject(self, target: Callable[..., T]) -> T:
|
||||
"""用于实例化Plugin的方法。用于给插件传入一些必要组件,如 MySQL、Redis等"""
|
||||
if isinstance(target, type):
|
||||
signature = inspect.signature(target.__init__)
|
||||
else:
|
||||
signature = inspect.signature(target)
|
||||
kwargs = {}
|
||||
for name, parameter in signature.parameters.items():
|
||||
if name != 'self' and parameter.annotation != inspect.Parameter.empty:
|
||||
if value := self._services.get(parameter.annotation):
|
||||
kwargs[name] = value
|
||||
return target(**kwargs)
|
||||
|
||||
def _gen_pkg(self, root: Path) -> Iterator[str]:
|
||||
"""生成可以用于 import_module 导入的字符串"""
|
||||
for path in root.iterdir():
|
||||
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, '.')
|
||||
|
||||
async def install_plugins(self):
|
||||
"""安装插件"""
|
||||
for pkg in self._gen_pkg(PLUGIN_DIR):
|
||||
try:
|
||||
import_module(pkg) # 导入插件
|
||||
except Exception as e: # pylint: disable=W0703
|
||||
logger.exception(f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]')
|
||||
continue # 如有错误则继续
|
||||
callback_dict: Dict[int, List[Callable]] = {}
|
||||
for plugin_cls in {*Plugin.__subclasses__(), *Plugin.Conversation.__subclasses__()}:
|
||||
path = f"{plugin_cls.__module__}.{plugin_cls.__name__}"
|
||||
try:
|
||||
plugin: PluginType = self.init_inject(plugin_cls)
|
||||
if hasattr(plugin, '__async_init__'):
|
||||
await plugin.__async_init__()
|
||||
handlers = plugin.handlers
|
||||
self.app.add_handlers(handlers)
|
||||
if handlers:
|
||||
logger.debug(f'插件 "{path}" 添加了 {len(handlers)} 个 handler ')
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
for priority, callback in plugin._new_chat_members_handler_funcs(): # pylint: disable=W0212
|
||||
if not callback_dict.get(priority):
|
||||
callback_dict[priority] = []
|
||||
callback_dict[priority].append(callback)
|
||||
|
||||
error_handlers = plugin.error_handlers
|
||||
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")
|
||||
|
||||
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}[/]')
|
||||
if callback_dict:
|
||||
num = sum(len(callback_dict[i]) for i in callback_dict)
|
||||
|
||||
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
|
||||
))
|
||||
logger.success(
|
||||
f'成功添加了 {num} 个针对 [blue]{StatusUpdate.NEW_CHAT_MEMBERS}[/] 的 [blue]MessageHandler[/]'
|
||||
)
|
||||
|
||||
async def _start_base_services(self):
|
||||
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}[/]')
|
||||
continue
|
||||
for base_service_cls in Service.__subclasses__():
|
||||
try:
|
||||
if hasattr(base_service_cls, 'from_config'):
|
||||
instance = base_service_cls.from_config(self._config)
|
||||
else:
|
||||
instance = self.init_inject(base_service_cls)
|
||||
await instance.start()
|
||||
logger.success(f'服务 "{base_service_cls.__name__}" 初始化成功')
|
||||
self._services.update({base_service_cls: instance})
|
||||
except Exception as e: # pylint: disable=W0703
|
||||
logger.exception(f'服务 "{base_service_cls.__name__}" 初始化失败: {e}')
|
||||
continue
|
||||
|
||||
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, '.')
|
||||
try:
|
||||
import_module(pkg)
|
||||
except Exception as e: # pylint: disable=W0703
|
||||
logger.exception(f'在导入文件 "{pkg}" 的过程中遇到了错误: \n[red bold]{type(e).__name__}: {e}[/]')
|
||||
continue
|
||||
|
||||
async def stop_services(self):
|
||||
"""关闭服务"""
|
||||
if not self._services:
|
||||
return
|
||||
logger.info('正在关闭服务')
|
||||
for _, service in self._services.items():
|
||||
try:
|
||||
if hasattr(service, 'stop'):
|
||||
if inspect.iscoroutinefunction(service.stop):
|
||||
await service.stop()
|
||||
else:
|
||||
service.stop()
|
||||
logger.success(f'服务 "{service.__class__.__name__}" 关闭成功')
|
||||
except Exception as e: # pylint: disable=W0703
|
||||
logger.exception(f"服务 \"{service.__class__.__name__}\" 关闭失败: \n{type(e).__name__}: {e}")
|
||||
|
||||
async def _post_init(self, _) -> NoReturn:
|
||||
logger.info('开始初始化服务')
|
||||
await self.start_services()
|
||||
logger.info('开始安装插件')
|
||||
await self.install_plugins()
|
||||
|
||||
def launch(self) -> NoReturn:
|
||||
"""启动机器人"""
|
||||
self._running = True
|
||||
logger.info('正在初始化BOT')
|
||||
self.app = (
|
||||
TgApplication.builder()
|
||||
.rate_limiter(AIORateLimiter())
|
||||
.defaults(Defaults(tzinfo=pytz.timezone("Asia/Shanghai")))
|
||||
.token(self._config.bot_token)
|
||||
.post_init(self._post_init)
|
||||
.build()
|
||||
)
|
||||
logger.info('BOT 初始化成功')
|
||||
try:
|
||||
for _ in range(5):
|
||||
try:
|
||||
self.app.run_polling(close_loop=False)
|
||||
break
|
||||
except TimedOut:
|
||||
logger.warning("连接至 [blue]telegram[/] 服务器失败,正在重试")
|
||||
continue
|
||||
except NetworkError as e:
|
||||
logger.exception()
|
||||
if 'SSLZeroReturnError' in str(e):
|
||||
logger.error("代理服务出现异常, 请检查您的代理服务是否配置成功.")
|
||||
else:
|
||||
logger.error("网络连接出现问题, 请检查您的网络状况.")
|
||||
break
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
pass
|
||||
except Exception as e: # pylint: disable=W0703
|
||||
logger.exception(f"BOT 执行过程中出现错误: {e}")
|
||||
finally:
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(self.stop_services())
|
||||
loop.close()
|
||||
logger.info("BOT 已经关闭")
|
||||
self._running = False
|
||||
|
||||
def find_service(self, target: Type[T]) -> T:
|
||||
"""查找服务。若没找到则抛出 ServiceNotFoundError"""
|
||||
if result := self._services.get(target) is None:
|
||||
raise ServiceNotFoundError(target)
|
||||
return result
|
||||
|
||||
def add_service(self, service: T) -> NoReturn:
|
||||
"""添加服务。若已经有同类型的服务,则会抛出异常"""
|
||||
if type(service) in self._services:
|
||||
raise ValueError(f"Service \"{type(service)}\" is already existed.")
|
||||
self.update_service(service)
|
||||
|
||||
def update_service(self, service: T):
|
||||
"""更新服务。若服务不存在,则添加;若存在,则更新"""
|
||||
self._services.update({type(service): service})
|
||||
|
||||
def contain_service(self, service: Any) -> bool:
|
||||
"""判断服务是否存在"""
|
||||
if isinstance(service, type):
|
||||
return service in self._services
|
||||
else:
|
||||
return service in self._services.values()
|
||||
|
||||
@property
|
||||
def job_queue(self) -> JobQueue:
|
||||
return self.app.job_queue
|
||||
|
||||
@property
|
||||
def services(self) -> Dict[Type[T], T]:
|
||||
return self._services
|
||||
|
||||
@property
|
||||
def config(self) -> BotConfig:
|
||||
return self._config
|
||||
|
||||
@property
|
||||
def is_running(self) -> bool:
|
||||
return self._running
|
||||
|
||||
|
||||
bot = Bot()
|
88
core/config.py
Normal file
88
core/config.py
Normal file
@ -0,0 +1,88 @@
|
||||
from typing import (
|
||||
List,
|
||||
Optional,
|
||||
Union,
|
||||
)
|
||||
|
||||
import dotenv
|
||||
import ujson as json
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
BaseSettings,
|
||||
)
|
||||
|
||||
__all__ = ['BotConfig', 'config']
|
||||
|
||||
dotenv.load_dotenv()
|
||||
|
||||
|
||||
class BotConfig(BaseSettings):
|
||||
debug: bool = False
|
||||
|
||||
db_host: str
|
||||
db_port: int
|
||||
db_username: str
|
||||
db_password: str
|
||||
db_database: str
|
||||
|
||||
redis_host: str
|
||||
redis_port: int
|
||||
redis_db: int
|
||||
|
||||
bot_token: str
|
||||
error_notification_chat_id: str
|
||||
|
||||
channels: List['ConfigChannel'] = []
|
||||
admins: List['ConfigUser'] = []
|
||||
verify_groups: List[Union[int, str]] = []
|
||||
|
||||
class Config:
|
||||
case_sensitive = False
|
||||
json_loads = json.loads
|
||||
json_dumps = json.dumps
|
||||
|
||||
@property
|
||||
def mysql(self) -> "MySqlConfig":
|
||||
return MySqlConfig(
|
||||
host=self.db_host,
|
||||
port=self.db_port,
|
||||
username=self.db_username,
|
||||
password=self.db_password,
|
||||
database=self.db_database,
|
||||
)
|
||||
|
||||
@property
|
||||
def redis(self) -> "RedisConfig":
|
||||
return RedisConfig(
|
||||
host=self.redis_host,
|
||||
port=self.redis_port,
|
||||
database=self.redis_db,
|
||||
)
|
||||
|
||||
|
||||
class ConfigChannel(BaseModel):
|
||||
name: str
|
||||
chat_id: int
|
||||
|
||||
|
||||
class ConfigUser(BaseModel):
|
||||
username: Optional[str]
|
||||
user_id: int
|
||||
|
||||
|
||||
class MySqlConfig(BaseModel):
|
||||
host: str = "127.0.0.1"
|
||||
port: int = 3306
|
||||
username: str
|
||||
password: str
|
||||
database: str
|
||||
|
||||
|
||||
class RedisConfig(BaseModel):
|
||||
host: str = '127.0.0.1'
|
||||
port: int
|
||||
database: int = 0
|
||||
|
||||
|
||||
BotConfig.update_forward_refs()
|
||||
config = BotConfig()
|
@ -1,19 +1,19 @@
|
||||
from utils.mysql import MySQL
|
||||
from utils.redisdb import RedisDB
|
||||
from utils.service.manager import listener_service
|
||||
from .cache import PublicCookiesCache
|
||||
from .repositories import CookiesRepository
|
||||
from .services import CookiesService, PublicCookiesService
|
||||
from core.base.mysql import MySQL
|
||||
from core.base.redisdb import RedisDB
|
||||
from core.cookies.cache import PublicCookiesCache
|
||||
from core.cookies.repositories import CookiesRepository
|
||||
from core.cookies.services import CookiesService, PublicCookiesService
|
||||
from core.service import init_service
|
||||
|
||||
|
||||
@listener_service()
|
||||
@init_service
|
||||
def create_cookie_service(mysql: MySQL):
|
||||
_repository = CookiesRepository(mysql)
|
||||
_service = CookiesService(_repository)
|
||||
return _service
|
||||
|
||||
|
||||
@listener_service()
|
||||
@init_service
|
||||
def create_public_cookie_service(mysql: MySQL, redis: RedisDB):
|
||||
_repository = CookiesRepository(mysql)
|
||||
_cache = PublicCookiesCache(redis)
|
||||
|
@ -1,8 +1,8 @@
|
||||
from typing import List, Union
|
||||
|
||||
from models.base import RegionEnum
|
||||
from core.base.redisdb import RedisDB
|
||||
from utils.error import RegionNotFoundError
|
||||
from utils.redisdb import RedisDB
|
||||
from utils.models.base import RegionEnum
|
||||
from .error import CookiesCachePoolExhausted
|
||||
|
||||
|
||||
@ -12,9 +12,11 @@ class PublicCookiesCache:
|
||||
def __init__(self, redis: RedisDB):
|
||||
self.client = redis.client
|
||||
self.score_qname = "cookie:public"
|
||||
self.user_times_qname = "cookie:public:times"
|
||||
self.end = 20
|
||||
self.user_times_ttl = 60 * 60 * 24
|
||||
|
||||
def get_queue_name(self, region: RegionEnum):
|
||||
def get_public_cookies_queue_name(self, region: RegionEnum):
|
||||
if region == RegionEnum.HYPERION:
|
||||
return self.score_qname + ":yuanshen"
|
||||
elif region == RegionEnum.HOYOLAB:
|
||||
@ -22,26 +24,26 @@ class PublicCookiesCache:
|
||||
else:
|
||||
raise RegionNotFoundError(region.name)
|
||||
|
||||
async def putback(self, uid: int, region: RegionEnum):
|
||||
async def putback_public_cookies(self, uid: int, region: RegionEnum):
|
||||
"""重新添加单个到缓存列表
|
||||
:param uid:
|
||||
:param region:
|
||||
:return:
|
||||
"""
|
||||
qname = self.get_queue_name(region)
|
||||
qname = self.get_public_cookies_queue_name(region)
|
||||
score_maps = {f"{uid}": 0}
|
||||
result = await self.client.zrem(qname, f"{uid}")
|
||||
if result == 1:
|
||||
await self.client.zadd(qname, score_maps)
|
||||
return result
|
||||
|
||||
async def add(self, uid: Union[List[int], int], region: RegionEnum):
|
||||
async def add_public_cookies(self, uid: Union[List[int], int], region: RegionEnum):
|
||||
"""单个或批量添加到缓存列表
|
||||
:param uid:
|
||||
:param region:
|
||||
:return: 成功返回列表大小
|
||||
"""
|
||||
qname = self.get_queue_name(region)
|
||||
qname = self.get_public_cookies_queue_name(region)
|
||||
if isinstance(uid, int):
|
||||
score_maps = {f"{uid}": 0}
|
||||
elif isinstance(uid, list):
|
||||
@ -57,16 +59,17 @@ class PublicCookiesCache:
|
||||
add, count = await pipe.execute()
|
||||
return int(add), count
|
||||
|
||||
async def get(self, region: RegionEnum):
|
||||
async def get_public_cookies(self, region: RegionEnum):
|
||||
"""从缓存列表获取
|
||||
:param region:
|
||||
:return:
|
||||
"""
|
||||
qname = self.get_queue_name(region)
|
||||
qname = self.get_public_cookies_queue_name(region)
|
||||
scores = await self.client.zrevrange(qname, 0, self.end, withscores=True, score_cast_func=int)
|
||||
if len(scores) > 0:
|
||||
def take_score(elem):
|
||||
return elem[1]
|
||||
|
||||
scores.sort(key=take_score)
|
||||
key = scores[0][0]
|
||||
score = scores[0][1]
|
||||
@ -77,16 +80,23 @@ class PublicCookiesCache:
|
||||
await pipe.execute()
|
||||
return int(key), score + 1
|
||||
|
||||
async def delete(self, uid: int, region: RegionEnum):
|
||||
qname = self.get_queue_name(region)
|
||||
async def delete_public_cookies(self, uid: int, region: RegionEnum):
|
||||
qname = self.get_public_cookies_queue_name(region)
|
||||
async with self.client.pipeline(transaction=True) as pipe:
|
||||
await pipe.zrem(qname, uid)
|
||||
return await pipe.execute()
|
||||
|
||||
async def count(self, limit: bool = True):
|
||||
async def get_public_cookies_count(self, limit: bool = True):
|
||||
async with self.client.pipeline(transaction=True) as pipe:
|
||||
if limit:
|
||||
await pipe.zcount(0, self.end)
|
||||
else:
|
||||
await pipe.zcard(self.score_qname)
|
||||
return await pipe.execute()
|
||||
|
||||
async def incr_by_user_times(self, user_id: Union[List[int], int]):
|
||||
qname = self.user_times_qname + f":{user_id}"
|
||||
times = await self.client.incrby(qname)
|
||||
if times <= 1:
|
||||
await self.client.expire(qname, self.user_times_ttl)
|
||||
return times
|
||||
|
@ -1,3 +1,17 @@
|
||||
class CookiesCachePoolExhausted(Exception):
|
||||
class CookieServiceError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class CookiesCachePoolExhausted(CookieServiceError):
|
||||
def __init__(self):
|
||||
super().__init__("Cookies cache pool is exhausted")
|
||||
|
||||
|
||||
class CookiesNotFoundError(CookieServiceError):
|
||||
def __init__(self, user_id):
|
||||
super().__init__(f"{user_id} cookies not found")
|
||||
|
||||
|
||||
class TooManyRequestPublicCookies(CookieServiceError):
|
||||
def __init__(self, user_id):
|
||||
super().__init__(f"{user_id} too many request public cookies")
|
||||
|
@ -3,9 +3,10 @@ from typing import cast, List
|
||||
from sqlalchemy import select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from models.base import RegionEnum
|
||||
from utils.error import NotFoundError, RegionNotFoundError
|
||||
from utils.mysql import MySQL
|
||||
from core.base.mysql import MySQL
|
||||
from utils.error import RegionNotFoundError
|
||||
from utils.models.base import RegionEnum
|
||||
from .error import CookiesNotFoundError
|
||||
from .models import HyperionCookie, HoyolabCookie, Cookies
|
||||
|
||||
|
||||
@ -47,16 +48,11 @@ class CookiesRepository:
|
||||
async def update_cookies_ex(self, cookies: Cookies, region: RegionEnum):
|
||||
async with self.mysql.Session() as session:
|
||||
session = cast(AsyncSession, session)
|
||||
if region == RegionEnum.HYPERION:
|
||||
if region not in [RegionEnum.HYPERION, RegionEnum.HOYOLAB]:
|
||||
raise RegionNotFoundError(region.name)
|
||||
session.add(cookies)
|
||||
await session.commit()
|
||||
await session.refresh(cookies)
|
||||
elif region == RegionEnum.HOYOLAB:
|
||||
await session.add(cookies)
|
||||
await session.commit()
|
||||
await session.refresh(cookies)
|
||||
else:
|
||||
raise RegionNotFoundError(region.name)
|
||||
|
||||
async def get_cookies(self, user_id, region: RegionEnum) -> Cookies:
|
||||
async with self.mysql.Session() as session:
|
||||
@ -93,8 +89,3 @@ class CookiesRepository:
|
||||
return [cookies[0] for cookies in db_cookies]
|
||||
else:
|
||||
raise RegionNotFoundError(region.name)
|
||||
|
||||
|
||||
class CookiesNotFoundError(NotFoundError):
|
||||
entity_name: str = "CookiesRepository"
|
||||
entity_value_name: str = "user_id"
|
||||
|
@ -1,13 +1,14 @@
|
||||
from typing import List
|
||||
|
||||
import genshin
|
||||
from genshin import types, InvalidCookies, TooManyRequests, GenshinException
|
||||
from genshin import GenshinException, InvalidCookies, TooManyRequests, types
|
||||
|
||||
from logger import Log
|
||||
from models.base import RegionEnum
|
||||
from utils.log import logger
|
||||
from utils.models.base import RegionEnum
|
||||
from .cache import PublicCookiesCache
|
||||
from .error import TooManyRequestPublicCookies, CookieServiceError
|
||||
from .models import CookiesStatusEnum
|
||||
from .repositories import CookiesRepository, CookiesNotFoundError
|
||||
from .repositories import CookiesNotFoundError, CookiesRepository
|
||||
|
||||
|
||||
class CookiesService:
|
||||
@ -30,6 +31,7 @@ class PublicCookiesService:
|
||||
self._cache = public_cookies_cache
|
||||
self._repository: CookiesRepository = cookies_repository
|
||||
self.count: int = 0
|
||||
self.user_times_limiter = 3 * 3
|
||||
|
||||
async def refresh(self):
|
||||
"""刷新公共Cookies 定时任务
|
||||
@ -41,14 +43,14 @@ class PublicCookiesService:
|
||||
if cookies.status is not None and cookies.status != CookiesStatusEnum.STATUS_SUCCESS:
|
||||
continue
|
||||
user_list.append(cookies.user_id)
|
||||
add, count = await self._cache.add(user_list, RegionEnum.HYPERION)
|
||||
Log.info(f"国服公共Cookies池已经添加[{add}]个 当前成员数为[{count}]")
|
||||
add, count = await self._cache.add_public_cookies(user_list, RegionEnum.HYPERION)
|
||||
logger.info(f"国服公共Cookies池已经添加[{add}]个 当前成员数为[{count}]")
|
||||
user_list.clear()
|
||||
cookies_list = await self._repository.get_all_cookies(RegionEnum.HOYOLAB)
|
||||
for cookies in cookies_list:
|
||||
user_list.append(cookies.user_id)
|
||||
add, count = await self._cache.add(user_list, RegionEnum.HOYOLAB)
|
||||
Log.info(f"国际服公共Cookies池已经添加[{add}]个 当前成员数为[{count}]")
|
||||
add, count = await self._cache.add_public_cookies(user_list, RegionEnum.HOYOLAB)
|
||||
logger.info(f"国际服公共Cookies池已经添加[{add}]个 当前成员数为[{count}]")
|
||||
|
||||
async def get_cookies(self, user_id: int, region: RegionEnum = RegionEnum.NULL):
|
||||
"""获取公共Cookies
|
||||
@ -56,12 +58,15 @@ class PublicCookiesService:
|
||||
:param region: 注册的服务器
|
||||
:return:
|
||||
"""
|
||||
user_times = await self._cache.incr_by_user_times(user_id)
|
||||
if int(user_times) > self.user_times_limiter:
|
||||
raise TooManyRequestPublicCookies
|
||||
while True:
|
||||
public_id, count = await self._cache.get(region)
|
||||
public_id, count = await self._cache.get_public_cookies(region)
|
||||
try:
|
||||
cookies = await self._repository.get_cookies(public_id, region)
|
||||
except CookiesNotFoundError:
|
||||
await self._cache.delete(public_id, region)
|
||||
await self._cache.delete_public_cookies(public_id, region)
|
||||
continue
|
||||
if region == RegionEnum.HYPERION:
|
||||
client = genshin.Client(cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.CHINESE)
|
||||
@ -69,31 +74,33 @@ class PublicCookiesService:
|
||||
client = genshin.Client(cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.OVERSEAS,
|
||||
lang="zh-cn")
|
||||
else:
|
||||
return None
|
||||
raise CookieServiceError
|
||||
try:
|
||||
await client.get_record_card()
|
||||
except InvalidCookies as exc:
|
||||
if "[10001]" in str(exc):
|
||||
Log.warning(f"用户 [{public_id}] Cookies无效")
|
||||
logger.warning(f"用户 [{public_id}] Cookies无效")
|
||||
elif "[-100]" in str(exc):
|
||||
Log.warning(f"用户 [{public_id}] Cookies无效")
|
||||
logger.warning(f"用户 [{public_id}] Cookies无效")
|
||||
elif "[10103]" in str(exc):
|
||||
Log.warning(f"用户 [{public_id}] Cookie有效,但没有绑定到游戏帐户")
|
||||
logger.warning(f"用户 [{public_id}] Cookie有效,但没有绑定到游戏帐户")
|
||||
else:
|
||||
Log.warning("Cookies无效,具体原因未知", exc)
|
||||
logger.warning("Cookies无效,具体原因未知")
|
||||
logger.exception(exc)
|
||||
cookies.status = CookiesStatusEnum.INVALID_COOKIES
|
||||
await self._repository.update_cookies_ex(cookies, region)
|
||||
await self._cache.delete(cookies.user_id, region)
|
||||
await self._cache.delete_public_cookies(cookies.user_id, region)
|
||||
continue
|
||||
except TooManyRequests as exc:
|
||||
Log.warning(f"用户 [{public_id}] 查询次数太多(操作频繁)")
|
||||
except TooManyRequests:
|
||||
logger.warning(f"用户 [{public_id}] 查询次数太多或操作频繁")
|
||||
cookies.status = CookiesStatusEnum.TOO_MANY_REQUESTS
|
||||
await self._repository.update_cookies_ex(cookies, region)
|
||||
await self._cache.delete(cookies.user_id, region)
|
||||
await self._cache.delete_public_cookies(cookies.user_id, region)
|
||||
continue
|
||||
except GenshinException as exc:
|
||||
Log.warning(f"用户 [{public_id}] 获取账号信息发生错误,错误信息为", exc)
|
||||
logger.warning(f"用户 [{public_id}] 获取账号信息发生错误,错误信息为")
|
||||
logger.exception(exc)
|
||||
continue
|
||||
Log.info(f"用户 user_id[{user_id}] 请求"
|
||||
logger.info(f"用户 user_id[{user_id}] 请求"
|
||||
f"用户 user_id[{public_id}] 的公共Cookies 该Cookie使用次数为[{count}]次 ")
|
||||
return cookies
|
||||
|
8
core/error.py
Normal file
8
core/error.py
Normal file
@ -0,0 +1,8 @@
|
||||
"""此模块包含核心模块的错误的基类"""
|
||||
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__}'")
|
@ -1,16 +1,16 @@
|
||||
from utils.redisdb import RedisDB
|
||||
from utils.service.manager import listener_service
|
||||
from core.base.redisdb import RedisDB
|
||||
from core.service import init_service
|
||||
from .cache import GameCache
|
||||
from .services import GameStrategyService, GameMaterialService
|
||||
from .services import GameMaterialService, GameStrategyService
|
||||
|
||||
|
||||
@listener_service()
|
||||
@init_service
|
||||
def create_game_strategy_service(redis: RedisDB):
|
||||
_cache = GameCache(redis, "game:strategy")
|
||||
return GameStrategyService(_cache)
|
||||
|
||||
|
||||
@listener_service()
|
||||
@init_service
|
||||
def create_game_material_service(redis: RedisDB):
|
||||
_cache = GameCache(redis, "game:material")
|
||||
return GameMaterialService(_cache)
|
||||
|
@ -1,6 +1,6 @@
|
||||
from typing import List
|
||||
|
||||
from utils.redisdb import RedisDB
|
||||
from core.base.redisdb import RedisDB
|
||||
|
||||
|
||||
class GameCache:
|
||||
|
@ -1,6 +1,6 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from models.apihelper.hyperion import Hyperion
|
||||
from modules.apihelper.hyperion import Hyperion
|
||||
from .cache import GameCache
|
||||
|
||||
|
||||
|
423
core/plugin.py
Normal file
423
core/plugin.py
Normal file
@ -0,0 +1,423 @@
|
||||
import datetime
|
||||
import re
|
||||
from importlib import import_module
|
||||
from re import Pattern
|
||||
from types import MethodType
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypeVar, Union
|
||||
|
||||
# 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'
|
||||
]
|
||||
|
||||
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')
|
||||
|
||||
_NORMAL_HANDLER_ATTR_NAME = "_handler_data"
|
||||
_CONVERSATION_HANDLER_ATTR_NAME = "_conversation_data"
|
||||
_JOB_ATTR_NAME = "_job_data"
|
||||
|
||||
_EXCLUDE_ATTRS = ['handlers', 'jobs', 'error_handlers']
|
||||
|
||||
|
||||
class _Plugin:
|
||||
|
||||
def _make_handler(self, data: Dict) -> HandlerType:
|
||||
func = getattr(self, data.pop('func'))
|
||||
return data.pop('type')(callback=func, **data.pop('kwargs'))
|
||||
|
||||
@property
|
||||
def handlers(self) -> List[HandlerType]:
|
||||
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
|
||||
(data := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
|
||||
):
|
||||
if data['type'] not in ['error', 'new_chat_member']:
|
||||
result.append(self._make_handler(data))
|
||||
|
||||
return result
|
||||
|
||||
def _new_chat_members_handler_funcs(self) -> List[Tuple[int, Callable]]:
|
||||
|
||||
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
|
||||
(data := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
|
||||
):
|
||||
if data['type'] == 'new_chat_member':
|
||||
result.append((data['priority'], func))
|
||||
|
||||
return result
|
||||
|
||||
@property
|
||||
def error_handlers(self) -> Dict[Callable, bool]:
|
||||
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
|
||||
(data := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
|
||||
):
|
||||
if 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
|
||||
(data := getattr(func, _JOB_ATTR_NAME, None))
|
||||
):
|
||||
_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
|
||||
|
||||
|
||||
class _Conversation(_Plugin):
|
||||
_conversation_kwargs: Dict
|
||||
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
cls._conversation_kwargs = kwargs
|
||||
super(_Conversation, cls).__init_subclass__()
|
||||
return cls
|
||||
|
||||
@property
|
||||
def handlers(self) -> List[HandlerType]:
|
||||
result: List[HandlerType] = []
|
||||
|
||||
entry_points: List[HandlerType] = []
|
||||
states: Dict[Any, List[HandlerType]] = {}
|
||||
fallbacks: List[HandlerType] = []
|
||||
for attr in dir(self):
|
||||
# noinspection PyUnboundLocalVariable
|
||||
if (
|
||||
not (attr.startswith('_') or attr == 'handlers')
|
||||
and
|
||||
isinstance(func := getattr(self, attr), Callable)
|
||||
and
|
||||
(handler_data := getattr(func, _NORMAL_HANDLER_ATTR_NAME, None))
|
||||
):
|
||||
_handler = self._make_handler(handler_data)
|
||||
if conversation_data := getattr(func, _CONVERSATION_HANDLER_ATTR_NAME, None):
|
||||
if (_type := conversation_data.pop('type')) == 'entry':
|
||||
entry_points.append(_handler)
|
||||
elif _type == 'state':
|
||||
if (key := conversation_data.pop('state')) in states:
|
||||
states[key].append(_handler)
|
||||
else:
|
||||
states[key] = [_handler]
|
||||
elif _type == 'fallback':
|
||||
fallbacks.append(_handler)
|
||||
else:
|
||||
result.append(_handler)
|
||||
if entry_points or states or fallbacks:
|
||||
result.append(
|
||||
ConversationHandler(
|
||||
entry_points, states, fallbacks,
|
||||
**self.__class__._conversation_kwargs # pylint: disable=W0212
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
class Plugin(_Plugin):
|
||||
Conversation = _Conversation
|
||||
|
||||
|
||||
class _Handler:
|
||||
def __init__(self, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
|
||||
@property
|
||||
def _type(self) -> Type[BaseHandler]:
|
||||
return getattr(_Module, f"{self.__class__.__name__.strip('_')}Handler")
|
||||
|
||||
def __call__(self, func: Callable[P, T]) -> Callable[P, T]:
|
||||
setattr(func, _NORMAL_HANDLER_ATTR_NAME, {'type': self._type, 'func': func.__name__, 'kwargs': self.kwargs})
|
||||
return func
|
||||
|
||||
|
||||
class _CallbackQuery(_Handler):
|
||||
def __init__(
|
||||
self,
|
||||
pattern: Union[str, Pattern, type, Callable[[object], Optional[bool]]] = None,
|
||||
block: DVInput[bool] = DEFAULT_TRUE,
|
||||
):
|
||||
super(_CallbackQuery, self).__init__(pattern=pattern, block=block)
|
||||
|
||||
|
||||
class _ChatJoinRequest(_Handler):
|
||||
def __init__(self, block: DVInput[bool] = DEFAULT_TRUE):
|
||||
super(_ChatJoinRequest, self).__init__(block=block)
|
||||
|
||||
|
||||
class _ChatMember(_Handler):
|
||||
def __init__(self, chat_member_types: int = -1):
|
||||
super().__init__(chat_member_types=chat_member_types)
|
||||
|
||||
|
||||
class _ChosenInlineResult(_Handler):
|
||||
def __init__(self, block: DVInput[bool] = DEFAULT_TRUE, pattern: Union[str, Pattern] = None):
|
||||
super().__init__(block=block, pattern=pattern)
|
||||
|
||||
|
||||
class _Command(_Handler):
|
||||
def __init__(self, command: str, filters: "BaseFilter" = None, block: DVInput[bool] = DEFAULT_TRUE):
|
||||
super(_Command, self).__init__(command=command, filters=filters, block=block)
|
||||
|
||||
|
||||
class _InlineQuery(_Handler):
|
||||
def __init__(
|
||||
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)
|
||||
|
||||
|
||||
class _MessageNewChatMembers(_Handler):
|
||||
def __init__(self, func: Callable[P, T] = None, *, priority: int = 5):
|
||||
super().__init__()
|
||||
self.func = func
|
||||
self.priority = priority
|
||||
|
||||
def __call__(self, func: Callable[P, T] = None) -> Callable[P, T]:
|
||||
self.func = self.func or func
|
||||
setattr(
|
||||
self.func, _NORMAL_HANDLER_ATTR_NAME,
|
||||
{'type': 'new_chat_member', 'priority': self.priority}
|
||||
)
|
||||
return self.func
|
||||
|
||||
|
||||
class _Message(_Handler):
|
||||
def __init__(self, filters: "BaseFilter", block: DVInput[bool] = DEFAULT_TRUE, ):
|
||||
super(_Message, self).__init__(filters=filters, block=block)
|
||||
|
||||
new_chat_members = _MessageNewChatMembers
|
||||
|
||||
|
||||
class _PollAnswer(_Handler):
|
||||
def __init__(self, block: DVInput[bool] = DEFAULT_TRUE):
|
||||
super(_PollAnswer, self).__init__(block=block)
|
||||
|
||||
|
||||
class _Poll(_Handler):
|
||||
def __init__(self, block: DVInput[bool] = DEFAULT_TRUE):
|
||||
super(_Poll, self).__init__(block=block)
|
||||
|
||||
|
||||
class _PreCheckoutQuery(_Handler):
|
||||
def __init__(self, block: DVInput[bool] = DEFAULT_TRUE):
|
||||
super(_PreCheckoutQuery, self).__init__(block=block)
|
||||
|
||||
|
||||
class _Prefix(_Handler):
|
||||
def __init__(
|
||||
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)
|
||||
|
||||
|
||||
class _ShippingQuery(_Handler):
|
||||
def __init__(self, block: DVInput[bool] = DEFAULT_TRUE):
|
||||
super(_ShippingQuery, self).__init__(block=block)
|
||||
|
||||
|
||||
class _StringCommand(_Handler):
|
||||
def __init__(self, command: str):
|
||||
super(_StringCommand, self).__init__(command=command)
|
||||
|
||||
|
||||
class _StringRegex(_Handler):
|
||||
def __init__(self, pattern: Union[str, Pattern], block: DVInput[bool] = DEFAULT_TRUE):
|
||||
super(_StringRegex, self).__init__(pattern=pattern, block=block)
|
||||
|
||||
|
||||
class _Type(_Handler):
|
||||
# noinspection PyShadowingBuiltins
|
||||
def __init__(
|
||||
self,
|
||||
type: Type, # pylint: disable=redefined-builtin
|
||||
strict: bool = False,
|
||||
block: DVInput[bool] = DEFAULT_TRUE
|
||||
):
|
||||
super(_Type, self).__init__(type=type, strict=strict, block=block)
|
||||
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
class handler(_Handler):
|
||||
def __init__(self, handler_type: Callable[P, HandlerType], **kwargs: P.kwargs):
|
||||
self._type_ = handler_type
|
||||
super(handler, self).__init__(**kwargs)
|
||||
|
||||
@property
|
||||
def _type(self) -> Type[BaseHandler]:
|
||||
# noinspection PyTypeChecker
|
||||
return self._type_
|
||||
|
||||
callback_query = _CallbackQuery
|
||||
chat_join_request = _ChatJoinRequest
|
||||
chat_member = _ChatMember
|
||||
chosen_inline_result = _ChosenInlineResult
|
||||
command = _Command
|
||||
inline_query = _InlineQuery
|
||||
message = _Message
|
||||
poll_answer = _PollAnswer
|
||||
pool = _Poll
|
||||
pre_checkout_query = _PreCheckoutQuery
|
||||
prefix = _Prefix
|
||||
shipping_query = _ShippingQuery
|
||||
string_command = _StringCommand
|
||||
string_regex = _StringRegex
|
||||
type = _Type
|
||||
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
class error_handler:
|
||||
def __init__(self, func: Callable[P, T] = None, *, block: bool = DEFAULT_TRUE):
|
||||
self._func = func
|
||||
self._block = block
|
||||
|
||||
def __call__(self, func: Callable[P, T] = None) -> Callable[P, T]:
|
||||
self._func = func or self._func
|
||||
setattr(self._func, _NORMAL_HANDLER_ATTR_NAME, {'type': 'error', 'block': self._block})
|
||||
return self._func
|
||||
|
||||
|
||||
def _entry(func: Callable[P, T]) -> Callable[P, T]:
|
||||
setattr(func, _CONVERSATION_HANDLER_ATTR_NAME, {'type': 'entry'})
|
||||
return func
|
||||
|
||||
|
||||
class _State:
|
||||
def __init__(self, state: Any):
|
||||
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})
|
||||
return func
|
||||
|
||||
|
||||
def _fallback(func: Callable[P, T]) -> Callable[P, T]:
|
||||
setattr(func, _CONVERSATION_HANDLER_ATTR_NAME, {'type': 'fallback'})
|
||||
return func
|
||||
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
class conversation(_Handler):
|
||||
entry_point = _entry
|
||||
state = _State
|
||||
fallback = _fallback
|
||||
|
||||
|
||||
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 = name
|
||||
self.data = data
|
||||
self.chat_id = chat_id
|
||||
self.user_id = user_id
|
||||
self.job_kwargs = {} if job_kwargs is None else job_kwargs
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __call__(self, func: JobCallback) -> JobCallback:
|
||||
setattr(func, _JOB_ATTR_NAME, {
|
||||
'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('_')
|
||||
})
|
||||
return func
|
||||
|
||||
|
||||
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
|
||||
):
|
||||
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
|
||||
):
|
||||
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
|
||||
):
|
||||
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
|
||||
):
|
||||
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):
|
||||
super().__init__(name, data, chat_id, user_id, job_kwargs)
|
||||
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
class job:
|
||||
run_once = _RunOnce
|
||||
run_repeating = _RunRepeating
|
||||
run_monthly = _RunMonthly
|
||||
run_daily = _RunDaily
|
||||
run_custom = _RunCustom
|
@ -1,12 +1,12 @@
|
||||
from utils.mysql import MySQL
|
||||
from utils.redisdb import RedisDB
|
||||
from utils.service.manager import listener_service
|
||||
from core.base.mysql import MySQL
|
||||
from core.base.redisdb import RedisDB
|
||||
from core.service import init_service
|
||||
from .cache import QuizCache
|
||||
from .repositories import QuizRepository
|
||||
from .services import QuizService
|
||||
|
||||
|
||||
@listener_service()
|
||||
@init_service
|
||||
def create_quiz_service(mysql: MySQL, redis: RedisDB):
|
||||
_repository = QuizRepository(mysql)
|
||||
_cache = QuizCache(redis)
|
||||
|
@ -1,6 +1,6 @@
|
||||
from typing import List
|
||||
|
||||
from .models import Question, Answer
|
||||
from .models import Answer, Question
|
||||
|
||||
|
||||
def CreatQuestionFromSQLData(data: tuple) -> List[Question]:
|
||||
@ -10,6 +10,7 @@ def CreatQuestionFromSQLData(data: tuple) -> List[Question]:
|
||||
temp_list.append(Question(question_id, text))
|
||||
return temp_list
|
||||
|
||||
|
||||
def CreatAnswerFromSQLData(data: tuple) -> List[Answer]:
|
||||
temp_list = []
|
||||
for temp_data in data:
|
||||
|
@ -2,8 +2,8 @@ from typing import List
|
||||
|
||||
import ujson
|
||||
|
||||
from utils.redisdb import RedisDB
|
||||
from .models import Question, Answer
|
||||
from core.base.redisdb import RedisDB
|
||||
from .models import Answer, Question
|
||||
|
||||
|
||||
class QuizCache:
|
||||
|
@ -2,8 +2,8 @@ from typing import List, Optional
|
||||
|
||||
from sqlmodel import SQLModel, Field, Column, Integer, ForeignKey
|
||||
|
||||
from models.baseobject import BaseObject
|
||||
from models.types import JSONDict
|
||||
from utils.baseobject import BaseObject
|
||||
from utils.typedefs import JSONDict
|
||||
|
||||
|
||||
class AnswerDB(SQLModel, table=True):
|
||||
|
@ -2,8 +2,8 @@ from typing import List
|
||||
|
||||
from sqlmodel import select
|
||||
|
||||
from utils.mysql import MySQL
|
||||
from .models import QuestionDB, AnswerDB
|
||||
from core.base.mysql import MySQL
|
||||
from .models import AnswerDB, QuestionDB
|
||||
|
||||
|
||||
class QuizRepository:
|
||||
|
@ -2,7 +2,7 @@ import asyncio
|
||||
from typing import List
|
||||
|
||||
from .cache import QuizCache
|
||||
from .models import Question, Answer
|
||||
from .models import Answer, Question
|
||||
from .repositories import QuizRepository
|
||||
|
||||
|
||||
|
30
core/service.py
Normal file
30
core/service.py
Normal file
@ -0,0 +1,30 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from types import FunctionType
|
||||
|
||||
from utils.log import logger
|
||||
|
||||
__all__ = ['Service', 'init_service']
|
||||
|
||||
|
||||
class Service(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""初始化"""
|
||||
|
||||
async def start(self):
|
||||
"""启动 service"""
|
||||
|
||||
async def stop(self):
|
||||
"""关闭 service"""
|
||||
|
||||
|
||||
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}')
|
||||
return func
|
@ -1,10 +1,10 @@
|
||||
from utils.mysql import MySQL
|
||||
from utils.service.manager import listener_service
|
||||
from core.base.mysql import MySQL
|
||||
from core.service import init_service
|
||||
from .repositories import SignRepository
|
||||
from .services import SignServices
|
||||
|
||||
|
||||
@listener_service()
|
||||
@init_service
|
||||
def create_game_strategy_service(mysql: MySQL):
|
||||
_repository = SignRepository(mysql)
|
||||
_service = SignServices(_repository)
|
||||
|
@ -1,9 +1,9 @@
|
||||
from typing import List, cast, Optional
|
||||
from typing import List, Optional, cast
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from utils.mysql import MySQL
|
||||
from core.base.mysql import MySQL
|
||||
from .models import Sign
|
||||
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
from utils.aiobrowser import AioBrowser
|
||||
from utils.service.manager import listener_service
|
||||
from core.base.aiobrowser import AioBrowser
|
||||
from core.service import init_service
|
||||
from .services import TemplateService
|
||||
|
||||
|
||||
@listener_service()
|
||||
@init_service
|
||||
def create_template_service(browser: AioBrowser):
|
||||
_service = TemplateService(browser)
|
||||
return _service
|
||||
|
@ -2,12 +2,12 @@ import os
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from jinja2 import PackageLoader, Environment, Template
|
||||
from jinja2 import Environment, PackageLoader, Template
|
||||
from playwright.async_api import ViewportSize
|
||||
|
||||
from config import config
|
||||
from logger import Log
|
||||
from utils.aiobrowser import AioBrowser
|
||||
from core.base.aiobrowser import AioBrowser
|
||||
from core.bot import bot
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
class TemplateService:
|
||||
@ -22,7 +22,7 @@ class TemplateService:
|
||||
self._jinja2_template = {}
|
||||
|
||||
def get_template(self, package_path: str, template_name: str) -> Template:
|
||||
if config.debug:
|
||||
if bot.config.debug:
|
||||
# DEBUG下 禁止复用 方便查看和修改模板
|
||||
loader = PackageLoader(self._template_package_name, package_path)
|
||||
jinja2_env = Environment(loader=loader, enable_async=True, autoescape=True)
|
||||
@ -38,16 +38,26 @@ class TemplateService:
|
||||
self._jinja2_template[package_path + template_name] = jinja2_template
|
||||
return jinja2_template
|
||||
|
||||
async def render_async(self, template_path: str, template_name: str, template_data: dict):
|
||||
"""模板渲染
|
||||
:param template_path: 模板目录
|
||||
:param template_name: 模板文件名
|
||||
:param template_data: 模板数据
|
||||
"""
|
||||
start_time = time.time()
|
||||
template = self.get_template(template_path, template_name)
|
||||
html = await template.render_async(**template_data)
|
||||
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, full_page: bool = True, evaluate: Optional[str] = None) -> bytes:
|
||||
"""
|
||||
模板渲染成图片
|
||||
"""模板渲染成图片
|
||||
:param template_path: 模板目录
|
||||
:param template_name: 模板文件名
|
||||
:param template_data: 模板数据
|
||||
:param viewport: 截图大小
|
||||
:param full_page: 是否长截图
|
||||
:param auto_escape: 是否自动转义
|
||||
:param evaluate: 页面加载后运行的 js
|
||||
:return:
|
||||
"""
|
||||
@ -55,7 +65,7 @@ class TemplateService:
|
||||
template = self.get_template(template_path, template_name)
|
||||
template_data["res_path"] = f"file://{self._current_dir}"
|
||||
html = await template.render_async(**template_data)
|
||||
Log.debug(f"{template_name} 模板渲染使用了 {str(time.time() - start_time)}")
|
||||
logger.debug(f"{template_name} 模板渲染使用了 {str(time.time() - start_time)}")
|
||||
browser = await self._browser.get_browser()
|
||||
start_time = time.time()
|
||||
page = await browser.new_page(viewport=viewport)
|
||||
@ -65,5 +75,5 @@ class TemplateService:
|
||||
await page.evaluate(evaluate)
|
||||
png_data = await page.screenshot(full_page=full_page)
|
||||
await page.close()
|
||||
Log.debug(f"{template_name} 图片渲染使用了 {str(time.time() - start_time)}")
|
||||
logger.debug(f"{template_name} 图片渲染使用了 {str(time.time() - start_time)}")
|
||||
return png_data
|
||||
|
@ -1,10 +1,10 @@
|
||||
from utils.mysql import MySQL
|
||||
from utils.service.manager import listener_service
|
||||
from core.base.mysql import MySQL
|
||||
from core.service import init_service
|
||||
from .repositories import UserRepository
|
||||
from .services import UserService
|
||||
|
||||
|
||||
@listener_service()
|
||||
@init_service
|
||||
def create_user_service(mysql: MySQL):
|
||||
_repository = UserRepository(mysql)
|
||||
_service = UserService(_repository)
|
||||
|
3
core/user/error.py
Normal file
3
core/user/error.py
Normal file
@ -0,0 +1,3 @@
|
||||
class UserNotFoundError(Exception):
|
||||
def __init__(self, user_id):
|
||||
super().__init__(f"user not found, user_id: {user_id}")
|
@ -2,7 +2,7 @@ from typing import Optional
|
||||
|
||||
from sqlmodel import SQLModel, Field, Enum, Column
|
||||
|
||||
from models.base import RegionEnum
|
||||
from utils.models.base import RegionEnum
|
||||
|
||||
|
||||
class User(SQLModel, table=True):
|
||||
|
@ -3,8 +3,8 @@ from typing import cast
|
||||
from sqlalchemy import select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from utils.error import NotFoundError
|
||||
from utils.mysql import MySQL
|
||||
from core.base.mysql import MySQL
|
||||
from .error import UserNotFoundError
|
||||
from .models import User
|
||||
|
||||
|
||||
@ -32,10 +32,5 @@ class UserRepository:
|
||||
async def add_user(self, user: User):
|
||||
async with self.mysql.Session() as session:
|
||||
session = cast(AsyncSession, session)
|
||||
await session.add(user)
|
||||
session.add(user)
|
||||
await session.commit()
|
||||
|
||||
|
||||
class UserNotFoundError(NotFoundError):
|
||||
entity_name: str = "User"
|
||||
entity_value_name: str = "user_id"
|
||||
|
@ -1,10 +1,10 @@
|
||||
from utils.redisdb import RedisDB
|
||||
from utils.service.manager import listener_service
|
||||
from core.base.redisdb import RedisDB
|
||||
from core.service import init_service
|
||||
from .cache import WikiCache
|
||||
from .services import WikiService
|
||||
|
||||
|
||||
@listener_service()
|
||||
@init_service
|
||||
def create_wiki_service(redis: RedisDB):
|
||||
_cache = WikiCache(redis)
|
||||
_service = WikiService(_cache)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import ujson as json
|
||||
|
||||
from models.wiki.base import Model
|
||||
from utils.redisdb import RedisDB
|
||||
from core.base.redisdb import RedisDB
|
||||
from modules.wiki.base import Model
|
||||
|
||||
|
||||
class WikiCache:
|
||||
|
@ -1,9 +1,9 @@
|
||||
from typing import List, NoReturn, Optional
|
||||
|
||||
from core.wiki.cache import WikiCache
|
||||
from logger import Log
|
||||
from models.wiki.character import Character
|
||||
from models.wiki.weapon import Weapon
|
||||
from modules.wiki.character import Character
|
||||
from modules.wiki.weapon import Weapon
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
class WikiService:
|
||||
@ -19,7 +19,7 @@ class WikiService:
|
||||
|
||||
async def refresh_weapon(self) -> NoReturn:
|
||||
weapon_name_list = await Weapon.get_name_list()
|
||||
Log.info(f"一共找到 {len(weapon_name_list)} 把武器信息")
|
||||
logger.info(f"一共找到 {len(weapon_name_list)} 把武器信息")
|
||||
|
||||
weapon_list = []
|
||||
num = 0
|
||||
@ -27,16 +27,16 @@ class WikiService:
|
||||
weapon_list.append(weapon)
|
||||
num += 1
|
||||
if num % 10 == 0:
|
||||
Log.info(f"现在已经获取到 {num} 把武器信息")
|
||||
logger.info(f"现在已经获取到 {num} 把武器信息")
|
||||
|
||||
Log.info("写入武器信息到Redis")
|
||||
logger.info("写入武器信息到Redis")
|
||||
self._weapon_list = weapon_list
|
||||
await self._cache.delete("weapon")
|
||||
await self._cache.set("weapon", [i.json() for i in weapon_list])
|
||||
|
||||
async def refresh_characters(self) -> NoReturn:
|
||||
character_name_list = await Character.get_name_list()
|
||||
Log.info(f"一共找到 {len(character_name_list)} 个角色信息")
|
||||
logger.info(f"一共找到 {len(character_name_list)} 个角色信息")
|
||||
|
||||
character_list = []
|
||||
num = 0
|
||||
@ -44,9 +44,9 @@ class WikiService:
|
||||
character_list.append(character)
|
||||
num += 1
|
||||
if num % 10 == 0:
|
||||
Log.info(f"现在已经获取到 {num} 个角色信息")
|
||||
logger.info(f"现在已经获取到 {num} 个角色信息")
|
||||
|
||||
Log.info("写入角色信息到Redis")
|
||||
logger.info("写入角色信息到Redis")
|
||||
self._character_list = character_list
|
||||
await self._cache.delete("characters")
|
||||
await self._cache.set("characters", [i.json() for i in character_list])
|
||||
@ -56,12 +56,12 @@ class WikiService:
|
||||
用于把Redis的缓存全部加载进Python
|
||||
:return:
|
||||
"""
|
||||
Log.info("正在重新获取Wiki")
|
||||
Log.info("正在重新获取武器信息")
|
||||
logger.info("正在重新获取Wiki")
|
||||
logger.info("正在重新获取武器信息")
|
||||
await self.refresh_weapon()
|
||||
Log.info("正在重新获取角色信息")
|
||||
logger.info("正在重新获取角色信息")
|
||||
await self.refresh_characters()
|
||||
Log.info("刷新成功")
|
||||
logger.info("刷新成功")
|
||||
|
||||
async def init(self) -> NoReturn:
|
||||
"""
|
||||
|
@ -1,35 +0,0 @@
|
||||
# jobs 目录
|
||||
|
||||
## 说明
|
||||
|
||||
改目录存放 BOT 的工作队列、注册和具体实现
|
||||
|
||||
## 基础代码
|
||||
|
||||
``` python
|
||||
import datetime
|
||||
|
||||
from telegram.ext import CallbackContext
|
||||
|
||||
from logger import Log
|
||||
from utils.job.manager import listener_jobs_class
|
||||
|
||||
@listener_jobs_class()
|
||||
class JobTest:
|
||||
|
||||
@classmethod
|
||||
def build_jobs(cls, job_queue: JobQueue):
|
||||
test = cls()
|
||||
# 注册每日执行任务
|
||||
# 执行时间为21点45分
|
||||
job_queue.run_daily(test.test, datetime.time(21, 45, 00), name="测试Job")
|
||||
|
||||
async def test(self, context: CallbackContext):
|
||||
Log.info("测试Job[OK]")
|
||||
```
|
||||
|
||||
### 注意
|
||||
|
||||
jobs 模块下的类必须提供 `build_jobs` 类方法作为构建相应处理程序给 `handle.py`
|
||||
|
||||
只需在构建的类前加上 `@listener_jobs_class()` 修饰器即可
|
13
jobs/base.py
13
jobs/base.py
@ -1,13 +0,0 @@
|
||||
from telegram.ext import CallbackContext
|
||||
|
||||
|
||||
class BaseJob:
|
||||
|
||||
@staticmethod
|
||||
def remove_job_if_exists(name: str, context: CallbackContext) -> bool:
|
||||
current_jobs = context.job_queue.get_jobs_by_name(name)
|
||||
if not current_jobs:
|
||||
return False
|
||||
for job in current_jobs:
|
||||
job.schedule_removal()
|
||||
return True
|
@ -1,26 +0,0 @@
|
||||
import datetime
|
||||
|
||||
from telegram.ext import CallbackContext, JobQueue
|
||||
|
||||
from core.cookies.services import PublicCookiesService
|
||||
from logger import Log
|
||||
from utils.job.manager import listener_jobs_class
|
||||
from utils.service.inject import inject
|
||||
|
||||
|
||||
@listener_jobs_class()
|
||||
class PublicCookies:
|
||||
|
||||
@inject
|
||||
def __init__(self, public_cookies_service: PublicCookiesService = None):
|
||||
self.public_cookies_service = public_cookies_service
|
||||
|
||||
@classmethod
|
||||
def build_jobs(cls, job_queue: JobQueue):
|
||||
jobs = cls()
|
||||
job_queue.run_repeating(jobs.refresh, datetime.timedelta(hours=2))
|
||||
|
||||
async def refresh(self, _: CallbackContext):
|
||||
Log.info("正在刷新公共Cookies池")
|
||||
await self.public_cookies_service.refresh()
|
||||
Log.info("刷新公共Cookies池成功")
|
61
logger.py
61
logger.py
@ -1,61 +0,0 @@
|
||||
import logging
|
||||
import os
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
import colorlog
|
||||
|
||||
from config import config
|
||||
|
||||
current_path = os.path.realpath(os.getcwd())
|
||||
log_path = os.path.join(current_path, "logs")
|
||||
if not os.path.exists(log_path):
|
||||
os.mkdir(log_path)
|
||||
log_file_name = os.path.join(log_path, "log.log")
|
||||
|
||||
log_colors_config = {
|
||||
"DEBUG": "cyan",
|
||||
"INFO": "green",
|
||||
"WARNING": "yellow",
|
||||
"ERROR": "red",
|
||||
"CRITICAL": "red",
|
||||
}
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger("TGPaimonBot")
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.CRITICAL)
|
||||
if config.debug:
|
||||
self.logger.setLevel(logging.DEBUG)
|
||||
else:
|
||||
self.logger.setLevel(logging.INFO)
|
||||
self.formatter = colorlog.ColoredFormatter(
|
||||
"%(log_color)s[%(asctime)s] [%(levelname)s] - %(message)s", log_colors=log_colors_config)
|
||||
self.formatter2 = logging.Formatter("[%(asctime)s] [%(levelname)s] - %(message)s")
|
||||
fh = RotatingFileHandler(filename=log_file_name, maxBytes=1024 * 1024 * 5, backupCount=5,
|
||||
encoding="utf-8")
|
||||
fh.setFormatter(self.formatter2)
|
||||
root_logger.addHandler(fh)
|
||||
|
||||
ch = colorlog.StreamHandler()
|
||||
ch.setFormatter(self.formatter)
|
||||
root_logger.addHandler(ch)
|
||||
|
||||
def getLogger(self):
|
||||
return self.logger
|
||||
|
||||
def debug(self, msg, exc_info=None):
|
||||
self.logger.debug(msg=msg, exc_info=exc_info)
|
||||
|
||||
def info(self, msg, exc_info=None):
|
||||
self.logger.info(msg=msg, exc_info=exc_info)
|
||||
|
||||
def warning(self, msg, exc_info=None):
|
||||
self.logger.warning(msg=msg, exc_info=exc_info)
|
||||
|
||||
def error(self, msg, exc_info=None):
|
||||
self.logger.error(msg=msg, exc_info=exc_info)
|
||||
|
||||
|
||||
Log = Logger()
|
97
main.py
97
main.py
@ -1,97 +0,0 @@
|
||||
import asyncio
|
||||
from warnings import filterwarnings
|
||||
|
||||
import pytz
|
||||
from telegram.ext import Application, Defaults, AIORateLimiter
|
||||
from telegram.warnings import PTBUserWarning
|
||||
|
||||
from config import config
|
||||
from logger import Log
|
||||
from utils.aiobrowser import AioBrowser
|
||||
from utils.job.register import register_job
|
||||
from utils.mysql import MySQL
|
||||
from utils.plugins.register import register_plugin_handlers
|
||||
from utils.redisdb import RedisDB
|
||||
from utils.service.manager import ServicesManager
|
||||
|
||||
# 无视相关警告
|
||||
# 该警告说明在官方GITHUB的WIKI中Frequently Asked Questions里的What do the per_* settings in ConversationHandler do?
|
||||
filterwarnings(action="ignore", message=r".*CallbackQueryHandler", category=PTBUserWarning)
|
||||
filterwarnings(action="ignore", message=r".*Prior to v20.0 the `days` parameter", category=PTBUserWarning)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
Log.info("正在启动项目")
|
||||
|
||||
# 初始化数据库
|
||||
Log.info("初始化数据库")
|
||||
mysql = MySQL(host=config.mysql["host"], user=config.mysql["user"], password=config.mysql["password"],
|
||||
port=config.mysql["port"], database=config.mysql["database"])
|
||||
|
||||
# 初始化Redis缓存
|
||||
Log.info("初始化Redis缓存")
|
||||
redis = RedisDB(host=config.redis["host"], port=config.redis["port"], db=config.redis["database"])
|
||||
|
||||
# 初始化Playwright
|
||||
Log.info("初始化Playwright")
|
||||
browser = AioBrowser()
|
||||
|
||||
# 传入服务并启动
|
||||
Log.info("正在启动服务")
|
||||
services = ServicesManager(mysql, redis, browser)
|
||||
services.refresh_list("core/*")
|
||||
services.import_module()
|
||||
services.add_service()
|
||||
|
||||
# 构建BOT
|
||||
Log.info("构建BOT")
|
||||
|
||||
defaults = Defaults(tzinfo=pytz.timezone("Asia/Shanghai"))
|
||||
rate_limiter = AIORateLimiter()
|
||||
|
||||
application = Application \
|
||||
.builder() \
|
||||
.token(config.bot_token) \
|
||||
.defaults(defaults) \
|
||||
.rate_limiter(rate_limiter) \
|
||||
.build()
|
||||
|
||||
register_plugin_handlers(application)
|
||||
|
||||
register_job(application)
|
||||
|
||||
# 启动BOT
|
||||
try:
|
||||
Log.info("BOT已经启动 开始处理命令")
|
||||
# BOT 在退出后默认关闭LOOP 这时候得让LOOP不要关闭
|
||||
application.run_polling(close_loop=False)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
pass
|
||||
except Exception as exc:
|
||||
Log.info("BOT执行过程中出现错误")
|
||||
raise exc
|
||||
finally:
|
||||
Log.info("项目收到退出命令 BOT停止处理并退出")
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
# 需要关闭数据库连接
|
||||
Log.info("正在关闭数据库连接")
|
||||
loop.run_until_complete(mysql.wait_closed())
|
||||
# 关闭Redis连接
|
||||
Log.info("正在关闭Redis连接")
|
||||
loop.run_until_complete(redis.close())
|
||||
# 关闭playwright
|
||||
Log.info("正在关闭Playwright")
|
||||
loop.run_until_complete(browser.close())
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
pass
|
||||
except Exception as exc:
|
||||
Log.error("关闭必要连接时出现错误 \n", exc)
|
||||
Log.info("正在关闭loop")
|
||||
# 关闭LOOP
|
||||
loop.close()
|
||||
Log.info("项目已经已结束")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,5 +1,5 @@
|
||||
# metadata 目录说明
|
||||
|
||||
| FileName | Introduce |
|
||||
| :----------: | ------------- |
|
||||
|:------------:|-----------|
|
||||
| shortname.py | 记录短名称MAP |
|
File diff suppressed because it is too large
Load Diff
@ -1,387 +0,0 @@
|
||||
{
|
||||
"20848859": "黑岩斩刀",
|
||||
"33330467": "元素熟练",
|
||||
"37147251": "匣里日月",
|
||||
"43015699": "待定",
|
||||
"54857595": "止水息雷",
|
||||
"83115355": "被怜爱的少女",
|
||||
"85795635": "专注",
|
||||
"88505754": "枫原万叶",
|
||||
"135182203": "止水息雷",
|
||||
"147298547": "流浪大地的乐团",
|
||||
"156294403": "沉沦之心",
|
||||
"160493219": "暗铁剑",
|
||||
"168956722": "七七",
|
||||
"197755235": "贯虹之槊",
|
||||
"212557731": "祭雷之人",
|
||||
"240385755": "破浪",
|
||||
"246984427": "踏火息雷",
|
||||
"262428003": "祭冰之人",
|
||||
"270124867": "护国的无垢之心",
|
||||
"287454963": "祭风之人",
|
||||
"288666635": "无垢之心",
|
||||
"302691299": "琥珀玥",
|
||||
"303155515": "离簇不归",
|
||||
"310247243": "神乐之真意",
|
||||
"334242634": "申鹤",
|
||||
"339931171": "乘胜追击",
|
||||
"342097547": "辰砂之纺锤",
|
||||
"346510395": "衔珠海皇",
|
||||
"368014203": "斩裂晴空的龙脊",
|
||||
"391273955": "斫断黑翼的利齿",
|
||||
"411685275": "钢轮弓",
|
||||
"479076483": "冷刃",
|
||||
"481755219": "黑岩刺枪",
|
||||
"486287579": "余热",
|
||||
"500612819": "「旗杆」",
|
||||
"500987603": "(test)穿模测试",
|
||||
"506630267": "顺风而行",
|
||||
"514784907": "踏火止水",
|
||||
"521221323": "护国的无垢之心",
|
||||
"540938627": "掠食者",
|
||||
"566772267": "御伽大王御伽话",
|
||||
"577103787": "能量沐浴",
|
||||
"578575283": "流月针",
|
||||
"597991835": "白夜皓月",
|
||||
"613846163": "降世",
|
||||
"618786571": "钺矛",
|
||||
"623494555": "摧坚",
|
||||
"623534363": "西风秘典",
|
||||
"630452219": "樱之斋宫",
|
||||
"646100491": "千岩诀·同心",
|
||||
"650049651": "风花之颂",
|
||||
"655825874": "云堇",
|
||||
"656120259": "神射手之誓",
|
||||
"680510411": "白影剑",
|
||||
"688991243": "息灾",
|
||||
"693354267": "尘世之锁",
|
||||
"697277554": "烟绯",
|
||||
"716252627": "千岩长枪",
|
||||
"729851187": "冰之川与雪之砂",
|
||||
"735056795": "西风大剑",
|
||||
"807607555": "天空之卷",
|
||||
"824949859": "嘟嘟!大冒险",
|
||||
"828711395": "阿莫斯之弓",
|
||||
"836208539": "炊金",
|
||||
"850802171": "白铁大剑",
|
||||
"855894507": "战狂",
|
||||
"862591315": "苍白之火",
|
||||
"877751435": "宗室大剑",
|
||||
"902264035": "风鹰剑",
|
||||
"902282051": "收割",
|
||||
"909145139": "护国的无垢之心",
|
||||
"930640955": "钟剑",
|
||||
"933076627": "冰风迷途的勇士",
|
||||
"942758755": "专注",
|
||||
"944332883": "斫峰之刃",
|
||||
"949506483": "海洋的胜利",
|
||||
"968378595": "西风之鹰的抗争",
|
||||
"968893378": "班尼特",
|
||||
"991968139": "非时之梦·常世灶食",
|
||||
"1006042610": "神里绫华",
|
||||
"1021898539": "弹弓",
|
||||
"1021947690": "魈",
|
||||
"1028735635": "抗争的践行之歌",
|
||||
"1053433018": "砂糖",
|
||||
"1072884907": "万国诸海图谱",
|
||||
"1075647299": "松籁响起之时",
|
||||
"1082448331": "微光的海渊民",
|
||||
"1089950259": "天空之傲",
|
||||
"1097898243": "沉重",
|
||||
"1103732675": "幸运儿",
|
||||
"1113306282": "莫娜",
|
||||
"1114777131": "和弦",
|
||||
"1119368259": "旅程",
|
||||
"1130996346": "香菱",
|
||||
"1133599347": "矢志不忘",
|
||||
"1148024603": "「渔获」",
|
||||
"1154009435": "试作星镰",
|
||||
"1163263227": "流浪乐章",
|
||||
"1163616891": "霜葬",
|
||||
"1182966603": "佣兵重剑",
|
||||
"1186209435": "赌徒",
|
||||
"1212345779": "角斗士的终幕礼",
|
||||
"1217552947": "白刃流转",
|
||||
"1240067179": "西风猎弓",
|
||||
"1319974859": "激励",
|
||||
"1321135667": "匣里龙吟",
|
||||
"1337666507": "千岩牢固",
|
||||
"1344953075": "顺风而行",
|
||||
"1345343763": "磐岩结绿",
|
||||
"1383639611": "奇迹",
|
||||
"1388004931": "飞天御剑",
|
||||
"1390797107": "白缨枪",
|
||||
"1404688115": "别离的思念之歌",
|
||||
"1406746947": "异世界行记",
|
||||
"1414366819": "金璋皇极",
|
||||
"1437658243": "螭骨剑",
|
||||
"1438974835": "逆飞的流星",
|
||||
"1455107995": "四风原典",
|
||||
"1468367538": "迪奥娜",
|
||||
"1479961579": "铁影阔剑",
|
||||
"1483922610": "九条裟罗",
|
||||
"1485303435": "注能之刺",
|
||||
"1492752155": "气定神闲",
|
||||
"1499235563": "乘胜追击",
|
||||
"1499817443": "苍翠之风",
|
||||
"1516554699": "石英大剑",
|
||||
"1522029867": "踏火息雷",
|
||||
"1524173875": "炽烈的炎之魔女",
|
||||
"1533656818": "旅行者",
|
||||
"1541919827": "染血的骑士道",
|
||||
"1545992315": "「正义」",
|
||||
"1558036915": "辰砂往生录",
|
||||
"1562601179": "翠绿之影",
|
||||
"1588620330": "神里绫人",
|
||||
"1595734083": "(test)穿模测试",
|
||||
"1600275315": "波乱月白经津",
|
||||
"1608953539": "黎明神剑",
|
||||
"1610242915": "传承",
|
||||
"1628928163": "风花之愿",
|
||||
"1632377563": "渡过烈火的贤人",
|
||||
"1651985379": "极昼的先兆者",
|
||||
"1660598451": "岩藏之胤",
|
||||
"1675686363": "祭礼大剑",
|
||||
"1706534267": "有话直说",
|
||||
"1722706579": "止水融冰",
|
||||
"1745286795": "名士振舞",
|
||||
"1745712907": "驭浪的海祇民",
|
||||
"1751039235": "昔日宗室之仪",
|
||||
"1756609915": "海染砗磲",
|
||||
"1771603299": "金璋皇极",
|
||||
"1773425155": "降临之剑",
|
||||
"1789612403": "回响",
|
||||
"1820235315": "无矢之歌",
|
||||
"1836628747": "叛逆的守护者",
|
||||
"1860795787": "曚云之月",
|
||||
"1864015138": "刻晴",
|
||||
"1873342283": "平息鸣雷的尊者",
|
||||
"1890163363": "不灭月华",
|
||||
"1901973075": "冬极白星",
|
||||
"1921418842": "诺艾尔",
|
||||
"1932742643": "灭却之戒法",
|
||||
"1934830979": "无尽的渴慕",
|
||||
"1940821986": "久岐忍",
|
||||
"1940919994": "胡桃",
|
||||
"1966438658": "安柏",
|
||||
"1982136171": "专注",
|
||||
"1990641987": "祭礼剑",
|
||||
"1990820123": "天目影打刀",
|
||||
"1991707099": "试作斩岩",
|
||||
"1997709467": "和璞鸢",
|
||||
"2006422931": "千岩古剑",
|
||||
"2009975571": "(test)穿模测试",
|
||||
"2017033267": "气定神闲",
|
||||
"2025598051": "顺风而行",
|
||||
"2040573235": "悠古的磐岩",
|
||||
"2060049099": "祭火之人",
|
||||
"2108574027": "碎石",
|
||||
"2109571443": "专注",
|
||||
"2125206395": "银剑",
|
||||
"2149411851": "金璋皇极",
|
||||
"2172529947": "乘胜追击",
|
||||
"2176134843": "专注",
|
||||
"2190368347": "决",
|
||||
"2191797987": "冒险家",
|
||||
"2195665683": "祭礼残章",
|
||||
"2242027395": "黑檀弓",
|
||||
"2276480763": "绝缘之旗印",
|
||||
"2279290283": "魔导绪论",
|
||||
"2297485451": "速射弓斗",
|
||||
"2312640651": "气定神闲",
|
||||
"2317820211": "注能之针",
|
||||
"2322648115": "粉碎",
|
||||
"2324146259": "白辰之环",
|
||||
"2340970067": "历练的猎弓",
|
||||
"2359799475": "恶王丸",
|
||||
"2364208851": "行者之心",
|
||||
"2365025043": "街巷游侠",
|
||||
"2375993851": "宗室长剑",
|
||||
"2383998915": "驭浪的海祇民",
|
||||
"2384519283": "弹弓",
|
||||
"2388785242": "早柚",
|
||||
"2400012995": "祭礼弓",
|
||||
"2410593283": "无锋剑",
|
||||
"2417717595": "暗巷猎手",
|
||||
"2425414923": "落霞",
|
||||
"2433755451": "揭旗的叛逆之歌",
|
||||
"2440850563": "回响长天的诗歌",
|
||||
"2466140362": "温迪",
|
||||
"2469300579": "乘胜追击",
|
||||
"2470306939": "飞雷御执",
|
||||
"2474354867": "西风剑",
|
||||
"2476346187": "踏火止水",
|
||||
"2491797315": "喜多院十文字",
|
||||
"2504399314": "宵宫",
|
||||
"2512309395": "如雷的盛怒",
|
||||
"2521338131": "试作金珀",
|
||||
"2534304035": "雾切御腰物",
|
||||
"2539208459": "证誓之明瞳",
|
||||
"2546254811": "华馆梦醒形骸记",
|
||||
"2556914683": "绝弦",
|
||||
"2587614459": "忍冬之果",
|
||||
"2614170427": "飞天大御剑",
|
||||
"2646367730": "北斗",
|
||||
"2664629131": "匣里灭辰",
|
||||
"2666951267": "训练大剑",
|
||||
"2673337443": "注能之矢",
|
||||
"2679781122": "甘雨",
|
||||
"2684365579": "登场乐",
|
||||
"2705029563": "口袋魔导书",
|
||||
"2713453234": "八重神子",
|
||||
"2719832059": "(test)穿模测试",
|
||||
"2743659331": "激流",
|
||||
"2749508387": "金璋皇极",
|
||||
"2749853923": "腐殖之剑",
|
||||
"2753539619": "雪葬的星银",
|
||||
"2764598579": "流放者",
|
||||
"2792766467": "无工之剑",
|
||||
"2796697027": "新手长枪",
|
||||
"2832648187": "宗室长弓",
|
||||
"2834803571": "金璋皇极",
|
||||
"2848374378": "夜兰",
|
||||
"2853296811": "穿刺高天的利齿",
|
||||
"2871793795": "锐利",
|
||||
"2876340530": "重云",
|
||||
"2890909531": "武人",
|
||||
"2893964243": "飞矢传书",
|
||||
"2915865819": "渊中霞彩",
|
||||
"2918525947": "飞雷之弦振",
|
||||
"2935286715": "宗室猎枪",
|
||||
"2947140987": "暗巷闪光",
|
||||
"2949448555": "苍古自由之誓",
|
||||
"2963220587": "翡玉法球",
|
||||
"3001782875": "气定神闲",
|
||||
"3018479371": "船歌",
|
||||
"3024507506": "雷电将军",
|
||||
"3063488107": "强力攻击",
|
||||
"3068316954": "荒泷一斗",
|
||||
"3070169307": "铁尖枪",
|
||||
"3079462611": "驭浪的海祇民",
|
||||
"3090373787": "暗巷的酒与诗",
|
||||
"3097441915": "以理服人",
|
||||
"3112448011": "决心",
|
||||
"3112679155": "终末嗟叹之诗",
|
||||
"3156385731": "昭心",
|
||||
"3169209451": "弓藏",
|
||||
"3192689683": "霜葬",
|
||||
"3221566250": "琴",
|
||||
"3235324891": "护摩之杖",
|
||||
"3252085691": "顺风而行",
|
||||
"3258658763": "嗜魔",
|
||||
"3265161211": "注能之锋",
|
||||
"3273999011": "黑岩绯玉",
|
||||
"3277782506": "菲谢尔",
|
||||
"3302787771": "霜葬",
|
||||
"3305772819": "奔袭战术",
|
||||
"3314157803": "克柔",
|
||||
"3337185491": "浅濑之弭",
|
||||
"3337249451": "过载",
|
||||
"3339083250": "可莉",
|
||||
"3344622722": "丽莎",
|
||||
"3364338659": "无边际的眷顾",
|
||||
"3371922315": "神樱神游神乐舞",
|
||||
"3378007475": "黑岩长剑",
|
||||
"3400133546": "五郎",
|
||||
"3406113971": "顺风而行",
|
||||
"3421967235": "吃虎鱼刀",
|
||||
"3439749859": "苍翠猎弓",
|
||||
"3443142923": "龙脊长枪",
|
||||
"3447737235": "黑岩战弓",
|
||||
"3456986819": "嘟嘟可故事集",
|
||||
"3465493459": "精准",
|
||||
"3500935003": "讨龙英杰谭",
|
||||
"3535784755": "勇士之心",
|
||||
"3541083923": "角斗士",
|
||||
"3555115602": "托马",
|
||||
"3584825427": "学徒笔记",
|
||||
"3587062891": "千岩诀·同心",
|
||||
"3587621259": "笛剑",
|
||||
"3600623979": "猎弓",
|
||||
"3608180322": "迪卢克",
|
||||
"3618167299": "学士",
|
||||
"3625393819": "试作澹月",
|
||||
"3626268211": "来歆余响",
|
||||
"3673792067": "旅行剑",
|
||||
"3684723963": "雨裁",
|
||||
"3689108098": "埃洛伊",
|
||||
"3717667418": "优菈",
|
||||
"3717849275": "薙草之稻光",
|
||||
"3719372715": "甲级宝珏",
|
||||
"3722933411": "试作古华",
|
||||
"3755004051": "西风长枪",
|
||||
"3762437019": "(test)穿模测试",
|
||||
"3775299170": "芭芭拉",
|
||||
"3782508715": "游医",
|
||||
"3796702635": "变化万端",
|
||||
"3796905611": "黑剑",
|
||||
"3816664530": "旅行者",
|
||||
"3827789435": "宗室秘法录",
|
||||
"3832443723": "不屈",
|
||||
"3836188467": "无羁的朱赤之蝶",
|
||||
"3847143266": "达达利亚",
|
||||
"3862787418": "钟离",
|
||||
"3890292467": "教官",
|
||||
"3898539027": "浮游四方的灵云",
|
||||
"3914045794": "珊瑚宫心海",
|
||||
"3914951691": "赤角石溃杵",
|
||||
"3933622347": "天空之翼",
|
||||
"3949653579": "幽夜华尔兹",
|
||||
"3966753539": "洗濯诸类之形",
|
||||
"3975746731": "鸦羽弓",
|
||||
"3995710363": "狼的末路",
|
||||
"3996017211": "收割",
|
||||
"3999792907": "祭水之人",
|
||||
"4000770243": "街巷伏击",
|
||||
"4022012131": "乘胜追击",
|
||||
"4049410651": "决斗之枪",
|
||||
"4055003299": "天空之刃",
|
||||
"4060235987": "日月辉",
|
||||
"4080317355": "勇气",
|
||||
"4082302819": "守护之心",
|
||||
"4090429643": "沐浴龙血的剑",
|
||||
"4103022435": "铁蜂刺",
|
||||
"4103766499": "黑缨枪",
|
||||
"4108620722": "阿贝多",
|
||||
"4113638323": "昭理的鸢之枪",
|
||||
"4119663210": "凯亚",
|
||||
"4122509083": "断浪长鳍",
|
||||
"4124851547": "雾切之回光",
|
||||
"4127888970": "凝光",
|
||||
"4137694339": "(test)竿测试",
|
||||
"4139294531": "信使",
|
||||
"4144069251": "追忆之注连",
|
||||
"4158505619": "天空之脊",
|
||||
"4160147242": "雷泽",
|
||||
"4162981171": "(test)穿模测试",
|
||||
"4186179883": "破魔之弓",
|
||||
"4193089947": "桂木斩长正",
|
||||
"4197635682": "行秋",
|
||||
"4226083179": "名士振舞",
|
||||
"4230231107": "若水",
|
||||
"4245213187": "注能之卷",
|
||||
"4258047555": "极夜二重奏",
|
||||
"4260733330": "罗莎莉亚",
|
||||
"4267718859": "反曲弓",
|
||||
"4273845410": "辛焱",
|
||||
"4275754179": "如狼般狩猎者",
|
||||
"FIGHT_PROP_MAX_HP": "生命值上限",
|
||||
"FIGHT_PROP_ATTACK": "攻击力",
|
||||
"FIGHT_PROP_DEFENSE": "防御力",
|
||||
"FIGHT_PROP_ELEMENT_MASTERY": "元素精通",
|
||||
"FIGHT_PROP_CRITICAL": "暴击率",
|
||||
"FIGHT_PROP_CRITICAL_HURT": "暴击伤害",
|
||||
"FIGHT_PROP_HEAL_ADD": "治疗加成",
|
||||
"FIGHT_PROP_HEALED_ADD": "受治疗加成",
|
||||
"FIGHT_PROP_CHARGE_EFFICIENCY": "元素充能效率",
|
||||
"FIGHT_PROP_SHIELD_COST_MINUS_RATIO": "护盾强效",
|
||||
"FIGHT_PROP_FIRE_ADD_HURT": "火元素伤害加成",
|
||||
"FIGHT_PROP_WATER_ADD_HURT": "水元素伤害加成",
|
||||
"FIGHT_PROP_GRASS_ADD_HURT": "草元素伤害加成",
|
||||
"FIGHT_PROP_ELEC_ADD_HURT": "雷元素伤害加成",
|
||||
"FIGHT_PROP_WIND_ADD_HURT": "风元素伤害加成",
|
||||
"FIGHT_PROP_ICE_ADD_HURT": "冰元素伤害加成",
|
||||
"FIGHT_PROP_ROCK_ADD_HURT": "岩元素伤害加成",
|
||||
"FIGHT_PROP_PHYSICAL_ADD_HURT": "物理伤害加成",
|
||||
"level": "等级"
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
{
|
||||
"FIGHT_PROP_BASE_ATTACK": "基础攻击力",
|
||||
"FIGHT_PROP_BASE_DEFENSE": "基础防御力",
|
||||
"FIGHT_PROP_BASE_HP": "基础血量",
|
||||
"FIGHT_PROP_ATTACK": "攻击力",
|
||||
"FIGHT_PROP_ATTACK_PERCENT": "百分比攻击力",
|
||||
"FIGHT_PROP_HP": "血量",
|
||||
"FIGHT_PROP_HP_PERCENT": "百分比血量",
|
||||
"FIGHT_PROP_DEFENSE": "防御力",
|
||||
"FIGHT_PROP_DEFENSE_PERCENT": "百分比防御力",
|
||||
"FIGHT_PROP_ELEMENT_MASTERY": "元素精通",
|
||||
"FIGHT_PROP_CRITICAL": "暴击率",
|
||||
"FIGHT_PROP_CRITICAL_HURT": "暴击伤害",
|
||||
"FIGHT_PROP_CHARGE_EFFICIENCY": "元素充能效率",
|
||||
"FIGHT_PROP_FIRE_SUB_HURT": "火元素抗性",
|
||||
"FIGHT_PROP_ELEC_SUB_HURT": "雷元素抗性",
|
||||
"FIGHT_PROP_ICE_SUB_HURT": "冰元素抗性",
|
||||
"FIGHT_PROP_WATER_SUB_HURT": "水元素抗性",
|
||||
"FIGHT_PROP_WIND_SUB_HURT": "风元素抗性",
|
||||
"FIGHT_PROP_ROCK_SUB_HURT": "岩元素抗性",
|
||||
"FIGHT_PROP_GRASS_SUB_HURT": "草元素抗性",
|
||||
"FIGHT_PROP_FIRE_ADD_HURT": "火元素伤害加成",
|
||||
"FIGHT_PROP_ELEC_ADD_HURT": "雷元素伤害加成",
|
||||
"FIGHT_PROP_ICE_ADD_HURT": "冰元素伤害加成",
|
||||
"FIGHT_PROP_WATER_ADD_HURT": "水元素伤害加成",
|
||||
"FIGHT_PROP_WIND_ADD_HURT": "风元素伤害加成",
|
||||
"FIGHT_PROP_ROCK_ADD_HURT": "岩元素伤害加成",
|
||||
"FIGHT_PROP_GRASS_ADD_HURT": "草元素伤害加成",
|
||||
"FIGHT_PROP_PHYSICAL_ADD_HURT": "物理伤害加成",
|
||||
"FIGHT_PROP_HEAL_ADD": "治疗加成"
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
import os
|
||||
from typing import Union, Optional
|
||||
|
||||
import httpx
|
||||
import ujson
|
||||
|
||||
from models.base import GameItem
|
||||
from models.game.artifact import ArtifactInfo
|
||||
from models.game.character import CharacterInfo, CharacterValueInfo
|
||||
from models.game.fetter import FetterInfo
|
||||
from models.game.skill import Skill
|
||||
from models.game.talent import Talent
|
||||
from models.game.weapon import WeaponInfo
|
||||
from .helpers import get_headers
|
||||
|
||||
|
||||
class PlayerCardsAPI:
|
||||
UI_URL = "https://enka.shinshin.moe/ui/"
|
||||
|
||||
def __init__(self):
|
||||
self.client = httpx.AsyncClient(headers=get_headers())
|
||||
project_path = os.path.dirname(__file__)
|
||||
characters_map_file = os.path.join(project_path, "metadata", "CharactersMap.json")
|
||||
name_text_map_hash_file = os.path.join(project_path, "metadata", "NameTextMapHash.json")
|
||||
reliquary_name_map_file = os.path.join(project_path, "metadata", "ReliquaryNameMap.json")
|
||||
with open(characters_map_file, "r", encoding="utf-8") as f:
|
||||
self._characters_map_json: dict = ujson.load(f)
|
||||
with open(name_text_map_hash_file, "r", encoding="utf-8") as f:
|
||||
self._name_text_map_hash_json: dict = ujson.load(f)
|
||||
with open(reliquary_name_map_file, "r", encoding="utf-8") as f:
|
||||
self._reliquary_name_map_json: dict = ujson.load(f)
|
||||
|
||||
def get_characters_name(self, item_id: Union[int, str]) -> str:
|
||||
if isinstance(item_id, int):
|
||||
item_id = str(item_id)
|
||||
characters = self.get_characters(item_id)
|
||||
name_text_map_hash = characters.get("NameTextMapHash", "-1")
|
||||
return self.get_text(str(name_text_map_hash))
|
||||
|
||||
def get_characters(self, item_id: Union[int, str]) -> dict:
|
||||
if isinstance(item_id, int):
|
||||
item_id = str(item_id)
|
||||
return self._characters_map_json.get(item_id, {})
|
||||
|
||||
def get_text(self, hash_value: Union[int, str]) -> str:
|
||||
if isinstance(hash_value, int):
|
||||
hash_value = str(hash_value)
|
||||
return self._name_text_map_hash_json.get(hash_value, "")
|
||||
|
||||
def get_reliquary_name(self, reliquary: str) -> str:
|
||||
return self._reliquary_name_map_json[reliquary]
|
||||
|
||||
async def get_data(self, uid: Union[str, int]):
|
||||
url = f"https://enka.shinshin.moe/u/{uid}/__data.json"
|
||||
response = await self.client.get(url)
|
||||
return response
|
||||
|
||||
def data_handler(self, avatar_data: dict, avatar_id: int) -> CharacterInfo:
|
||||
artifact_list = []
|
||||
|
||||
weapon_info: Optional[WeaponInfo] = None
|
||||
|
||||
equip_list = avatar_data["equipList"] # 圣遗物和武器相关
|
||||
fetter_info = avatar_data["fetterInfo"] # 好感等级
|
||||
fight_prop_map = avatar_data["fightPropMap"] # 属性
|
||||
# inherent_proud_skill_list = avatar_data["inherentProudSkillList"] # 不知道
|
||||
prop_map = avatar_data["propMap"] # 角色等级 其他信息
|
||||
# proud_skill_extra_level_map = avatar_data["proudSkillExtraLevelMap"] # 不知道
|
||||
# skill_depot_id = avatar_data["skillDepotId"] # 不知道
|
||||
skill_level_map = avatar_data["skillLevelMap"] # 技能等级
|
||||
|
||||
# 角色等级
|
||||
character_level = prop_map['4001']['val']
|
||||
|
||||
# 角色姓名
|
||||
character_name = self.get_characters_name(avatar_id)
|
||||
characters_data = self.get_characters(avatar_id)
|
||||
|
||||
# 圣遗物和武器
|
||||
for equip in equip_list:
|
||||
if "reliquary" in equip: # 圣遗物
|
||||
flat = equip["flat"]
|
||||
reliquary = equip["reliquary"]
|
||||
reliquary_main_stat = flat["reliquaryMainstat"]
|
||||
reliquary_sub_stats = flat['reliquarySubstats']
|
||||
sub_item = []
|
||||
for reliquary_sub in reliquary_sub_stats:
|
||||
sub_item.append(GameItem(name=self.get_reliquary_name(reliquary_sub["appendPropId"]),
|
||||
item_type=reliquary_sub["appendPropId"], value=reliquary_sub["statValue"]))
|
||||
main_item = GameItem(name=self.get_reliquary_name(reliquary_main_stat["mainPropId"]),
|
||||
item_type=reliquary_main_stat["mainPropId"],
|
||||
value=reliquary_main_stat["statValue"])
|
||||
name = self.get_text(flat["nameTextMapHash"])
|
||||
artifact_list.append(ArtifactInfo(item_id=equip["itemId"], name=name, star=flat["rankLevel"],
|
||||
level=reliquary["level"] - 1, main_item=main_item, sub_item=sub_item))
|
||||
if "weapon" in equip: # 武器
|
||||
flat = equip["flat"]
|
||||
weapon_data = equip["weapon"]
|
||||
# 防止未精炼
|
||||
if 'promoteLevel' in weapon_data:
|
||||
weapon_level = weapon_data['promoteLevel'] - 1
|
||||
else:
|
||||
weapon_level = 0
|
||||
if 'affixMap' in weapon_data:
|
||||
affix = list(weapon_data['affixMap'].values())[0] + 1
|
||||
else:
|
||||
|
||||
affix = 1
|
||||
reliquary_main_stat = flat["weaponStats"][0]
|
||||
reliquary_sub_stats = flat['weaponStats'][1]
|
||||
sub_item = GameItem(name=self.get_reliquary_name(reliquary_main_stat["appendPropId"]),
|
||||
item_type=reliquary_sub_stats["appendPropId"],
|
||||
value=reliquary_sub_stats["statValue"])
|
||||
main_item = GameItem(name=self.get_reliquary_name(reliquary_main_stat["appendPropId"]),
|
||||
item_type=reliquary_main_stat["appendPropId"],
|
||||
value=reliquary_main_stat["statValue"])
|
||||
weapon_name = self.get_text(flat["nameTextMapHash"])
|
||||
weapon_info = WeaponInfo(item_id=equip["itemId"], name=weapon_name, star=flat["rankLevel"],
|
||||
level=weapon_level, main_item=main_item, sub_item=sub_item, affix=affix)
|
||||
|
||||
# 好感度
|
||||
fetter = FetterInfo(fetter_info["expLevel"])
|
||||
|
||||
# 基础数值处理
|
||||
for i in range(40, 47):
|
||||
if fight_prop_map[str(i)] > 0:
|
||||
dmg_bonus = fight_prop_map[str(i)]
|
||||
break
|
||||
else:
|
||||
dmg_bonus = 0
|
||||
|
||||
base_value = CharacterValueInfo(fight_prop_map["2000"], fight_prop_map["1"], fight_prop_map["2001"],
|
||||
fight_prop_map["4"], fight_prop_map["2002"], fight_prop_map["7"],
|
||||
fight_prop_map["28"], fight_prop_map["20"], fight_prop_map["22"],
|
||||
fight_prop_map["23"], fight_prop_map["26"], fight_prop_map["27"],
|
||||
fight_prop_map["29"], fight_prop_map["30"], dmg_bonus)
|
||||
|
||||
# 技能处理
|
||||
skill_list = []
|
||||
skills = characters_data["Skills"]
|
||||
for skill_id in skill_level_map:
|
||||
skill_list.append(Skill(skill_id, name=skill_level_map[skill_id], icon=skills[skill_id]))
|
||||
|
||||
# 命座处理
|
||||
talent_list = []
|
||||
consts = characters_data["Consts"]
|
||||
if 'talentIdList' in avatar_data:
|
||||
talent_id_list = avatar_data["talentIdList"]
|
||||
for index, _ in enumerate(talent_id_list):
|
||||
talent_list.append(Talent(talent_id_list[index], icon=consts[index]))
|
||||
|
||||
element = characters_data["Element"]
|
||||
icon = characters_data["SideIconName"]
|
||||
character_info = CharacterInfo(character_name, element, character_level, fetter, base_value, weapon_info,
|
||||
artifact_list, skill_list, talent_list, icon)
|
||||
return character_info
|
@ -1,51 +0,0 @@
|
||||
from enum import Enum
|
||||
from typing import Union, Optional, List
|
||||
|
||||
from models.base import GameItem
|
||||
from models.baseobject import BaseObject
|
||||
from models.types import JSONDict
|
||||
|
||||
|
||||
class ArtifactInfo(BaseObject):
|
||||
"""
|
||||
圣遗物信息
|
||||
"""
|
||||
|
||||
def __init__(self, item_id: int = 0, name: str = "", level: int = 0, main_item: Optional[GameItem] = None,
|
||||
pos: Union[Enum, str] = "", star: int = 1, sub_item: Optional[List[GameItem]] = None, icon: str = ""):
|
||||
"""
|
||||
:param item_id: item_id
|
||||
:param name: 圣遗物名字
|
||||
:param level: 圣遗物等级
|
||||
:param main_item: 主词条
|
||||
:param pos: 圣遗物类型
|
||||
:param star: 星级
|
||||
:param sub_item: 副词条
|
||||
:param icon: 图片
|
||||
"""
|
||||
self.icon = icon
|
||||
self.item_id = item_id
|
||||
self.level = level
|
||||
self.main_item = main_item
|
||||
self.name = name
|
||||
self.pos = pos
|
||||
self.star = star
|
||||
self.sub_item: List[GameItem] = []
|
||||
if sub_item is not None:
|
||||
self.sub_item = sub_item
|
||||
|
||||
def to_dict(self) -> JSONDict:
|
||||
data = super().to_dict()
|
||||
if self.sub_item:
|
||||
data["sub_item"] = [e.to_dict() for e in self.sub_item]
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict]) -> Optional["ArtifactInfo"]:
|
||||
data = cls._parse_data(data)
|
||||
if not data:
|
||||
return None
|
||||
data["sub_item"] = GameItem.de_list(data.get("sub_item"))
|
||||
return cls(**data)
|
||||
|
||||
__slots__ = ("name", "type", "value", "pos", "star", "sub_item", "main_item", "level", "item_id", "icon")
|
@ -1,123 +0,0 @@
|
||||
from typing import Optional, List
|
||||
|
||||
from models.baseobject import BaseObject
|
||||
from models.game.artifact import ArtifactInfo
|
||||
from models.game.fetter import FetterInfo
|
||||
from models.game.skill import Skill
|
||||
from models.game.talent import Talent
|
||||
from models.game.weapon import WeaponInfo
|
||||
from models.types import JSONDict
|
||||
|
||||
|
||||
class CharacterValueInfo(BaseObject):
|
||||
"""角色数值信息
|
||||
"""
|
||||
|
||||
def __init__(self, hp: float = 0, base_hp: float = 0, atk: float = 0, base_atk: float = 0,
|
||||
def_value: float = 0, base_def: float = 0, elemental_mastery: float = 0, crit_rate: float = 0,
|
||||
crit_dmg: float = 0, energy_recharge: float = 0, heal_bonus: float = 0, healed_bonus: float = 0,
|
||||
physical_dmg_sub: float = 0, physical_dmg_bonus: float = 0, dmg_bonus: float = 0):
|
||||
"""
|
||||
:param hp: 生命值
|
||||
:param base_hp: 基础生命值
|
||||
:param atk: 攻击力
|
||||
:param base_atk: 基础攻击力
|
||||
:param def_value: 防御力
|
||||
:param base_def: 基础防御力
|
||||
:param elemental_mastery: 元素精通
|
||||
:param crit_rate: 暴击率
|
||||
:param crit_dmg: 暴击伤害
|
||||
:param energy_recharge: 充能效率
|
||||
:param heal_bonus: 治疗
|
||||
:param healed_bonus: 受治疗
|
||||
:param physical_dmg_sub: 物理伤害加成
|
||||
:param physical_dmg_bonus: 物理伤害抗性
|
||||
:param dmg_bonus: 伤害加成
|
||||
"""
|
||||
self.dmg_bonus = dmg_bonus
|
||||
self.physical_dmg_bonus = physical_dmg_bonus
|
||||
self.physical_dmg_sub = physical_dmg_sub
|
||||
self.healed_bonus = healed_bonus
|
||||
self.heal_bonus = heal_bonus
|
||||
self.energy_recharge = energy_recharge
|
||||
self.crit_dmg = crit_dmg
|
||||
self.crit_rate = crit_rate
|
||||
self.elemental_mastery = elemental_mastery
|
||||
self.base_def = base_def
|
||||
self.def_value = def_value
|
||||
self.base_atk = base_atk
|
||||
self.atk = atk
|
||||
self.base_hp = base_hp
|
||||
self.hp = hp
|
||||
|
||||
@property
|
||||
def add_hp(self) -> float:
|
||||
return self.hp - self.base_hp
|
||||
|
||||
@property
|
||||
def add_atk(self) -> float:
|
||||
return self.atk - self.base_atk
|
||||
|
||||
@property
|
||||
def add_def(self) -> float:
|
||||
return self.def_value - self.base_def
|
||||
|
||||
__slots__ = (
|
||||
"hp", "base_hp", "atk", "base_atk", "def_value", "base_def", "elemental_mastery", "crit_rate", "crit_dmg",
|
||||
"energy_recharge", "dmg_bonus", "physical_dmg_bonus", "physical_dmg_sub", "healed_bonus",
|
||||
"heal_bonus")
|
||||
|
||||
|
||||
class CharacterInfo(BaseObject):
|
||||
"""角色信息
|
||||
"""
|
||||
|
||||
def __init__(self, name: str = "", elementl: str = 0, level: int = 0, fetter: Optional[FetterInfo] = None,
|
||||
base_value: Optional[CharacterValueInfo] = None, weapon: Optional[WeaponInfo] = None,
|
||||
artifact: Optional[List[ArtifactInfo]] = None, skill: Optional[List[Skill]] = None,
|
||||
talent: Optional[List[Talent]] = None, icon: str = ""):
|
||||
"""
|
||||
:param name: 角色名字
|
||||
:param level: 角色等级
|
||||
:param elementl: 属性
|
||||
:param fetter: 好感度
|
||||
:param base_value: 基础数值
|
||||
:param weapon: 武器
|
||||
:param artifact: 圣遗物
|
||||
:param skill: 技能
|
||||
:param talent: 命座
|
||||
:param icon: 角色图片
|
||||
"""
|
||||
self.icon = icon
|
||||
self.elementl = elementl
|
||||
self.talent = talent
|
||||
self.skill = skill
|
||||
self.artifact = artifact
|
||||
self.weapon = weapon
|
||||
self.base_value = base_value
|
||||
self.fetter = fetter
|
||||
self.level = level
|
||||
self.name = name
|
||||
|
||||
def to_dict(self) -> JSONDict:
|
||||
data = super().to_dict()
|
||||
if self.artifact:
|
||||
data["artifact"] = [e.to_dict() for e in self.artifact]
|
||||
if self.artifact:
|
||||
data["skill"] = [e.to_dict() for e in self.skill]
|
||||
if self.artifact:
|
||||
data["talent"] = [e.to_dict() for e in self.talent]
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict]) -> Optional["CharacterInfo"]:
|
||||
data = cls._parse_data(data)
|
||||
if not data:
|
||||
return None
|
||||
data["artifact"] = ArtifactInfo.de_list(data.get("sub_item"))
|
||||
data["skill"] = Skill.de_list(data.get("sub_item"))
|
||||
data["talent"] = Talent.de_list(data.get("sub_item"))
|
||||
return cls(**data)
|
||||
|
||||
__slots__ = (
|
||||
"name", "level", "level", "fetter", "base_value", "weapon", "artifact", "skill", "talent", "elementl", "icon")
|
@ -1,15 +0,0 @@
|
||||
from models.baseobject import BaseObject
|
||||
|
||||
|
||||
class FetterInfo(BaseObject):
|
||||
"""
|
||||
好感度信息
|
||||
"""
|
||||
|
||||
def __init__(self, level: int = 0):
|
||||
"""
|
||||
:param level: 等级
|
||||
"""
|
||||
self.level = level
|
||||
|
||||
__slots__ = ("level",)
|
@ -1,21 +0,0 @@
|
||||
from models.baseobject import BaseObject
|
||||
|
||||
|
||||
class Skill(BaseObject):
|
||||
"""
|
||||
技能信息
|
||||
"""
|
||||
|
||||
def __init__(self, skill_id: int = 0, name: str = "", level: int = 0, icon: str = ""):
|
||||
"""
|
||||
:param skill_id: 技能ID
|
||||
:param name: 技能名称
|
||||
:param level: 技能等级
|
||||
:param icon: 技能图标
|
||||
"""
|
||||
self.icon = icon
|
||||
self.level = level
|
||||
self.name = name
|
||||
self.skill_id = skill_id
|
||||
|
||||
__slots__ = ("skill_id", "name", "level", "icon")
|
@ -1,19 +0,0 @@
|
||||
from models.baseobject import BaseObject
|
||||
|
||||
|
||||
class Talent(BaseObject):
|
||||
"""
|
||||
命座
|
||||
"""
|
||||
|
||||
def __init__(self, talent_id: int = 0, name: str = "", icon: str = ""):
|
||||
"""
|
||||
:param talent_id: 命座ID
|
||||
:param name: 命座名字
|
||||
:param icon: 图标
|
||||
"""
|
||||
self.icon = icon
|
||||
self.name = name
|
||||
self.talent_id = talent_id
|
||||
|
||||
__slots__ = ("talent_id", "name", "icon")
|
@ -1,36 +0,0 @@
|
||||
from enum import Enum
|
||||
from typing import Union, Optional
|
||||
|
||||
from models.base import GameItem
|
||||
from models.baseobject import BaseObject
|
||||
|
||||
|
||||
class WeaponInfo(BaseObject):
|
||||
"""武器信息
|
||||
"""
|
||||
|
||||
def __init__(self, item_id: int = 0, name: str = "", level: int = 0, main_item: Optional[GameItem] = None,
|
||||
affix: int = 0, pos: Union[Enum, str] = "", star: int = 1, sub_item: Optional[GameItem] = None,
|
||||
icon: str = ""):
|
||||
"""
|
||||
:param item_id: item_id
|
||||
:param name: 武器名字
|
||||
:param level: 武器等级
|
||||
:param main_item: 主词条
|
||||
:param affix: 精炼等级
|
||||
:param pos: 武器类型
|
||||
:param star: 星级
|
||||
:param sub_item: 副词条
|
||||
:param icon: 图片
|
||||
"""
|
||||
self.affix = affix
|
||||
self.icon = icon
|
||||
self.item_id = item_id
|
||||
self.level = level
|
||||
self.main_item = main_item
|
||||
self.name = name
|
||||
self.pos = pos
|
||||
self.star = star
|
||||
self.sub_item = sub_item
|
||||
|
||||
__slots__ = ("name", "type", "value", "pos", "star", "sub_item", "main_item", "level", "item_id", "icon", "affix")
|
@ -1,5 +0,0 @@
|
||||
from typing import Dict, Any, Callable, TypeVar
|
||||
|
||||
JSONDict = Dict[str, Any]
|
||||
|
||||
Func = TypeVar("Func", bound=Callable[..., Any])
|
@ -1,4 +1,4 @@
|
||||
# model 目录说明
|
||||
# modules 目录说明
|
||||
|
||||
## apihelpe 模块
|
||||
|
||||
@ -23,5 +23,5 @@
|
||||
### 感谢
|
||||
|
||||
| Nickname | Contribution |
|
||||
| :--------------------------------------------------------: | -------------------- |
|
||||
|:----------------------------------------------------------:|--------------|
|
||||
| [Crawler-ghhw](https://github.com/DGP-Studio/Crawler-ghhw) | 本项目参考的爬虫代码 |
|
@ -38,7 +38,7 @@ class ArtifactOcrRate:
|
||||
HEADERS = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
||||
"(KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36",
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
"Content-Type": "bot/json; charset=utf-8",
|
||||
}
|
||||
|
||||
def __init__(self):
|
@ -1,6 +1,6 @@
|
||||
import httpx
|
||||
|
||||
from .base import BaseResponseData
|
||||
from modules.apihelper.base import BaseResponseData
|
||||
|
||||
|
||||
class GachaInfo:
|
@ -20,7 +20,7 @@ def get_device_id(name: str) -> str:
|
||||
|
||||
|
||||
def md5(text: str) -> str:
|
||||
_md5 = hashlib.md5()
|
||||
_md5 = hashlib.md5() # nosec B303
|
||||
_md5.update(text.encode())
|
||||
return _md5.hexdigest()
|
||||
|
@ -1,7 +1,7 @@
|
||||
from httpx import AsyncClient
|
||||
|
||||
from .base import BaseResponseData
|
||||
from .helpers import get_ds, get_device_id, get_recognize_server
|
||||
from modules.apihelper.base import BaseResponseData
|
||||
from modules.apihelper.helpers import get_ds, get_device_id, get_recognize_server
|
||||
|
||||
|
||||
class Genshin:
|
@ -5,8 +5,8 @@ from typing import List
|
||||
import httpx
|
||||
from httpx import AsyncClient
|
||||
|
||||
from .base import HyperionResponse, ArtworkImage, BaseResponseData
|
||||
from .helpers import get_ds, get_device_id
|
||||
from modules.apihelper.base import HyperionResponse, ArtworkImage, BaseResponseData
|
||||
from modules.apihelper.helpers import get_ds, get_device_id
|
||||
|
||||
|
||||
class Hyperion:
|
8
modules/playercards/models/talent.py
Normal file
8
modules/playercards/models/talent.py
Normal file
@ -0,0 +1,8 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Talent(BaseModel):
|
||||
"""命座"""
|
||||
talent_id: int = 0
|
||||
name: str = ""
|
||||
icon: str = ""
|
@ -35,6 +35,7 @@ class Model(PydanticBaseModel):
|
||||
|
||||
|
||||
class WikiModel(Model):
|
||||
# noinspection PyUnresolvedReferences
|
||||
"""wiki所用到的基类
|
||||
|
||||
Attributes:
|
||||
@ -199,7 +200,7 @@ class WikiModel(Model):
|
||||
queue: Queue[Union[str, Tuple[str, URL]]] = Queue() # 存放 Model 的队列
|
||||
signal = Value('i', len(urls)) # 一个用于异步任务同步的信号,初始值为存放所需要爬取的页面数
|
||||
|
||||
async def task(page: URL, s: Value):
|
||||
async def task(page: URL):
|
||||
"""包装的爬虫任务"""
|
||||
response = await cls._client_get(page)
|
||||
# 从页面中获取对应的 chaos data (未处理的json格式字符串)
|
||||
@ -215,7 +216,7 @@ class WikiModel(Model):
|
||||
signal.value = signal.value - 1 # 信号量减少 1 ,说明该爬虫任务已经完成
|
||||
|
||||
for url in urls: # 遍历需要爬出的页面
|
||||
asyncio.create_task(task(url, signal)) # 添加爬虫任务
|
||||
asyncio.create_task(task(url)) # 添加爬虫任务
|
||||
while signal.value > 0 or not queue.empty(): # 当还有未完成的爬虫任务或存放数据的队列不为空时
|
||||
yield await queue.get() # 取出并返回一个存放的 Model
|
||||
|
@ -4,9 +4,9 @@ from typing import List, Optional
|
||||
from bs4 import BeautifulSoup
|
||||
from httpx import URL
|
||||
|
||||
from models.wiki.base import Model, SCRAPE_HOST
|
||||
from models.wiki.base import WikiModel
|
||||
from models.wiki.other import Association, Element, WeaponType
|
||||
from modules.wiki.base import Model, SCRAPE_HOST
|
||||
from modules.wiki.base import WikiModel
|
||||
from modules.wiki.other import Association, Element, WeaponType
|
||||
|
||||
|
||||
class Birth(Model):
|
||||
@ -163,7 +163,7 @@ class Character(WikiModel):
|
||||
async def get_url_by_name(cls, name: str) -> Optional[URL]:
|
||||
# 重写此函数的目的是处理主角名字的 ID
|
||||
_map = {'荧': "playergirl_007", '空': "playerboy_005"}
|
||||
if (id_ := _map.get(name, None)) is not None:
|
||||
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)
|
||||
|
@ -5,7 +5,7 @@ from bs4 import BeautifulSoup
|
||||
from httpx import URL
|
||||
from typing_extensions import Self
|
||||
|
||||
from models.wiki.base import SCRAPE_HOST, WikiModel
|
||||
from modules.wiki.base import SCRAPE_HOST, WikiModel
|
||||
|
||||
__all__ = ['Material']
|
||||
|
@ -3,7 +3,7 @@ from typing import Optional
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from models.wiki.base import SCRAPE_HOST
|
||||
from modules.wiki.base import SCRAPE_HOST
|
||||
|
||||
__all__ = [
|
||||
'Element',
|
@ -5,8 +5,8 @@ from typing import List, Optional, Tuple, Union
|
||||
from bs4 import BeautifulSoup
|
||||
from httpx import URL
|
||||
|
||||
from models.wiki.base import Model, SCRAPE_HOST, WikiModel
|
||||
from models.wiki.other import AttributeType, WeaponType
|
||||
from modules.wiki.base import Model, SCRAPE_HOST, WikiModel
|
||||
from modules.wiki.other import AttributeType, WeaponType
|
||||
|
||||
__all__ = ['Weapon', 'WeaponAffix', 'WeaponAttribute']
|
||||
|
@ -4,49 +4,160 @@
|
||||
|
||||
该目录仅限处理交互层和业务层数据交换的任务
|
||||
|
||||
如有任何新业务接口,请转到 `core` 目录添加
|
||||
如有任何核心接口,请转到 `core` 目录添加
|
||||
|
||||
如有任何API请求接口,请转到 `models` 目录添加
|
||||
|
||||
## 基础代码
|
||||
## 新版插件 Plugin 的写法
|
||||
|
||||
### 关于路径
|
||||
|
||||
插件应该写在 `plugins` 文件夹下,可以是一个包或者是一个文件,但文件名、文件夹名中不能包含`_`字符
|
||||
|
||||
### 关于类
|
||||
|
||||
1. 除了要使用`ConversationHandler` 的插件外,都要继承 `core.plugin.Plugin`
|
||||
|
||||
```python
|
||||
from core.plugin import Plugin
|
||||
|
||||
|
||||
class TestPlugin(Plugin):
|
||||
pass
|
||||
```
|
||||
|
||||
2. 针对要用 `ConversationHandler` 的插件,要继承 `core.plugin.Plugin.Conversation`
|
||||
|
||||
```python
|
||||
from core.plugin import Plugin
|
||||
|
||||
|
||||
class TestConversationPlugin(Plugin.Conversation):
|
||||
pass
|
||||
```
|
||||
|
||||
3. 关于初始化方法以及依赖注入
|
||||
|
||||
初始化类, 可写在 `__init__` 和 `__async_init__` 中, 其中 `__async_init__` 应该是异步方法,
|
||||
用于执行初始化时需要的异步操作. 这两个方法的执行顺序是 `__init__` 在前, `__async_init__` 在后
|
||||
|
||||
若需要注入依赖, 直接在插件类的`__init__`方法中,提供相应的参数以及标注标注即可, 例如我需要注入一个 `MySQL`
|
||||
|
||||
```python
|
||||
from service.mysql import MySQL
|
||||
from core.plugin import Plugin
|
||||
|
||||
class TestPlugin(Plugin):
|
||||
def __init__(self, mysql: MySQL):
|
||||
self.mysql = mysql
|
||||
|
||||
async def __async_init__(self):
|
||||
"""do something"""
|
||||
|
||||
```
|
||||
|
||||
## 关于 `handler`
|
||||
|
||||
给函数加上 `core.plugin.handler` 这一装饰器即可将这个函数注册为`handler`
|
||||
|
||||
### 非 `ConversationHandler` 的 `handler`
|
||||
|
||||
1. 直接使用 `core.plugin.handler` 装饰器
|
||||
|
||||
第一个参数是 `handler` 的种类,后续参数为该 `handler` 除 `callback` 参数外的其余参数
|
||||
|
||||
```python
|
||||
from core.plugin import Plugin, handler
|
||||
from telegram import Update
|
||||
from telegram.ext import CommandHandler, CallbackContext
|
||||
|
||||
from logger import Log
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
|
||||
@listener_plugins_class()
|
||||
class Example:
|
||||
class TestPlugin(Plugin):
|
||||
@handler(CommandHandler, command='start', block=False)
|
||||
async def start(self, update: Update, context: CallbackContext):
|
||||
await update.effective_chat.send_message('hello world!')
|
||||
```
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls):
|
||||
example = cls()
|
||||
return [CommandHandler('example', example.command_start)]
|
||||
比如上面代码中的 `command='start', block=False` 就是 `CommandHandler` 的参数
|
||||
|
||||
@error_callable
|
||||
@restricts()
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> None:
|
||||
user = update.effective_user
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 发出example命令")
|
||||
await message.reply_text("Example")
|
||||
2. 使用 `core.plugin.handler` 的子装饰器
|
||||
|
||||
这种方式比第一种简单, 不需要声明 `handler` 的类型
|
||||
|
||||
```python
|
||||
from core.plugin import Plugin, handler
|
||||
from telegram import Update
|
||||
from telegram.ext import CallbackContext
|
||||
|
||||
|
||||
class TestPlugin(Plugin):
|
||||
@handler.command(command='start', block=False)
|
||||
async def start(self, update: Update, context: CallbackContext):
|
||||
await update.effective_chat.send_message('hello world!')
|
||||
```
|
||||
|
||||
### 对于 `ConversationHandler`
|
||||
|
||||
由于 `ConversationHandler` 比较特殊,所以**一个 Plugin 类中只能存在一个 `ConversationHandler`**
|
||||
|
||||
`conversation.entry_point` 、`conversation.state` 和 `conversation.fallback` 装饰器分别对应
|
||||
`ConversationHandler` 的 `entry_points`、`stats` 和 `fallbacks` 参数
|
||||
|
||||
```python
|
||||
from telegram import Update
|
||||
from telegram.ext import CallbackContext, filters
|
||||
|
||||
from core.plugin import Plugin, conversation, handler
|
||||
|
||||
STATE_A, STATE_B, STATE_C = range(3)
|
||||
|
||||
|
||||
class TestConversation(Plugin.Conversation, allow_reentry=True, block=False):
|
||||
|
||||
@conversation.entry_point # 标注这个handler是ConversationHandler的一个entry_point
|
||||
@handler.command(command='entry')
|
||||
async def entry_point(self, update: Update, context: CallbackContext):
|
||||
"""do something"""
|
||||
|
||||
@conversation.state(state=STATE_A)
|
||||
@handler.message(filters=filters.TEXT)
|
||||
async def state(self, update: Update, context: CallbackContext):
|
||||
"""do something"""
|
||||
|
||||
@conversation.fallback
|
||||
@handler.message(filters=filters.TEXT)
|
||||
async def fallback(self, update: Update, context: CallbackContext):
|
||||
"""do something"""
|
||||
|
||||
@handler.inline_query() # 你可以在此 Plugin 下定义其它类型的 handler
|
||||
async def inline_query(self, update: Update, context: CallbackContext):
|
||||
"""do something"""
|
||||
|
||||
```
|
||||
|
||||
### 对于 `Job`
|
||||
|
||||
1. 依然需要继承 `core.plugin.Plugin`
|
||||
2. 直接使用 `core.plugin.job` 装饰器 参数都与官方 `JobQueue` 类对应
|
||||
|
||||
```python
|
||||
from core.plugin import Plugin, job
|
||||
|
||||
class TestJob(Plugin):
|
||||
|
||||
@job.run_repeating(interval=datetime.timedelta(hours=2), name="TestJob")
|
||||
async def refresh(self, _: CallbackContext):
|
||||
logger.info("TestJob")
|
||||
```
|
||||
|
||||
### 注意
|
||||
|
||||
plugins 模块下的类必须提供 `create_handlers` 类方法作为构建相应处理程序给 `handle.py`
|
||||
被注册到 `handler` 的函数需要添加 `error_callable` 修饰器作为错误统一处理
|
||||
|
||||
在函数注册为命令处理过程(如 `CommandHandler` )需要添加 `error_callable` 修饰器作为错误统一处理
|
||||
被注册到 `handler` 的函数必须使用 `@restricts()` 修饰器 **预防洪水攻击** 但 `ConversationHandler` 外只需要注册入口函数使用
|
||||
|
||||
如果引用服务,参数需要声明需要引用服务的类型并设置默认传入为 `None` ,并且添加 `inject` 修饰器
|
||||
如果引用服务,参数需要声明需要引用服务的类型并设置默认传入为 `None`
|
||||
|
||||
必要的函数必须捕获异常后通知用户或者直接抛出异常
|
||||
|
||||
入口函数必须使用 `@restricts()` 修饰器 预防洪水攻击
|
||||
|
||||
只需在构建的类前加上 `@listener_plugins_class()` 修饰器即可向程序注册插件
|
||||
|
||||
**注意:`@restricts()` 修饰器带参,必须带括号,否则会出现调用错误**
|
||||
**部分修饰器为带参修饰器,必须带括号,否则会出现调用错误**
|
@ -1,97 +0,0 @@
|
||||
import asyncio
|
||||
import datetime
|
||||
from typing import List, Tuple, Callable
|
||||
|
||||
from telegram import Update, ReplyKeyboardRemove
|
||||
from telegram.error import BadRequest
|
||||
from telegram.ext import CallbackContext, ConversationHandler, filters
|
||||
|
||||
from core.admin.services import BotAdminService
|
||||
from logger import Log
|
||||
from utils.service.inject import inject
|
||||
|
||||
|
||||
async def clean_message(context: CallbackContext, chat_id: int, message_id: int) -> bool:
|
||||
try:
|
||||
await context.bot.delete_message(chat_id=chat_id, message_id=message_id)
|
||||
return True
|
||||
except BadRequest as error:
|
||||
if "not found" in str(error):
|
||||
Log.warning(f"定时删除消息 chat_id[{chat_id}] message_id[{message_id}]失败 消息不存在")
|
||||
elif "Message can't be deleted" in str(error):
|
||||
Log.warning(f"定时删除消息 chat_id[{chat_id}] message_id[{message_id}]失败 消息无法删除 可能是没有授权")
|
||||
else:
|
||||
Log.warning(f"定时删除消息 chat_id[{chat_id}] message_id[{message_id}]失败 \n", error)
|
||||
return False
|
||||
|
||||
|
||||
def add_delete_message_job(context: CallbackContext, chat_id: int, message_id: int,
|
||||
delete_seconds: int = 60):
|
||||
context.job_queue.scheduler.add_job(clean_message, "date",
|
||||
id=f"{chat_id}|{message_id}|auto_clean_message",
|
||||
name=f"{chat_id}|{message_id}|auto_clean_message",
|
||||
args=[context, chat_id, message_id],
|
||||
run_date=context.job_queue._tz_now() + datetime.timedelta(
|
||||
seconds=delete_seconds), replace_existing=True)
|
||||
|
||||
|
||||
class BasePlugins:
|
||||
|
||||
@staticmethod
|
||||
async def cancel(update: Update, _: CallbackContext) -> int:
|
||||
await update.message.reply_text("退出命令", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
|
||||
@staticmethod
|
||||
async def _clean(context: CallbackContext, chat_id: int, message_id: int) -> bool:
|
||||
return await clean_message(context, chat_id, message_id)
|
||||
|
||||
@staticmethod
|
||||
def _add_delete_message_job(context: CallbackContext, chat_id: int, message_id: int,
|
||||
delete_seconds: int = 60):
|
||||
return add_delete_message_job(context, chat_id, message_id, delete_seconds)
|
||||
|
||||
|
||||
class NewChatMembersHandler:
|
||||
|
||||
@inject
|
||||
def __init__(self, bot_admin_service: BotAdminService = None):
|
||||
self.bot_admin_service = bot_admin_service
|
||||
self.callback: List[Tuple[Callable, int]] = []
|
||||
|
||||
def add_callback(self, callback, chat_id: int):
|
||||
if chat_id >= 0:
|
||||
raise ValueError
|
||||
self.callback.append((callback, chat_id))
|
||||
|
||||
async def new_member(self, update: Update, context: CallbackContext) -> None:
|
||||
message = update.message
|
||||
chat = message.chat
|
||||
from_user = message.from_user
|
||||
quit_status = False
|
||||
if filters.ChatType.GROUPS.filter(message):
|
||||
for user in message.new_chat_members:
|
||||
if user.id == context.bot.id:
|
||||
if from_user is not None:
|
||||
Log.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 查看咱已经学会的功能。')
|
||||
else:
|
||||
quit_status = True
|
||||
else:
|
||||
Log.info(f"未知用户 在群 {chat.title}[{chat.id}] 邀请BOT")
|
||||
quit_status = True
|
||||
if quit_status:
|
||||
Log.warning("不是管理员邀请!退出群聊。")
|
||||
await context.bot.send_message(message.chat_id, "派蒙不想进去!不是旅行者的邀请!")
|
||||
await context.bot.leave_chat(chat.id)
|
||||
else:
|
||||
tasks = []
|
||||
for callback, chat_id in self.callback:
|
||||
if chat.id == chat_id:
|
||||
task = asyncio.create_task(callback(update, context))
|
||||
tasks.append(task)
|
||||
if len(tasks) >= 1:
|
||||
await asyncio.gather(*tasks)
|
@ -1,19 +1,21 @@
|
||||
from typing import Dict
|
||||
|
||||
from genshin import Client
|
||||
from telegram import Update
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.ext import CommandHandler, MessageHandler, filters, CallbackContext
|
||||
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters
|
||||
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.cookies.error import CookiesNotFoundError
|
||||
from core.cookies.services import CookiesService
|
||||
from core.template.services import TemplateService
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template import TemplateService
|
||||
from core.user import UserService
|
||||
from core.user.repositories import UserNotFoundError
|
||||
from logger import Log
|
||||
from plugins.base import BasePlugins
|
||||
from core.user.error import UserNotFoundError
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.helpers import get_genshin_client, url_to_file
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.service.inject import inject
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
class AbyssUnlocked(Exception):
|
||||
@ -26,25 +28,15 @@ class NoMostKills(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class Abyss(BasePlugins):
|
||||
class Abyss(Plugin, BasePlugin):
|
||||
"""深渊数据查询"""
|
||||
|
||||
@inject
|
||||
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
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls) -> list:
|
||||
abyss = cls()
|
||||
return [
|
||||
CommandHandler("abyss", abyss.command_start, block=False),
|
||||
MessageHandler(filters.Regex(r"^深渊数据查询(.*)"), abyss.command_start, block=True)
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _get_role_star_bg(value: int):
|
||||
if value == 4:
|
||||
@ -54,7 +46,7 @@ class Abyss(BasePlugins):
|
||||
else:
|
||||
raise ValueError("错误的数据")
|
||||
|
||||
async def _get_abyss_data(self, client: Client) -> dict:
|
||||
async def _get_abyss_data(self, client: Client) -> Dict:
|
||||
uid = client.uid
|
||||
await client.get_record_cards()
|
||||
spiral_abyss_info = await client.get_spiral_abyss(uid)
|
||||
@ -101,17 +93,19 @@ class Abyss(BasePlugins):
|
||||
abyss_data["most_played_list"].append(temp)
|
||||
return abyss_data
|
||||
|
||||
@handler(CommandHandler, command="abyss", block=False)
|
||||
@handler(MessageHandler, filters=filters.Regex("^深渊数据查询(.*)"), block=False)
|
||||
@restricts()
|
||||
@error_callable
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> None:
|
||||
user = update.effective_user
|
||||
message = update.message
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 查深渊挑战命令请求")
|
||||
message = update.effective_message
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 查深渊挑战命令请求")
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
try:
|
||||
client = await get_genshin_client(user.id, self.user_service, self.cookies_service)
|
||||
client = await get_genshin_client(user.id)
|
||||
abyss_data = await self._get_abyss_data(client)
|
||||
except UserNotFoundError:
|
||||
except (UserNotFoundError, CookiesNotFoundError):
|
||||
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, 10)
|
||||
@ -128,4 +122,3 @@ class Abyss(BasePlugins):
|
||||
{"width": 690, "height": 504}, full_page=False)
|
||||
await message.reply_photo(png_data, filename=f"abyss_{user.id}.png",
|
||||
allow_sending_without_reply=True)
|
||||
return
|
||||
|
@ -3,106 +3,96 @@ from typing import Optional
|
||||
|
||||
import genshin
|
||||
from genshin import InvalidCookies, GenshinException, DataNotPublic
|
||||
from sqlalchemy.exc import NoResultFound
|
||||
from telegram import Update, ReplyKeyboardRemove, ReplyKeyboardMarkup, TelegramObject
|
||||
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters, ConversationHandler
|
||||
from telegram.ext import CallbackContext, filters, ConversationHandler
|
||||
from telegram.helpers import escape_markdown
|
||||
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.cookies.error import CookiesNotFoundError
|
||||
from core.cookies.models import Cookies
|
||||
from core.cookies.services import CookiesService
|
||||
from core.plugin import Plugin, handler, conversation
|
||||
from core.user.error import UserNotFoundError
|
||||
from core.user.models import User
|
||||
from core.user.repositories import UserNotFoundError
|
||||
from core.user.services import UserService
|
||||
from logger import Log
|
||||
from models.base import RegionEnum
|
||||
from plugins.base import BasePlugins
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.service.inject import inject
|
||||
from utils.log import logger
|
||||
from utils.models.base import RegionEnum
|
||||
|
||||
|
||||
class AddUserCommandData(TelegramObject):
|
||||
user: Optional[User] = None
|
||||
cookies_database_data: Optional[Cookies] = None
|
||||
region: RegionEnum = RegionEnum.HYPERION
|
||||
cookies: dict = {}
|
||||
game_uid: int = 0
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class AddUser(BasePlugins):
|
||||
"""用户绑定"""
|
||||
|
||||
CHECK_SERVER, CHECK_COOKIES, COMMAND_RESULT = range(10100, 10103)
|
||||
|
||||
@inject
|
||||
|
||||
class AddUser(Plugin.Conversation, BasePlugin.Conversation):
|
||||
"""用户绑定"""
|
||||
|
||||
def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None):
|
||||
self.cookies_service = cookies_service
|
||||
self.user_service = user_service
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls):
|
||||
cookies = cls()
|
||||
cookies_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler('adduser', cookies.command_start, filters.ChatType.PRIVATE, block=True),
|
||||
MessageHandler(filters.Regex(r"^绑定账号(.*)") & filters.ChatType.PRIVATE,
|
||||
cookies.command_start, block=True)],
|
||||
states={
|
||||
cookies.CHECK_SERVER: [MessageHandler(filters.TEXT & ~filters.COMMAND,
|
||||
cookies.check_server, block=True)],
|
||||
cookies.CHECK_COOKIES: [MessageHandler(filters.TEXT & ~filters.COMMAND,
|
||||
cookies.check_cookies, block=True)],
|
||||
cookies.COMMAND_RESULT: [MessageHandler(filters.TEXT & ~filters.COMMAND,
|
||||
cookies.command_result, block=True)],
|
||||
},
|
||||
fallbacks=[CommandHandler('cancel', cookies.cancel, block=True)],
|
||||
)
|
||||
return [cookies_handler]
|
||||
|
||||
@conversation.entry_point
|
||||
@handler.command(command='adduser', filters=filters.ChatType.PRIVATE, block=True)
|
||||
@restricts()
|
||||
@error_callable
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> int:
|
||||
user = update.effective_user
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 绑定账号命令请求")
|
||||
message = update.effective_message
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 绑定账号命令请求")
|
||||
add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data")
|
||||
if add_user_command_data is None:
|
||||
cookies_command_data = AddUserCommandData()
|
||||
context.chat_data["add_user_command_data"] = cookies_command_data
|
||||
|
||||
message = f'你好 {user.mention_markdown_v2()} {escape_markdown("!请选择要绑定的服务器!或回复退出取消操作")}'
|
||||
text = f'你好 {user.mention_markdown_v2()} {escape_markdown("!请选择要绑定的服务器!或回复退出取消操作")}'
|
||||
reply_keyboard = [['米游社', 'HoYoLab'], ["退出"]]
|
||||
await update.message.reply_markdown_v2(message,
|
||||
reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
|
||||
|
||||
return self.CHECK_SERVER
|
||||
await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
|
||||
return CHECK_SERVER
|
||||
|
||||
@conversation.state(state=CHECK_SERVER)
|
||||
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
|
||||
@error_callable
|
||||
async def check_server(self, update: Update, context: CallbackContext) -> int:
|
||||
user = update.effective_user
|
||||
message = update.effective_message
|
||||
add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data")
|
||||
if message.text == "退出":
|
||||
await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
elif message.text == "米游社":
|
||||
region = RegionEnum.HYPERION
|
||||
bbs_url = "https://bbs.mihoyo.com/ys/"
|
||||
bbs_name = "米游社"
|
||||
elif message.text == "HoYoLab":
|
||||
bbs_url = "https://www.hoyolab.com/home"
|
||||
bbs_name = "HoYoLab"
|
||||
region = RegionEnum.HOYOLAB
|
||||
else:
|
||||
await message.reply_text("选择错误,请重新选择")
|
||||
return CHECK_SERVER
|
||||
try:
|
||||
user_info = await self.user_service.get_user_by_id(user.id)
|
||||
except UserNotFoundError:
|
||||
user_info = None
|
||||
add_user_command_data.user = user_info
|
||||
if update.message.text == "退出":
|
||||
await update.message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
elif update.message.text == "米游社":
|
||||
add_user_command_data.region = RegionEnum.HYPERION
|
||||
bbs_url = "https://bbs.mihoyo.com/ys/"
|
||||
bbs_name = "米游社"
|
||||
if user_info is not None:
|
||||
await update.message.reply_text("警告,你已经绑定Cookie,如果继续操作会覆盖当前Cookie。")
|
||||
elif update.message.text == "HoYoLab":
|
||||
bbs_url = "https://www.hoyolab.com/home"
|
||||
bbs_name = "HoYoLab"
|
||||
add_user_command_data.region = RegionEnum.HOYOLAB
|
||||
if user_info is not None:
|
||||
await update.message.reply_text("警告,你已经绑定Cookie,如果继续操作会覆盖当前Cookie。")
|
||||
try:
|
||||
cookies_database_data = await self.cookies_service.get_cookies(user.id, add_user_command_data.region)
|
||||
add_user_command_data.cookies_database_data = cookies_database_data
|
||||
except CookiesNotFoundError:
|
||||
await message.reply_text("你已经绑定UID,如果继续操作会覆盖当前UID。")
|
||||
else:
|
||||
await update.message.reply_text("选择错误,请重新选择")
|
||||
return self.CHECK_SERVER
|
||||
await update.message.reply_text(f"请输入{bbs_name}的Cookies!或回复退出取消操作", reply_markup=ReplyKeyboardRemove())
|
||||
await message.reply_text("警告,你已经绑定Cookie,如果继续操作会覆盖当前Cookie。")
|
||||
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复制到剪贴板?')&©(document.cookie)})(); "
|
||||
@ -118,76 +108,79 @@ class AddUser(BasePlugins):
|
||||
f"[1、通过 Via 浏览器打开{bbs_name}并登录]({bbs_url})\n" \
|
||||
f"2、复制下方的代码,并将其粘贴在地址栏中,点击右侧箭头\n" \
|
||||
f"`{escape_markdown(javascript_android, version=2, entity_type='code')}`"
|
||||
await update.message.reply_markdown_v2(help_message, disable_web_page_preview=True)
|
||||
return self.CHECK_COOKIES
|
||||
await message.reply_markdown_v2(help_message, disable_web_page_preview=True)
|
||||
return CHECK_COOKIES
|
||||
|
||||
@conversation.state(state=CHECK_COOKIES)
|
||||
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
|
||||
@error_callable
|
||||
async def check_cookies(self, update: Update, context: CallbackContext) -> int:
|
||||
user = update.effective_user
|
||||
message = update.effective_message
|
||||
add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data")
|
||||
if update.message.text == "退出":
|
||||
await update.message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
if message.text == "退出":
|
||||
await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
str_cookies = update.message.text
|
||||
str_cookies = message.text
|
||||
cookie = SimpleCookie()
|
||||
try:
|
||||
cookie.load(str_cookies)
|
||||
except CookieError:
|
||||
await update.message.reply_text("Cookies格式有误,请检查", reply_markup=ReplyKeyboardRemove())
|
||||
await message.reply_text("Cookies格式有误,请检查", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
if len(cookie) == 0:
|
||||
await update.message.reply_text("Cookies格式有误,请检查", reply_markup=ReplyKeyboardRemove())
|
||||
await message.reply_text("Cookies格式有误,请检查", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
cookies = {key: morsel.value for key, morsel in cookie.items()}
|
||||
if not cookies:
|
||||
await update.message.reply_text("Cookies格式有误,请检查", reply_markup=ReplyKeyboardRemove())
|
||||
await message.reply_text("Cookies格式有误,请检查", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
if add_user_command_data.region == RegionEnum.HYPERION:
|
||||
client = genshin.ChineseClient(cookies=cookies)
|
||||
elif add_user_command_data.region == RegionEnum.HOYOLAB:
|
||||
client = genshin.GenshinClient(cookies=cookies)
|
||||
else:
|
||||
await update.message.reply_text("数据错误", reply_markup=ReplyKeyboardRemove())
|
||||
await message.reply_text("数据错误", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
try:
|
||||
user_info = await client.get_record_card()
|
||||
except DataNotPublic:
|
||||
await update.message.reply_text("账号疑似被注销,请检查账号状态", reply_markup=ReplyKeyboardRemove())
|
||||
await message.reply_text("账号疑似被注销,请检查账号状态", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
except InvalidCookies:
|
||||
await update.message.reply_text("Cookies已经过期,请检查是否正确", reply_markup=ReplyKeyboardRemove())
|
||||
await message.reply_text("Cookies已经过期,请检查是否正确", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
except GenshinException as error:
|
||||
await update.message.reply_text(f"获取账号信息发生错误,错误信息为 {str(error)},请检查Cookie或者账号是否正常",
|
||||
await message.reply_text(f"获取账号信息发生错误,错误信息为 {str(error)},请检查Cookie或者账号是否正常",
|
||||
reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
except (AttributeError, ValueError):
|
||||
await update.message.reply_text("Cookies错误,请检查是否正确", reply_markup=ReplyKeyboardRemove())
|
||||
await message.reply_text("Cookies错误,请检查是否正确", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
add_user_command_data.cookies = cookies
|
||||
add_user_command_data.game_uid = user_info.uid
|
||||
reply_keyboard = [['确认', '退出']]
|
||||
await update.message.reply_text("获取角色基础信息成功,请检查是否正确!")
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 获取账号 {user_info.nickname}[{user_info.uid}] 信息成功")
|
||||
message = f"*角色信息*\n" \
|
||||
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"
|
||||
await update.message.reply_markdown_v2(
|
||||
message,
|
||||
reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
|
||||
)
|
||||
return self.COMMAND_RESULT
|
||||
await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
|
||||
return COMMAND_RESULT
|
||||
|
||||
@conversation.state(state=COMMAND_RESULT)
|
||||
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
|
||||
@error_callable
|
||||
async def command_result(self, update: Update, context: CallbackContext) -> int:
|
||||
user = update.effective_user
|
||||
message = update.effective_message
|
||||
add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data")
|
||||
if update.message.text == "退出":
|
||||
await update.message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
if message.text == "退出":
|
||||
await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
elif update.message.text == "确认":
|
||||
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,
|
||||
@ -196,11 +189,9 @@ class AddUser(BasePlugins):
|
||||
user_db = User(user_id=user.id, genshin_uid=add_user_command_data.game_uid,
|
||||
region=add_user_command_data.region)
|
||||
else:
|
||||
await update.message.reply_text("数据错误")
|
||||
await message.reply_text("数据错误")
|
||||
return ConversationHandler.END
|
||||
await self.user_service.add_user(user_db)
|
||||
await self.cookies_service.add_cookies(user.id, add_user_command_data.cookies,
|
||||
add_user_command_data.region)
|
||||
else:
|
||||
user_db = add_user_command_data.user
|
||||
user_db.region = add_user_command_data.region
|
||||
@ -209,19 +200,18 @@ class AddUser(BasePlugins):
|
||||
elif add_user_command_data.region == RegionEnum.HOYOLAB:
|
||||
user_db.genshin_uid = add_user_command_data.game_uid
|
||||
else:
|
||||
await update.message.reply_text("数据错误")
|
||||
await message.reply_text("数据错误")
|
||||
return ConversationHandler.END
|
||||
await self.user_service.update_user(user_db)
|
||||
# 临时解决错误
|
||||
try:
|
||||
await self.cookies_service.update_cookies(user.id, add_user_command_data.cookies,
|
||||
add_user_command_data.region)
|
||||
except NoResultFound:
|
||||
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)
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 绑定账号成功")
|
||||
await update.message.reply_text("保存成功", reply_markup=ReplyKeyboardRemove())
|
||||
else:
|
||||
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
|
||||
else:
|
||||
await update.message.reply_text("回复错误,请重新输入")
|
||||
return self.COMMAND_RESULT
|
||||
await message.reply_text("回复错误,请重新输入")
|
||||
return COMMAND_RESULT
|
||||
|
@ -2,24 +2,22 @@ from typing import Optional
|
||||
|
||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, File
|
||||
from telegram.constants import ChatAction, ParseMode
|
||||
from telegram.ext import CallbackContext, ConversationHandler, CommandHandler, CallbackQueryHandler, MessageHandler, \
|
||||
filters
|
||||
from telegram.ext import CallbackContext, ConversationHandler, filters
|
||||
from telegram.helpers import escape_markdown
|
||||
|
||||
from logger import Log
|
||||
from models.apihelper.artifact import ArtifactOcrRate, get_comment, get_format_sub_item
|
||||
from plugins.base import BasePlugins
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.plugin import Plugin, conversation, handler
|
||||
from modules.apihelper.artifact import ArtifactOcrRate, get_comment, get_format_sub_item
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class ArtifactRate(BasePlugins):
|
||||
"""圣遗物评分"""
|
||||
from utils.log import logger
|
||||
|
||||
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)
|
||||
@ -33,21 +31,6 @@ class ArtifactRate(BasePlugins):
|
||||
def __init__(self):
|
||||
self.artifact_rate = ArtifactOcrRate()
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls) -> list:
|
||||
artifact_rate = cls()
|
||||
return [
|
||||
ConversationHandler(
|
||||
entry_points=[CommandHandler('artifact_rate', artifact_rate.command_start),
|
||||
MessageHandler(filters.Regex(r"^圣遗物评分(.*)"), artifact_rate.command_start),
|
||||
MessageHandler(filters.CaptionRegex(r"^圣遗物评分(.*)"), artifact_rate.command_start)],
|
||||
states={
|
||||
artifact_rate.COMMAND_RESULT: [CallbackQueryHandler(artifact_rate.command_result)]
|
||||
},
|
||||
fallbacks=[CommandHandler('cancel', artifact_rate.cancel)]
|
||||
)
|
||||
]
|
||||
|
||||
async def get_rate(self, artifact_attr: dict) -> str:
|
||||
rate_result_req = await self.artifact_rate.rate_artifact(artifact_attr)
|
||||
if rate_result_req.status_code != 200:
|
||||
@ -67,12 +50,16 @@ class ArtifactRate(BasePlugins):
|
||||
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.message(filters=filters.Regex(r"^圣遗物评分(.*)"), block=True)
|
||||
@handler.message(filters=filters.CaptionRegex(r"^圣遗物评分(.*)"), block=True)
|
||||
@error_callable
|
||||
@restricts(return_data=ConversationHandler.END)
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> int:
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
user = update.effective_user
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 圣遗物评分命令请求")
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 圣遗物评分命令请求")
|
||||
context.user_data["artifact_attr"] = None
|
||||
photo_file: Optional[File] = None
|
||||
if message is None:
|
||||
@ -110,17 +97,19 @@ class ArtifactRate(BasePlugins):
|
||||
if artifact_attr.get("star") is None:
|
||||
await message.reply_text("无法识别圣遗物星级,请选择圣遗物星级",
|
||||
reply_markup=InlineKeyboardMarkup(self.STAR_KEYBOARD))
|
||||
return self.COMMAND_RESULT
|
||||
return COMMAND_RESULT
|
||||
if artifact_attr.get("level") is None:
|
||||
await message.reply_text("无法识别圣遗物等级,请选择圣遗物等级",
|
||||
reply_markup=InlineKeyboardMarkup(self.LEVEL_KEYBOARD))
|
||||
return self.COMMAND_RESULT
|
||||
return COMMAND_RESULT
|
||||
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
|
||||
|
||||
@conversation.state(state=COMMAND_RESULT)
|
||||
@handler.callback_query()
|
||||
@error_callable
|
||||
async def command_result(self, update: Update, context: CallbackContext) -> int:
|
||||
query = update.callback_query
|
||||
@ -151,11 +140,11 @@ class ArtifactRate(BasePlugins):
|
||||
if artifact_attr.get("level") is None:
|
||||
await query.edit_message_text("无法识别圣遗物等级,请选择圣遗物等级",
|
||||
reply_markup=InlineKeyboardMarkup(self.LEVEL_KEYBOARD))
|
||||
return self.COMMAND_RESULT
|
||||
return COMMAND_RESULT
|
||||
if artifact_attr.get("star") is None:
|
||||
await query.edit_message_text("无法识别圣遗物星级,请选择圣遗物星级",
|
||||
reply_markup=InlineKeyboardMarkup(self.STAR_KEYBOARD))
|
||||
return self.COMMAND_RESULT
|
||||
return COMMAND_RESULT
|
||||
await query.edit_message_text("正在评分中...")
|
||||
rate_text = await self.get_rate(artifact_attr)
|
||||
await query.edit_message_text(rate_text, parse_mode=ParseMode.MARKDOWN_V2)
|
||||
|
@ -8,26 +8,22 @@ from telegram.constants import ChatAction
|
||||
from telegram.ext import CommandHandler, MessageHandler, ConversationHandler, filters, \
|
||||
CallbackContext
|
||||
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.cookies.error import CookiesNotFoundError
|
||||
from core.cookies.services import CookiesService
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template.services import TemplateService
|
||||
from core.user.repositories import UserNotFoundError
|
||||
from core.user.error import UserNotFoundError
|
||||
from core.user.services import UserService
|
||||
from logger import Log
|
||||
from plugins.base import BasePlugins
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.helpers import get_genshin_client
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.service.inject import inject
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class DailyNote(BasePlugins):
|
||||
class DailyNote(Plugin, BasePlugin):
|
||||
"""每日便签"""
|
||||
|
||||
COMMAND_RESULT, = range(10200, 10201)
|
||||
|
||||
@inject
|
||||
def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None,
|
||||
template_service: TemplateService = None):
|
||||
self.template_service = template_service
|
||||
@ -35,12 +31,6 @@ class DailyNote(BasePlugins):
|
||||
self.user_service = user_service
|
||||
self.current_dir = os.getcwd()
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls) -> list:
|
||||
daily_note = cls()
|
||||
return [CommandHandler('dailynote', daily_note.command_start, block=True),
|
||||
MessageHandler(filters.Regex(r"^当前状态(.*)"), daily_note.command_start, block=True)]
|
||||
|
||||
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()]
|
||||
@ -89,16 +79,18 @@ class DailyNote(BasePlugins):
|
||||
{"width": 600, "height": 548}, full_page=False)
|
||||
return png_data
|
||||
|
||||
@restricts()
|
||||
@handler(CommandHandler, command="dailynote", block=False)
|
||||
@handler(MessageHandler, filters=filters.Regex("^当前状态(.*)"), block=False)
|
||||
@restricts(return_data=ConversationHandler.END)
|
||||
@error_callable
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> Optional[int]:
|
||||
user = update.effective_user
|
||||
message = update.message
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 查询游戏状态命令请求")
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 查询游戏状态命令请求")
|
||||
try:
|
||||
client = await get_genshin_client(user.id, self.user_service, self.cookies_service)
|
||||
client = await get_genshin_client(user.id)
|
||||
png_data = await self._get_daily_note(client)
|
||||
except UserNotFoundError:
|
||||
except (UserNotFoundError, CookiesNotFoundError):
|
||||
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)
|
||||
|
@ -1,7 +1,5 @@
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from .gacha import Gacha
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class GachaPlugins(Gacha):
|
||||
pass
|
||||
|
@ -7,34 +7,20 @@ from telegram import Update
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.ext import filters, CommandHandler, MessageHandler, CallbackContext
|
||||
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template import TemplateService
|
||||
from logger import Log
|
||||
from models.apihelper.gacha import GachaInfo
|
||||
from plugins.base import BasePlugins
|
||||
from modules.apihelper.gacha import GachaInfo
|
||||
from plugins.genshin.gacha.wish import WishCountInfo, get_one
|
||||
from utils.bot import get_all_args
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.service.inject import inject
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class Gacha(BasePlugins):
|
||||
class Gacha(Plugin, BasePlugin):
|
||||
"""抽卡模拟器(非首模拟器/减寿模拟器)"""
|
||||
|
||||
CHECK_SERVER, COMMAND_RESULT = range(10600, 10602)
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls) -> list:
|
||||
gacha = cls()
|
||||
return [
|
||||
CommandHandler("gacha", gacha.command_start, block=False),
|
||||
MessageHandler(filters.Regex("^抽卡模拟器(.*)"), gacha.command_start, block=False),
|
||||
MessageHandler(filters.Regex("^非首模拟器(.*)"), gacha.command_start, block=False),
|
||||
]
|
||||
|
||||
@inject
|
||||
def __init__(self, template_service: TemplateService = None):
|
||||
self.gacha = GachaInfo()
|
||||
self.template_service = template_service
|
||||
@ -59,6 +45,9 @@ class Gacha(BasePlugins):
|
||||
gacha_info["gacha_id"] = gacha_id
|
||||
return gacha_info
|
||||
|
||||
@handler(CommandHandler, command="gacha", block=False)
|
||||
@handler(MessageHandler, filters=filters.Regex("^深渊数据查询(.*)"), block=False)
|
||||
@handler(MessageHandler, filters=filters.Regex("^非首模拟器(.*)"), block=False)
|
||||
@restricts(filters.ChatType.GROUPS, restricts_time=20, try_delete_message=True)
|
||||
@restricts(filters.ChatType.PRIVATE)
|
||||
@error_callable
|
||||
@ -80,7 +69,7 @@ class Gacha(BasePlugins):
|
||||
return
|
||||
else:
|
||||
gacha_info = await self.gacha_info(default=True)
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 抽卡模拟器命令请求 || 参数 {gacha_name}")
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 抽卡模拟器命令请求 || 参数 {gacha_name}")
|
||||
# 用户数据储存和处理
|
||||
gacha_id: str = gacha_info["gacha_id"]
|
||||
user_gacha: dict[str, WishCountInfo] = context.user_data.get("gacha")
|
||||
@ -95,7 +84,7 @@ class Gacha(BasePlugins):
|
||||
if re_color is None:
|
||||
title_html = BeautifulSoup(title, "lxml")
|
||||
pool_name = title_html.text
|
||||
Log.warning(f"卡池信息 title 提取 color 失败 title[{title}]")
|
||||
logger.warning(f"卡池信息 title 提取 color 失败 title[{title}]")
|
||||
else:
|
||||
color = re_color.group(1)
|
||||
title_html = BeautifulSoup(title, "lxml")
|
||||
|
@ -3,41 +3,32 @@ from telegram.constants import ChatAction
|
||||
from telegram.error import BadRequest
|
||||
from telegram.ext import CommandHandler, CallbackContext
|
||||
|
||||
from config import config
|
||||
from core.template.services import TemplateService
|
||||
from logger import Log
|
||||
from core.bot import bot
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template import TemplateService
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.service.inject import inject
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class Help:
|
||||
"""帮助菜单"""
|
||||
|
||||
@inject
|
||||
class HelpPlugin(Plugin):
|
||||
def __init__(self, template_service: TemplateService = None):
|
||||
self.template_service = template_service
|
||||
self.help_png = None
|
||||
self.file_id = None
|
||||
self.help_png = None
|
||||
if template_service is None:
|
||||
raise ModuleNotFoundError
|
||||
self.template_service = template_service
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls) -> list:
|
||||
_help = cls()
|
||||
return [
|
||||
CommandHandler("help", _help.command_start, block=False),
|
||||
]
|
||||
|
||||
@handler(CommandHandler, command="help", block=False)
|
||||
@error_callable
|
||||
@restricts()
|
||||
async def command_start(self, update: Update, _: CallbackContext) -> None:
|
||||
message = update.message
|
||||
async def start(self, update: Update, _: CallbackContext):
|
||||
user = update.effective_user
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 发出help命令")
|
||||
if self.file_id is None or config.debug:
|
||||
message = update.effective_message
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 发出help命令")
|
||||
if self.file_id is None or bot.config.debug:
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
help_png = await self.template_service.render('bot/help', "help.html", {}, {"width": 768, "height": 768})
|
||||
help_png = await self.template_service.render("bot/help", "help.html", {}, {"width": 768, "height": 768})
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
reply_photo = await message.reply_photo(help_png, filename="help.png", allow_sending_without_reply=True)
|
||||
photo = reply_photo.photo[0]
|
||||
@ -48,5 +39,5 @@ class Help:
|
||||
await message.reply_photo(self.file_id, allow_sending_without_reply=True)
|
||||
except BadRequest as error:
|
||||
self.file_id = None
|
||||
Log.error("发送图片失败,尝试清空已经保存的file_id,错误信息为", error)
|
||||
logger.error("发送图片失败,尝试清空已经保存的file_id,错误信息为", error)
|
||||
await message.reply_text("发送图片失败", allow_sending_without_reply=True)
|
||||
|
@ -2,34 +2,30 @@ import json
|
||||
from os import sep
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import CommandHandler, CallbackContext, filters
|
||||
from telegram.ext import CommandHandler, CallbackContext
|
||||
from telegram.ext import filters
|
||||
|
||||
from logger import Log
|
||||
from plugins.base import BasePlugins
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.plugin import Plugin, handler
|
||||
from utils.bot import get_all_args
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class Hilichurls(BasePlugins):
|
||||
class HilichurlsPlugin(Plugin, BasePlugin):
|
||||
"""丘丘语字典."""
|
||||
|
||||
def __init__(self):
|
||||
"""加载数据文件.数据整理自 https://wiki.biligame.com/ys By @zhxycn."""
|
||||
with open(f"resources{sep}json{sep}hilichurls_dictionary.json", "r", encoding="utf8") as f:
|
||||
self.hilichurls_dictionary = json.load(f)
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls):
|
||||
hilichurls = cls()
|
||||
return [CommandHandler('hilichurls', hilichurls.command_start)]
|
||||
|
||||
@error_callable
|
||||
@handler(CommandHandler, command="hilichurls", block=False)
|
||||
@restricts()
|
||||
@error_callable
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> None:
|
||||
"""丘丘语字典."""
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
user = update.effective_user
|
||||
args = get_all_args(context)
|
||||
if len(args) >= 1:
|
||||
@ -47,6 +43,6 @@ class Hilichurls(BasePlugins):
|
||||
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)
|
||||
return
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 查询丘丘语字典命令请求 || 参数 {msg}")
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 查询丘丘语字典命令请求 || 参数 {msg}")
|
||||
result = self.hilichurls_dictionary[f"{search}"]
|
||||
await message.reply_markdown_v2(f"丘丘语: `{search}`\n\n`{result}`")
|
||||
|
@ -6,20 +6,20 @@ from datetime import datetime, timedelta
|
||||
from genshin import GenshinException, DataNotPublic
|
||||
from telegram import Update
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, ConversationHandler, filters
|
||||
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters
|
||||
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.cookies.error import CookiesNotFoundError
|
||||
from core.cookies.services import CookiesService
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template.services import TemplateService
|
||||
from core.user.repositories import UserNotFoundError
|
||||
from core.user.error import UserNotFoundError
|
||||
from core.user.services import UserService
|
||||
from logger import Log
|
||||
from plugins.base import BasePlugins
|
||||
from utils.bot import get_all_args
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.helpers import get_genshin_client
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.service.inject import inject
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
def check_ledger_month(context: CallbackContext) -> int:
|
||||
@ -50,13 +50,9 @@ def check_ledger_month(context: CallbackContext) -> int:
|
||||
return now_time.month
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class Ledger(BasePlugins):
|
||||
class Ledger(Plugin, BasePlugin):
|
||||
"""旅行札记"""
|
||||
|
||||
COMMAND_RESULT, = range(10200, 10201)
|
||||
|
||||
@inject
|
||||
def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None,
|
||||
template_service: TemplateService = None):
|
||||
self.template_service = template_service
|
||||
@ -64,12 +60,6 @@ class Ledger(BasePlugins):
|
||||
self.user_service = user_service
|
||||
self.current_dir = os.getcwd()
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls):
|
||||
ledger = cls()
|
||||
return [CommandHandler("ledger", ledger.command_start, block=True),
|
||||
MessageHandler(filters.Regex(r"^旅行扎记(.*)"), ledger.command_start, block=True)]
|
||||
|
||||
async def _start_get_ledger(self, client, month=None) -> bytes:
|
||||
try:
|
||||
diary_info = await client.get_diary(client.uid, month=month)
|
||||
@ -142,8 +132,10 @@ class Ledger(BasePlugins):
|
||||
evaluate=evaluate)
|
||||
return png_data
|
||||
|
||||
@handler(CommandHandler, command="ledger", block=False)
|
||||
@handler(MessageHandler, filters=filters.Regex("^旅行扎记(.*)"), block=False)
|
||||
@restricts()
|
||||
@error_callable
|
||||
@restricts(return_data=ConversationHandler.END)
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> None:
|
||||
user = update.effective_user
|
||||
message = update.message
|
||||
@ -155,12 +147,12 @@ class Ledger(BasePlugins):
|
||||
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)
|
||||
return
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 查询原石手扎")
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 查询原石手扎")
|
||||
await update.message.reply_chat_action(ChatAction.TYPING)
|
||||
try:
|
||||
client = await get_genshin_client(user.id, self.user_service, self.cookies_service)
|
||||
client = await get_genshin_client(user.id)
|
||||
png_data = await self._start_get_ledger(client, month)
|
||||
except UserNotFoundError:
|
||||
except (UserNotFoundError, CookiesNotFoundError):
|
||||
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)
|
||||
|
@ -1,7 +1,5 @@
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from .map import Map
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class MapPlugins(Map):
|
||||
pass
|
||||
|
@ -5,34 +5,29 @@ from telegram import Update
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.ext import CommandHandler, MessageHandler, filters, CallbackContext
|
||||
|
||||
from logger import Log
|
||||
from plugins.base import BasePlugins
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.plugin import handler, Plugin
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.log import logger
|
||||
from .model import MapHelper
|
||||
|
||||
|
||||
class Map(BasePlugins):
|
||||
class Map(Plugin, BasePlugin):
|
||||
"""支持资源点查询"""
|
||||
|
||||
def __init__(self):
|
||||
self.init_resource_map = False
|
||||
self.map_helper = MapHelper()
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls) -> list:
|
||||
map_res = cls()
|
||||
return [
|
||||
CommandHandler("map", map_res.command_start, block=False),
|
||||
MessageHandler(filters.Regex(r"^资源点查询(.*)"), map_res.command_start, block=True)
|
||||
]
|
||||
|
||||
async def init_point_list_and_map(self):
|
||||
Log.info("正在初始化地图资源节点")
|
||||
logger.info("正在初始化地图资源节点")
|
||||
if not self.init_resource_map:
|
||||
await self.map_helper.init_point_list_and_map()
|
||||
self.init_resource_map = True
|
||||
|
||||
@handler(CommandHandler, command="map", block=False)
|
||||
@handler(MessageHandler, filters=filters.Regex("^资源点查询(.*)"), block=False)
|
||||
@error_callable
|
||||
@restricts(restricts_time=20)
|
||||
async def command_start(self, update: Update, context: CallbackContext):
|
||||
@ -45,7 +40,7 @@ class Map(BasePlugins):
|
||||
if len(args) >= 1:
|
||||
resource_name = args[0]
|
||||
else:
|
||||
Log.info(f"用户: {user.full_name} [{user.id}] 使用了 map 命令")
|
||||
logger.info(f"用户: {user.full_name} [{user.id}] 使用了 map 命令")
|
||||
await message.reply_text("请输入要查找的资源,或私聊派蒙发送 `/map list` 查看资源列表", parse_mode="Markdown")
|
||||
return
|
||||
if resource_name in ("list", "列表"):
|
||||
@ -54,11 +49,11 @@ class Map(BasePlugins):
|
||||
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 300)
|
||||
self._add_delete_message_job(context, message.chat_id, message.message_id, 300)
|
||||
return
|
||||
Log.info(f"用户: {user.full_name} [{user.id}] 使用 map 命令查询了 资源列表")
|
||||
logger.info(f"用户: {user.full_name} [{user.id}] 使用 map 命令查询了 资源列表")
|
||||
text = self.map_helper.get_resource_list_mes()
|
||||
await message.reply_text(text)
|
||||
return
|
||||
Log.info(f"用户: {user.full_name} [{user.id}] 使用 map 命令查询了 {resource_name}")
|
||||
logger.info(f"用户: {user.full_name} [{user.id}] 使用 map 命令查询了 {resource_name}")
|
||||
text = await self.map_helper.get_resource_map_mes(resource_name)
|
||||
if "不知道" in text or "没有找到" in text:
|
||||
await message.reply_text(text, parse_mode="Markdown")
|
||||
|
@ -2,41 +2,32 @@ from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.constants import ChatAction, ParseMode
|
||||
from telegram.ext import filters, ConversationHandler, CommandHandler, MessageHandler, CallbackContext
|
||||
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.game.services import GameMaterialService
|
||||
from logger import Log
|
||||
from plugins.base import BasePlugins
|
||||
from core.plugin import Plugin, handler
|
||||
from utils.bot import get_all_args
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.helpers import url_to_file
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.service.inject import inject
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class Material(BasePlugins):
|
||||
class Material(Plugin, BasePlugin):
|
||||
"""角色培养素材查询"""
|
||||
|
||||
KEYBOARD = [[InlineKeyboardButton(
|
||||
text="查看角色培养素材列表并查询",
|
||||
switch_inline_query_current_chat="查看角色培养素材列表并查询")]]
|
||||
|
||||
@inject
|
||||
def __init__(self, game_material_service: GameMaterialService = None):
|
||||
self.game_material_service = game_material_service
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls) -> list:
|
||||
material = cls()
|
||||
return [
|
||||
CommandHandler("material", material.command_start, block=False),
|
||||
MessageHandler(filters.Regex("^角色培养素材查询(.*)"), material.command_start, block=False),
|
||||
]
|
||||
|
||||
@error_callable
|
||||
@handler(CommandHandler, command="material", block=False)
|
||||
@handler(MessageHandler, filters=filters.Regex("^角色培养素材查询(.*)"), block=False)
|
||||
@restricts(return_data=ConversationHandler.END)
|
||||
@error_callable
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> None:
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
user = update.effective_user
|
||||
args = get_all_args(context)
|
||||
if len(args) >= 1:
|
||||
@ -56,7 +47,7 @@ class Material(BasePlugins):
|
||||
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)
|
||||
return
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 查询角色培养素材命令请求 || 参数 {character_name}")
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 查询角色培养素材命令请求 || 参数 {character_name}")
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
file_path = await url_to_file(url, "")
|
||||
caption = "From 米游社 " \
|
||||
|
@ -4,17 +4,18 @@ from bs4 import BeautifulSoup
|
||||
from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputMediaPhoto
|
||||
from telegram.constants import ParseMode, MessageLimit
|
||||
from telegram.error import BadRequest
|
||||
from telegram.ext import CallbackContext, ConversationHandler, CommandHandler, MessageHandler, filters
|
||||
from telegram.ext import CallbackContext, ConversationHandler, filters
|
||||
from telegram.helpers import escape_markdown
|
||||
|
||||
from config import config
|
||||
from logger import Log
|
||||
from models.apihelper.base import ArtworkImage
|
||||
from models.apihelper.hyperion import Hyperion
|
||||
from plugins.base import BasePlugins
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.bot import bot
|
||||
from core.plugin import Plugin, conversation, handler
|
||||
from modules.apihelper.base import ArtworkImage
|
||||
from modules.apihelper.hyperion import Hyperion
|
||||
from utils.decorators.admins import bot_admins_rights_check
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
class PostHandlerData:
|
||||
@ -27,44 +28,27 @@ class PostHandlerData:
|
||||
self.tags: Optional[List[str]] = []
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class Post(BasePlugins):
|
||||
"""文章推送"""
|
||||
|
||||
CHECK_POST, SEND_POST, CHECK_COMMAND, GTE_DELETE_PHOTO = range(10900, 10904)
|
||||
GET_POST_CHANNEL, GET_TAGS, GET_TEXT = range(10904, 10907)
|
||||
|
||||
|
||||
class Post(Plugin.Conversation, BasePlugin):
|
||||
"""文章推送"""
|
||||
|
||||
MENU_KEYBOARD = ReplyKeyboardMarkup([["推送频道", "添加TAG"], ["编辑文字", "删除图片"], ["退出"]], True, True)
|
||||
|
||||
def __init__(self):
|
||||
self.bbs = Hyperion()
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls):
|
||||
post = cls()
|
||||
post_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler('post', post.command_start, block=True)],
|
||||
states={
|
||||
post.CHECK_POST: [MessageHandler(filters.TEXT & ~filters.COMMAND, post.check_post, block=True)],
|
||||
post.SEND_POST: [MessageHandler(filters.TEXT & ~filters.COMMAND, post.send_post, block=True)],
|
||||
post.CHECK_COMMAND: [MessageHandler(filters.TEXT & ~filters.COMMAND, post.check_command, block=True)],
|
||||
post.GTE_DELETE_PHOTO: [
|
||||
MessageHandler(filters.TEXT & ~filters.COMMAND, post.get_delete_photo, block=True)],
|
||||
post.GET_POST_CHANNEL: [
|
||||
MessageHandler(filters.TEXT & ~filters.COMMAND, post.get_post_channel, block=True)],
|
||||
post.GET_TAGS: [MessageHandler(filters.TEXT & ~filters.COMMAND, post.get_tags, block=True)],
|
||||
post.GET_TEXT: [MessageHandler(filters.TEXT & ~filters.COMMAND, post.get_edit_text, block=True)]
|
||||
},
|
||||
fallbacks=[CommandHandler('cancel', post.cancel, block=True)]
|
||||
)
|
||||
return [post_handler]
|
||||
|
||||
@conversation.entry_point
|
||||
@handler.command(command='post', filters=filters.ChatType.PRIVATE, block=True)
|
||||
@restricts()
|
||||
@bot_admins_rights_check
|
||||
@error_callable
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> int:
|
||||
user = update.effective_user
|
||||
message = update.message
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] POST命令请求")
|
||||
message = update.effective_message
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] POST命令请求")
|
||||
post_handler_data = context.chat_data.get("post_handler_data")
|
||||
if post_handler_data is None:
|
||||
post_handler_data = PostHandlerData()
|
||||
@ -76,11 +60,13 @@ class Post(BasePlugins):
|
||||
await message.reply_text(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, True, True))
|
||||
return self.CHECK_POST
|
||||
|
||||
@conversation.state(state=CHECK_POST)
|
||||
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
|
||||
@error_callable
|
||||
async def check_post(self, update: Update, context: CallbackContext) -> int:
|
||||
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
|
||||
message = update.message
|
||||
if update.message.text == "退出":
|
||||
message = update.effective_message
|
||||
if message.text == "退出":
|
||||
await message.reply_text("退出投稿", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
|
||||
@ -114,9 +100,10 @@ class Post(BasePlugins):
|
||||
else:
|
||||
await message.reply_text("图片获取错误", reply_markup=ReplyKeyboardRemove()) # excuse?
|
||||
return ConversationHandler.END
|
||||
except (BadRequest, TypeError) as error:
|
||||
except (BadRequest, TypeError) as exc:
|
||||
await message.reply_text("发送图片时发生错误,错误信息已经写到日记", reply_markup=ReplyKeyboardRemove())
|
||||
Log.error("Post模块发送图片时发生错误", error)
|
||||
logger.error("Post模块发送图片时发生错误")
|
||||
logger.exception(exc)
|
||||
return ConversationHandler.END
|
||||
post_handler_data.post_text = post_text
|
||||
post_handler_data.post_images = post_images
|
||||
@ -126,9 +113,11 @@ class Post(BasePlugins):
|
||||
await message.reply_text("请选择你的操作", reply_markup=self.MENU_KEYBOARD)
|
||||
return self.CHECK_COMMAND
|
||||
|
||||
@conversation.state(state=CHECK_COMMAND)
|
||||
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
|
||||
@error_callable
|
||||
async def check_command(self, update: Update, context: CallbackContext) -> int:
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
if message.text == "退出":
|
||||
await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
@ -150,11 +139,13 @@ class Post(BasePlugins):
|
||||
f"当前一共有 {photo_len} 张图片")
|
||||
return self.GTE_DELETE_PHOTO
|
||||
|
||||
@conversation.state(state=GTE_DELETE_PHOTO)
|
||||
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
|
||||
@error_callable
|
||||
async def get_delete_photo(self, update: Update, context: CallbackContext) -> int:
|
||||
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
|
||||
photo_len = len(post_handler_data.post_images)
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
args = message.text.split(" ")
|
||||
index: List[int] = []
|
||||
try:
|
||||
@ -171,31 +162,34 @@ class Post(BasePlugins):
|
||||
return self.CHECK_COMMAND
|
||||
|
||||
async def get_channel(self, update: Update, _: CallbackContext) -> int:
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
reply_keyboard = []
|
||||
try:
|
||||
for channel_info in config.channels:
|
||||
for channel_info in bot.config.channels:
|
||||
name = channel_info["name"]
|
||||
reply_keyboard.append([f"{name}"])
|
||||
except KeyError as error:
|
||||
Log.error("从配置文件获取频道信息发生错误,退出任务", error)
|
||||
logger.error("从配置文件获取频道信息发生错误,退出任务", error)
|
||||
await message.reply_text("从配置文件获取频道信息发生错误,退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
await message.reply_text("请选择你要推送的频道",
|
||||
reply_markup=ReplyKeyboardMarkup(reply_keyboard, True, True))
|
||||
return self.GET_POST_CHANNEL
|
||||
|
||||
@conversation.state(state=GET_POST_CHANNEL)
|
||||
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
|
||||
@error_callable
|
||||
async def get_post_channel(self, update: Update, context: CallbackContext) -> int:
|
||||
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
channel_id = -1
|
||||
try:
|
||||
for channel_info in config.channels:
|
||||
for channel_info in bot.config.channels:
|
||||
if message.text == channel_info["name"]:
|
||||
channel_id = channel_info["chat_id"]
|
||||
except KeyError as error:
|
||||
Log.error("从配置文件获取频道信息发生错误,退出任务", error)
|
||||
except KeyError as exc:
|
||||
logger.error("从配置文件获取频道信息发生错误,退出任务", exc)
|
||||
logger.exception(exc)
|
||||
await message.reply_text("从配置文件获取频道信息发生错误,退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
if channel_id == -1:
|
||||
@ -208,14 +202,16 @@ class Post(BasePlugins):
|
||||
return self.SEND_POST
|
||||
|
||||
async def add_tags(self, update: Update, _: CallbackContext) -> int:
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
await message.reply_text("请回复添加的tag名称,如果要添加多个tag请以空格作为分隔符,不用添加 # 作为开头,推送时程序会自动添加")
|
||||
return self.GET_TAGS
|
||||
|
||||
@conversation.state(state=GET_TAGS)
|
||||
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
|
||||
@error_callable
|
||||
async def get_tags(self, update: Update, context: CallbackContext) -> int:
|
||||
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
args = message.text.split(" ")
|
||||
post_handler_data.tags = args
|
||||
await message.reply_text("添加成功")
|
||||
@ -223,14 +219,16 @@ class Post(BasePlugins):
|
||||
return self.CHECK_COMMAND
|
||||
|
||||
async def edit_text(self, update: Update, _: CallbackContext) -> int:
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
await message.reply_text("请回复替换的文本")
|
||||
return self.GET_TEXT
|
||||
|
||||
@conversation.state(state=GET_TEXT)
|
||||
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
|
||||
@error_callable
|
||||
async def get_edit_text(self, update: Update, context: CallbackContext) -> int:
|
||||
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
post_handler_data.post_text = message.text_markdown_v2
|
||||
await message.reply_text("替换成功")
|
||||
await message.reply_text("请选择你的操作", reply_markup=self.MENU_KEYBOARD)
|
||||
@ -240,19 +238,20 @@ class Post(BasePlugins):
|
||||
@error_callable
|
||||
async def send_post(update: Update, context: CallbackContext) -> int:
|
||||
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
|
||||
message = update.message
|
||||
if update.message.text == "退出":
|
||||
message = update.effective_message
|
||||
if message.text == "退出":
|
||||
await message.reply_text(text="退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
await message.reply_text("正在推送", reply_markup=ReplyKeyboardRemove())
|
||||
channel_id = post_handler_data.channel_id
|
||||
channel_name = None
|
||||
try:
|
||||
for channel_info in config.channels:
|
||||
for channel_info in bot.config.channels:
|
||||
if post_handler_data.channel_id == channel_info["chat_id"]:
|
||||
channel_name = channel_info["name"]
|
||||
except KeyError as error:
|
||||
Log.error("从配置文件获取频道信息发生错误,退出任务", error)
|
||||
except KeyError as exc:
|
||||
logger.error("从配置文件获取频道信息发生错误,退出任务")
|
||||
logger.exception(exc)
|
||||
await message.reply_text("从配置文件获取频道信息发生错误,退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
post_text = post_handler_data.post_text
|
||||
@ -277,9 +276,10 @@ class Post(BasePlugins):
|
||||
else:
|
||||
await message.reply_text("图片获取错误", reply_markup=ReplyKeyboardRemove()) # excuse?
|
||||
return ConversationHandler.END
|
||||
except (BadRequest, TypeError) as error:
|
||||
except (BadRequest, TypeError) as exc:
|
||||
await message.reply_text("发送图片时发生错误,错误信息已经写到日记", reply_markup=ReplyKeyboardRemove())
|
||||
Log.error("Post模块发送图片时发生错误", error)
|
||||
logger.error("Post模块发送图片时发生错误")
|
||||
logger.exception(exc)
|
||||
return ConversationHandler.END
|
||||
await message.reply_text("推送成功", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
|
@ -1,81 +1,37 @@
|
||||
import random
|
||||
import re
|
||||
from typing import List, Optional
|
||||
|
||||
from redis import DataError, ResponseError
|
||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup, Poll, \
|
||||
ReplyKeyboardRemove, Message
|
||||
from telegram import Update, Poll
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.ext import CallbackContext, ConversationHandler, CommandHandler, MessageHandler, filters
|
||||
from telegram.helpers import escape_markdown
|
||||
from telegram.ext import CallbackContext, CommandHandler, filters
|
||||
|
||||
from core.admin import BotAdminService
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.plugin import Plugin, handler
|
||||
from core.quiz import QuizService
|
||||
from core.quiz.models import Answer, Question
|
||||
from logger import Log
|
||||
from plugins.base import BasePlugins
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.random import MT19937_Random
|
||||
from utils.service.inject import inject
|
||||
from utils.log import logger
|
||||
from utils.random import MT19937Random
|
||||
|
||||
|
||||
class QuizCommandData:
|
||||
question_id: int = -1
|
||||
new_question: str = ""
|
||||
new_correct_answer: str = ""
|
||||
new_wrong_answer: List[str] = []
|
||||
status: int = 0
|
||||
class QuizPlugin(Plugin, BasePlugin):
|
||||
"""派蒙的十万个为什么"""
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class QuizPlugin(BasePlugins):
|
||||
"""派蒙的十万个为什么
|
||||
合并了问题修改/添加/删除
|
||||
"""
|
||||
|
||||
CHECK_COMMAND, VIEW_COMMAND, CHECK_QUESTION, \
|
||||
GET_NEW_QUESTION, GET_NEW_CORRECT_ANSWER, GET_NEW_WRONG_ANSWER, \
|
||||
QUESTION_EDIT, SAVE_QUESTION = range(10300, 10308)
|
||||
|
||||
@inject
|
||||
def __init__(self, quiz_service: QuizService = None, bot_admin_service: BotAdminService = None):
|
||||
self.bot_admin_service = bot_admin_service
|
||||
self.user_time = {}
|
||||
self.quiz_service = quiz_service
|
||||
self.time_out = 120
|
||||
self.random = MT19937_Random()
|
||||
self.random = MT19937Random()
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls):
|
||||
quiz = cls()
|
||||
quiz_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler('quiz', quiz.command_start, block=True)],
|
||||
states={
|
||||
quiz.CHECK_COMMAND: [MessageHandler(filters.TEXT & ~filters.COMMAND,
|
||||
quiz.check_command, block=True)],
|
||||
quiz.CHECK_QUESTION: [MessageHandler(filters.TEXT & ~filters.COMMAND,
|
||||
quiz.check_question, block=True)],
|
||||
quiz.GET_NEW_QUESTION: [MessageHandler(filters.TEXT & ~filters.COMMAND,
|
||||
quiz.get_new_question, block=True)],
|
||||
quiz.GET_NEW_CORRECT_ANSWER: [MessageHandler(filters.TEXT & ~filters.COMMAND,
|
||||
quiz.get_new_correct_answer, block=True)],
|
||||
quiz.GET_NEW_WRONG_ANSWER: [MessageHandler(filters.TEXT & ~filters.COMMAND,
|
||||
quiz.get_new_wrong_answer, block=True),
|
||||
CommandHandler("finish", quiz.finish_edit)],
|
||||
quiz.SAVE_QUESTION: [MessageHandler(filters.TEXT & ~filters.COMMAND,
|
||||
quiz.save_question, block=True)],
|
||||
},
|
||||
fallbacks=[CommandHandler('cancel', quiz.cancel, block=True)]
|
||||
)
|
||||
return [quiz_handler]
|
||||
|
||||
async def send_poll(self, update: Update) -> Optional[Message]:
|
||||
chat = update.message.chat
|
||||
@handler(CommandHandler, command="quiz", block=False)
|
||||
@restricts(restricts_time=20, try_delete_message=True)
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> None:
|
||||
user = update.effective_user
|
||||
message = update.effective_message
|
||||
chat = message.chat
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
question_id_list = await self.quiz_service.get_question_id_list()
|
||||
if filters.ChatType.GROUPS.filter(update.message):
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 发送挑战问题命令请求")
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 发送挑战问题命令请求")
|
||||
if len(question_id_list) == 0:
|
||||
return None
|
||||
if len(question_id_list) == 0:
|
||||
@ -90,204 +46,12 @@ class QuizPlugin(BasePlugins):
|
||||
correct_option = answer.text
|
||||
if correct_option is None:
|
||||
question_id = question["question_id"]
|
||||
Log.warning(f"Quiz模块 correct_option 异常 question_id[{question_id}] ")
|
||||
logger.warning(f"Quiz模块 correct_option 异常 question_id[{question_id}] ")
|
||||
return None
|
||||
random.shuffle(_options)
|
||||
index = _options.index(correct_option)
|
||||
return await update.effective_message.reply_poll(question.text, _options,
|
||||
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)
|
||||
|
||||
@restricts(filters.ChatType.GROUPS, ConversationHandler.END, restricts_time=20, try_delete_message=True)
|
||||
@restricts(filters.ChatType.PRIVATE, ConversationHandler.END)
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> int:
|
||||
user = update.effective_user
|
||||
message = update.message
|
||||
if filters.ChatType.PRIVATE.filter(message):
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] quiz命令请求")
|
||||
admin_list = await self.bot_admin_service.get_admin_list()
|
||||
if user.id in admin_list:
|
||||
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
|
||||
if quiz_command_data is None:
|
||||
quiz_command_data = QuizCommandData()
|
||||
context.chat_data["quiz_command_data"] = quiz_command_data
|
||||
text = f'你好 {user.mention_markdown_v2()} {escape_markdown("!请选择你的操作!")}'
|
||||
reply_keyboard = [
|
||||
["查看问题", "添加问题"],
|
||||
["重载问题"],
|
||||
["退出"]
|
||||
]
|
||||
await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard,
|
||||
one_time_keyboard=True))
|
||||
return self.CHECK_COMMAND
|
||||
else:
|
||||
await self.send_poll(update)
|
||||
elif filters.ChatType.GROUPS.filter(update.message):
|
||||
await update.message.reply_chat_action(ChatAction.TYPING)
|
||||
poll_message = await self.send_poll(update)
|
||||
if poll_message is None:
|
||||
return ConversationHandler.END
|
||||
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)
|
||||
return ConversationHandler.END
|
||||
|
||||
async def view_command(self, update: Update, _: CallbackContext) -> int:
|
||||
keyboard = [
|
||||
[
|
||||
InlineKeyboardButton(text="选择问题", switch_inline_query_current_chat="查看问题 ")
|
||||
]
|
||||
]
|
||||
await update.message.reply_text("请回复你要查看的问题",
|
||||
reply_markup=InlineKeyboardMarkup(keyboard))
|
||||
return self.CHECK_COMMAND
|
||||
|
||||
async def check_question(self, update: Update, _: CallbackContext) -> int:
|
||||
reply_keyboard = [
|
||||
["删除问题"],
|
||||
["退出"]
|
||||
]
|
||||
await update.message.reply_text("请选择你的操作", reply_markup=ReplyKeyboardMarkup(reply_keyboard))
|
||||
return self.CHECK_COMMAND
|
||||
|
||||
async def check_command(self, update: Update, context: CallbackContext) -> int:
|
||||
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
|
||||
if update.message.text == "退出":
|
||||
await update.message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
elif update.message.text == "查看问题":
|
||||
return await self.view_command(update, context)
|
||||
elif update.message.text == "添加问题":
|
||||
return await self.add_question(update, context)
|
||||
elif update.message.text == "删除问题":
|
||||
return await self.delete_question(update, context)
|
||||
# elif update.message.text == "修改问题":
|
||||
# return await self.edit_question(update, context)
|
||||
elif update.message.text == "重载问题":
|
||||
return await self.refresh_question(update, context)
|
||||
else:
|
||||
result = re.findall(r"问题ID (\d+)", update.message.text)
|
||||
if len(result) == 1:
|
||||
try:
|
||||
question_id = int(result[0])
|
||||
except ValueError:
|
||||
await update.message.reply_text("获取问题ID失败")
|
||||
return ConversationHandler.END
|
||||
quiz_command_data.question_id = question_id
|
||||
await update.message.reply_text("获取问题ID成功")
|
||||
return await self.check_question(update, context)
|
||||
await update.message.reply_text("命令错误", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
|
||||
async def refresh_question(self, update: Update, _: CallbackContext) -> int:
|
||||
try:
|
||||
await self.quiz_service.refresh_quiz()
|
||||
except DataError:
|
||||
await update.message.reply_text("Redis数据错误,重载失败", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
except ResponseError as error:
|
||||
Log.error("重载问题失败", error)
|
||||
await update.message.reply_text("重载问题失败,异常抛出Redis请求错误异常,详情错误请看日记",
|
||||
reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
await update.message.reply_text("重载成功", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
|
||||
async def add_question(self, update: Update, context: CallbackContext) -> int:
|
||||
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
|
||||
quiz_command_data.new_wrong_answer = []
|
||||
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())
|
||||
return self.GET_NEW_QUESTION
|
||||
|
||||
async def get_new_question(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"请填写正确答案:"
|
||||
quiz_command_data.new_question = update.message.text
|
||||
await update.message.reply_markdown_v2(reply_text)
|
||||
return self.GET_NEW_CORRECT_ANSWER
|
||||
|
||||
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"请填写错误答案:"
|
||||
await update.message.reply_markdown_v2(reply_text)
|
||||
quiz_command_data.new_correct_answer = update.message.text
|
||||
return self.GET_NEW_WRONG_ANSWER
|
||||
|
||||
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)} 结束。"
|
||||
await update.message.reply_markdown_v2(reply_text)
|
||||
quiz_command_data.new_wrong_answer.append(update.message.text)
|
||||
return self.GET_NEW_WRONG_ANSWER
|
||||
|
||||
async def finish_edit(self, update: Update, context: CallbackContext):
|
||||
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)}`"
|
||||
await update.message.reply_markdown_v2(reply_text)
|
||||
reply_keyboard = [["保存并重载配置", "抛弃修改并退出"]]
|
||||
await update.message.reply_text("请核对问题,并选择下一步操作。", reply_markup=ReplyKeyboardMarkup(reply_keyboard))
|
||||
return self.SAVE_QUESTION
|
||||
|
||||
async def save_question(self, update: Update, context: CallbackContext):
|
||||
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
|
||||
if update.message.text == "抛弃修改并退出":
|
||||
await update.message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
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.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 update.message.reply_text("保存成功", reply_markup=ReplyKeyboardRemove())
|
||||
try:
|
||||
await self.quiz_service.refresh_quiz()
|
||||
except ResponseError as error:
|
||||
Log.error("重载问题失败", error)
|
||||
await update.message.reply_text("重载问题失败,异常抛出Redis请求错误异常,详情错误请看日记",
|
||||
reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
await update.message.reply_text("重载配置成功", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
else:
|
||||
await update.message.reply_text("回复错误,请重新选择")
|
||||
return self.SAVE_QUESTION
|
||||
|
||||
async def edit_question(self, update: Update, context: CallbackContext) -> int:
|
||||
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
|
||||
quiz_command_data.new_wrong_answer = []
|
||||
quiz_command_data.new_question = ""
|
||||
quiz_command_data.new_correct_answer = ""
|
||||
quiz_command_data.status = 2
|
||||
await update.message.reply_text("请回复你要修改的问题", reply_markup=ReplyKeyboardRemove())
|
||||
return self.GET_NEW_QUESTION
|
||||
|
||||
async def delete_question(self, update: Update, context: CallbackContext) -> int:
|
||||
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
|
||||
# 再问题重载Redis 以免redis数据为空时出现奔溃
|
||||
try:
|
||||
await self.quiz_service.refresh_quiz()
|
||||
question = await self.quiz_service.get_question(quiz_command_data.question_id)
|
||||
# 因为外键的存在,先删除答案
|
||||
for answer in question.answers:
|
||||
await self.quiz_service.delete_question_by_id(answer.answer_id)
|
||||
await self.quiz_service.delete_question_by_id(question.question_id)
|
||||
await update.message.reply_text("删除问题成功", reply_markup=ReplyKeyboardRemove())
|
||||
await self.quiz_service.refresh_quiz()
|
||||
except ResponseError as error:
|
||||
Log.error("重载问题失败", error)
|
||||
await update.message.reply_text("重载问题失败,异常抛出Redis请求错误异常,详情错误请看日记",
|
||||
reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
await update.message.reply_text("重载配置成功", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
|
178
plugins/genshin/set_uid.py
Normal file
178
plugins/genshin/set_uid.py
Normal file
@ -0,0 +1,178 @@
|
||||
from typing import Optional
|
||||
|
||||
import genshin
|
||||
from genshin import GenshinException, types
|
||||
from telegram import Update, ReplyKeyboardRemove, ReplyKeyboardMarkup, TelegramObject
|
||||
from telegram.ext import CallbackContext, filters, ConversationHandler
|
||||
from telegram.helpers import escape_markdown
|
||||
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.cookies.error import CookiesNotFoundError, TooManyRequestPublicCookies
|
||||
from core.cookies.services import CookiesService, PublicCookiesService
|
||||
from core.plugin import Plugin, handler, conversation
|
||||
from core.user.error import UserNotFoundError
|
||||
from core.user.models import User
|
||||
from core.user.services import UserService
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.log import logger
|
||||
from utils.models.base import RegionEnum
|
||||
|
||||
|
||||
class AddUserCommandData(TelegramObject):
|
||||
user: Optional[User] = None
|
||||
region: RegionEnum = RegionEnum.HYPERION
|
||||
game_uid: int = 0
|
||||
|
||||
|
||||
CHECK_SERVER, CHECK_UID, COMMAND_RESULT = range(10100, 10103)
|
||||
|
||||
|
||||
class SetUid(Plugin.Conversation, BasePlugin.Conversation):
|
||||
"""UID用户绑定"""
|
||||
|
||||
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='set_uid', filters=filters.ChatType.PRIVATE, block=True)
|
||||
@restricts()
|
||||
@error_callable
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> int:
|
||||
user = update.effective_user
|
||||
message = update.effective_message
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 绑定账号命令请求")
|
||||
add_user_command_data: AddUserCommandData = context.chat_data.get("add_uid_command_data")
|
||||
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()} {escape_markdown("!请选择要绑定的服务器!或回复退出取消操作")}'
|
||||
reply_keyboard = [['米游社', 'HoYoLab'], ["退出"]]
|
||||
await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
|
||||
return CHECK_SERVER
|
||||
|
||||
@conversation.state(state=CHECK_SERVER)
|
||||
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
|
||||
@error_callable
|
||||
async def check_server(self, update: Update, context: CallbackContext) -> int:
|
||||
user = update.effective_user
|
||||
message = update.effective_message
|
||||
add_user_command_data: AddUserCommandData = context.chat_data.get("add_uid_command_data")
|
||||
if message.text == "退出":
|
||||
await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
elif message.text == "米游社":
|
||||
region = add_user_command_data.region = RegionEnum.HYPERION
|
||||
elif message.text == "HoYoLab":
|
||||
region = add_user_command_data.region = RegionEnum.HOYOLAB
|
||||
else:
|
||||
await message.reply_text("选择错误,请重新选择")
|
||||
return CHECK_SERVER
|
||||
try:
|
||||
user_info = await self.user_service.get_user_by_id(user.id)
|
||||
add_user_command_data.user = user_info
|
||||
except UserNotFoundError:
|
||||
user_info = None
|
||||
if user_info is not None:
|
||||
try:
|
||||
await self.cookies_service.get_cookies(user.id, region)
|
||||
except CookiesNotFoundError:
|
||||
pass
|
||||
else:
|
||||
await message.reply_text("你已经绑定Cookie,无法继续下一步")
|
||||
return ConversationHandler.END
|
||||
await message.reply_text("请输入你的UID", reply_markup=ReplyKeyboardRemove())
|
||||
return CHECK_UID
|
||||
|
||||
@conversation.state(state=CHECK_UID)
|
||||
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
|
||||
@error_callable
|
||||
async def check_cookies(self, update: Update, context: CallbackContext) -> int:
|
||||
user = update.effective_user
|
||||
message = update.effective_message
|
||||
add_user_command_data: AddUserCommandData = context.chat_data.get("add_uid_command_data")
|
||||
region = add_user_command_data.region
|
||||
if message.text == "退出":
|
||||
await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
try:
|
||||
uid = int(message.text)
|
||||
except ValueError:
|
||||
await message.reply_text("Cookies格式有误,请检查", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
try:
|
||||
cookies = await self.public_cookies_service.get_cookies(user.id, region)
|
||||
except TooManyRequestPublicCookies:
|
||||
await message.reply_text("Cookies公共池已经使用完,请稍后重试", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
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")
|
||||
else:
|
||||
return ConversationHandler.END
|
||||
try:
|
||||
user_info = await client.get_record_card(uid)
|
||||
except GenshinException as exc:
|
||||
await message.reply_text("获取账号信息发生错误", reply_markup=ReplyKeyboardRemove())
|
||||
logger.error("获取账号信息发生错误")
|
||||
logger.exception(exc)
|
||||
return ConversationHandler.END
|
||||
add_user_command_data.game_uid = uid
|
||||
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"
|
||||
await message.reply_markdown_v2(
|
||||
text,
|
||||
reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
|
||||
)
|
||||
return COMMAND_RESULT
|
||||
|
||||
@conversation.state(state=COMMAND_RESULT)
|
||||
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
|
||||
@error_callable
|
||||
async def command_result(self, update: Update, context: CallbackContext) -> int:
|
||||
user = update.effective_user
|
||||
message = update.effective_message
|
||||
add_user_command_data: AddUserCommandData = context.chat_data.get("add_uid_command_data")
|
||||
if message.text == "退出":
|
||||
await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
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)
|
||||
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)
|
||||
else:
|
||||
await message.reply_text("数据错误")
|
||||
return ConversationHandler.END
|
||||
await self.user_service.add_user(user_db)
|
||||
else:
|
||||
user_db = add_user_command_data.user
|
||||
user_db.region = add_user_command_data.region
|
||||
if add_user_command_data.region == RegionEnum.HYPERION:
|
||||
user_db.yuanshen_uid = add_user_command_data.game_uid
|
||||
elif add_user_command_data.region == RegionEnum.HOYOLAB:
|
||||
user_db.genshin_uid = add_user_command_data.game_uid
|
||||
else:
|
||||
await message.reply_text("数据错误")
|
||||
return ConversationHandler.END
|
||||
await self.user_service.update_user(user_db)
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 绑定UID账号成功")
|
||||
await message.reply_text("保存成功", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
else:
|
||||
await message.reply_text("回复错误,请重新输入")
|
||||
return COMMAND_RESULT
|
@ -3,72 +3,65 @@ import time
|
||||
|
||||
from genshin import Game, GenshinException, AlreadyClaimed, Client
|
||||
from telegram import Update
|
||||
from telegram.ext import CommandHandler, MessageHandler, ConversationHandler, filters, CallbackContext
|
||||
from telegram.ext import CommandHandler, CallbackContext
|
||||
from telegram.ext import MessageHandler, filters
|
||||
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.cookies.error import CookiesNotFoundError
|
||||
from core.cookies.services import CookiesService
|
||||
from core.plugin import Plugin, handler
|
||||
from core.sign.models import Sign as SignUser, SignStatusEnum
|
||||
from core.sign.services import SignServices
|
||||
from core.user.repositories import UserNotFoundError
|
||||
from core.user.error import UserNotFoundError
|
||||
from core.user.services import UserService
|
||||
from logger import Log
|
||||
from plugins.base import BasePlugins
|
||||
from utils.bot import get_all_args
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.helpers import get_genshin_client
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.service.inject import inject
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class Sign(BasePlugins):
|
||||
class Sign(Plugin, BasePlugin):
|
||||
"""每日签到"""
|
||||
|
||||
CHECK_SERVER, COMMAND_RESULT = range(10400, 10402)
|
||||
|
||||
@inject
|
||||
def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None,
|
||||
sign_service: SignServices = None):
|
||||
self.cookies_service = cookies_service
|
||||
self.user_service = user_service
|
||||
self.sign_service = sign_service
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls):
|
||||
sign = cls()
|
||||
return [CommandHandler('sign', sign.command_start, block=True),
|
||||
MessageHandler(filters.Regex(r"^每日签到(.*)"), sign.command_start, block=True)]
|
||||
|
||||
@staticmethod
|
||||
async def _start_sign(client: Client) -> str:
|
||||
try:
|
||||
rewards = await client.get_monthly_rewards(game=Game.GENSHIN, lang="zh-cn")
|
||||
except GenshinException as error:
|
||||
Log.error(f"UID {client.uid} 获取签到信息失败,API返回信息为 {str(error)}")
|
||||
logger.error(f"UID {client.uid} 获取签到信息失败,API返回信息为 {str(error)}")
|
||||
return f"获取签到信息失败,API返回信息为 {str(error)}"
|
||||
try:
|
||||
daily_reward_info = await client.get_reward_info(game=Game.GENSHIN, lang="zh-cn") # 获取签到信息失败
|
||||
except GenshinException as error:
|
||||
Log.error(f"UID {client.uid} 获取签到状态失败,API返回信息为 {str(error)}")
|
||||
logger.error(f"UID {client.uid} 获取签到状态失败,API返回信息为 {str(error)}")
|
||||
return f"获取签到状态失败,API返回信息为 {str(error)}"
|
||||
if not daily_reward_info.signed_in:
|
||||
try:
|
||||
request_daily_reward = await client.request_daily_reward("sign", method="POST",
|
||||
game=Game.GENSHIN, lang="zh-cn")
|
||||
Log.info(f"UID {client.uid} 签到请求 {request_daily_reward}")
|
||||
logger.info(f"UID {client.uid} 签到请求 {request_daily_reward}")
|
||||
if request_daily_reward and request_daily_reward.get("success", 0) == 1:
|
||||
Log.warning(f"UID {client.uid} 签到失败,触发验证码风控")
|
||||
logger.warning(f"UID {client.uid} 签到失败,触发验证码风控")
|
||||
return f"UID {client.uid} 签到失败,触发验证码风控,请尝试重新签到。"
|
||||
except AlreadyClaimed:
|
||||
result = "今天旅行者已经签到过了~"
|
||||
except GenshinException as error:
|
||||
Log.error(f"UID {client.uid} 签到失败,API返回信息为 {str(error)}")
|
||||
logger.error(f"UID {client.uid} 签到失败,API返回信息为 {str(error)}")
|
||||
return f"获取签到状态失败,API返回信息为 {str(error)}"
|
||||
else:
|
||||
result = "OK"
|
||||
else:
|
||||
result = "今天旅行者已经签到过了~"
|
||||
Log.info(f"UID {client.uid} 签到结果 {result}")
|
||||
logger.info(f"UID {client.uid} 签到结果 {result}")
|
||||
reward = rewards[daily_reward_info.claimed_rewards - (1 if daily_reward_info.signed_in else 0)]
|
||||
today = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||
cn_timezone = datetime.timezone(datetime.timedelta(hours=8))
|
||||
@ -85,8 +78,8 @@ class Sign(BasePlugins):
|
||||
|
||||
async def _process_auto_sign(self, user_id: int, chat_id: int, method: str) -> str:
|
||||
try:
|
||||
await get_genshin_client(user_id, self.user_service, self.cookies_service)
|
||||
except UserNotFoundError:
|
||||
await get_genshin_client(user_id)
|
||||
except (UserNotFoundError, CookiesNotFoundError):
|
||||
return "未查询到账号信息,请先私聊派蒙绑定账号"
|
||||
user: SignUser = await self.sign_service.get_by_user_id(user_id)
|
||||
if user:
|
||||
@ -107,11 +100,13 @@ class Sign(BasePlugins):
|
||||
await self.sign_service.add(user)
|
||||
return "开启自动签到成功"
|
||||
|
||||
@handler(CommandHandler, command="sign", block=False)
|
||||
@handler(MessageHandler, filters=filters.Regex("^每日签到(.*)"), block=False)
|
||||
@restricts()
|
||||
@error_callable
|
||||
@restricts(return_data=ConversationHandler.END)
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> None:
|
||||
user = update.effective_user
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
args = get_all_args(context)
|
||||
if len(args) >= 1:
|
||||
msg = None
|
||||
@ -120,22 +115,22 @@ class Sign(BasePlugins):
|
||||
elif args[0] == "关闭自动签到":
|
||||
msg = await self._process_auto_sign(user.id, message.chat_id, "关闭")
|
||||
if msg:
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 自动签到命令请求 || 参数 {args[0]}")
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 自动签到命令请求 || 参数 {args[0]}")
|
||||
reply_message = await message.reply_text(msg)
|
||||
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, message.chat_id, message.message_id, 30)
|
||||
return
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 每日签到命令请求")
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 每日签到命令请求")
|
||||
if filters.ChatType.GROUPS.filter(message):
|
||||
self._add_delete_message_job(context, message.chat_id, message.message_id)
|
||||
try:
|
||||
client = await get_genshin_client(user.id, self.user_service, self.cookies_service)
|
||||
client = await get_genshin_client(user.id)
|
||||
sign_text = await self._start_sign(client)
|
||||
reply_message = await message.reply_text(sign_text, allow_sending_without_reply=True)
|
||||
if filters.ChatType.GROUPS.filter(reply_message):
|
||||
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
|
||||
except UserNotFoundError:
|
||||
except (UserNotFoundError, CookiesNotFoundError):
|
||||
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)
|
||||
|
@ -1,40 +1,34 @@
|
||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.constants import ChatAction, ParseMode
|
||||
from telegram.ext import filters, ConversationHandler, CommandHandler, MessageHandler, CallbackContext
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram import Update
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.constants import ParseMode
|
||||
from telegram.ext import CommandHandler, CallbackContext
|
||||
from telegram.ext import MessageHandler, filters
|
||||
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.game.services import GameStrategyService
|
||||
from logger import Log
|
||||
from plugins.base import BasePlugins
|
||||
from core.plugin import Plugin, handler
|
||||
from utils.bot import get_all_args
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.helpers import url_to_file
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.service.inject import inject
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class Strategy(BasePlugins):
|
||||
class StrategyPlugin(Plugin, BasePlugin):
|
||||
"""角色攻略查询"""
|
||||
|
||||
KEYBOARD = [[InlineKeyboardButton(text="查看角色攻略列表并查询", switch_inline_query_current_chat="查看角色攻略列表并查询")]]
|
||||
|
||||
@inject
|
||||
def __init__(self, game_strategy_service: GameStrategyService = None):
|
||||
self.game_strategy_service = game_strategy_service
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls) -> list:
|
||||
strategy = cls()
|
||||
return [
|
||||
CommandHandler("strategy", strategy.command_start, block=False),
|
||||
MessageHandler(filters.Regex("^角色攻略查询(.*)"), strategy.command_start, block=False),
|
||||
]
|
||||
|
||||
@handler(CommandHandler, command="strategy", block=False)
|
||||
@handler(MessageHandler, filters=filters.Regex("^角色攻略查询(.*)"), block=False)
|
||||
@restricts()
|
||||
@error_callable
|
||||
@restricts(return_data=ConversationHandler.END)
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> None:
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
user = update.effective_user
|
||||
args = get_all_args(context)
|
||||
if len(args) >= 1:
|
||||
@ -54,7 +48,7 @@ class Strategy(BasePlugins):
|
||||
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)
|
||||
return
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 查询角色攻略命令请求 || 参数 {character_name}")
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 查询角色攻略命令请求 || 参数 {character_name}")
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
file_path = await url_to_file(url, "")
|
||||
caption = "From 米游社 西风驿站 " \
|
||||
|
@ -7,39 +7,28 @@ from telegram import Update
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, ConversationHandler, filters
|
||||
|
||||
from core.cookies.services import CookiesService
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.cookies.error import CookiesNotFoundError
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template.services import TemplateService
|
||||
from core.user.repositories import UserNotFoundError
|
||||
from core.user.services import UserService
|
||||
from logger import Log
|
||||
from plugins.base import BasePlugins
|
||||
from core.user.error import UserNotFoundError
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.helpers import url_to_file, get_genshin_client
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.service.inject import inject
|
||||
from utils.helpers import url_to_file, get_genshin_client, get_public_genshin_client
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class UserStats(BasePlugins):
|
||||
class TeapotUnlocked(Exception):
|
||||
"""尘歌壶未解锁"""
|
||||
|
||||
|
||||
class UserStatsPlugins(Plugin, BasePlugin):
|
||||
"""玩家统计查询"""
|
||||
|
||||
COMMAND_RESULT, = range(10200, 10201)
|
||||
|
||||
@inject
|
||||
def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None,
|
||||
template_service: TemplateService = None):
|
||||
def __init__(self, template_service: TemplateService = None):
|
||||
self.template_service = template_service
|
||||
self.cookies_service = cookies_service
|
||||
self.user_service = user_service
|
||||
self.current_dir = os.getcwd()
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls):
|
||||
uid = cls()
|
||||
return [CommandHandler('stats', uid.command_start, block=True),
|
||||
MessageHandler(filters.Regex(r"^玩家统计查询(.*)"), uid.command_start, block=True)]
|
||||
|
||||
async def _start_get_user_info(self, client: Client, uid: int = -1) -> bytes:
|
||||
if uid == -1:
|
||||
_uid = client.uid
|
||||
@ -47,24 +36,22 @@ class UserStats(BasePlugins):
|
||||
_uid = uid
|
||||
try:
|
||||
user_info = await client.get_genshin_user(_uid)
|
||||
except GenshinException as error:
|
||||
Log.warning("get_record_card请求失败", error)
|
||||
raise error
|
||||
except GenshinException as exc:
|
||||
raise exc
|
||||
if user_info.teapot is None:
|
||||
raise ValueError("洞庭湖未解锁")
|
||||
raise TeapotUnlocked
|
||||
try:
|
||||
# 查询的UID如果是自己的,会返回DataNotPublic,自己查不了自己可还行......
|
||||
if uid > 0:
|
||||
record_card_info = await client.get_record_card(uid)
|
||||
else:
|
||||
record_card_info = await client.get_record_card()
|
||||
except DataNotPublic as error:
|
||||
Log.warning("get_record_card请求失败 查询的用户数据未公开", error)
|
||||
except DataNotPublic:
|
||||
logger.warning("get_record_card请求失败 查询的用户数据未公开")
|
||||
nickname = _uid
|
||||
user_uid = ""
|
||||
except GenshinException as error:
|
||||
Log.warning("get_record_card请求失败", error)
|
||||
raise error
|
||||
except GenshinException as exc:
|
||||
raise exc
|
||||
else:
|
||||
nickname = record_card_info.nickname
|
||||
user_uid = record_card_info.uid
|
||||
@ -129,24 +116,31 @@ class UserStats(BasePlugins):
|
||||
{"width": 1024, "height": 1024})
|
||||
return png_data
|
||||
|
||||
@error_callable
|
||||
@handler(CommandHandler, command="stats", block=False)
|
||||
@handler(MessageHandler, filters=filters.Regex("^玩家统计查询(.*)"), block=False)
|
||||
@restricts(return_data=ConversationHandler.END)
|
||||
@error_callable
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> Optional[int]:
|
||||
user = update.effective_user
|
||||
message = update.message
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 查询游戏用户命令请求")
|
||||
message = update.effective_message
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 查询游戏用户命令请求")
|
||||
uid: int = -1
|
||||
try:
|
||||
args = context.args
|
||||
if args is not None and len(args) >= 1:
|
||||
uid = int(args[0])
|
||||
except ValueError as error:
|
||||
Log.error("获取 uid 发生错误! 错误信息为", error)
|
||||
except ValueError as exc:
|
||||
logger.error("获取 uid 发生错误! 错误信息为")
|
||||
logger.exception(exc)
|
||||
await message.reply_text("输入错误")
|
||||
return ConversationHandler.END
|
||||
try:
|
||||
client = await get_genshin_client(user.id, self.user_service, self.cookies_service)
|
||||
|
||||
try:
|
||||
client = await get_genshin_client(user.id)
|
||||
except CookiesNotFoundError:
|
||||
client, _uid = await get_public_genshin_client(user.id)
|
||||
if uid == -1:
|
||||
uid = _uid
|
||||
png_data = await self._start_get_user_info(client, uid)
|
||||
except UserNotFoundError:
|
||||
reply_message = await message.reply_text("未查询到账号信息,请先私聊派蒙绑定账号")
|
||||
@ -155,13 +149,11 @@ class UserStats(BasePlugins):
|
||||
|
||||
self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
|
||||
return
|
||||
except ValueError as exc:
|
||||
if "洞庭湖未解锁" not in str(exc):
|
||||
raise exc
|
||||
except TeapotUnlocked:
|
||||
await message.reply_text("角色尘歌壶未解锁 如果想要查看具体数据 嗯...... 咕咕咕~")
|
||||
return ConversationHandler.END
|
||||
except AttributeError as exc:
|
||||
Log.warning("角色数据有误", exc)
|
||||
logger.warning("角色数据有误", exc)
|
||||
await message.reply_text("角色数据有误 估计是派蒙晕了")
|
||||
return ConversationHandler.END
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
|
@ -1,47 +1,40 @@
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram import Update
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters
|
||||
from telegram.ext import CommandHandler, CallbackContext
|
||||
from telegram.ext import MessageHandler, filters
|
||||
|
||||
from core.template.services import TemplateService
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template import TemplateService
|
||||
from core.wiki.services import WikiService
|
||||
from logger import Log
|
||||
from metadata.shortname import weaponToName
|
||||
from models.wiki.base import SCRAPE_HOST
|
||||
from models.wiki.weapon import Weapon
|
||||
from plugins.base import BasePlugins
|
||||
from modules.wiki.base import SCRAPE_HOST
|
||||
from modules.wiki.weapon import Weapon
|
||||
from utils.bot import get_all_args
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.helpers import url_to_file
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.service.inject import inject
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class WeaponPlugin(BasePlugins):
|
||||
class WeaponPlugin(Plugin, BasePlugin):
|
||||
"""武器查询"""
|
||||
|
||||
KEYBOARD = [[
|
||||
InlineKeyboardButton(text="查看武器列表并查询", switch_inline_query_current_chat="查看武器列表并查询")
|
||||
]]
|
||||
|
||||
@inject
|
||||
def __init__(self, template_service: TemplateService = None, wiki_service: WikiService = None):
|
||||
self.wiki_service = wiki_service
|
||||
self.template_service = template_service
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls) -> list:
|
||||
weapon = cls()
|
||||
return [
|
||||
CommandHandler("weapon", weapon.command_start, block=False),
|
||||
MessageHandler(filters.Regex("^武器查询(.*)"), weapon.command_start, block=False)
|
||||
]
|
||||
|
||||
@handler(CommandHandler, command="weapon", block=False)
|
||||
@handler(MessageHandler, filters=filters.Regex("^武器查询(.*)"), block=False)
|
||||
@error_callable
|
||||
@restricts()
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> None:
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
user = update.effective_user
|
||||
args = get_all_args(context)
|
||||
if len(args) >= 1:
|
||||
@ -66,7 +59,7 @@ class WeaponPlugin(BasePlugins):
|
||||
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)
|
||||
return
|
||||
Log.info(f"用户 {user.full_name}[{user.id}] 查询武器命令请求 || 参数 {weapon_name}")
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 查询武器命令请求 || 参数 {weapon_name}")
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
|
||||
async def input_template_data(_weapon_data: Weapon):
|
||||
@ -85,7 +78,7 @@ class WeaponPlugin(BasePlugins):
|
||||
"weapon_info_max_level": _weapon_data.stats[-1].level,
|
||||
"progression_base_atk": round(_weapon_data.stats[-1].ATK),
|
||||
"weapon_info_source_list": [
|
||||
await url_to_file(str(SCRAPE_HOST.join(f'/img/{mid}.webp')))
|
||||
await url_to_file(str(SCRAPE_HOST.join(f'/img/{mid}.png')))
|
||||
for mid in _weapon_data.ascension[-3:]
|
||||
],
|
||||
"special_ability_name": _weapon_data.affix.name,
|
||||
@ -101,7 +94,7 @@ class WeaponPlugin(BasePlugins):
|
||||
"weapon_info_max_level": _weapon_data.stats[-1].level,
|
||||
"progression_base_atk": round(_weapon_data.stats[-1].ATK),
|
||||
"weapon_info_source_list": [
|
||||
await url_to_file(str(SCRAPE_HOST.join(f'/img/{mid}.webp')))
|
||||
await url_to_file(str(SCRAPE_HOST.join(f'/img/{mid}.png')))
|
||||
for mid in _weapon_data.ascension[-3:]
|
||||
],
|
||||
"special_ability_name": '',
|
||||
|
@ -1,33 +1,21 @@
|
||||
from telegram import Update
|
||||
from telegram.ext import CommandHandler, CallbackContext
|
||||
|
||||
from core.plugin import Plugin, handler
|
||||
from core.wiki.services import WikiService
|
||||
from plugins.base import BasePlugins
|
||||
from utils.decorators.admins import bot_admins_rights_check
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.plugins.manager import listener_plugins_class
|
||||
from utils.service.inject import inject
|
||||
|
||||
|
||||
@listener_plugins_class()
|
||||
class Wiki(BasePlugins):
|
||||
class Wiki(Plugin):
|
||||
"""有关WIKI操作"""
|
||||
|
||||
@inject
|
||||
def __init__(self, wiki_service: WikiService = None):
|
||||
self.wiki_service = wiki_service
|
||||
|
||||
@classmethod
|
||||
def create_handlers(cls) -> list:
|
||||
wiki = cls()
|
||||
return [
|
||||
CommandHandler("refresh_wiki", wiki.refresh_wiki, block=False),
|
||||
]
|
||||
|
||||
@handler(CommandHandler, command="refresh_wiki", block=False)
|
||||
@bot_admins_rights_check
|
||||
@error_callable
|
||||
async def refresh_wiki(self, update: Update, _: CallbackContext):
|
||||
message = update.message
|
||||
message = update.effective_message
|
||||
await message.reply_text("正在刷新Wiki缓存,请稍等")
|
||||
await self.wiki_service.refresh_wiki()
|
||||
await message.reply_text("刷新Wiki缓存成功")
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user