diff --git a/.gitignore b/.gitignore index fb7add7..c119a90 100644 --- a/.gitignore +++ b/.gitignore @@ -116,11 +116,11 @@ env.bak/ venv.bak/ config.env config.yml -pagermaid.session -pagermaid.session-journal +*.session +*.session-journal *.pagermaid docker-compose.yml -plugins/ +plugins*/ data/* pagermaid.egg-info/ dump.rdb diff --git a/config.gen.yml b/config.gen.yml index 8924a98..4759325 100644 --- a/config.gen.yml +++ b/config.gen.yml @@ -58,7 +58,7 @@ mtp_port: "" mtp_secret: "" # Apt Git source -git_source: "https://raw.githubusercontent.com/TeamPGM/PagerMaid_Plugins_Pyro/v2/" +git_source: "https://v2.xtaolabs.com/" git_ssh: "https://github.com/TeamPGM/PagerMaid-Pyro.git" # Update Notice diff --git a/pagermaid/__init__.py b/pagermaid/__init__.py index b7f08ee..b513f85 100644 --- a/pagermaid/__init__.py +++ b/pagermaid/__init__.py @@ -21,8 +21,8 @@ from pagermaid.scheduler import scheduler import pyromod.listen from pyrogram import Client -pgm_version = "1.3.3" -pgm_version_code = 1303 +pgm_version = "1.3.4" +pgm_version_code = 1304 CMD_LIST = {} module_dir = __path__[0] working_dir = getcwd() @@ -76,7 +76,7 @@ bot = Client( api_hash=Config.API_HASH, ipv6=Config.IPV6, proxy=Config.PROXY, - app_version=f"PagerMaid {pgm_version}", + app_version=f"PGP {pgm_version}", ) bot.job = scheduler diff --git a/pagermaid/__main__.py b/pagermaid/__main__.py index a8ea56b..d983674 100644 --- a/pagermaid/__main__.py +++ b/pagermaid/__main__.py @@ -1,58 +1,73 @@ -from sys import path, platform +import asyncio from os import sep -from importlib import import_module +from signal import signal as signal_fn, SIGINT, SIGTERM, SIGABRT +from sys import path, platform -from pyrogram import idle from pyrogram.errors import AuthKeyUnregistered from pagermaid import bot, logs, working_dir -from pagermaid.common.plugin import plugin_manager -from pagermaid.hook import Hook -from pagermaid.modules import module_list, plugin_list +from pagermaid.common.reload import load_all from pagermaid.single_utils import safe_remove from pagermaid.utils import lang, process_exit +from pagermaid.web import web from pyromod.methods.sign_in_qrcode import start_client path.insert(1, f"{working_dir}{sep}plugins") -async def main(): - logs.info(lang("platform") + platform + lang("platform_load")) +async def idle(): + task = None + def signal_handler(_, __): + if web.web_server_task: + web.web_server_task.cancel() + task.cancel() + + for s in (SIGINT, SIGTERM, SIGABRT): + signal_fn(s, signal_handler) + + while True: + task = asyncio.create_task(asyncio.sleep(600)) + web.bot_main_task = task + try: + await task + except asyncio.CancelledError: + break + + +async def console_bot(): try: await start_client(bot) except AuthKeyUnregistered: safe_remove("pagermaid.session") exit() - me = await bot.get_me() if me.is_bot: safe_remove("pagermaid.session") exit() logs.info(f"{lang('save_id')} {me.first_name}({me.id})") - - for module_name in module_list: - try: - import_module(f"pagermaid.modules.{module_name}") - except BaseException as exception: - logs.info( - f"{lang('module')} {module_name} {lang('error')}: {type(exception)}: {exception}" - ) - for plugin_name in plugin_list.copy(): - try: - import_module(f"plugins.{plugin_name}") - except BaseException as exception: - logs.info(f"{lang('module')} {plugin_name} {lang('error')}: {exception}") - plugin_list.remove(plugin_name) - plugin_manager.load_local_plugins() - + await load_all() await process_exit(start=True, _client=bot) - logs.info(lang("start")) - await Hook.load_success_exec() - await Hook.startup() - await idle() - await bot.stop() + +async def main(): + logs.info(lang("platform") + platform + lang("platform_load")) + await web.start() + await console_bot() + logs.info(lang("start")) + + try: + await idle() + finally: + try: + await bot.stop() + except ConnectionError: + pass + if web.web_server: + try: + await web.web_server.shutdown() + except AttributeError: + pass bot.run(main()) diff --git a/pagermaid/common/reload.py b/pagermaid/common/reload.py index 92fdb1a..3803046 100644 --- a/pagermaid/common/reload.py +++ b/pagermaid/common/reload.py @@ -53,3 +53,22 @@ async def reload_all(): plugin_manager.load_local_plugins() plugin_manager.save_local_version_map() await Hook.load_success_exec() + + +async def load_all(): + for module_name in pagermaid.modules.module_list.copy(): + try: + importlib.import_module(f"pagermaid.modules.{module_name}") + except BaseException as exception: + logs.info( + f"{lang('module')} {module_name} {lang('error')}: {type(exception)}: {exception}" + ) + for plugin_name in pagermaid.modules.plugin_list.copy(): + try: + importlib.import_module(f"plugins.{plugin_name}") + except BaseException as exception: + logs.info(f"{lang('module')} {plugin_name} {lang('error')}: {exception}") + pagermaid.modules.plugin_list.remove(plugin_name) + plugin_manager.load_local_plugins() + await Hook.load_success_exec() + await Hook.startup() diff --git a/pagermaid/config.py b/pagermaid/config.py index 1e21f19..f7a5a2e 100644 --- a/pagermaid/config.py +++ b/pagermaid/config.py @@ -91,6 +91,12 @@ class Config: "TeamPGM/PagerMaid_Plugins/", "TeamPGM/PagerMaid_Plugins_Pyro/" ) try: + with open( + f"languages{os.sep}built-in{os.sep}en.yml", + "r", + encoding="utf-8", + ) as f: + lang_default_dict = safe_load(f) with open( f"languages{os.sep}built-in{os.sep}{LANGUAGE}.yml", "r", @@ -104,7 +110,7 @@ class Config: print(e) try: with open( - f"languages{os.sep}built-in{os.sep}{LANGUAGE}.yml", + f"languages{os.sep}built-in{os.sep}en.yml", "r", encoding="utf-8", ) as f: diff --git a/pagermaid/modules/system.py b/pagermaid/modules/system.py index 1d5b33e..78da180 100644 --- a/pagermaid/modules/system.py +++ b/pagermaid/modules/system.py @@ -1,14 +1,14 @@ -from os.path import exists, sep -from sys import exit -from platform import node from getpass import getuser +from os.path import exists, sep +from platform import node from pyrogram.enums import ParseMode from pagermaid.common.system import run_eval -from pagermaid.listener import listener from pagermaid.enums import Message +from pagermaid.listener import listener from pagermaid.utils import attach_log, execute, lang, upload_attachment +from pagermaid.web import web @listener( @@ -51,7 +51,7 @@ async def restart(message: Message): """To re-execute PagerMaid.""" if not message.text[0].isalpha(): await message.edit(lang("restart_log")) - exit(0) + web.stop() @listener( diff --git a/pagermaid/modules/web.py b/pagermaid/modules/web.py deleted file mode 100644 index 5dcf344..0000000 --- a/pagermaid/modules/web.py +++ /dev/null @@ -1,20 +0,0 @@ -from pagermaid import logs -from pagermaid.config import Config -from pagermaid.hook import Hook -from pagermaid.services import bot - - -@Hook.on_startup() -async def init_web(): - if not Config.WEB_ENABLE: - return - if not Config.WEB_SECRET_KEY: - logs.warning("未设置 WEB_SECRET_KEY ,请勿将 PagerMaid-Pyro 暴露在公网") - import uvicorn - from pagermaid.web import app, init_web - - init_web() - server = uvicorn.Server( - config=uvicorn.Config(app, host=Config.WEB_HOST, port=Config.WEB_PORT) - ) - bot.loop.create_task(server.serve()) diff --git a/pagermaid/utils.py b/pagermaid/utils.py index 4dca63e..e725ba7 100644 --- a/pagermaid/utils.py +++ b/pagermaid/utils.py @@ -19,7 +19,7 @@ from pagermaid.single_utils import _status_sudo, get_sudo_list, Message, sqlite def lang(text: str) -> str: """i18n""" - return Config.lang_dict.get(text, text) + return Config.lang_dict.get(text, Config.lang_default_dict.get(text, text)) def alias_command(command: str, disallow_alias: bool = False) -> str: diff --git a/pagermaid/web/__init__.py b/pagermaid/web/__init__.py index ed5f3c2..e641af5 100644 --- a/pagermaid/web/__init__.py +++ b/pagermaid/web/__init__.py @@ -1,8 +1,11 @@ +import asyncio + from fastapi import FastAPI from fastapi.responses import HTMLResponse from starlette.middleware.cors import CORSMiddleware from starlette.responses import RedirectResponse +from pagermaid import logs from pagermaid.config import Config from pagermaid.web.api import base_api_router from pagermaid.web.pages import admin_app, login_page @@ -25,36 +28,81 @@ responseAdaptor(api, payload, query, request, response) { }, """ icon_path = "https://xtaolabs.com/pagermaid-logo.png" -app: FastAPI = FastAPI() -def init_web(): - app.include_router(base_api_router) +class Web: + def __init__(self): + self.app: FastAPI = FastAPI() + self.web_server = None + self.web_server_task = None + self.bot_main_task = None - app.add_middleware( - CORSMiddleware, - allow_origins=Config.WEB_ORIGINS, - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) + def init_web(self): + self.app.include_router(base_api_router) - @app.get("/", response_class=RedirectResponse) - async def index(): - return "/admin" - - @app.get("/admin", response_class=HTMLResponse) - async def admin(): - return admin_app.render( - site_title="PagerMaid-Pyro 后台管理", - site_icon=icon_path, - requestAdaptor=requestAdaptor, - responseAdaptor=responseAdaptor, + self.app.add_middleware( + CORSMiddleware, + allow_origins=Config.WEB_ORIGINS, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], ) - @app.get("/login", response_class=HTMLResponse) - async def login(): - return login_page.render( - site_title="登录 | PagerMaid-Pyro 后台管理", - site_icon=icon_path, + @self.app.get("/", response_class=RedirectResponse) + async def index(): + return "/admin" + + @self.app.get("/admin", response_class=HTMLResponse) + async def admin(): + return admin_app.render( + site_title="PagerMaid-Pyro 后台管理", + site_icon=icon_path, + requestAdaptor=requestAdaptor, + responseAdaptor=responseAdaptor, + ) + + @self.app.get("/login", response_class=HTMLResponse) + async def login(): + return login_page.render( + site_title="登录 | PagerMaid-Pyro 后台管理", + site_icon=icon_path, + ) + + async def start(self): + if not Config.WEB_ENABLE: + return + if not Config.WEB_SECRET_KEY: + logs.warning("未设置 WEB_SECRET_KEY ,请勿将 PagerMaid-Pyro 暴露在公网") + import uvicorn + + self.init_web() + self.web_server = uvicorn.Server( + config=uvicorn.Config(self.app, host=Config.WEB_HOST, port=Config.WEB_PORT) ) + server_config = self.web_server.config + server_config.setup_event_loop() + if not server_config.loaded: + server_config.load() + self.web_server.lifespan = server_config.lifespan_class(server_config) + try: + await self.web_server.startup() + except OSError as e: + if e.errno == 10048: + logs.error("Web Server 端口被占用:%s", e) + logs.error("Web Server 启动失败,正在退出") + raise SystemExit from None + + if self.web_server.should_exit: + logs.error("Web Server 启动失败,正在退出") + raise SystemExit from None + logs.info("Web Server 启动成功") + self.web_server_task = asyncio.create_task(self.web_server.main_loop()) + + def stop(self): + if self.web_server_task: + self.web_server_task.cancel() + if self.bot_main_task: + self.bot_main_task.cancel() + + +web = Web() diff --git a/pagermaid/web/api/bot_info.py b/pagermaid/web/api/bot_info.py index 99c4eca..3d0b49f 100644 --- a/pagermaid/web/api/bot_info.py +++ b/pagermaid/web/api/bot_info.py @@ -1,6 +1,3 @@ -import os -import signal - from fastapi import APIRouter from fastapi.responses import JSONResponse @@ -20,5 +17,7 @@ async def bot_update(): "/bot_restart", response_class=JSONResponse, dependencies=[authentication()] ) async def bot_restart(): - os.kill(os.getpid(), signal.SIGINT) + from pagermaid.web import web + + web.stop() return {} diff --git a/pagermaid/web/api/login.py b/pagermaid/web/api/login.py index e9b5353..f41d960 100644 --- a/pagermaid/web/api/login.py +++ b/pagermaid/web/api/login.py @@ -1,3 +1,5 @@ +from typing import Optional + from fastapi import APIRouter from fastapi.responses import JSONResponse from pydantic import BaseModel @@ -8,7 +10,7 @@ from pagermaid.config import Config class UserModel(BaseModel): - password: str + password: Optional[str] = None route = APIRouter() @@ -16,11 +18,11 @@ route = APIRouter() @route.post("/login", response_class=JSONResponse) async def login(user: UserModel): - if user.password != Config.WEB_SECRET_KEY: - return {"status": -100, "msg": "登录失败,请重新输入密钥"} - token = create_token() - return { - "status": 0, - "msg": "登录成功", - "data": {"version": pgm_version_code, "token": token}, - } + if not Config.WEB_SECRET_KEY or user.password == Config.WEB_SECRET_KEY: + token = create_token() + return { + "status": 0, + "msg": "登录成功", + "data": {"version": pgm_version_code, "token": token}, + } + return {"status": -100, "msg": "登录失败,请重新输入密钥"} diff --git a/requirements.txt b/requirements.txt index a22a56c..d8a09ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,13 +8,13 @@ psutil>=5.8.0 httpx==0.24.1 apscheduler>=3.10.1 sqlitedict~=2.1.0 -casbin==1.18.2 -sentry-sdk==1.23.1 +casbin==1.19.0 +sentry-sdk==1.25.1 PyQRCode>=1.2.1 PyPng -fastapi==0.95.2 -amis-python==1.0.7 +fastapi==0.97.0 +amis-python==1.0.8.post2 python-jose uvicorn -pydantic==1.10.7 -starlette==0.27.0 +pydantic==1.10.9 +starlette