fix commands

This commit is contained in:
jxxghp 2023-06-09 11:26:53 +08:00
parent 50d71621d7
commit 01428e9f8f
25 changed files with 228 additions and 202 deletions

View File

@ -2,7 +2,7 @@ import traceback
from abc import abstractmethod from abc import abstractmethod
from typing import Optional, Any from typing import Optional, Any
from app.core import Context, ModuleManager, EventManager from app.core import Context, ModuleManager
from app.log import logger from app.log import logger
from app.utils.singleton import AbstractSingleton, Singleton from app.utils.singleton import AbstractSingleton, Singleton
@ -17,7 +17,6 @@ class ChainBase(AbstractSingleton, metaclass=Singleton):
公共初始化 公共初始化
""" """
self.modulemanager = ModuleManager() self.modulemanager = ModuleManager()
self.eventmanager = EventManager()
@abstractmethod @abstractmethod
def process(self, *args, **kwargs) -> Optional[Context]: def process(self, *args, **kwargs) -> Optional[Context]:

View File

@ -1,11 +1,8 @@
from typing import Dict, Any from typing import Any
from fastapi import Request
from app.chain import ChainBase
from app.chain.common import * from app.chain.common import *
from app.chain.search import SearchChain from app.chain.search import SearchChain
from app.core import MediaInfo, TorrentInfo, MetaInfo from app.core import MediaInfo, TorrentInfo, MetaInfo, EventManager
from app.db.subscribes import Subscribes from app.db.subscribes import Subscribes
from app.log import logger from app.log import logger
from app.utils.types import EventType from app.utils.types import EventType
@ -32,6 +29,7 @@ class UserMessageChain(ChainBase):
self.subscribes = Subscribes() self.subscribes = Subscribes()
self.searchchain = SearchChain() self.searchchain = SearchChain()
self.torrent = TorrentHelper() self.torrent = TorrentHelper()
self.eventmanager = EventManager()
def process(self, body: Any, form: Any, args: Any) -> None: def process(self, body: Any, form: Any, args: Any) -> None:
""" """
@ -60,6 +58,8 @@ class UserMessageChain(ChainBase):
"cmd": text "cmd": text
} }
) )
self.common.post_message(title=f"正在运行,请稍候 ...", userid=userid)
elif text.isdigit(): elif text.isdigit():
# 缓存 # 缓存
cache_data: dict = self._user_cache.get(userid) cache_data: dict = self._user_cache.get(userid)
@ -285,7 +285,7 @@ class UserMessageChain(ChainBase):
发送媒体列表消息 发送媒体列表消息
""" """
self.run_module('post_medias_message', self.run_module('post_medias_message',
title=f"共找到{total}条相关信息请回复数字选择对应媒体p:上一页 n:下一页)", title=f"共找到{total}条相关信息请回复数字选择对应媒体p: 上一页 n: 下一页)",
items=items, items=items,
userid=userid) userid=userid)
@ -294,6 +294,6 @@ class UserMessageChain(ChainBase):
发送种子列表消息 发送种子列表消息
""" """
self.run_module('post_torrents_message', self.run_module('post_torrents_message',
title=f"共找到{total}条相关信息请回复数字下载对应资源0:自动选择 p:上一页 n:下一页)", title=f"共找到{total}条相关信息请回复数字下载对应资源0: 自动选择 p: 上一页 n: 下一页)",
items=items, items=items,
userid=userid) userid=userid)

View File

