mirror of
https://github.com/TeamPGM/PagerMaid-Pyro.git
synced 2024-11-21 19:38:17 +00:00
🔖 Update to v1.2.30
support web
This commit is contained in:
parent
9211a7d843
commit
29ae48d1ae
@ -12,7 +12,7 @@ from pagermaid.scheduler import scheduler
|
||||
import pyromod.listen
|
||||
from pyrogram import Client
|
||||
|
||||
pgm_version = "1.2.26"
|
||||
pgm_version = "1.2.30"
|
||||
CMD_LIST = {}
|
||||
module_dir = __path__[0]
|
||||
working_dir = getcwd()
|
||||
|
@ -6,6 +6,7 @@ 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.single_utils import safe_remove
|
||||
@ -41,6 +42,7 @@ async def main():
|
||||
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 process_exit(start=True, _client=bot)
|
||||
logs.info(lang('start'))
|
||||
|
66
pagermaid/common/alias.py
Normal file
66
pagermaid/common/alias.py
Normal file
@ -0,0 +1,66 @@
|
||||
from os import sep
|
||||
from json import dump as json_dump
|
||||
from typing import List, Dict
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from pagermaid.common.reload import reload_all
|
||||
from pagermaid.config import Config
|
||||
|
||||
|
||||
class Alias(BaseModel):
|
||||
command: str
|
||||
alias: str
|
||||
|
||||
|
||||
class AliasManager:
|
||||
def __init__(self):
|
||||
self.alias_list = []
|
||||
for key, value in Config.alias_dict.items():
|
||||
temp = Alias(command=key, alias=value)
|
||||
self.alias_list.append(temp)
|
||||
|
||||
def get_all_alias(self):
|
||||
return self.alias_list
|
||||
|
||||
def get_all_alias_dict(self):
|
||||
return [i.dict() for i in self.alias_list]
|
||||
|
||||
def get_all_alias_text(self) -> str:
|
||||
texts = []
|
||||
texts.extend(
|
||||
f'`{i.command}` > `{i.alias}`'
|
||||
for i in self.alias_list
|
||||
)
|
||||
return '\n'.join(texts)
|
||||
|
||||
@staticmethod
|
||||
def save():
|
||||
with open(f"data{sep}alias.json", 'w', encoding="utf-8") as f:
|
||||
json_dump(Config.alias_dict, f)
|
||||
|
||||
@staticmethod
|
||||
def delete_alias(source_command: str):
|
||||
del Config.alias_dict[source_command]
|
||||
AliasManager.save()
|
||||
|
||||
@staticmethod
|
||||
def add_alias(source_command: str, to_command: str):
|
||||
Config.alias_dict[source_command] = to_command
|
||||
AliasManager.save()
|
||||
|
||||
@staticmethod
|
||||
async def save_from_web(data: List[Dict]):
|
||||
for i in data:
|
||||
temp = Alias(**i)
|
||||
Config.alias_dict[temp.command] = temp.alias
|
||||
AliasManager.save()
|
||||
await reload_all()
|
||||
|
||||
def test_alias(self, message: str) -> str:
|
||||
r = message.split(" ")
|
||||
for i in self.alias_list:
|
||||
if i.command == r[0]:
|
||||
r[0] = i.alias
|
||||
break
|
||||
return " ".join(r)
|
35
pagermaid/common/cache.py
Normal file
35
pagermaid/common/cache.py
Normal file
@ -0,0 +1,35 @@
|
||||
import datetime
|
||||
import functools
|
||||
import inspect
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Cache(BaseModel):
|
||||
value: Any
|
||||
time: Optional[datetime.datetime]
|
||||
|
||||
|
||||
def cache(ttl=datetime.timedelta(minutes=15)):
|
||||
def wrap(func):
|
||||
cache_data: Dict[str, Cache] = {}
|
||||
|
||||
@functools.wraps(func)
|
||||
async def wrapped(*args, **kw):
|
||||
nonlocal cache_data
|
||||
bound = inspect.signature(func).bind(*args, **kw)
|
||||
bound.apply_defaults()
|
||||
ins_key = '|'.join([f'{k}_{v}' for k, v in bound.arguments.items()])
|
||||
data: Cache = cache_data.get(ins_key, Cache(value=None, time=None))
|
||||
now = datetime.datetime.now()
|
||||
if (not data.time) or ((now - data.time) > ttl):
|
||||
try:
|
||||
data.value = await func(*args, **kw)
|
||||
data.time = datetime.datetime.now()
|
||||
cache_data[ins_key] = data
|
||||
except Exception as e:
|
||||
raise e
|
||||
return data.value
|
||||
return wrapped
|
||||
return wrap
|
22
pagermaid/common/ignore.py
Normal file
22
pagermaid/common/ignore.py
Normal file
@ -0,0 +1,22 @@
|
||||
import json
|
||||
|
||||
from pyrogram.enums import ChatType
|
||||
|
||||
from pagermaid import bot
|
||||
from pagermaid.sub_utils import Sub
|
||||
|
||||
ignore_groups_manager = Sub("ignore_groups")
|
||||
|
||||
|
||||
async def get_group_list():
|
||||
try:
|
||||
return [
|
||||
json.loads(str(dialog.chat))
|
||||
for dialog in await bot.get_dialogs_list()
|
||||
if (
|
||||
dialog.chat
|
||||
and dialog.chat.type in [ChatType.SUPERGROUP, ChatType.GROUP]
|
||||
)
|
||||
]
|
||||
except BaseException:
|
||||
return []
|
181
pagermaid/common/plugin.py
Normal file
181
pagermaid/common/plugin.py
Normal file
@ -0,0 +1,181 @@
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from pagermaid import Config
|
||||
from pagermaid.common.cache import cache
|
||||
from pagermaid.utils import client
|
||||
|
||||
plugins_path = Path('plugins')
|
||||
|
||||
|
||||
class LocalPlugin(BaseModel):
|
||||
name: str
|
||||
status: bool
|
||||
installed: bool = False
|
||||
version: Optional[float]
|
||||
|
||||
@property
|
||||
def normal_path(self) -> Path:
|
||||
return plugins_path / f"{self.name}.py"
|
||||
|
||||
@property
|
||||
def disabled_path(self) -> Path:
|
||||
return plugins_path / f"{self.name}.py.disabled"
|
||||
|
||||
def remove(self):
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
os.remove(self.normal_path)
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
os.remove(self.disabled_path)
|
||||
|
||||
def enable(self) -> bool:
|
||||
try:
|
||||
os.rename(self.disabled_path, self.normal_path)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def disable(self) -> bool:
|
||||
try:
|
||||
os.rename(self.normal_path, self.disabled_path)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
class RemotePlugin(LocalPlugin):
|
||||
section: str
|
||||
maintainer: str
|
||||
size: str
|
||||
supported: bool
|
||||
des: str
|
||||
...
|
||||
|
||||
async def install(self) -> bool:
|
||||
html = await client.get(f'{Config.GIT_SOURCE}{self.name}/main.py')
|
||||
if html.status_code == 200:
|
||||
self.remove()
|
||||
with open(plugins_path / f"{self.name}.py", mode="wb") as f:
|
||||
f.write(html.text.encode('utf-8'))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class PluginManager:
|
||||
def __init__(self):
|
||||
self.version_map = {}
|
||||
self.remote_version_map = {}
|
||||
self.plugins: List[LocalPlugin] = []
|
||||
self.remote_plugins: List[RemotePlugin] = []
|
||||
|
||||
def load_local_version_map(self):
|
||||
if not os.path.exists(plugins_path / "version.json"):
|
||||
return
|
||||
with open(plugins_path / "version.json", 'r', encoding="utf-8") as f:
|
||||
self.version_map = json.load(f)
|
||||
|
||||
def save_local_version_map(self):
|
||||
with open(plugins_path / "version.json", 'w', encoding="utf-8") as f:
|
||||
json.dump(self.version_map, f, indent=4)
|
||||
|
||||
def get_local_version(self, name: str) -> Optional[float]:
|
||||
return self.version_map.get(name)
|
||||
|
||||
def set_local_version(self, name: str, version: float) -> None:
|
||||
self.version_map[name] = version
|
||||
self.save_local_version_map()
|
||||
|
||||
def get_plugin_install_status(self, name: str) -> bool:
|
||||
return name in self.version_map
|
||||
|
||||
@staticmethod
|
||||
def get_plugin_load_status(name: str) -> bool:
|
||||
return bool(os.path.exists(plugins_path / f"{name}.py"))
|
||||
|
||||
def remove_plugin(self, name: str) -> bool:
|
||||
if plugin := self.get_local_plugin(name):
|
||||
plugin.remove()
|
||||
if name in self.version_map:
|
||||
self.version_map.pop(name)
|
||||
self.save_local_version_map()
|
||||
return True
|
||||
return False
|
||||
|
||||
def enable_plugin(self, name: str) -> bool:
|
||||
return plugin.enable() if (plugin := self.get_local_plugin(name)) else False
|
||||
|
||||
def disable_plugin(self, name: str) -> bool:
|
||||
return plugin.disable() if (plugin := self.get_local_plugin(name)) else False
|
||||
|
||||
def load_local_plugins(self) -> List[LocalPlugin]:
|
||||
self.load_local_version_map()
|
||||
self.plugins = []
|
||||
for plugin in os.listdir('plugins'):
|
||||
if plugin.endswith('.py') or plugin.endswith('.py.disabled'):
|
||||
plugin = plugin[:-12] if plugin.endswith('.py.disabled') else plugin[:-3]
|
||||
self.plugins.append(
|
||||
LocalPlugin(
|
||||
name=plugin,
|
||||
installed=self.get_plugin_install_status(plugin),
|
||||
status=self.get_plugin_load_status(plugin),
|
||||
version=self.get_local_version(plugin)
|
||||
)
|
||||
)
|
||||
return self.plugins
|
||||
|
||||
def get_local_plugin(self, name: str) -> LocalPlugin:
|
||||
return next(filter(lambda x: x.name == name, self.plugins), None)
|
||||
|
||||
@cache()
|
||||
async def load_remote_plugins(self) -> List[RemotePlugin]:
|
||||
plugin_list = await client.get(f"{Config.GIT_SOURCE}list.json")
|
||||
plugin_list = plugin_list.json()["list"]
|
||||
plugins = [
|
||||
RemotePlugin(
|
||||
**plugin,
|
||||
status=self.get_plugin_load_status(plugin["name"])
|
||||
) for plugin in plugin_list
|
||||
]
|
||||
self.remote_plugins = plugins
|
||||
self.remote_version_map = {}
|
||||
for plugin in plugins:
|
||||
self.remote_version_map[plugin.name] = plugin.version
|
||||
return plugins
|
||||
|
||||
def get_remote_plugin(self, name: str) -> RemotePlugin:
|
||||
return next(filter(lambda x: x.name == name, self.remote_plugins), None)
|
||||
|
||||
def plugin_need_update(self, name: str) -> bool:
|
||||
if local_version := self.get_local_version(name):
|
||||
if local_version == 0.0:
|
||||
return False
|
||||
if remote_version := self.remote_version_map.get(name):
|
||||
return local_version < remote_version
|
||||
return False
|
||||
|
||||
async def install_remote_plugin(self, name: str) -> bool:
|
||||
if plugin := self.get_remote_plugin(name):
|
||||
if await plugin.install():
|
||||
self.set_local_version(name, plugin.version)
|
||||
return True
|
||||
return False
|
||||
|
||||
async def update_remote_plugin(self, name: str) -> bool:
|
||||
if self.plugin_need_update(name):
|
||||
return await self.install_remote_plugin(name)
|
||||
return False
|
||||
|
||||
async def update_all_remote_plugin(self) -> List[RemotePlugin]:
|
||||
updated_plugins = []
|
||||
for i in self.remote_plugins:
|
||||
if await self.update_remote_plugin(i.name):
|
||||
updated_plugins.append(i)
|
||||
return updated_plugins
|
||||
|
||||
|
||||
plugin_manager = PluginManager()
|
46
pagermaid/common/reload.py
Normal file
46
pagermaid/common/reload.py
Normal file
@ -0,0 +1,46 @@
|
||||
import contextlib
|
||||
import importlib
|
||||
import os
|
||||
|
||||
import pagermaid.config
|
||||
import pagermaid.modules
|
||||
from pagermaid import read_context, bot, help_messages, all_permissions, hook_functions, logs
|
||||
from pagermaid.common.plugin import plugin_manager
|
||||
from pagermaid.hook import Hook
|
||||
from pagermaid.utils import lang
|
||||
|
||||
|
||||
async def reload_all():
|
||||
read_context.clear()
|
||||
bot.dispatcher.remove_all_handlers()
|
||||
bot.job.remove_all_jobs()
|
||||
with contextlib.suppress(RuntimeError):
|
||||
bot.cancel_all_listener()
|
||||
loaded_plugins = list(pagermaid.modules.plugin_list)
|
||||
loaded_plugins.extend(iter(pagermaid.modules.module_list))
|
||||
# init
|
||||
importlib.reload(pagermaid.modules)
|
||||
importlib.reload(pagermaid.config)
|
||||
help_messages.clear()
|
||||
all_permissions.clear()
|
||||
for functions in hook_functions.values():
|
||||
functions.clear() # noqa: clear all hooks
|
||||
|
||||
for module_name in pagermaid.modules.module_list:
|
||||
try:
|
||||
module = importlib.import_module(f"pagermaid.modules.{module_name}")
|
||||
if module_name in loaded_plugins:
|
||||
importlib.reload(module)
|
||||
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:
|
||||
plugin = importlib.import_module(f"plugins.{plugin_name}")
|
||||
if plugin_name in loaded_plugins and os.path.exists(plugin.__file__):
|
||||
importlib.reload(plugin)
|
||||
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()
|
||||
plugin_manager.save_local_version_map()
|
||||
await Hook.load_success_exec()
|
54
pagermaid/common/status.py
Normal file
54
pagermaid/common/status.py
Normal file
@ -0,0 +1,54 @@
|
||||
import asyncio
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import psutil
|
||||
from pydantic import BaseModel
|
||||
from pagermaid import start_time, Config, pgm_version
|
||||
|
||||
|
||||
class Status(BaseModel):
|
||||
version: str
|
||||
run_time: str
|
||||
cpu_percent: str
|
||||
ram_percent: str
|
||||
swap_percent: str
|
||||
|
||||
|
||||
async def human_time_duration(seconds) -> str:
|
||||
parts = {}
|
||||
time_units = (
|
||||
('%m', 60 * 60 * 24 * 30),
|
||||
('%d', 60 * 60 * 24),
|
||||
('%H', 60 * 60),
|
||||
('%M', 60),
|
||||
('%S', 1)
|
||||
)
|
||||
for unit, div in time_units:
|
||||
amount, seconds = divmod(int(seconds), div)
|
||||
parts[unit] = str(amount)
|
||||
time_form = Config.START_FORM
|
||||
for key, value in parts.items():
|
||||
time_form = time_form.replace(key, value)
|
||||
return time_form
|
||||
|
||||
|
||||
async def get_bot_uptime() -> str:
|
||||
current_time = datetime.now(timezone.utc)
|
||||
uptime_sec = (current_time - start_time).total_seconds()
|
||||
return await human_time_duration(int(uptime_sec))
|
||||
|
||||
|
||||
async def get_status() -> Status:
|
||||
uptime = await get_bot_uptime()
|
||||
psutil.cpu_percent()
|
||||
await asyncio.sleep(0.1)
|
||||
cpu_percent = psutil.cpu_percent()
|
||||
ram_stat = psutil.virtual_memory()
|
||||
swap_stat = psutil.swap_memory()
|
||||
return Status(
|
||||
version=pgm_version,
|
||||
run_time=uptime,
|
||||
cpu_percent=f'{cpu_percent}%',
|
||||
ram_percent=f'{ram_stat.percent}%',
|
||||
swap_percent=f'{swap_stat.percent}%',
|
||||
)
|
45
pagermaid/common/system.py
Normal file
45
pagermaid/common/system.py
Normal file
@ -0,0 +1,45 @@
|
||||
import io
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from pagermaid import bot
|
||||
|
||||
|
||||
async def run_eval(cmd: str, message=None, only_result: bool = False) -> str:
|
||||
old_stderr = sys.stderr
|
||||
old_stdout = sys.stdout
|
||||
redirected_output = sys.stdout = io.StringIO()
|
||||
redirected_error = sys.stderr = io.StringIO()
|
||||
stdout, stderr, exc = None, None, None
|
||||
try:
|
||||
await aexec(cmd, message, bot)
|
||||
except Exception: # noqa
|
||||
exc = traceback.format_exc()
|
||||
stdout = redirected_output.getvalue()
|
||||
stderr = redirected_error.getvalue()
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
if exc:
|
||||
evaluation = exc
|
||||
elif stderr:
|
||||
evaluation = stderr
|
||||
elif stdout:
|
||||
evaluation = stdout
|
||||
else:
|
||||
evaluation = "Success"
|
||||
return evaluation if only_result else f"**>>>** `{cmd}` \n`{evaluation}`"
|
||||
|
||||
|
||||
async def aexec(code, event, client):
|
||||
exec(
|
||||
(
|
||||
(
|
||||
("async def __aexec(e, client): " + "\n msg = message = e")
|
||||
+ "\n reply = message.reply_to_message if message else None"
|
||||
)
|
||||
+ "\n chat = e.chat if e else None"
|
||||
)
|
||||
+ "".join(f"\n {x}" for x in code.split("\n"))
|
||||
)
|
||||
|
||||
return await locals()["__aexec"](event, client)
|
12
pagermaid/common/update.py
Normal file
12
pagermaid/common/update.py
Normal file
@ -0,0 +1,12 @@
|
||||
from sys import executable
|
||||
|
||||
from pagermaid.utils import execute
|
||||
|
||||
|
||||
async def update(force: bool = False):
|
||||
await execute('git fetch --all')
|
||||
if force:
|
||||
await execute('git reset --hard origin/master')
|
||||
await execute('git pull --all')
|
||||
await execute(f"{executable} -m pip install --upgrade -r requirements.txt")
|
||||
await execute(f"{executable} -m pip install -r requirements.txt")
|
@ -98,6 +98,11 @@ class Config:
|
||||
alias_dict = load_json(f)
|
||||
except Exception as e:
|
||||
alias_dict = {}
|
||||
web_interface = config.get("web_interface", {})
|
||||
WEB_ENABLE = strtobool(os.environ.get("WEB_ENABLE", web_interface.get("enable", "False")))
|
||||
WEB_SECRET_KEY = os.environ.get("WEB_SECRET_KEY", web_interface.get("secret_key", "secret_key"))
|
||||
WEB_HOST = os.environ.get("WEB_HOST", web_interface.get("host", "127.0.0.1"))
|
||||
WEB_PORT = int(os.environ.get("WEB_PORT", web_interface.get("port", 3333)))
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
sys.exit(1)
|
||||
|
@ -15,6 +15,7 @@ from pyrogram.errors.exceptions.bad_request_400 import (
|
||||
from pyrogram.handlers import MessageHandler, EditedMessageHandler
|
||||
|
||||
from pagermaid import help_messages, logs, Config, bot, read_context, all_permissions
|
||||
from pagermaid.common.ignore import ignore_groups_manager
|
||||
from pagermaid.group_manager import Permission
|
||||
from pagermaid.inject import inject
|
||||
from pagermaid.single_utils import Message, AlreadyInConversationError, TimeoutConversationError, ListenerCanceled
|
||||
@ -114,6 +115,14 @@ def listener(**args):
|
||||
|
||||
async def handler(client: Client, message: Message):
|
||||
try:
|
||||
# ignore
|
||||
try:
|
||||
if ignore_groups_manager.check_id(message.chat.id):
|
||||
raise ContinuePropagation
|
||||
except ContinuePropagation:
|
||||
raise ContinuePropagation
|
||||
except BaseException:
|
||||
pass
|
||||
try:
|
||||
parameter = message.matches[0].group(2).split(" ")
|
||||
if parameter == [""]:
|
||||
@ -229,6 +238,14 @@ def raw_listener(filter_s):
|
||||
|
||||
def decorator(function):
|
||||
async def handler(client, message):
|
||||
# ignore
|
||||
try:
|
||||
if ignore_groups_manager.check_id(message.chat.id):
|
||||
raise ContinuePropagation
|
||||
except ContinuePropagation:
|
||||
raise ContinuePropagation
|
||||
except BaseException:
|
||||
pass
|
||||
# solve same process
|
||||
async with _lock:
|
||||
if (message.chat.id, message.id) in read_context:
|
||||
|
@ -6,8 +6,9 @@ from os import listdir, sep
|
||||
from pyrogram.enums import ParseMode
|
||||
|
||||
from pagermaid import help_messages, Config
|
||||
from pagermaid.common.alias import AliasManager
|
||||
from pagermaid.group_manager import enforce_permission
|
||||
from pagermaid.modules.reload import reload_all
|
||||
from pagermaid.common.reload import reload_all
|
||||
from pagermaid.utils import lang, Message, from_self, from_msg_get_sudo_uid
|
||||
from pagermaid.listener import listener
|
||||
|
||||
@ -120,31 +121,18 @@ async def lang_change(message: Message):
|
||||
description=lang('alias_des'),
|
||||
parameters='{list|del|set} <source> <to>')
|
||||
async def alias_commands(message: Message):
|
||||
source_commands = []
|
||||
to_commands = []
|
||||
texts = []
|
||||
for key, value in Config.alias_dict.items():
|
||||
source_commands.append(key)
|
||||
to_commands.append(value)
|
||||
alias_manager = AliasManager()
|
||||
if len(message.parameter) == 0:
|
||||
await message.edit(lang('arg_error'))
|
||||
return
|
||||
elif len(message.parameter) == 1:
|
||||
if source_commands:
|
||||
texts.extend(
|
||||
f'`{source_commands[i]}` > `{to_commands[i]}`'
|
||||
for i in range(len(source_commands))
|
||||
)
|
||||
|
||||
await message.edit(lang('alias_list') + '\n\n' + '\n'.join(texts))
|
||||
if alias_manager.alias_list:
|
||||
await message.edit(lang('alias_list') + '\n\n' + alias_manager.get_all_alias_text())
|
||||
else:
|
||||
await message.edit(lang('alias_no'))
|
||||
elif len(message.parameter) == 2:
|
||||
source_command = message.parameter[1]
|
||||
try:
|
||||
del Config.alias_dict[source_command]
|
||||
with open(f"data{sep}alias.json", 'w', encoding="utf-8") as f:
|
||||
json_dump(Config.alias_dict, f)
|
||||
alias_manager.delete_alias(source_command)
|
||||
await message.edit(lang('alias_success'))
|
||||
await reload_all()
|
||||
except KeyError:
|
||||
@ -156,8 +144,6 @@ async def alias_commands(message: Message):
|
||||
if to_command in help_messages:
|
||||
await message.edit(lang('alias_exist'))
|
||||
return
|
||||
Config.alias_dict[source_command] = to_command
|
||||
with open(f"data{sep}alias.json", 'w', encoding="utf-8") as f:
|
||||
json_dump(Config.alias_dict, f)
|
||||
alias_manager.add_alias(source_command, to_command)
|
||||
await message.edit(lang('alias_success'))
|
||||
await reload_all()
|
||||
|
@ -2,20 +2,18 @@
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import importlib
|
||||
|
||||
from re import search, I
|
||||
from os import remove, rename, chdir, path, sep
|
||||
from os.path import exists
|
||||
from shutil import copyfile, move
|
||||
from glob import glob
|
||||
from os import remove, chdir, path, sep
|
||||
from os.path import exists
|
||||
from re import search, I
|
||||
from shutil import copyfile, move
|
||||
|
||||
from pagermaid import log, working_dir, Config, scheduler
|
||||
from pagermaid import log, working_dir, Config
|
||||
from pagermaid.common.plugin import plugin_manager
|
||||
from pagermaid.common.reload import reload_all
|
||||
from pagermaid.listener import listener
|
||||
from pagermaid.single_utils import safe_remove
|
||||
from pagermaid.utils import upload_attachment, lang, Message, client
|
||||
from pagermaid.modules import plugin_list as active_plugins, __list_plugins
|
||||
from pagermaid.modules.reload import reload_all
|
||||
from pagermaid.utils import upload_attachment, lang, Message, client
|
||||
|
||||
|
||||
def remove_plugin(name):
|
||||
@ -33,14 +31,6 @@ def move_plugin(file_path):
|
||||
move(file_path, plugin_directory)
|
||||
|
||||
|
||||
async def download(name):
|
||||
html = await client.get(f'{Config.GIT_SOURCE}{name}/main.py')
|
||||
assert html.status_code == 200
|
||||
with open(f'plugins{sep}{name}.py', mode='wb') as f:
|
||||
f.write(html.text.encode('utf-8'))
|
||||
return f'plugins{sep}{name}.py'
|
||||
|
||||
|
||||
def update_version(plugin_name, version):
|
||||
plugin_directory = f"{working_dir}{sep}plugins{sep}"
|
||||
with open(f"{plugin_directory}version.json", 'r', encoding="utf-8") as f:
|
||||
@ -82,43 +72,27 @@ async def plugin(message: Message):
|
||||
await log(f"{lang('apt_install_success')} {path.basename(file_path)[:-3]}.")
|
||||
await reload_all()
|
||||
elif len(message.parameter) >= 2:
|
||||
await plugin_manager.load_remote_plugins()
|
||||
process_list = message.parameter
|
||||
message = await message.edit(lang('apt_processing'))
|
||||
del process_list[0]
|
||||
success_list = []
|
||||
failed_list = []
|
||||
no_need_list = []
|
||||
plugin_list = await client.get(f"{Config.GIT_SOURCE}list.json")
|
||||
plugin_list = plugin_list.json()["list"]
|
||||
for i in process_list:
|
||||
if exists(f"{plugin_directory}version.json"):
|
||||
with open(f"{plugin_directory}version.json", 'r', encoding="utf-8") as f:
|
||||
version_json = json.load(f)
|
||||
try:
|
||||
plugin_version = version_json[i]
|
||||
except: # noqa
|
||||
plugin_version = 0
|
||||
if plugin_manager.get_remote_plugin(i):
|
||||
local_temp = plugin_manager.get_local_plugin(i)
|
||||
if local_temp and not plugin_manager.plugin_need_update(i):
|
||||
no_need_list.append(i)
|
||||
else:
|
||||
try:
|
||||
if await plugin_manager.install_remote_plugin(i):
|
||||
success_list.append(i)
|
||||
else:
|
||||
failed_list.append(i)
|
||||
except Exception:
|
||||
failed_list.append(i)
|
||||
else:
|
||||
temp_dict = {}
|
||||
with open(f"{plugin_directory}version.json", 'w') as f:
|
||||
json.dump(temp_dict, f)
|
||||
plugin_version = 0
|
||||
temp = True
|
||||
for x in plugin_list:
|
||||
if x["name"] == i:
|
||||
if (float(x["version"]) - float(plugin_version)) <= 0:
|
||||
no_need_list.append(i)
|
||||
else:
|
||||
remove_plugin(i)
|
||||
try:
|
||||
await download(i)
|
||||
except AssertionError:
|
||||
break
|
||||
update_version(i, x["version"])
|
||||
success_list.append(i)
|
||||
temp = False
|
||||
break
|
||||
if temp:
|
||||
failed_list.append(i)
|
||||
text = f"<b>{lang('apt_name')}</b>\n\n"
|
||||
if len(success_list) > 0:
|
||||
@ -136,15 +110,7 @@ async def plugin(message: Message):
|
||||
await message.edit(lang('arg_error'))
|
||||
elif message.parameter[0] == "remove":
|
||||
if len(message.parameter) == 2:
|
||||
if exists(f"{plugin_directory}{message.parameter[1]}.py") or \
|
||||
exists(f"{plugin_directory}{message.parameter[1]}.py.disabled"):
|
||||
remove_plugin(message.parameter[1])
|
||||
if exists(f"{plugin_directory}version.json"):
|
||||
with open(f"{plugin_directory}version.json", 'r', encoding="utf-8") as f:
|
||||
version_json = json.load(f)
|
||||
version_json[message.parameter[1]] = "0.0"
|
||||
with open(f"{plugin_directory}version.json", 'w') as f:
|
||||
json.dump(version_json, f)
|
||||
if plugin_manager.remove_plugin(message.parameter[1]):
|
||||
await message.edit(f"{lang('apt_remove_success')} {message.parameter[1]}")
|
||||
await log(f"{lang('apt_remove')} {message.parameter[1]}.")
|
||||
await reload_all()
|
||||
@ -192,9 +158,7 @@ async def plugin(message: Message):
|
||||
await message.edit(lang('arg_error'))
|
||||
elif message.parameter[0] == "enable":
|
||||
if len(message.parameter) == 2:
|
||||
if exists(f"{plugin_directory}{message.parameter[1]}.py.disabled"):
|
||||
rename(f"{plugin_directory}{message.parameter[1]}.py.disabled",
|
||||
f"{plugin_directory}{message.parameter[1]}.py")
|
||||
if plugin_manager.enable_plugin(message.parameter[1]):
|
||||
await message.edit(f"{lang('apt_plugin')} {message.parameter[1]} "
|
||||
f"{lang('apt_enable')}")
|
||||
await log(f"{lang('apt_enable')} {message.parameter[1]}.")
|
||||
@ -205,9 +169,7 @@ async def plugin(message: Message):
|
||||
await message.edit(lang('arg_error'))
|
||||
elif message.parameter[0] == "disable":
|
||||
if len(message.parameter) == 2:
|
||||
if exists(f"{plugin_directory}{message.parameter[1]}.py") is True:
|
||||
rename(f"{plugin_directory}{message.parameter[1]}.py",
|
||||
f"{plugin_directory}{message.parameter[1]}.py.disabled")
|
||||
if plugin_manager.disable_plugin(message.parameter[1]):
|
||||
await message.edit(f"{lang('apt_plugin')} {message.parameter[1]} "
|
||||
f"{lang('apt_disable')}")
|
||||
await log(f"{lang('apt_disable')} {message.parameter[1]}.")
|
||||
@ -240,55 +202,20 @@ async def plugin(message: Message):
|
||||
else:
|
||||
await message.edit(lang('arg_error'))
|
||||
elif message.parameter[0] == "update":
|
||||
un_need_update = lang('apt_no_update')
|
||||
need_update = f"\n{lang('apt_updated')}:"
|
||||
need_update_list = []
|
||||
if not exists(f"{plugin_directory}version.json"):
|
||||
await message.edit(lang('apt_why_not_install_a_plugin'))
|
||||
return
|
||||
with open(f"{plugin_directory}version.json", 'r', encoding="utf-8") as f:
|
||||
version_json = json.load(f)
|
||||
plugin_list = await client.get(f"{Config.GIT_SOURCE}list.json")
|
||||
plugin_online = plugin_list.json()["list"]
|
||||
for key, value in version_json.items():
|
||||
if value == "0.0":
|
||||
continue
|
||||
for i in plugin_online:
|
||||
if key == i["name"]:
|
||||
if (float(i["version"]) - float(value)) <= 0:
|
||||
un_need_update += "\n`" + key + "`:Ver " + value
|
||||
else:
|
||||
need_update_list.extend([key])
|
||||
need_update += "\n<code>" + key + "</code>:Ver " + value + " > Ver " + i['version']
|
||||
continue
|
||||
if un_need_update == f"{lang('apt_no_update')}:":
|
||||
un_need_update = ""
|
||||
if need_update == f"\n{lang('apt_updated')}:":
|
||||
need_update = ""
|
||||
if not un_need_update and not need_update:
|
||||
await message.edit(lang("apt_why_not_install_a_plugin"))
|
||||
await plugin_manager.load_remote_plugins()
|
||||
updated_plugins = await plugin_manager.update_all_remote_plugin()
|
||||
if len(updated_plugins) == 0:
|
||||
await message.edit(f"<b>{lang('apt_name')}</b>\n\n" +
|
||||
lang("apt_loading_from_online_but_nothing_need_to_update"))
|
||||
else:
|
||||
if len(need_update_list) == 0:
|
||||
await message.edit(f"<b>{lang('apt_name')}</b>\n\n" +
|
||||
lang("apt_loading_from_online_but_nothing_need_to_update"))
|
||||
else:
|
||||
message = await message.edit(lang("apt_loading_from_online_and_updating"))
|
||||
plugin_directory = f"{working_dir}{sep}plugins{sep}"
|
||||
for i in need_update_list:
|
||||
remove_plugin(i)
|
||||
try:
|
||||
await download(i)
|
||||
except AssertionError:
|
||||
continue
|
||||
with open(f"{plugin_directory}version.json", "r", encoding="utf-8") as f:
|
||||
version_json = json.load(f)
|
||||
for m in plugin_online:
|
||||
if m["name"] == i:
|
||||
version_json[i] = m["version"]
|
||||
with open(f"{plugin_directory}version.json", "w") as f:
|
||||
json.dump(version_json, f)
|
||||
await message.edit(f"<b>{lang('apt_name')}</b>\n\n" + lang("apt_reading_list") + need_update)
|
||||
await reload_all()
|
||||
message = await message.edit(lang("apt_loading_from_online_and_updating"))
|
||||
await message.edit(
|
||||
f"<b>{lang('apt_name')}</b>\n\n" + lang("apt_reading_list") + "\n".join(updated_plugins)
|
||||
)
|
||||
await reload_all()
|
||||
elif message.parameter[0] == "search":
|
||||
if len(message.parameter) == 1:
|
||||
await message.edit(lang("apt_search_no_name"))
|
||||
@ -354,9 +281,5 @@ async def plugin(message: Message):
|
||||
await message.edit(lang("apt_why_not_install_a_plugin"))
|
||||
else:
|
||||
await message.edit(",apt install " + " ".join(list_plugin))
|
||||
elif message.parameter[0] == "reload":
|
||||
# bot.dispatcher.remove_all_handlers() # noqa
|
||||
scheduler.remove_all_jobs()
|
||||
importlib.reload(importlib.import_module("plugins.sign"))
|
||||
else:
|
||||
await message.edit(lang("arg_error"))
|
||||
|
@ -1,51 +1,11 @@
|
||||
import contextlib
|
||||
import importlib
|
||||
import os
|
||||
|
||||
import pagermaid.config
|
||||
import pagermaid.modules
|
||||
from pagermaid import logs, help_messages, all_permissions, hook_functions, read_context
|
||||
from pagermaid import read_context
|
||||
from pagermaid.common.reload import reload_all
|
||||
from pagermaid.enums import Message
|
||||
from pagermaid.hook import Hook
|
||||
from pagermaid.listener import listener
|
||||
from pagermaid.services import bot, scheduler
|
||||
from pagermaid.services import scheduler
|
||||
from pagermaid.utils import lang
|
||||
|
||||
|
||||
async def reload_all():
|
||||
read_context.clear()
|
||||
bot.dispatcher.remove_all_handlers()
|
||||
bot.job.remove_all_jobs()
|
||||
with contextlib.suppress(RuntimeError):
|
||||
bot.cancel_all_listener()
|
||||
loaded_plugins = list(pagermaid.modules.plugin_list)
|
||||
loaded_plugins.extend(iter(pagermaid.modules.module_list))
|
||||
# init
|
||||
importlib.reload(pagermaid.modules)
|
||||
importlib.reload(pagermaid.config)
|
||||
help_messages.clear()
|
||||
all_permissions.clear()
|
||||
for functions in hook_functions.values():
|
||||
functions.clear() # noqa: clear all hooks
|
||||
|
||||
for module_name in pagermaid.modules.module_list:
|
||||
try:
|
||||
module = importlib.import_module(f"pagermaid.modules.{module_name}")
|
||||
if module_name in loaded_plugins:
|
||||
importlib.reload(module)
|
||||
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:
|
||||
plugin = importlib.import_module(f"plugins.{plugin_name}")
|
||||
if plugin_name in loaded_plugins and os.path.exists(plugin.__file__):
|
||||
importlib.reload(plugin)
|
||||
except BaseException as exception:
|
||||
logs.info(f"{lang('module')} {plugin_name} {lang('error')}: {exception}")
|
||||
pagermaid.modules.plugin_list.remove(plugin_name)
|
||||
await Hook.load_success_exec()
|
||||
|
||||
|
||||
@listener(is_plugin=False, command="reload",
|
||||
need_admin=True,
|
||||
description=lang('reload_des'))
|
||||
|
@ -19,6 +19,7 @@ from shutil import disk_usage
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from pagermaid import start_time, Config, pgm_version
|
||||
from pagermaid.common.status import get_bot_uptime
|
||||
from pagermaid.enums import Client, Message
|
||||
from pagermaid.listener import listener
|
||||
from pagermaid.utils import lang, execute
|
||||
@ -50,27 +51,7 @@ async def status(message: Message):
|
||||
# database
|
||||
# database = lang('status_online') if redis_status() else lang('status_offline')
|
||||
# uptime https://gist.github.com/borgstrom/936ca741e885a1438c374824efb038b3
|
||||
time_units = (
|
||||
('%m', 60 * 60 * 24 * 30),
|
||||
('%d', 60 * 60 * 24),
|
||||
('%H', 60 * 60),
|
||||
('%M', 60),
|
||||
('%S', 1)
|
||||
)
|
||||
|
||||
async def human_time_duration(seconds):
|
||||
parts = {}
|
||||
for unit, div in time_units:
|
||||
amount, seconds = divmod(int(seconds), div)
|
||||
parts[unit] = str(amount)
|
||||
time_form = Config.START_FORM
|
||||
for key, value in parts.items():
|
||||
time_form = time_form.replace(key, value)
|
||||
return time_form
|
||||
|
||||
current_time = datetime.now(timezone.utc)
|
||||
uptime_sec = (current_time - start_time).total_seconds()
|
||||
uptime = await human_time_duration(int(uptime_sec))
|
||||
uptime = await get_bot_uptime()
|
||||
text = (f"**{lang('status_hint')}** \n"
|
||||
f"{lang('status_name')}: `{uname().node}` \n"
|
||||
f"{lang('status_platform')}: `{platform}` \n"
|
||||
@ -88,7 +69,7 @@ async def status(message: Message):
|
||||
async def stats(client: Client, message: Message):
|
||||
msg = await message.edit(lang("stats_loading"))
|
||||
a, u, g, s, c, b = 0, 0, 0, 0, 0, 0
|
||||
async for dialog in client.get_dialogs():
|
||||
for dialog in await client.get_dialogs_list():
|
||||
chat_type = dialog.chat.type
|
||||
if chat_type == ChatType.BOT:
|
||||
b += 1
|
||||
|
@ -1,7 +1,3 @@
|
||||
import io
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from os.path import exists, sep
|
||||
from sys import exit
|
||||
from platform import node
|
||||
@ -9,9 +5,9 @@ from getpass import getuser
|
||||
|
||||
from pyrogram.enums import ParseMode
|
||||
|
||||
from pagermaid.common.system import run_eval
|
||||
from pagermaid.listener import listener
|
||||
from pagermaid.enums import Message
|
||||
from pagermaid.services import bot
|
||||
from pagermaid.utils import attach_log, execute, lang, upload_attachment
|
||||
|
||||
|
||||
@ -72,31 +68,10 @@ async def sh_eval(message: Message):
|
||||
cmd = message.text.split(" ", maxsplit=1)[1]
|
||||
except (IndexError, AssertionError):
|
||||
return await message.edit(lang('eval_need_dev'))
|
||||
old_stderr = sys.stderr
|
||||
old_stdout = sys.stdout
|
||||
redirected_output = sys.stdout = io.StringIO()
|
||||
redirected_error = sys.stderr = io.StringIO()
|
||||
stdout, stderr, exc = None, None, None
|
||||
try:
|
||||
await aexec(cmd, message, bot)
|
||||
except Exception: # noqa
|
||||
exc = traceback.format_exc()
|
||||
stdout = redirected_output.getvalue()
|
||||
stderr = redirected_error.getvalue()
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
if exc:
|
||||
evaluation = exc
|
||||
elif stderr:
|
||||
evaluation = stderr
|
||||
elif stdout:
|
||||
evaluation = stdout
|
||||
else:
|
||||
evaluation = "Success"
|
||||
final_output = f"**>>>** `{cmd}` \n`{evaluation}`"
|
||||
final_output = await run_eval(cmd, message)
|
||||
if len(final_output) > 4096:
|
||||
message = await message.edit(f"**>>>** `{cmd}`", parse_mode=ParseMode.MARKDOWN)
|
||||
await attach_log(evaluation, message.chat.id, "output.log", message.id)
|
||||
await attach_log(final_output, message.chat.id, "output.log", message.id)
|
||||
else:
|
||||
await message.edit(final_output)
|
||||
|
||||
@ -114,18 +89,3 @@ async def send_log(message: Message):
|
||||
thumb=f"pagermaid{sep}assets{sep}logo.jpg",
|
||||
caption=lang("send_log_caption"))
|
||||
await message.safe_delete()
|
||||
|
||||
|
||||
async def aexec(code, event, client):
|
||||
exec(
|
||||
(
|
||||
(
|
||||
("async def __aexec(e, client): " + "\n msg = message = e")
|
||||
+ "\n reply = message.reply_to_message"
|
||||
)
|
||||
+ "\n chat = e.chat"
|
||||
)
|
||||
+ "".join(f"\n {x}" for x in code.split("\n"))
|
||||
)
|
||||
|
||||
return await locals()["__aexec"](event, client)
|
||||
|
@ -1,7 +1,8 @@
|
||||
from sys import executable, exit
|
||||
from sys import exit
|
||||
|
||||
from pagermaid.common.update import update as update_function
|
||||
from pagermaid.listener import listener
|
||||
from pagermaid.utils import lang, execute, Message, alias_command
|
||||
from pagermaid.utils import lang, Message, alias_command
|
||||
|
||||
|
||||
@listener(is_plugin=False, outgoing=True, command=alias_command("update"),
|
||||
@ -9,11 +10,6 @@ from pagermaid.utils import lang, execute, Message, alias_command
|
||||
description=lang('update_des'),
|
||||
parameters="<true/debug>")
|
||||
async def update(message: Message):
|
||||
await execute('git fetch --all')
|
||||
if len(message.parameter) > 0:
|
||||
await execute('git reset --hard origin/master')
|
||||
await execute('git pull --all')
|
||||
await execute(f"{executable} -m pip install --upgrade -r requirements.txt")
|
||||
await execute(f"{executable} -m pip install -r requirements.txt")
|
||||
await update_function(len(message.parameter) > 0)
|
||||
await message.edit(lang('update_success'))
|
||||
exit(0)
|
||||
|
15
pagermaid/modules/web.py
Normal file
15
pagermaid/modules/web.py
Normal file
@ -0,0 +1,15 @@
|
||||
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
|
||||
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())
|
@ -6,7 +6,7 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from httpx import AsyncClient
|
||||
|
||||
from pyrogram import Client as OldClient
|
||||
from pyrogram.types import Chat as OldChat, Message as OldMessage
|
||||
from pyrogram.types import Chat as OldChat, Message as OldMessage, Dialog
|
||||
|
||||
from pyromod.utils.conversation import Conversation
|
||||
from pyromod.utils.errors import AlreadyInConversationError, TimeoutConversationError, ListenerCanceled
|
||||
@ -70,6 +70,9 @@ class Client(OldClient):
|
||||
once_timeout: int = 60, filters=None) -> Optional[Conversation]:
|
||||
""" Initialize a conversation with the given chat_id. """
|
||||
|
||||
async def get_dialogs_list(self) -> List[Dialog]:
|
||||
""" Get a list of all dialogs. """
|
||||
|
||||
|
||||
class Chat(OldChat):
|
||||
is_forum: Optional[bool] = None
|
||||
|
@ -77,6 +77,7 @@ async def execute(command, pass_error=True):
|
||||
""" Executes command and returns output, with the option of enabling stderr. """
|
||||
executor = await create_subprocess_shell(
|
||||
command,
|
||||
loop=bot.loop,
|
||||
stdout=PIPE,
|
||||
stderr=PIPE,
|
||||
stdin=PIPE
|
||||
|
50
pagermaid/web/__init__.py
Normal file
50
pagermaid/web/__init__.py
Normal file
@ -0,0 +1,50 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import HTMLResponse
|
||||
from starlette.responses import RedirectResponse
|
||||
|
||||
from .api import base_api_router
|
||||
from .pages import admin_app, login_page
|
||||
|
||||
requestAdaptor = '''
|
||||
requestAdaptor(api) {
|
||||
api.headers["token"] = localStorage.getItem("token");
|
||||
return api;
|
||||
},
|
||||
'''
|
||||
responseAdaptor = '''
|
||||
responseAdaptor(api, payload, query, request, response) {
|
||||
if (response.data.detail == '登录验证失败或已失效,请重新登录') {
|
||||
window.location.href = '/login'
|
||||
window.localStorage.clear()
|
||||
window.sessionStorage.clear()
|
||||
window.alert('登录验证失败或已失效,请重新登录')
|
||||
}
|
||||
return payload
|
||||
},
|
||||
'''
|
||||
icon_path = 'https://xtaolabs.com/pagermaid-logo.png'
|
||||
app: FastAPI = FastAPI()
|
||||
|
||||
|
||||
def init_web():
|
||||
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
|
||||
)
|
||||
|
||||
@app.get('/login', response_class=HTMLResponse)
|
||||
async def login():
|
||||
return login_page.render(
|
||||
site_title='登录 | PagerMaid-Pyro 后台管理',
|
||||
site_icon=icon_path,
|
||||
)
|
18
pagermaid/web/api/__init__.py
Normal file
18
pagermaid/web/api/__init__.py
Normal file
@ -0,0 +1,18 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from pagermaid.web.api.utils import authentication
|
||||
from pagermaid.web.api.bot_info import route as bot_info_route
|
||||
from pagermaid.web.api.command_alias import route as command_alias_route
|
||||
from pagermaid.web.api.ignore_groups import route as ignore_groups_route
|
||||
from pagermaid.web.api.login import route as login_route
|
||||
from pagermaid.web.api.plugin import route as plugin_route
|
||||
from pagermaid.web.api.status import route as status_route
|
||||
|
||||
base_api_router = APIRouter(prefix='/pagermaid/api')
|
||||
|
||||
base_api_router.include_router(plugin_route)
|
||||
base_api_router.include_router(bot_info_route)
|
||||
base_api_router.include_router(status_route)
|
||||
base_api_router.include_router(login_route)
|
||||
base_api_router.include_router(command_alias_route)
|
||||
base_api_router.include_router(ignore_groups_route)
|
24
pagermaid/web/api/bot_info.py
Normal file
24
pagermaid/web/api/bot_info.py
Normal file
@ -0,0 +1,24 @@
|
||||
import os
|
||||
import signal
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from pagermaid.web.api.utils import authentication
|
||||
from pagermaid.common.update import update
|
||||
|
||||
route = APIRouter()
|
||||
|
||||
|
||||
@route.post('/bot_update', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def bot_update():
|
||||
await update()
|
||||
return {
|
||||
"status": 0,
|
||||
"msg": "更新成功,请重启 PagerMaid-Pyro 以应用更新。"
|
||||
}
|
||||
|
||||
|
||||
@route.post('/bot_restart', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def bot_restart():
|
||||
os.kill(os.getppid(), signal.SIGINT)
|
47
pagermaid/web/api/command_alias.py
Normal file
47
pagermaid/web/api/command_alias.py
Normal file
@ -0,0 +1,47 @@
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from pagermaid.common.alias import AliasManager
|
||||
from pagermaid.web.api.utils import authentication
|
||||
|
||||
route = APIRouter()
|
||||
|
||||
|
||||
@route.get('/command_alias', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_command_alias():
|
||||
alias = AliasManager()
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'items': alias.get_all_alias_dict(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@route.post('/command_alias', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def add_command_alias(data: dict):
|
||||
data = data['items']
|
||||
try:
|
||||
await AliasManager.save_from_web(data)
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': '命令别名保存成功'
|
||||
}
|
||||
except Exception:
|
||||
return {
|
||||
'status': 1,
|
||||
'msg': '命令别名保存失败'
|
||||
}
|
||||
|
||||
|
||||
@route.get('/test_command_alias', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def test_command_alias(message: str):
|
||||
alias = AliasManager()
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': '测试成功',
|
||||
'data': {
|
||||
'new_msg': alias.test_alias(message),
|
||||
}
|
||||
}
|
45
pagermaid/web/api/ignore_groups.py
Normal file
45
pagermaid/web/api/ignore_groups.py
Normal file
@ -0,0 +1,45 @@
|
||||
from fastapi import APIRouter
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
from pagermaid.common.ignore import ignore_groups_manager, get_group_list
|
||||
from pagermaid.web.api import authentication
|
||||
|
||||
route = APIRouter()
|
||||
|
||||
|
||||
@route.get("/get_ignore_group_list", response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_ignore_group_list():
|
||||
try:
|
||||
groups = []
|
||||
for data in await get_group_list():
|
||||
data["status"] = ignore_groups_manager.check_id(data["id"])
|
||||
groups.append(data)
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'groups': groups
|
||||
}
|
||||
}
|
||||
except BaseException:
|
||||
return {
|
||||
'status': -100,
|
||||
'msg': '获取群组列表失败'
|
||||
}
|
||||
|
||||
|
||||
@route.post('/set_ignore_group_status', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def set_ignore_group_status(data: dict):
|
||||
cid: int = data.get('id')
|
||||
status: bool = data.get('status')
|
||||
if status:
|
||||
ignore_groups_manager.add_id(cid)
|
||||
else:
|
||||
ignore_groups_manager.del_id(cid)
|
||||
return {'status': 0, 'msg': f'成功{"忽略" if status else "取消忽略"} {cid}'}
|
||||
|
||||
|
||||
@route.post('/clear_ignore_group', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def clear_ignore_group():
|
||||
ignore_groups_manager.clear_subs()
|
||||
return {'status': 0, 'msg': '成功清空忽略列表'}
|
30
pagermaid/web/api/login.py
Normal file
30
pagermaid/web/api/login.py
Normal file
@ -0,0 +1,30 @@
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from pagermaid.web.api.utils import create_token
|
||||
from pagermaid.config import Config
|
||||
|
||||
|
||||
class UserModel(BaseModel):
|
||||
password: str
|
||||
|
||||
|
||||
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": {
|
||||
"token": token
|
||||
}
|
||||
}
|
65
pagermaid/web/api/plugin.py
Normal file
65
pagermaid/web/api/plugin.py
Normal file
@ -0,0 +1,65 @@
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from pagermaid.common.plugin import plugin_manager
|
||||
from pagermaid.common.reload import reload_all
|
||||
from pagermaid.web.api.utils import authentication
|
||||
|
||||
route = APIRouter()
|
||||
|
||||
|
||||
@route.get('/get_local_plugins', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_local_plugins():
|
||||
plugins = [i.dict() for i in plugin_manager.plugins]
|
||||
plugins.sort(key=lambda x: x['name'])
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'rows': plugins,
|
||||
'total': len(plugins)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@route.post('/set_local_plugin_status', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def set_local_plugin_status(data: dict):
|
||||
module_name: str = data.get('plugin')
|
||||
status: bool = data.get('status')
|
||||
if not (plugin := plugin_manager.get_local_plugin(module_name)):
|
||||
return {'status': 1, 'msg': f'插件 {module_name} 不存在'}
|
||||
if status:
|
||||
plugin.enable()
|
||||
else:
|
||||
plugin.disable()
|
||||
await reload_all()
|
||||
return {'status': 0, 'msg': f'成功{"开启" if status else "关闭"} {module_name}'}
|
||||
|
||||
|
||||
@route.get('/get_remote_plugins', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_remote_plugins():
|
||||
await plugin_manager.load_remote_plugins()
|
||||
plugins = [i.dict() for i in plugin_manager.remote_plugins]
|
||||
plugins.sort(key=lambda x: x['name'])
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'rows': plugins,
|
||||
'total': len(plugins)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@route.post('/set_remote_plugin_status', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def set_remote_plugin_status(data: dict):
|
||||
module_name: str = data.get('plugin')
|
||||
status: bool = data.get('status')
|
||||
if not plugin_manager.get_remote_plugin(module_name):
|
||||
return {'status': 1, 'msg': f'插件 {module_name} 不存在'}
|
||||
if status:
|
||||
await plugin_manager.install_remote_plugin(module_name)
|
||||
else:
|
||||
plugin_manager.remove_plugin(module_name)
|
||||
await reload_all()
|
||||
return {'status': 0, 'msg': f'成功{"开启" if status else "关闭"} {module_name}'}
|
69
pagermaid/web/api/status.py
Normal file
69
pagermaid/web/api/status.py
Normal file
@ -0,0 +1,69 @@
|
||||
import asyncio
|
||||
from typing import Union, Optional
|
||||
|
||||
from fastapi import APIRouter, Header
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
|
||||
from pagermaid.common.status import get_status
|
||||
from pagermaid.common.system import run_eval
|
||||
from pagermaid.config import Config
|
||||
from pagermaid.utils import execute
|
||||
from pagermaid.web.api.utils import authentication
|
||||
|
||||
route = APIRouter()
|
||||
|
||||
|
||||
@route.get('/log')
|
||||
async def get_log(token: Optional[str] = Header(...), num: Union[int, str] = 100):
|
||||
if token != Config.WEB_SECRET_KEY:
|
||||
return "非法请求"
|
||||
try:
|
||||
num = int(num)
|
||||
except ValueError:
|
||||
num = 100
|
||||
|
||||
async def streaming_logs():
|
||||
with open("pagermaid.log.txt", "r", encoding="utf-8") as f:
|
||||
for log in f.readlines()[-num:]:
|
||||
yield log
|
||||
await asyncio.sleep(0.02)
|
||||
|
||||
return StreamingResponse(streaming_logs())
|
||||
|
||||
|
||||
@route.get('/run_eval')
|
||||
async def run_cmd(token: Optional[str] = Header(...), cmd: str = ''):
|
||||
if token != Config.WEB_SECRET_KEY:
|
||||
return "非法请求"
|
||||
|
||||
async def run_cmd_func():
|
||||
result = (await run_eval(cmd, only_result=True)).split("\n")
|
||||
for i in result:
|
||||
yield i + "\n"
|
||||
await asyncio.sleep(0.02)
|
||||
|
||||
return StreamingResponse(run_cmd_func()) if cmd else "无效命令"
|
||||
|
||||
|
||||
@route.get('/run_sh')
|
||||
async def run_sh(token: Optional[str] = Header(...), cmd: str = ''):
|
||||
if token != Config.WEB_SECRET_KEY:
|
||||
return "非法请求"
|
||||
|
||||
async def run_sh_func():
|
||||
result = (await execute(cmd)).split("\n")
|
||||
for i in result:
|
||||
yield i + "\n"
|
||||
await asyncio.sleep(0.02)
|
||||
|
||||
return StreamingResponse(run_sh_func()) if cmd else "无效命令"
|
||||
|
||||
|
||||
@route.get('/status', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def status():
|
||||
return (await get_status()).dict()
|
||||
|
||||
|
||||
@route.get('/status', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def status():
|
||||
return (await get_status()).dict()
|
27
pagermaid/web/api/utils.py
Normal file
27
pagermaid/web/api/utils.py
Normal file
@ -0,0 +1,27 @@
|
||||
import datetime
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import Header, HTTPException, Depends
|
||||
from jose import jwt
|
||||
|
||||
from pagermaid.config import Config
|
||||
|
||||
ALGORITHM = 'HS256'
|
||||
TOKEN_EXPIRE_MINUTES = 30
|
||||
|
||||
|
||||
def authentication():
|
||||
def inner(token: Optional[str] = Header(...)):
|
||||
try:
|
||||
jwt.decode(token, Config.WEB_SECRET_KEY, algorithms=ALGORITHM)
|
||||
except (jwt.JWTError, jwt.ExpiredSignatureError, AttributeError):
|
||||
raise HTTPException(status_code=400, detail='登录验证失败或已失效,请重新登录')
|
||||
|
||||
return Depends(inner)
|
||||
|
||||
|
||||
def create_token():
|
||||
data = {
|
||||
"exp": datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=TOKEN_EXPIRE_MINUTES),
|
||||
}
|
||||
return jwt.encode(data, Config.WEB_SECRET_KEY, algorithm=ALGORITHM)
|
24
pagermaid/web/html/__init__.py
Normal file
24
pagermaid/web/html/__init__.py
Normal file
@ -0,0 +1,24 @@
|
||||
from pathlib import Path
|
||||
|
||||
html_base_path = Path(__file__).parent
|
||||
|
||||
|
||||
def get_html(path: Path) -> str:
|
||||
"""获取 HTML 模板。"""
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def get_logo() -> str:
|
||||
"""获取 logo。"""
|
||||
return get_html(html_base_path / 'logo.html')
|
||||
|
||||
|
||||
def get_github_logo() -> str:
|
||||
"""获取 github logo。"""
|
||||
return get_html(html_base_path / 'github_logo.html')
|
||||
|
||||
|
||||
def get_footer() -> str:
|
||||
"""获取 footer。"""
|
||||
return get_html(html_base_path / 'footer.html')
|
9
pagermaid/web/html/footer.html
Normal file
9
pagermaid/web/html/footer.html
Normal file
@ -0,0 +1,9 @@
|
||||
<div class="p-2 text-center bg-blue-100">
|
||||
Copyright © 2023
|
||||
<a href="https://github.com/TeamPGM/PagerMaid-Pyro" target="_blank" class="link-secondary">
|
||||
PagerMaid-Pyro
|
||||
</a> X
|
||||
<a target="_blank" href="https://github.com/baidu/amis" class="link-secondary" rel="noopener">
|
||||
amis v2.2.0
|
||||
</a>
|
||||
</div>
|
8
pagermaid/web/html/github_logo.html
Normal file
8
pagermaid/web/html/github_logo.html
Normal file
@ -0,0 +1,8 @@
|
||||
<div class="flex justify-between">
|
||||
<div></div>
|
||||
<div>
|
||||
<a href="https://github.com/TeamPGM/PagerMaid-Pyro" target="_blank" title="Copyright">
|
||||
<i class="fa fa-github fa-2x"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
14
pagermaid/web/html/logo.html
Normal file
14
pagermaid/web/html/logo.html
Normal file
@ -0,0 +1,14 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/TeamPGM/PagerMaid-Pyro">
|
||||
<img src="https://xtaolabs.com/pagermaid-logo.png"
|
||||
width="256" height="256" alt="pagermaid">
|
||||
</a>
|
||||
</p>
|
||||
<h1 align="center">PagerMaid-Pyro 控制台</h1>
|
||||
<div align="center">
|
||||
<a href="https://github.com/TeamPGM/PagerMaid-Pyro" target="_blank">
|
||||
Github仓库</a> ·
|
||||
<a href="https://xtaolabs.com"
|
||||
target="_blank">文档</a>
|
||||
</div>
|
||||
<br>
|
2
pagermaid/web/pages/__init__.py
Normal file
2
pagermaid/web/pages/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .login import login_page
|
||||
from .main import admin_app, blank_page
|
50
pagermaid/web/pages/command_alias.py
Normal file
50
pagermaid/web/pages/command_alias.py
Normal file
@ -0,0 +1,50 @@
|
||||
from amis import Form, InputSubForm, InputText, Static, Alert, PageSchema, Page
|
||||
|
||||
main_form = Form(
|
||||
title='命令别名',
|
||||
initApi='get:/pagermaid/api/command_alias',
|
||||
api='post:/pagermaid/api/command_alias',
|
||||
submitText='保存',
|
||||
body=[
|
||||
InputSubForm(
|
||||
name='items',
|
||||
label='已设置的命令别名',
|
||||
multiple=True,
|
||||
btnLabel='${alias} >> ${command}',
|
||||
draggable=True,
|
||||
addable=True,
|
||||
removable=True,
|
||||
addButtonText='添加命令别名',
|
||||
showErrorMsg=False,
|
||||
form=Form(
|
||||
title='命令别名',
|
||||
body=[
|
||||
InputText(name='alias', label='命令别名', required=True),
|
||||
InputText(name='command', label='原命令', required=True),
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
test_form = Form(
|
||||
title='测试',
|
||||
api='get:/pagermaid/api/test_command_alias?message=${message}',
|
||||
submitText='测试',
|
||||
body=[
|
||||
InputText(name='message', label='测试消息(无需输入逗号前缀)', required=True),
|
||||
Static(className='text-red-600', name='new_msg', label='命令别名修改后消息',
|
||||
visibleOn="typeof data.new_msg !== 'undefined'")
|
||||
]
|
||||
)
|
||||
|
||||
tips = Alert(level='info')
|
||||
|
||||
page = PageSchema(
|
||||
url='/bot_config/command_alias',
|
||||
icon='fa fa-link', label='命令别名',
|
||||
schema=Page(
|
||||
title='',
|
||||
body=[tips, main_form, test_form]
|
||||
)
|
||||
)
|
178
pagermaid/web/pages/home_page.py
Normal file
178
pagermaid/web/pages/home_page.py
Normal file
@ -0,0 +1,178 @@
|
||||
from amis import Page, PageSchema, Html, Property, Service, Flex, ActionType, LevelEnum, Divider, Log, Alert, Form, \
|
||||
Dialog, Select, Group, InputText, DisplayModeEnum, Horizontal
|
||||
|
||||
from pagermaid.config import Config
|
||||
from pagermaid.web.html import get_logo
|
||||
|
||||
logo = Html(html=get_logo())
|
||||
select_log_num = Select(
|
||||
label='日志数量',
|
||||
name='log_num',
|
||||
value=100,
|
||||
options=[
|
||||
{
|
||||
'label': 100,
|
||||
'value': 100
|
||||
},
|
||||
{
|
||||
'label': 200,
|
||||
'value': 200
|
||||
},
|
||||
{
|
||||
'label': 300,
|
||||
'value': 300
|
||||
},
|
||||
{
|
||||
'label': 400,
|
||||
'value': 400
|
||||
},
|
||||
{
|
||||
'label': 500,
|
||||
'value': 500
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
log_page = Log(
|
||||
autoScroll=True,
|
||||
placeholder='暂无日志数据...',
|
||||
operation=['stop', 'showLineNumber', 'filter'],
|
||||
source={
|
||||
'method': 'get',
|
||||
'url': '/pagermaid/api/log?num=${log_num | raw}',
|
||||
'headers': {
|
||||
'token': Config.WEB_SECRET_KEY
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
cmd_input = Form(
|
||||
mode=DisplayModeEnum.horizontal,
|
||||
horizontal=Horizontal(left=0),
|
||||
wrapWithPanel=False,
|
||||
body=[
|
||||
InputText(name='command', required=True, clearable=True, addOn=ActionType.Dialog(
|
||||
label='执行',
|
||||
level=LevelEnum.primary,
|
||||
dialog=Dialog(
|
||||
title='命令执行结果',
|
||||
size='xl',
|
||||
body=Log(
|
||||
autoScroll=True,
|
||||
placeholder='执行命令中,请稍候...',
|
||||
operation=['stop', 'showLineNumber', 'filter'],
|
||||
source={
|
||||
'method': 'get',
|
||||
'url': '/pagermaid/api/run_sh?cmd=${command | raw}',
|
||||
'headers': {
|
||||
'token': Config.WEB_SECRET_KEY
|
||||
}
|
||||
}),
|
||||
)
|
||||
))
|
||||
]
|
||||
)
|
||||
eval_input = Form(
|
||||
mode=DisplayModeEnum.horizontal,
|
||||
horizontal=Horizontal(left=0),
|
||||
wrapWithPanel=False,
|
||||
body=[
|
||||
InputText(name='command', required=True, clearable=True, addOn=ActionType.Dialog(
|
||||
label='执行',
|
||||
level=LevelEnum.primary,
|
||||
dialog=Dialog(
|
||||
title='命令执行结果',
|
||||
size='xl',
|
||||
body=Log(
|
||||
autoScroll=True,
|
||||
placeholder='执行命令中,请稍候...',
|
||||
operation=['stop', 'showLineNumber', 'filter'],
|
||||
source={
|
||||
'method': 'get',
|
||||
'url': '/pagermaid/api/run_eval?cmd=${command | raw}',
|
||||
'headers': {
|
||||
'token': Config.WEB_SECRET_KEY
|
||||
}
|
||||
}),
|
||||
)
|
||||
))
|
||||
]
|
||||
)
|
||||
|
||||
operation_button = Flex(justify='center', items=[
|
||||
ActionType.Ajax(
|
||||
label='更新',
|
||||
api='/pagermaid/api/bot_update',
|
||||
confirmText='该操作会更新 PagerMaid-Pyro ,请在更新完成后手动重启,请确认执行该操作',
|
||||
level=LevelEnum.info
|
||||
),
|
||||
ActionType.Ajax(
|
||||
label='重启',
|
||||
className='m-l',
|
||||
api='/pagermaid/api/bot_restart',
|
||||
confirmText='该操作会重启 PagerMaid-Pyro ,请耐心等待重启',
|
||||
level=LevelEnum.danger
|
||||
),
|
||||
ActionType.Dialog(
|
||||
label='日志',
|
||||
className='m-l',
|
||||
level=LevelEnum.primary,
|
||||
dialog=Dialog(title='查看日志',
|
||||
size='xl',
|
||||
actions=[],
|
||||
body=[
|
||||
Alert(level=LevelEnum.info,
|
||||
body='查看最近最多500条日志,不会自动刷新,需要手动点击两次"暂停键"来进行刷新。'),
|
||||
Form(
|
||||
body=[Group(body=[select_log_num]), log_page]
|
||||
)])
|
||||
),
|
||||
ActionType.Dialog(
|
||||
label='shell',
|
||||
className='m-l',
|
||||
level=LevelEnum.warning,
|
||||
dialog=Dialog(title='shell',
|
||||
size='lg',
|
||||
actions=[],
|
||||
body=[cmd_input])
|
||||
),
|
||||
ActionType.Dialog(
|
||||
label='eval',
|
||||
className='m-l',
|
||||
level=LevelEnum.warning,
|
||||
dialog=Dialog(title='eval',
|
||||
size='lg',
|
||||
actions=[],
|
||||
body=[eval_input])
|
||||
)
|
||||
])
|
||||
|
||||
status = Service(
|
||||
api='/pagermaid/api/status',
|
||||
body=Property(
|
||||
title='运行信息',
|
||||
column=2,
|
||||
items=[
|
||||
Property.Item(
|
||||
label='Bot 运行时间',
|
||||
content='${run_time}'
|
||||
),
|
||||
Property.Item(
|
||||
label='CPU占用率',
|
||||
content='${cpu_percent}'
|
||||
),
|
||||
Property.Item(
|
||||
label='RAM占用率',
|
||||
content='${ram_percent}'
|
||||
),
|
||||
Property.Item(
|
||||
label='SWAP占用率',
|
||||
content='${swap_percent}',
|
||||
span=2
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
page_detail = Page(title='', body=[logo, operation_button, Divider(), status])
|
||||
page = PageSchema(url='/home', label='首页', icon='fa fa-home', isDefaultPage=True, schema=page_detail)
|
63
pagermaid/web/pages/ignore_groups.py
Normal file
63
pagermaid/web/pages/ignore_groups.py
Normal file
@ -0,0 +1,63 @@
|
||||
from amis import InputText, Switch, Card, Tpl, CardsCRUD, PageSchema, Page, Button, Select
|
||||
|
||||
card = Card(
|
||||
header=Card.Header(
|
||||
title='$title',
|
||||
description='$id',
|
||||
avatarText='$title',
|
||||
avatarTextClassName='overflow-hidden'
|
||||
),
|
||||
actions=[],
|
||||
toolbar=[
|
||||
Switch(
|
||||
name='enable',
|
||||
value='${status}',
|
||||
onText='已忽略',
|
||||
offText='未忽略',
|
||||
onEvent={
|
||||
'change': {
|
||||
'actions': {
|
||||
'actionType': 'ajax',
|
||||
'args': {
|
||||
'api': {
|
||||
'url': '/pagermaid/api/set_ignore_group_status',
|
||||
'method': 'post'
|
||||
},
|
||||
'messages': {
|
||||
'success': '成功${IF(event.data.value, "忽略", "取消忽略")}了 ${title}',
|
||||
'failed': '操作失败'
|
||||
},
|
||||
'status': '${event.data.value}',
|
||||
'id': '${id}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
cards_curd = CardsCRUD(
|
||||
mode='cards',
|
||||
title='',
|
||||
syncLocation=False,
|
||||
api='/pagermaid/api/get_ignore_group_list',
|
||||
loadDataOnce=True,
|
||||
source='${groups | filter:title:match:keywords_name}',
|
||||
filter={
|
||||
'body': [
|
||||
InputText(name='keywords_name', label='群组名')
|
||||
]
|
||||
},
|
||||
perPage=12,
|
||||
autoJumpToTopOnPagerChange=True,
|
||||
placeholder='群组列表为空',
|
||||
footerToolbar=['switch-per-page', 'pagination'],
|
||||
columnsCount=3,
|
||||
card=card
|
||||
)
|
||||
page = PageSchema(
|
||||
url='/bot_config/ignore_groups',
|
||||
icon='fa fa-ban',
|
||||
label='忽略群组',
|
||||
schema=Page(title='忽略群组', subTitle="忽略后,Bot 不再响应指定群组的消息(群组列表将会缓存一小时)", body=cards_curd)
|
||||
)
|
32
pagermaid/web/pages/login.py
Normal file
32
pagermaid/web/pages/login.py
Normal file
@ -0,0 +1,32 @@
|
||||
from amis import Form, InputPassword, DisplayModeEnum, Horizontal, Remark, Html, Page, AmisAPI, Wrapper
|
||||
|
||||
from pagermaid.web.html import get_logo
|
||||
|
||||
logo = Html(html=get_logo())
|
||||
login_api = AmisAPI(
|
||||
url='/pagermaid/api/login',
|
||||
method='post',
|
||||
adaptor='''
|
||||
if (payload.status == 0) {
|
||||
localStorage.setItem("token", payload.data.token);
|
||||
}
|
||||
return payload;
|
||||
'''
|
||||
)
|
||||
|
||||
login_form = Form(
|
||||
api=login_api,
|
||||
title='',
|
||||
body=[
|
||||
InputPassword(
|
||||
name='password',
|
||||
label='密码',
|
||||
labelRemark=Remark(shape='circle', content='登录密码')
|
||||
),
|
||||
],
|
||||
mode=DisplayModeEnum.horizontal,
|
||||
horizontal=Horizontal(left=3, right=9, offset=5),
|
||||
redirect='/admin',
|
||||
)
|
||||
body = Wrapper(className='w-2/5 mx-auto my-0 m:w-full', body=login_form)
|
||||
login_page = Page(title='', body=[logo, body])
|
32
pagermaid/web/pages/main.py
Normal file
32
pagermaid/web/pages/main.py
Normal file
@ -0,0 +1,32 @@
|
||||
from amis import App, PageSchema, Tpl, Page, Flex
|
||||
|
||||
from pagermaid.web.html import get_footer, get_github_logo
|
||||
from pagermaid.web.pages.command_alias import page as command_alias_page
|
||||
from pagermaid.web.pages.ignore_groups import page as ignore_groups_page
|
||||
from pagermaid.web.pages.home_page import page as home_page
|
||||
from pagermaid.web.pages.plugin_local_manage import page as plugin_local_manage_page
|
||||
from pagermaid.web.pages.plugin_remote_manage import page as plugin_remote_manage_page
|
||||
|
||||
github_logo = Tpl(
|
||||
className='w-full',
|
||||
tpl=get_github_logo(),
|
||||
)
|
||||
header = Flex(className='w-full', justify='flex-end', alignItems='flex-end', items=[github_logo])
|
||||
admin_app = App(
|
||||
brandName='pagermaid',
|
||||
logo='https://xtaolabs.com/pagermaid-logo.png',
|
||||
header=header,
|
||||
pages=[
|
||||
{
|
||||
'children': [
|
||||
home_page,
|
||||
PageSchema(label='Bot 设置', icon='fa fa-wrench',
|
||||
children=[command_alias_page, ignore_groups_page]),
|
||||
PageSchema(label='插件管理', icon='fa fa-cube',
|
||||
children=[plugin_local_manage_page, plugin_remote_manage_page]),
|
||||
]
|
||||
}
|
||||
],
|
||||
footer=get_footer(),
|
||||
)
|
||||
blank_page = Page(title='PagerMaid-Pyro 404', body='404')
|
64
pagermaid/web/pages/plugin_local_manage.py
Normal file
64
pagermaid/web/pages/plugin_local_manage.py
Normal file
@ -0,0 +1,64 @@
|
||||
from amis import InputText, Switch, Card, CardsCRUD, PageSchema, Page
|
||||
|
||||
card = Card(
|
||||
header=Card.Header(
|
||||
title='$name',
|
||||
avatarText='$name',
|
||||
avatarTextClassName='overflow-hidden'
|
||||
),
|
||||
actions=[],
|
||||
toolbar=[
|
||||
Switch(
|
||||
name='enable',
|
||||
value='${status}',
|
||||
onText='启用',
|
||||
offText='禁用',
|
||||
onEvent={
|
||||
'change': {
|
||||
'actions': [
|
||||
{
|
||||
'actionType': 'ajax',
|
||||
'args': {
|
||||
'api': {
|
||||
'url': '/pagermaid/api/set_local_plugin_status',
|
||||
'method': 'post'
|
||||
},
|
||||
'messages': {
|
||||
'success': '成功${IF(event.data.value, "开启", "禁用")}了 ${name}',
|
||||
'failed': '操作失败'
|
||||
},
|
||||
'status': '${event.data.value}',
|
||||
'plugin': '${name}'
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
cards_curd = CardsCRUD(
|
||||
mode='cards',
|
||||
title='',
|
||||
syncLocation=False,
|
||||
api='/pagermaid/api/get_local_plugins',
|
||||
loadDataOnce=True,
|
||||
source='${rows | filter:name:match:keywords_name}',
|
||||
filter={
|
||||
'body': [
|
||||
InputText(name='keywords_name', label='插件名')
|
||||
]
|
||||
},
|
||||
perPage=12,
|
||||
autoJumpToTopOnPagerChange=True,
|
||||
placeholder='暂无插件信息',
|
||||
footerToolbar=['switch-per-page', 'pagination'],
|
||||
columnsCount=3,
|
||||
card=card
|
||||
)
|
||||
page = PageSchema(
|
||||
url='/plugins/local',
|
||||
icon='fa fa-database',
|
||||
label='本地插件管理',
|
||||
schema=Page(title='本地插件管理', body=cards_curd)
|
||||
)
|
64
pagermaid/web/pages/plugin_remote_manage.py
Normal file
64
pagermaid/web/pages/plugin_remote_manage.py
Normal file
@ -0,0 +1,64 @@
|
||||
from amis import InputText, Switch, Card, Tpl, CardsCRUD, PageSchema, Page, Button
|
||||
|
||||
card = Card(
|
||||
header=Card.Header(
|
||||
title='$name',
|
||||
description='$des',
|
||||
avatarText='$name',
|
||||
avatarTextClassName='overflow-hidden'
|
||||
),
|
||||
actions=[],
|
||||
toolbar=[
|
||||
Switch(
|
||||
name='enable',
|
||||
value='${status}',
|
||||
onText='已安装',
|
||||
offText='未安装',
|
||||
onEvent={
|
||||
'change': {
|
||||
'actions': {
|
||||
'actionType': 'ajax',
|
||||
'args': {
|
||||
'api': {
|
||||
'url': '/pagermaid/api/set_remote_plugin_status',
|
||||
'method': 'post'
|
||||
},
|
||||
'messages': {
|
||||
'success': '成功${IF(event.data.value, "安装", "卸载")}了 ${name}',
|
||||
'failed': '操作失败'
|
||||
},
|
||||
'status': '${event.data.value}',
|
||||
'plugin': '${name}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
cards_curd = CardsCRUD(
|
||||
mode='cards',
|
||||
title='',
|
||||
syncLocation=False,
|
||||
api='/pagermaid/api/get_remote_plugins',
|
||||
loadDataOnce=True,
|
||||
source='${rows | filter:name:match:keywords_name | filter:des:match:keywords_description}',
|
||||
filter={
|
||||
'body': [
|
||||
InputText(name='keywords_name', label='插件名'),
|
||||
InputText(name='keywords_description', label='插件描述')
|
||||
]
|
||||
},
|
||||
perPage=12,
|
||||
autoJumpToTopOnPagerChange=True,
|
||||
placeholder='暂无插件信息',
|
||||
footerToolbar=['switch-per-page', 'pagination'],
|
||||
columnsCount=3,
|
||||
card=card
|
||||
)
|
||||
page = PageSchema(
|
||||
url='/plugins/remote',
|
||||
icon='fa fa-cloud-download',
|
||||
label='插件仓库',
|
||||
schema=Page(title='插件仓库', body=cards_curd)
|
||||
)
|
@ -29,6 +29,7 @@ from pyrogram.enums import ChatType
|
||||
|
||||
from pagermaid.single_utils import get_sudo_list, Message
|
||||
from pagermaid.scheduler import add_delete_message_job
|
||||
from ..methods.get_dialogs_list import get_dialogs_list as get_dialogs_list_func
|
||||
|
||||
from ..utils import patch, patchable
|
||||
from ..utils.conversation import Conversation
|
||||
@ -123,6 +124,10 @@ class Client:
|
||||
)
|
||||
return await self.oldread_chat_history(chat_id, max_id) # noqa
|
||||
|
||||
@patchable
|
||||
async def get_dialogs_list(self: "Client"):
|
||||
return await get_dialogs_list_func(self)
|
||||
|
||||
|
||||
@patch(pyrogram.handlers.message_handler.MessageHandler)
|
||||
class MessageHandler:
|
||||
|
16
pyromod/methods/get_dialogs_list.py
Normal file
16
pyromod/methods/get_dialogs_list.py
Normal file
@ -0,0 +1,16 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from pyrogram import Client
|
||||
from pyrogram.enums import ChatType
|
||||
|
||||
from pagermaid.common.cache import cache
|
||||
|
||||
|
||||
@cache(ttl=timedelta(hours=1))
|
||||
async def get_dialogs_list(client: Client):
|
||||
dialogs = []
|
||||
async for dialog in client.get_dialogs():
|
||||
dialogs.append(dialog)
|
||||
if dialog.chat.type == ChatType.SUPERGROUP:
|
||||
break
|
||||
return dialogs
|
@ -5,10 +5,16 @@ pytz>=2021.3
|
||||
PyYAML>=6.0
|
||||
coloredlogs>=15.0.1
|
||||
psutil>=5.8.0
|
||||
httpx
|
||||
apscheduler
|
||||
sqlitedict
|
||||
httpx~=0.23.3
|
||||
apscheduler~=3.9.1.post1
|
||||
sqlitedict~=2.1.0
|
||||
casbin==1.17.5
|
||||
sentry-sdk==1.13.0
|
||||
sentry-sdk==1.14.0
|
||||
PyQRCode>=1.2.1
|
||||
PyPng
|
||||
fastapi~=0.89.1
|
||||
amis-python
|
||||
python-jose
|
||||
uvicorn
|
||||
pydantic~=1.10.4
|
||||
starlette~=0.22.0
|
||||
|
Loading…
Reference in New Issue
Block a user