@ -1,24 +1,36 @@
import traceback
from threading import Thread, Event
from typing import Any from typing import Any
from app.chain import ChainBase
from app.chain.cookiecloud import CookieCloudChain from app.chain.cookiecloud import CookieCloudChain
from app.chain.douban_sync import DoubanSyncChain from app.chain.douban_sync import DoubanSyncChain
from app.chain.subscribe import SubscribeChain from app.chain.subscribe import SubscribeChain
from app.core import eventmanager, PluginManager, EventManager from app.core import eventmanager, PluginManager, EventManager
from app.core.event_manager import Event from app.core.event_manager import Event as ManagerEvent
from app.log import logger from app.log import logger
from app.utils.singleton import Singleton from app.utils.singleton import Singleton
from app.utils.types import EventType from app.utils.types import EventType
class CommandChian(ChainBase):
"""
插件处理链
"""
def process(self, *args, **kwargs):
pass
class Command(metaclass=Singleton): class Command(metaclass=Singleton):
""" """
全局命令管理 全局命令管理消费事件
""" """
# 内建命令 # 内建命令
_commands = { _commands = {
"/cookiecloud": { "/cookiecloud": {
"func": CookieCloudChain().process, "func": CookieCloudChain().process,
"description": "同步CookieCloud的Cookie", "description": "同步站点Cookie",
"data": {} "data": {}
}, },
"/doubansync": { "/doubansync": {
@ -35,9 +47,16 @@ class Command(metaclass=Singleton):
} }
} }
# 退出事件
_event = Event()
def __init__(self): def __init__(self):
# 注册插件命令 # 事件管理器
plugin_commands = PluginManager().get_plugin_commands() self.eventmanager = EventManager()
# 插件管理器
self.pluginmanager = PluginManager()
# 汇总插件命令
plugin_commands = self.pluginmanager.get_plugin_commands()
for command in plugin_commands: for command in plugin_commands:
self.register( self.register(
cmd=command.get('cmd'), cmd=command.get('cmd'),
@ -48,6 +67,45 @@ class Command(metaclass=Singleton):
'data': command.get('data') 'data': command.get('data')
} }
) )
# 处理链
self.chain = CommandChian()
# 广播注册命令
self.chain.run_module("register_commands", commands=self.get_commands())
# 消息处理线程
self._thread = Thread(target=self.__run)
# 启动事件处理线程
self._thread.start()
def __run(self):
"""
事件处理线程
"""
while not self._event.is_set():
event, handlers = self.eventmanager.get_event()
if event:
logger.info(f"处理事件:{event.event_type} - {handlers}")
for handler in handlers:
try:
names = handler.__qualname__.split(".")
if names[0] == "Command":
self.command_event(event)
else:
self.pluginmanager.run_plugin_method(names[0], names[1], event)
except Exception as e:
logger.error(f"事件处理出错:{str(e)} - {traceback.format_exc()}")
def stop(self):
"""
停止事件处理线程
"""
self._event.set()
self._thread.join()
def get_commands(self):
"""
获取命令列表
"""
return self._commands
def register(self, cmd: str, func: Any, data: dict = None, desc: str = None) -> None: def register(self, cmd: str, func: Any, data: dict = None, desc: str = None) -> None:
""" """
@ -83,7 +141,7 @@ class Command(metaclass=Singleton):
EventManager().send_event(etype, data) EventManager().send_event(etype, data)
@eventmanager.register(EventType.CommandExcute) @eventmanager.register(EventType.CommandExcute)
def command_event(self, event: Event) -> None: def command_event(self, event: ManagerEvent) -> None:
""" """
注册命令执行事件 注册命令执行事件
event_data: { event_data: {

View File

@ -39,6 +39,14 @@ class ModuleManager(metaclass=Singleton):
self._running_modules[module_id].init_module() self._running_modules[module_id].init_module()
logger.info(f"Moudle Loaded{module_id}") logger.info(f"Moudle Loaded{module_id}")
def stop(self):
"""
停止所有模块
"""
for _, module in self._running_modules.items():
if hasattr(module, "stop"):
module.stop()
def get_modules(self, method: str) -> Generator: def get_modules(self, method: str) -> Generator:
""" """
获取模块列表 获取模块列表

View File

@ -1,8 +1,6 @@
import traceback import traceback
from threading import Thread from typing import List, Any
from typing import Tuple, Optional, List, Any
from app.core import EventManager
from app.db.systemconfigs import SystemConfigs from app.db.systemconfigs import SystemConfigs
from app.helper import ModuleHelper from app.helper import ModuleHelper
from app.log import logger from app.log import logger
@ -14,7 +12,6 @@ class PluginManager(metaclass=Singleton):
插件管理器 插件管理器
""" """
systemconfigs: SystemConfigs = None systemconfigs: SystemConfigs = None
eventmanager: EventManager = None
# 插件列表 # 插件列表
_plugins: dict = {} _plugins: dict = {}
@ -22,37 +19,17 @@ class PluginManager(metaclass=Singleton):
_running_plugins: dict = {} _running_plugins: dict = {}
# 配置Key # 配置Key
_config_key: str = "plugin.%s" _config_key: str = "plugin.%s"
# 事件处理线程
_thread: Thread = None
# 开关
_active: bool = False
def __init__(self): def __init__(self):
self.init_config() self.init_config()
def init_config(self): def init_config(self):
self.systemconfigs = SystemConfigs() self.systemconfigs = SystemConfigs()
self.eventmanager = EventManager()
# 停止已有插件 # 停止已有插件
self.stop() self.stop()
# 启动插件 # 启动插件
self.start() self.start()
def __run(self):
"""
事件处理线程
"""
while self._active:
event, handlers = self.eventmanager.get_event()
if event:
logger.info(f"处理事件:{event.event_type} - {handlers}")
for handler in handlers:
try:
names = handler.__qualname__.split(".")
self.run_plugin_method(names[0], names[1], event)
except Exception as e:
logger.error(f"事件处理出错:{str(e)} - {traceback.format_exc()}")
def start(self): def start(self):
""" """
启动 启动
@ -60,21 +37,10 @@ class PluginManager(metaclass=Singleton):
# 加载插件 # 加载插件
self.__load_plugins() self.__load_plugins()
# 将事件管理器设为启动
self._active = True
self._thread = Thread(target=self.__run)
# 启动事件处理线程
self._thread.start()
def stop(self): def stop(self):
""" """
停止 停止
""" """
# 将事件管理器设为停止
self._active = False
# 等待事件处理线程退出
if self._thread:
self._thread.join()
# 停止所有插件 # 停止所有插件
self.__stop_plugins() self.__stop_plugins()
@ -131,37 +97,6 @@ class PluginManager(metaclass=Singleton):
return {} return {}
return self.systemconfigs.get(self._config_key % pid) or {} return self.systemconfigs.get(self._config_key % pid) or {}
def get_plugin_page(self, pid: str) -> Tuple[Optional[str], Optional[str], Optional[str]]:
"""
获取插件额外页面数据
:return: 标题页面内容确定按钮响应函数
"""
if not self._running_plugins.get(pid):
return None, None, None
if not hasattr(self._running_plugins[pid], "get_page"):
return None, None, None
return self._running_plugins[pid].get_page()
def get_plugin_script(self, pid: str) -> Optional[str]:
"""
获取插件额外脚本
"""
if not self._running_plugins.get(pid):
return None
if not hasattr(self._running_plugins[pid], "get_script"):
return None
return self._running_plugins[pid].get_script()
def get_plugin_state(self, pid: str) -> Optional[bool]:
"""
获取插件状态
"""
if not self._running_plugins.get(pid):
return None
if not hasattr(self._running_plugins[pid], "get_state"):
return None
return self._running_plugins[pid].get_state()
def save_plugin_config(self, pid: str, conf: dict) -> bool: def save_plugin_config(self, pid: str, conf: dict) -> bool:
""" """
保存插件配置 保存插件配置
@ -170,98 +105,6 @@ class PluginManager(metaclass=Singleton):
return False return False
return self.systemconfigs.set(self._config_key % pid, conf) return self.systemconfigs.set(self._config_key % pid, conf)
@staticmethod
def __get_plugin_color(plugin: str) -> str:
"""
获取插件的主题色
"""
if hasattr(plugin, "plugin_color") and plugin.plugin_color:
return plugin.plugin_color
return ""
def get_plugins_conf(self, auth_level: int) -> dict:
"""
获取所有插件配置
"""
all_confs = {}
for pid, plugin in self._running_plugins.items():
# 基本属性
conf = {}
# 权限
if hasattr(plugin, "auth_level") \
and plugin.auth_level > auth_level:
continue
# 名称
if hasattr(plugin, "plugin_name"):
conf.update({"name": plugin.plugin_name})
# 描述
if hasattr(plugin, "plugin_desc"):
conf.update({"desc": plugin.plugin_desc})
# 版本号
if hasattr(plugin, "plugin_version"):
conf.update({"version": plugin.plugin_version})
# 图标
if hasattr(plugin, "plugin_icon"):
conf.update({"icon": plugin.plugin_icon})
# ID前缀
if hasattr(plugin, "plugin_config_prefix"):
conf.update({"prefix": plugin.plugin_config_prefix})
# 插件额外的页面
if hasattr(plugin, "get_page"):
title, _, _ = plugin.get_page()
conf.update({"page": title})
# 插件额外的脚本
if hasattr(plugin, "get_script"):
conf.update({"script": plugin.get_script()})
# 主题色
conf.update({"color": self.__get_plugin_color(plugin)})
# 配置项
conf.update({"fields": plugin.get_fields() or {}})
# 配置值
conf.update({"config": self.get_plugin_config(pid)})
# 状态
conf.update({"state": plugin.get_state()})
# 汇总
all_confs[pid] = conf
return all_confs
def get_plugin_apps(self, auth_level: int) -> dict:
"""
获取所有插件
"""
all_confs = {}
for pid, plugin in self._plugins.items():
# 基本属性
conf = {}
# 权限
if hasattr(plugin, "auth_level") \
and plugin.auth_level > auth_level:
continue
# ID
conf.update({"id": pid})
# 名称
if hasattr(plugin, "plugin_name"):
conf.update({"name": plugin.plugin_name})
# 描述
if hasattr(plugin, "plugin_desc"):
conf.update({"desc": plugin.plugin_desc})
# 版本
if hasattr(plugin, "plugin_version"):
conf.update({"version": plugin.plugin_version})
# 图标
if hasattr(plugin, "plugin_icon"):
conf.update({"icon": plugin.plugin_icon})
# 主题色
conf.update({"color": self.__get_plugin_color(plugin)})
if hasattr(plugin, "plugin_author"):
conf.update({"author": plugin.plugin_author})
# 作者链接
if hasattr(plugin, "author_url"):
conf.update({"author_url": plugin.author_url})
# 汇总
all_confs[pid] = conf
return all_confs
def get_plugin_commands(self) -> List[dict]: def get_plugin_commands(self) -> List[dict]:
""" """
获取插件命令 获取插件命令

View File

@ -3,6 +3,7 @@ from fastapi import FastAPI
from uvicorn import Config from uvicorn import Config
from app.api.apiv1 import api_router from app.api.apiv1 import api_router
from app.command import Command
from app.core import settings, ModuleManager, PluginManager from app.core import settings, ModuleManager, PluginManager
from app.db.init import init_db, update_db from app.db.init import init_db, update_db
from app.helper.sites import SitesHelper from app.helper.sites import SitesHelper
@ -28,6 +29,10 @@ def shutdown_server():
Scheduler().stop() Scheduler().stop()
# 停止插件 # 停止插件
PluginManager().stop() PluginManager().stop()
# 停止模块
ModuleManager().stop()
# 停止事件消费
Command().stop()
@App.on_event("startup") @App.on_event("startup")
@ -43,6 +48,8 @@ def start_module():
SitesHelper() SitesHelper()
# 启动定时服务 # 启动定时服务
Scheduler() Scheduler()
# 启动事件消费
Command()
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -225,3 +225,17 @@ class _ModuleBase(metaclass=ABCMeta):
:return: 成功或失败 :return: 成功或失败
""" """
pass pass
def register_commands(self, commands: dict):
"""
注册命令实现这个函数接收系统可用的命令菜单
:param commands: 命令字典
"""
pass
@abstractmethod
def stop(self):
"""
如果关闭时模块有服务需要停止需要实现此方法
"""
pass

View File

@ -23,6 +23,9 @@ class Douban(_ModuleBase):
def init_module(self) -> None: def init_module(self) -> None:
pass pass
def stop(self):
pass
def init_setting(self) -> Tuple[str, Union[str, bool]]: def init_setting(self) -> Tuple[str, Union[str, bool]]:
pass pass

View File

@ -14,6 +14,9 @@ class EmbyModule(_ModuleBase):
def init_module(self) -> None: def init_module(self) -> None:
self.emby = Emby() self.emby = Emby()
def stop(self):
pass
def init_setting(self) -> Tuple[str, Union[str, bool]]: def init_setting(self) -> Tuple[str, Union[str, bool]]:
return "MEDIASERVER", "emby" return "MEDIASERVER", "emby"

View File

@ -21,6 +21,9 @@ class FanartModule(_ModuleBase):
def init_module(self) -> None: def init_module(self) -> None:
pass pass
def stop(self):
pass
def init_setting(self) -> Tuple[str, Union[str, bool]]: def init_setting(self) -> Tuple[str, Union[str, bool]]:
return "FANART_API_KEY", True return "FANART_API_KEY", True

View File

@ -20,6 +20,9 @@ class FileTransferModule(_ModuleBase):
def init_module(self) -> None: def init_module(self) -> None:
pass pass
def stop(self):
pass
def init_setting(self) -> Tuple[str, Union[str, bool]]: def init_setting(self) -> Tuple[str, Union[str, bool]]:
pass pass

View File

@ -7,6 +7,7 @@ from app.modules.filter.RuleParser import RuleParser
class FilterModule(_ModuleBase): class FilterModule(_ModuleBase):
# 规则解析器 # 规则解析器
parser: RuleParser = None parser: RuleParser = None
@ -62,6 +63,9 @@ class FilterModule(_ModuleBase):
def init_module(self) -> None: def init_module(self) -> None:
self.parser = RuleParser() self.parser = RuleParser()
def stop(self):
pass
def init_setting(self) -> Tuple[str, Union[str, bool]]: def init_setting(self) -> Tuple[str, Union[str, bool]]:
return "FILTER_RULE", True return "FILTER_RULE", True

View File

@ -23,6 +23,9 @@ class IndexerModule(_ModuleBase):
def init_module(self) -> None: def init_module(self) -> None:
pass pass
def stop(self):
pass
def init_setting(self) -> Tuple[str, Union[str, bool]]: def init_setting(self) -> Tuple[str, Union[str, bool]]:
return "INDEXER", "builtin" return "INDEXER", "builtin"

View File

@ -13,6 +13,9 @@ class JellyfinModule(_ModuleBase):
def init_module(self) -> None: def init_module(self) -> None:
self.jellyfin = Jellyfin() self.jellyfin = Jellyfin()
def stop(self):
pass
def init_setting(self) -> Tuple[str, Union[str, bool]]: def init_setting(self) -> Tuple[str, Union[str, bool]]:
return "MEDIASERVER", "jellyfin" return "MEDIASERVER", "jellyfin"

View File

@ -14,6 +14,9 @@ class PlexModule(_ModuleBase):
def init_module(self) -> None: def init_module(self) -> None:
self.plex = Plex() self.plex = Plex()
def stop(self):
pass
def init_setting(self) -> Tuple[str, Union[str, bool]]: def init_setting(self) -> Tuple[str, Union[str, bool]]:
return "MEDIASERVER", "plex" return "MEDIASERVER", "plex"

View File

@ -14,6 +14,9 @@ class QbittorrentModule(_ModuleBase):
def init_module(self) -> None: def init_module(self) -> None:
self.qbittorrent = Qbittorrent() self.qbittorrent = Qbittorrent()
def stop(self):
pass
def init_setting(self) -> Tuple[str, Union[str, bool]]: def init_setting(self) -> Tuple[str, Union[str, bool]]:
return "DOWNLOADER", "qbittorrent" return "DOWNLOADER", "qbittorrent"

View File

@ -8,12 +8,14 @@ from app.modules.telegram.telegram import Telegram
class TelegramModule(_ModuleBase): class TelegramModule(_ModuleBase):
telegram: Telegram = None telegram: Telegram = None
def init_module(self) -> None: def init_module(self) -> None:
self.telegram = Telegram() self.telegram = Telegram()
def stop(self):
self.telegram.stop()
def init_setting(self) -> Tuple[str, Union[str, bool]]: def init_setting(self) -> Tuple[str, Union[str, bool]]:
return "MESSAGER", "telegram" return "MESSAGER", "telegram"
@ -51,13 +53,16 @@ class TelegramModule(_ModuleBase):
} }
} }
""" """
# 校验token
token = args.get("token")
if not token or token != settings.API_TOKEN:
return None
try: try:
msg_json: dict = json.loads(body) message: dict = json.loads(body)
except Exception as err: except Exception as err:
logger.error(f"解析Telegram消息失败{err}") logger.error(f"解析Telegram消息失败{err}")
return None return None
if msg_json: if message:
message = msg_json.get("message", {})
text = message.get("text") text = message.get("text")
user_id = message.get("from", {}).get("id") user_id = message.get("from", {}).get("id")
# 获取用户名 # 获取用户名
@ -117,3 +122,10 @@ class TelegramModule(_ModuleBase):
:return: 成功或失败 :return: 成功或失败
""" """
return self.telegram.send_torrents_msg(title=title, torrents=items, userid=userid) return self.telegram.send_torrents_msg(title=title, torrents=items, userid=userid)
def register_commands(self, commands: dict):
"""
注册命令实现这个函数接收系统可用的命令菜单
:param commands: 命令字典
"""
self.telegram.register_commands(commands)

View File

@ -1,13 +1,13 @@
from threading import Event, Thread import threading
from threading import Event
from typing import Optional, List from typing import Optional, List
from urllib.parse import urlencode
from app.core import settings, MediaInfo, TorrentInfo, Context import telebot
from app.core import settings, MediaInfo, Context
from app.log import logger from app.log import logger
from app.utils.http import RequestUtils from app.utils.http import RequestUtils
from app.utils.singleton import Singleton from app.utils.singleton import Singleton
import telebot
from app.utils.string import StringUtils from app.utils.string import StringUtils
@ -26,16 +26,25 @@ class Telegram(metaclass=Singleton):
self._telegram_chat_id = settings.TELEGRAM_CHAT_ID self._telegram_chat_id = settings.TELEGRAM_CHAT_ID
# 初始化机器人 # 初始化机器人
if self._telegram_token and self._telegram_chat_id: if self._telegram_token and self._telegram_chat_id:
# bot
_bot = telebot.TeleBot(self._telegram_token, parse_mode="markdown") _bot = telebot.TeleBot(self._telegram_token, parse_mode="markdown")
# 记录句柄
self._bot = _bot self._bot = _bot
@_bot.message_handler(func=lambda message: True) @_bot.message_handler(func=lambda message: True)
def echo_all(message): def echo_all(message):
RequestUtils(timeout=10).post_res(self._ds_url, json=message.json) RequestUtils(timeout=10).post_res(self._ds_url, json=message.json)
# 启动轮询 def run_polling():
"""
定义线程函数来运行 infinity_polling
"""
_bot.infinity_polling() _bot.infinity_polling()
# 启动线程来运行 infinity_polling
self._polling_thread = threading.Thread(target=run_polling)
self._polling_thread.start()
def send_msg(self, title: str, text: str = "", image: str = "", userid: str = "") -> Optional[bool]: def send_msg(self, title: str, text: str = "", image: str = "", userid: str = "") -> Optional[bool]:
""" """
发送Telegram消息 发送Telegram消息
@ -156,8 +165,25 @@ class Telegram(metaclass=Singleton):
return True if ret else False return True if ret else False
def register_commands(self, commands: dict):
"""
注册菜单命令
"""
if not self._bot:
return
# 设置bot命令
if commands:
self._bot.delete_my_commands()
self._bot.set_my_commands(
commands=[
telebot.types.BotCommand(cmd[1:], str(desc.get("description"))) for cmd, desc in
commands.items()
]
)
def stop(self): def stop(self):
""" """
停止Telegram消息接收服务 停止Telegram消息接收服务
""" """
self._bot.stop_polling() self._bot.stop_polling()
self._polling_thread.join()

View File

@ -33,6 +33,9 @@ class TheMovieDb(_ModuleBase):
self.tmdb = TmdbHelper() self.tmdb = TmdbHelper()
self.category = CategoryHelper() self.category = CategoryHelper()
def stop(self):
pass
def init_setting(self) -> Tuple[str, Union[str, bool]]: def init_setting(self) -> Tuple[str, Union[str, bool]]:
pass pass

View File

@ -14,6 +14,9 @@ class TransmissionModule(_ModuleBase):
def init_module(self) -> None: def init_module(self) -> None:
self.transmission = Transmission() self.transmission = Transmission()
def stop(self):
pass
def init_setting(self) -> Tuple[str, Union[str, bool]]: def init_setting(self) -> Tuple[str, Union[str, bool]]:
return "DOWNLOADER", "transmission" return "DOWNLOADER", "transmission"

View File

@ -16,6 +16,9 @@ class WechatModule(_ModuleBase):
def init_module(self) -> None: def init_module(self) -> None:
self.wechat = WeChat() self.wechat = WeChat()
def stop(self):
pass
def init_setting(self) -> Tuple[str, Union[str, bool]]: def init_setting(self) -> Tuple[str, Union[str, bool]]:
return "MESSAGER", "wechat" return "MESSAGER", "wechat"

View File

@ -1,10 +1,10 @@
import json import json
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from pathlib import Path from pathlib import Path
from typing import Any, Optional from typing import Any
from app.chain import ChainBase from app.chain import ChainBase
from app.core import settings, Context from app.core import settings
from app.db import SessionLocal from app.db import SessionLocal
from app.db.models import Base from app.db.models import Base
from app.db.models.plugin import PluginData from app.db.models.plugin import PluginData
@ -17,7 +17,7 @@ class PluginChian(ChainBase):
插件处理链 插件处理链
""" """
def process(self, *args, **kwargs) -> Optional[Context]: def process(self, *args, **kwargs):
pass pass

View File

@ -31,11 +31,8 @@ class AutoSignIn(_PluginBase):
event: EventManager = None event: EventManager = None
# 定时器 # 定时器
_scheduler = None _scheduler = None
# 加载的模块 # 加载的模块
_site_schema: list = [] _site_schema: list = []
# 退出事件
_event = Event()
def init_plugin(self, config: dict = None): def init_plugin(self, config: dict = None):
self.sites = SitesHelper() self.sites = SitesHelper()
@ -70,7 +67,7 @@ class AutoSignIn(_PluginBase):
:return: 命令关键字事件描述附带数据 :return: 命令关键字事件描述附带数据
""" """
return { return {
"cmd": "/pts", "cmd": "/sitesignin",
"event": EventType.SiteSignin, "event": EventType.SiteSignin,
"desc": "站点自动签到", "desc": "站点自动签到",
"data": {} "data": {}
@ -191,9 +188,7 @@ class AutoSignIn(_PluginBase):
if self._scheduler: if self._scheduler:
self._scheduler.remove_all_jobs() self._scheduler.remove_all_jobs()
if self._scheduler.running: if self._scheduler.running:
self._event.set()
self._scheduler.shutdown() self._scheduler.shutdown()
self._event.clear()
self._scheduler = None self._scheduler = None
except Exception as e: except Exception as e:
logger.error("退出插件失败:%s" % str(e)) logger.error("退出插件失败:%s" % str(e))

View File

@ -7,7 +7,8 @@ import requests
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from ruamel.yaml import CommentedMap from ruamel.yaml import CommentedMap
from app.core import settings from app.core import settings, eventmanager
from app.core.event_manager import Event
from app.helper import ModuleHelper from app.helper import ModuleHelper
from app.helper.sites import SitesHelper from app.helper.sites import SitesHelper
from app.log import logger from app.log import logger
@ -17,6 +18,9 @@ from app.utils.http import RequestUtils
from app.utils.timer import TimerUtils from app.utils.timer import TimerUtils
import warnings import warnings
from app.utils.types import EventType
warnings.filterwarnings("ignore", category=FutureWarning) warnings.filterwarnings("ignore", category=FutureWarning)
@ -57,6 +61,19 @@ class SiteStatistic(_PluginBase):
self._scheduler.print_jobs() self._scheduler.print_jobs()
self._scheduler.start() self._scheduler.start()
@staticmethod
def get_command() -> dict:
"""
定义远程控制命令
:return: 命令关键字事件描述附带数据
"""
return {
"cmd": "/sitestatistic",
"event": EventType.SiteStatistic,
"desc": "站点数据统计",
"data": {}
}
def stop_service(self): def stop_service(self):
pass pass
@ -221,6 +238,14 @@ class SiteStatistic(_PluginBase):
title=f"站点 {site_user_info.site_name} 收到 " title=f"站点 {site_user_info.site_name} 收到 "
f"{site_user_info.message_unread} 条新消息,请登陆查看") f"{site_user_info.message_unread} 条新消息,请登陆查看")
@eventmanager.register(EventType.SiteStatistic)
def refresh(self, event: Event):
"""
刷新站点数据
"""
logger.info("开始执行站点数据刷新 ...")
self.refresh_all_site_data(force=True)
def refresh_all_site_data(self, force: bool = False, specify_sites: list = None): def refresh_all_site_data(self, force: bool = False, specify_sites: list = None):
""" """
多线程刷新站点下载上传量默认间隔6小时 多线程刷新站点下载上传量默认间隔6小时
@ -228,6 +253,8 @@ class SiteStatistic(_PluginBase):
if not self.sites.get_indexers(): if not self.sites.get_indexers():
return return
logger.info("开始刷新站点数据 ...")
with lock: with lock:
if not force \ if not force \
@ -251,15 +278,13 @@ class SiteStatistic(_PluginBase):
# 并发刷新 # 并发刷新
with ThreadPool(min(len(refresh_sites), self._MAX_CONCURRENCY)) as p: with ThreadPool(min(len(refresh_sites), self._MAX_CONCURRENCY)) as p:
site_user_infos: List[ISiteUserInfo] = p.map(self.__refresh_site_data, refresh_sites) p.map(self.__refresh_site_data, refresh_sites)
site_user_infos = [info for info in site_user_infos if info]
# 保存数据
for site_user_info in site_user_infos:
# 获取今天的日期
key = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
value = site_user_info.to_dict()
# 按日期保存为字典
self.save_data(key, value)
# 获取今天的日期
key = datetime.now().strftime('%Y-%m-%d')
# 保存数据
self.save_data(key, self._sites_data)
# 更新时间 # 更新时间
self._last_update_time = datetime.now() self._last_update_time = datetime.now()
logger.info("站点数据刷新完成")

View File

@ -19,6 +19,8 @@ class EventType(Enum):
CommandExcute = "command.excute" CommandExcute = "command.excute"
# 站点签到 # 站点签到
SiteSignin = "site.signin" SiteSignin = "site.signin"
# 站点数据统计
SiteStatistic = "site.statistic"
# 系统配置Key字典 # 系统配置Key字典