mirror of
https://github.com/xuthus83/LittlePaimon.git
synced 2024-10-21 16:27:15 +08:00
🎉 Web UI
管理页面
This commit is contained in:
parent
46ed8f893c
commit
c95b0df400
@ -7,7 +7,7 @@ from LittlePaimon.utils.migration import migrate_database
|
||||
from LittlePaimon.utils.tool import check_resource
|
||||
|
||||
DRIVER = get_driver()
|
||||
__version__ = '3.0.0beta9'
|
||||
__version__ = '3.0.0rc1'
|
||||
|
||||
try:
|
||||
SUPERUSERS: List[int] = [int(s) for s in DRIVER.config.superusers]
|
||||
@ -33,7 +33,7 @@ logo = """<g>
|
||||
async def startup():
|
||||
logger.opt(colors=True).info(logo)
|
||||
await database.connect()
|
||||
from LittlePaimon import admin
|
||||
from LittlePaimon import web
|
||||
# await migrate_database()
|
||||
await check_resource()
|
||||
|
||||
|
@ -1,12 +0,0 @@
|
||||
import nonebot
|
||||
from fastapi import FastAPI
|
||||
from LittlePaimon.utils import logger
|
||||
from LittlePaimon.manager.plugin_manager import plugin_manager
|
||||
|
||||
app: FastAPI = nonebot.get_app()
|
||||
|
||||
if plugin_manager.config.CookieWeb_enable:
|
||||
from pywebio.platform.fastapi import webio_routes
|
||||
from .bind_cookie import bind_cookie_page
|
||||
logger.info('原神Cookie', f'<g>启用CookieWeb成功,地址{plugin_manager.config.CookieWeb_url}</g>')
|
||||
app.mount('/LittlePaimon/cookie', FastAPI(routes=webio_routes(bind_cookie_page)))
|
@ -1,62 +0,0 @@
|
||||
import datetime
|
||||
|
||||
from pywebio.input import *
|
||||
from pywebio.output import *
|
||||
from pywebio.platform import config
|
||||
from pywebio.session import run_asyncio_coroutine
|
||||
|
||||
from LittlePaimon.utils import logger
|
||||
from LittlePaimon.database.models import LastQuery, PrivateCookie
|
||||
from LittlePaimon.utils.api import get_bind_game_info, get_stoken_by_cookie
|
||||
|
||||
css_style = 'body {background: #000000 url(https://static.cherishmoon.fun/blog/h-wallpaper/zhounianqing.png);} #input-container {background: rgba(0,0,0,0);} summary {background-color: rgba(255,255,255,1);} .markdown-body {background-color: rgba(255,255,255,1);}'
|
||||
|
||||
|
||||
@config(title='小派蒙绑定原神Cookie', description='绑定Cookie', css_style=css_style)
|
||||
async def bind_cookie_page():
|
||||
with put_collapse('获取Cookie的方法'):
|
||||
put_markdown('**重要提醒**:Cookie的作用相当于账号密码,非常重要,如是非可信任的机器人,请勿绑定!!')
|
||||
put_markdown('**方法**:详见[Cookie获取教程](https://docs.qq.com/doc/DQ3JLWk1vQVllZ2Z1)')
|
||||
data = await input_group('绑定Cookie', [
|
||||
input('QQ号', name='qq', required=True, validate=is_qq_number),
|
||||
textarea('米游社Cookie', name='cookie', required=True),
|
||||
checkbox(name='confirm', options=['我已知晓Cookie的重要性,确认绑定'], validate=is_confirm)
|
||||
])
|
||||
result = await run_asyncio_coroutine(bind_cookie(data))
|
||||
popup('绑定结果', put_markdown(result))
|
||||
|
||||
|
||||
def is_qq_number(qq: str):
|
||||
if not qq.isdigit():
|
||||
return '必须是合法的QQ号'
|
||||
|
||||
|
||||
def is_cookie(cookie: str):
|
||||
if 'cookie_token' not in cookie or all(i not in cookie for i in ['account_id', 'login_uid', 'ltuid', 'stuid']):
|
||||
return 'Cookie必须包含cookie_token以及account_id、login_uid、ltuid、stuid其中之一'
|
||||
|
||||
|
||||
def is_confirm(confirm: list):
|
||||
if not confirm:
|
||||
return '请先勾选确认'
|
||||
|
||||
|
||||
async def bind_cookie(data: dict):
|
||||
if result := await get_bind_game_info(data['cookie']):
|
||||
game_name = result['nickname']
|
||||
game_uid = result['game_role_id']
|
||||
mys_id = result['mys_id']
|
||||
await LastQuery.update_or_create(user_id=str(data['qq']),
|
||||
defaults={'uid': game_uid, 'last_time': datetime.datetime.now()})
|
||||
logger.info('原神Cookie', '', {'用户': str(data['qq']), 'uid': game_uid}, '成功绑定cookie', True)
|
||||
if 'login_ticket' in data['cookie'] and (stoken := await get_stoken_by_cookie(data['cookie'])):
|
||||
await PrivateCookie.update_or_create(user_id=str(data['qq']), uid=game_uid, mys_id=mys_id,
|
||||
defaults={'cookie': data['cookie'],
|
||||
'stoken': f'stuid={mys_id};stoken={stoken};'})
|
||||
return f'QQ`{data["qq"]}`成功绑定原神玩家`{game_name}`-UID`{game_uid}`'
|
||||
else:
|
||||
await PrivateCookie.update_or_create(user_id=str(data['qq']), uid=game_uid, mys_id=mys_id,
|
||||
defaults={'cookie': data['cookie']})
|
||||
return f'QQ`{data["qq"]}`成功绑定原神玩家`{game_name}`-UID`{game_uid}`\n但cookie中没有`login_ticket`或`login_ticket`无效,`米游币相关功能`无法使用哦'
|
||||
else:
|
||||
return '这个cookie**无效**,请确认是否正确\n请重新获取cookie后**刷新**本页面再次绑定'
|
@ -83,9 +83,8 @@ class PluginManager:
|
||||
await asyncio.gather(*[PluginPermission.update_or_create(name=plugin.name, session_id=user['user_id'],
|
||||
session_type='user') for user in user_list])
|
||||
if plugin.name not in hidden_plugins:
|
||||
metadata = plugin.metadata
|
||||
if plugin.name not in self.data:
|
||||
if metadata:
|
||||
if metadata := plugin.metadata:
|
||||
self.data[plugin.name] = PluginInfo.parse_obj({
|
||||
'name': metadata.name,
|
||||
'module_name': plugin.name,
|
||||
@ -122,3 +121,14 @@ class PluginManager:
|
||||
plugin.matchers.sort(key=lambda x: x.pm_priority)
|
||||
plugin.matchers = [m for m in plugin.matchers if m.pm_show and m.pm_usage]
|
||||
return plugin_list
|
||||
|
||||
async def get_plugin_list_for_admin(self) -> List[dict]:
|
||||
load_plugins = nb_plugin.get_loaded_plugins()
|
||||
load_plugins = [p.name for p in load_plugins]
|
||||
plugin_list = [p.dict() for p in self.data.values()]
|
||||
for plugin in plugin_list:
|
||||
plugin['matchers'].sort(key=lambda x: x['pm_priority'])
|
||||
plugin['isLoad'] = plugin['module_name'] in load_plugins
|
||||
plugin['status'] = await PluginPermission.filter(name=plugin['module_name'], status=True).exists()
|
||||
plugin_list.sort(key=lambda x: (x['isLoad'], x['status'], -x['priority']), reverse=True)
|
||||
return plugin_list
|
||||
|
@ -71,3 +71,7 @@ class Config(BaseModel):
|
||||
screenshot_enable: bool = Field(True, alias='启用网页截图权限')
|
||||
|
||||
guess_voice_time: int = Field(30, alias='原神猜语音时间')
|
||||
|
||||
admin_enable: bool = Field(True, alias='启用Web端')
|
||||
admin_password: str = Field('admin', alias='Web端管理员密码')
|
||||
secret_key: str = Field('49c294d32f69b732ef6447c18379451ce1738922a75cd1d4812ef150318a2ed0', alias='Web端token密钥')
|
||||
|
@ -11,7 +11,7 @@ from LittlePaimon.utils.brower import screenshot
|
||||
async def permission_check(event: MessageEvent) -> bool:
|
||||
if pm.config.screenshot_enable:
|
||||
return True
|
||||
return event.user_id not in SUPERUSERS and event.sender.role not in ['admin', 'owner']
|
||||
return event.user_id not in SUPERUSERS
|
||||
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
|
53
LittlePaimon/utils/status.py
Normal file
53
LittlePaimon/utils/status.py
Normal file
@ -0,0 +1,53 @@
|
||||
import asyncio
|
||||
import datetime
|
||||
import psutil
|
||||
from nonebot import get_bot
|
||||
|
||||
from LittlePaimon import DRIVER, NICKNAME
|
||||
|
||||
start_time: str
|
||||
|
||||
|
||||
async def get_status():
|
||||
status_result = {
|
||||
'nickname': NICKNAME
|
||||
}
|
||||
try:
|
||||
status_result['start_time'] = start_time
|
||||
except Exception:
|
||||
status_result['start_time'] = '未知'
|
||||
try:
|
||||
bot = get_bot()
|
||||
bot_status = await bot.get_status()
|
||||
status_result['bot_id'] = bot.self_id
|
||||
if bot_status := bot_status.get('stat'):
|
||||
print(bot_status)
|
||||
status_result['msg_received'] = bot_status.get('message_received', '未知')
|
||||
status_result['msg_sent'] = bot_status.get('message_sent', '未知')
|
||||
except Exception:
|
||||
status_result['bot_id'] = '未知'
|
||||
status_result['msg_received'] = '未知'
|
||||
status_result['msg_sent'] = '未知'
|
||||
|
||||
status_result['system_start_time'] = datetime.datetime.fromtimestamp(psutil.boot_time()).strftime(
|
||||
"%Y-%m-%d %H:%M:%S")
|
||||
|
||||
psutil.cpu_percent()
|
||||
await asyncio.sleep(0.1)
|
||||
cpu_percent = psutil.cpu_percent()
|
||||
# cpu_count = psutil.cpu_count(logical=False)
|
||||
# cpu_count_logical = psutil.cpu_count()
|
||||
# cpu_freq = psutil.cpu_freq()
|
||||
ram_stat = psutil.virtual_memory()
|
||||
swap_stat = psutil.swap_memory()
|
||||
status_result['cpu_percent'] = f'{cpu_percent}%'
|
||||
status_result['ram_percent'] = f'{ram_stat.percent}%'
|
||||
status_result['swap_percent'] = f'{swap_stat.percent}%'
|
||||
|
||||
return status_result
|
||||
|
||||
|
||||
@DRIVER.on_startup
|
||||
async def start_up():
|
||||
global start_time
|
||||
start_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
@ -1,5 +1,8 @@
|
||||
import asyncio
|
||||
import datetime
|
||||
import functools
|
||||
import hashlib
|
||||
import inspect
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from LittlePaimon.utils import aiorequests, logger
|
||||
@ -45,6 +48,37 @@ class FreqLimiter:
|
||||
freq_limiter = FreqLimiter()
|
||||
|
||||
|
||||
def cache(ttl=datetime.timedelta(hours=1)):
|
||||
"""
|
||||
缓存装饰器
|
||||
:param ttl: 过期时间
|
||||
"""
|
||||
def wrap(func):
|
||||
cache_data = {}
|
||||
|
||||
@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()])
|
||||
default_data = {"time": None, "value": None}
|
||||
data = cache_data.get(ins_key, default_data)
|
||||
now = datetime.datetime.now()
|
||||
if not data['time'] or now - data['time'] > ttl:
|
||||
try:
|
||||
data['value'] = await func(*args, **kw)
|
||||
data['time'] = now
|
||||
cache_data[ins_key] = data
|
||||
except Exception as e:
|
||||
raise e
|
||||
return data['value']
|
||||
|
||||
return wrapped
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
async def check_resource():
|
||||
logger.info('资源检查', '开始检查资源')
|
||||
resource_list = await aiorequests.get('http://img.genshin.cherishmoon.fun/resources/resources_list')
|
||||
@ -59,7 +93,8 @@ async def check_resource():
|
||||
file_path.unlink()
|
||||
flag = True
|
||||
try:
|
||||
await aiorequests.download(url=f'http://img.genshin.cherishmoon.fun/resources/{resource["path"]}', save_path=file_path)
|
||||
await aiorequests.download(url=f'http://img.genshin.cherishmoon.fun/resources/{resource["path"]}',
|
||||
save_path=file_path)
|
||||
await asyncio.sleep(0.5)
|
||||
except Exception as e:
|
||||
logger.warning('资源检查', f'下载<m>{resource.split("/")[-1]}</m>时<r>出错: {e}</r>')
|
||||
@ -67,5 +102,3 @@ async def check_resource():
|
||||
logger.info('资源检查', '<g>资源下载完成</g>')
|
||||
else:
|
||||
logger.info('资源检查', '<g>资源完好,无需下载</g>')
|
||||
|
||||
|
||||
|
56
LittlePaimon/web/__init__.py
Normal file
56
LittlePaimon/web/__init__.py
Normal file
@ -0,0 +1,56 @@
|
||||
import nonebot
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import HTMLResponse
|
||||
from LittlePaimon.utils import logger
|
||||
from .pages import admin_app, login_page, bind_cookie_page, blank_page
|
||||
from .api import BaseApiRouter
|
||||
|
||||
from LittlePaimon.manager.plugin_manager import plugin_manager
|
||||
|
||||
app: FastAPI = nonebot.get_app()
|
||||
app.include_router(BaseApiRouter)
|
||||
|
||||
logger.info('Web UI', '<g>启用成功</g>,默认地址为https://127.0.0.1:13579/LittlePaimon/login')
|
||||
|
||||
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 = '/LittlePaimon/login'
|
||||
window.localStorage.clear()
|
||||
window.sessionStorage.clear()
|
||||
window.alert('登录验证失败或已失效,请重新登录')
|
||||
}
|
||||
return payload
|
||||
},
|
||||
'''
|
||||
|
||||
|
||||
@app.get('/LittlePaimon/admin', response_class=HTMLResponse)
|
||||
async def admin():
|
||||
if plugin_manager.config.admin_enable:
|
||||
return admin_app.render(site_title='LittlePaimon 后台管理', theme='antd', requestAdaptor=requestAdaptor,
|
||||
responseAdaptor=responseAdaptor)
|
||||
else:
|
||||
return blank_page.render(site_title='LittlePaimon')
|
||||
|
||||
|
||||
@app.get('/LittlePaimon/login', response_class=HTMLResponse)
|
||||
async def login():
|
||||
if plugin_manager.config.admin_enable:
|
||||
return login_page.render(site_title='登录 | LittlePaimon 后台管理', theme='antd')
|
||||
else:
|
||||
return blank_page.render(site_title='LittlePaimon')
|
||||
|
||||
|
||||
@app.get('/LittlePaimon/cookie', response_class=HTMLResponse)
|
||||
async def bind_cookie():
|
||||
if plugin_manager.config.CookieWeb_enable:
|
||||
return bind_cookie_page.render(site_title='绑定Cookie | LittlePaimon')
|
||||
else:
|
||||
return blank_page.render(site_title='LittlePaimon')
|
17
LittlePaimon/web/api/__init__.py
Normal file
17
LittlePaimon/web/api/__init__.py
Normal file
@ -0,0 +1,17 @@
|
||||
from fastapi import APIRouter
|
||||
from .cookie import route as cookie_route
|
||||
from .plugin import route as plugin_route
|
||||
from .bot_info import route as bot_info_route
|
||||
from .status import route as status_route
|
||||
from .login import route as login_route
|
||||
from .utils import authentication
|
||||
# from .learning_chat import route as chat_route
|
||||
|
||||
BaseApiRouter = APIRouter(prefix='/LittlePaimon/api')
|
||||
|
||||
BaseApiRouter.include_router(cookie_route)
|
||||
BaseApiRouter.include_router(plugin_route)
|
||||
BaseApiRouter.include_router(bot_info_route)
|
||||
BaseApiRouter.include_router(status_route)
|
||||
BaseApiRouter.include_router(login_route)
|
||||
# BaseApiRouter.include_router(chat_route)
|
105
LittlePaimon/web/api/bot_info.py
Normal file
105
LittlePaimon/web/api/bot_info.py
Normal file
@ -0,0 +1,105 @@
|
||||
import asyncio
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi import APIRouter
|
||||
from nonebot import get_bot
|
||||
from nonebot.adapters.onebot.v11 import Bot
|
||||
from LittlePaimon import SUPERUSERS
|
||||
from LittlePaimon.manager.bot_manager.handler import update
|
||||
from LittlePaimon.utils.files import save_json
|
||||
from LittlePaimon.utils.tool import cache
|
||||
from .utils import authentication
|
||||
|
||||
route = APIRouter()
|
||||
|
||||
|
||||
@route.get('/get_group_list', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_group_list():
|
||||
bot: Bot = get_bot()
|
||||
return await bot.get_group_list()
|
||||
|
||||
|
||||
@route.get('/get_group_members', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_group_members(group_id: int):
|
||||
bot: Bot = get_bot()
|
||||
return await bot.get_group_member_list(group_id=group_id)
|
||||
|
||||
|
||||
@route.get('/get_groups_and_members', response_class=JSONResponse, dependencies=[authentication()])
|
||||
@cache(datetime.timedelta(minutes=10))
|
||||
async def get_groups_and_members():
|
||||
result = []
|
||||
try:
|
||||
bot: Bot = get_bot()
|
||||
except Exception:
|
||||
return {
|
||||
'status': -100,
|
||||
'msg': '获取群和好友列表失败,请确认已连接GOCQ'
|
||||
}
|
||||
group_list = await bot.get_group_list()
|
||||
friend_list = await bot.get_friend_list()
|
||||
for group in group_list:
|
||||
group_members = await bot.get_group_member_list(group_id=group['group_id'])
|
||||
result.append({
|
||||
'left_label': f'{group["group_name"]}({group["group_id"]})',
|
||||
'right_label': f'{group["group_name"]}(群{group["group_id"]})',
|
||||
'value': f'群{group["group_id"]}',
|
||||
'children': [{'left_label': f'{m["card"] or m["nickname"]}({m["user_id"]})',
|
||||
'right_label': f'群({group["group_name"]}) - {m["card"] or m["nickname"]}({m["user_id"]})',
|
||||
'value': f'群{group["group_id"]}.{m["user_id"]}'} for m in group_members if
|
||||
str(m['user_id']) != bot.self_id]
|
||||
})
|
||||
await asyncio.sleep(0.2)
|
||||
result = [
|
||||
{
|
||||
'label': '群组',
|
||||
'selectMode': 'tree',
|
||||
'searchable': True,
|
||||
'children': result
|
||||
},
|
||||
{
|
||||
'label': '好友',
|
||||
'selectMode': 'list',
|
||||
'searchable': True,
|
||||
'children': [{
|
||||
'left_label': f'{f["nickname"]}({f["user_id"]})',
|
||||
'right_label': f'{f["nickname"]}({f["user_id"]})',
|
||||
'value': f'{f["user_id"]}'
|
||||
} for f in friend_list if str(f['user_id']) != bot.self_id]
|
||||
}]
|
||||
return result
|
||||
|
||||
|
||||
@route.get('/get_friend_list', response_class=JSONResponse, dependencies=[authentication()])
|
||||
@cache(datetime.timedelta(minutes=10))
|
||||
async def get_friend_list():
|
||||
try:
|
||||
bot: Bot = get_bot()
|
||||
friend_list = await bot.get_friend_list()
|
||||
return [{'label': f'{friend["nickname"]}({friend["user_id"]})', 'value': friend['user_id']} for friend in
|
||||
friend_list]
|
||||
except Exception:
|
||||
return {'status': 100, 'msg': '获取好友列表失败'}
|
||||
|
||||
|
||||
@route.post('/bot_update', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def bot_update():
|
||||
result = await update()
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': result
|
||||
}
|
||||
|
||||
|
||||
@route.post('/bot_restart', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def bot_restart():
|
||||
save_json({'session_type': 'private',
|
||||
'session_id': SUPERUSERS[0]},
|
||||
Path() / 'rebooting.json')
|
||||
if sys.argv[0].endswith('nb'):
|
||||
sys.argv[0] = 'bot.py'
|
||||
os.execv(sys.executable, ['python'] + sys.argv)
|
111
LittlePaimon/web/api/cookie.py
Normal file
111
LittlePaimon/web/api/cookie.py
Normal file
@ -0,0 +1,111 @@
|
||||
import datetime
|
||||
from typing import Optional
|
||||
from LittlePaimon.database.models import PublicCookie, PrivateCookie, LastQuery
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel
|
||||
|
||||
from LittlePaimon.utils.api import get_bind_game_info, get_stoken_by_cookie
|
||||
from .utils import authentication
|
||||
|
||||
route = APIRouter()
|
||||
|
||||
|
||||
class BindCookie(BaseModel):
|
||||
user_id: int
|
||||
cookie: str
|
||||
|
||||
|
||||
@route.post('/bind_cookie', response_class=JSONResponse)
|
||||
async def bind_cookie(data: BindCookie):
|
||||
if game_info := await get_bind_game_info(data.cookie):
|
||||
game_uid = game_info['game_role_id']
|
||||
mys_id = game_info['mys_id']
|
||||
await LastQuery.update_or_create(user_id=data.user_id,
|
||||
defaults={'uid': game_uid, 'last_time': datetime.datetime.now()})
|
||||
if 'login_ticket' in data.cookie and (stoken := await get_stoken_by_cookie(data.cookie)):
|
||||
await PrivateCookie.update_or_create(user_id=data.user_id, uid=game_uid, mys_id=mys_id,
|
||||
defaults={'cookie': data.cookie,
|
||||
'stoken': f'stuid={mys_id};stoken={stoken};'})
|
||||
return {'status': 0, 'msg': f'QQ{data.user_id}的UID{game_uid}的Cookie以及Stoken绑定成功。'}
|
||||
else:
|
||||
await PrivateCookie.update_or_create(user_id=data.user_id, uid=game_uid, mys_id=mys_id,
|
||||
defaults={'cookie': data.cookie})
|
||||
return {'status': 0, 'msg': f'QQ{data.user_id}的UID{game_uid}绑定Cookie成功,但未绑定stoken。'}
|
||||
else:
|
||||
return {'status': 200, 'msg': '该Cookie无效,请根据教程重新获取'}
|
||||
|
||||
|
||||
@route.get('/get_public_cookies', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_public_cookies(status: Optional[int] = None):
|
||||
if status is None:
|
||||
return await PublicCookie.all().values()
|
||||
else:
|
||||
return await PublicCookie.filter(status=status).values()
|
||||
|
||||
|
||||
@route.get('/get_private_cookies', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_private_cookies(page: int = 1, perPage: int = 10, user_id: Optional[str] = None,
|
||||
uid: Optional[str] = None, mys_id: Optional[str] = None, status: Optional[int] = None):
|
||||
query = {'user_id__contains': user_id} if user_id else {'uid__contains': uid} if uid else {
|
||||
'mys_id__contains': mys_id} if mys_id else {}
|
||||
if status is not None:
|
||||
query['status'] = status
|
||||
data = await PrivateCookie.filter(**query).offset((page - 1) * perPage).limit(perPage).values()
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'items': data,
|
||||
'total': await PrivateCookie.filter(**query).count()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@route.get('/get_private_cookie', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_private_cookie(id: int):
|
||||
return await PrivateCookie.get_or_none(id=id).values()
|
||||
|
||||
|
||||
@route.delete('/delete_public_cookie', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def delete_public_cookie(id: int):
|
||||
await PublicCookie.filter(id=id).delete()
|
||||
return {'status': 0, 'msg': f'{id}号公共Cookie删除成功'}
|
||||
|
||||
|
||||
@route.delete('/delete_private_cookie', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def delete_private_cookie(id: int):
|
||||
await PrivateCookie.filter(id=id).delete()
|
||||
return {'status': 0, 'msg': f'{id}号私人Cookie删除成功'}
|
||||
|
||||
|
||||
@route.post('/add_public_cookie', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def add_public_cookie(data: dict):
|
||||
cookie = data.get('cookie')
|
||||
if not cookie:
|
||||
return {'status': 100, 'msg': '参数错误'}
|
||||
if await get_bind_game_info(cookie, True):
|
||||
new_cookie = await PublicCookie.create(cookie=cookie)
|
||||
return {'status': 0, 'msg': f'{new_cookie.id}号公共Cookie添加成功'}
|
||||
else:
|
||||
return {'status': 200, 'msg': '该Cookie无效'}
|
||||
|
||||
|
||||
@route.post('/save_private_cookie', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def save_private_cookie(data: BindCookie):
|
||||
if game_info := await get_bind_game_info(data.cookie):
|
||||
game_uid = game_info['game_role_id']
|
||||
mys_id = game_info['mys_id']
|
||||
await LastQuery.update_or_create(user_id=data.user_id,
|
||||
defaults={'uid': game_uid, 'last_time': datetime.datetime.now()})
|
||||
if 'login_ticket' in data.cookie and (stoken := await get_stoken_by_cookie(data.cookie)):
|
||||
await PrivateCookie.update_or_create(user_id=data.user_id, uid=game_uid, mys_id=mys_id,
|
||||
defaults={'cookie': data.cookie,
|
||||
'stoken': f'stuid={mys_id};stoken={stoken};'})
|
||||
return {'status': 0, 'msg': f'QQ{data.user_id}的UID{game_uid}的Cookie以及Stoken添加/保存成功。'}
|
||||
else:
|
||||
await PrivateCookie.update_or_create(user_id=data.user_id, uid=game_uid, mys_id=mys_id,
|
||||
defaults={'cookie': data.cookie})
|
||||
return {'status': 0, 'msg': f'QQ{data.user_id}的UID{game_uid}的Cookie添加/保存成功,但未绑定stoken。'}
|
||||
else:
|
||||
return {'status': 200, 'msg': '该Cookie无效,请根据教程重新获取'}
|
33
LittlePaimon/web/api/login.py
Normal file
33
LittlePaimon/web/api/login.py
Normal file
@ -0,0 +1,33 @@
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel
|
||||
from LittlePaimon import SUPERUSERS
|
||||
from LittlePaimon.manager.plugin_manager import plugin_manager
|
||||
from .utils import create_token
|
||||
|
||||
PASSWORD = plugin_manager.config.admin_password
|
||||
|
||||
|
||||
class UserModel(BaseModel):
|
||||
user_id: int
|
||||
password: str
|
||||
|
||||
|
||||
route = APIRouter()
|
||||
|
||||
|
||||
@route.post('/login', response_class=JSONResponse)
|
||||
async def login(user: UserModel):
|
||||
if user.user_id not in SUPERUSERS or user.password != PASSWORD:
|
||||
return {
|
||||
'status': -100,
|
||||
'msg': '登录失败,请确认用户ID和密码无误'
|
||||
}
|
||||
token = create_token(user.user_id)
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': '登录成功',
|
||||
'data': {
|
||||
'token': token
|
||||
}
|
||||
}
|
151
LittlePaimon/web/api/plugin.py
Normal file
151
LittlePaimon/web/api/plugin.py
Normal file
@ -0,0 +1,151 @@
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi import APIRouter
|
||||
|
||||
from LittlePaimon.database.models import PluginPermission
|
||||
from LittlePaimon.manager.plugin_manager import plugin_manager
|
||||
from LittlePaimon.manager.plugin_manager.model import PluginInfo, Config
|
||||
|
||||
from .utils import authentication
|
||||
|
||||
route = APIRouter()
|
||||
|
||||
|
||||
@route.get('/get_plugins', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_plugins():
|
||||
plugins = await plugin_manager.get_plugin_list_for_admin()
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'rows': plugins,
|
||||
'total': len(plugins)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@route.post('/set_plugin_status', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def set_plugin_status(data: dict):
|
||||
module_name = data.get('plugin')
|
||||
status = data.get('status')
|
||||
await PluginPermission.filter(name=module_name).update(status=status)
|
||||
return {'status': 0, 'msg': f'成功设置{module_name}插件状态为{status}'}
|
||||
|
||||
|
||||
@route.get('/get_plugin_bans', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_plugin_status(module_name: str):
|
||||
result = []
|
||||
bans = await PluginPermission.filter(name=module_name).all()
|
||||
for ban in bans:
|
||||
if ban.session_type == 'group':
|
||||
result.extend(f'群{ban.session_id}.{b}' for b in ban.ban)
|
||||
if not ban.status:
|
||||
result.append(f'群{ban.session_id}')
|
||||
elif ban.session_type == 'user' and not ban.status:
|
||||
result.append(f'{ban.session_id}')
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'module_name': module_name,
|
||||
'bans': result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@route.post('/set_plugin_bans', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def set_plugin_bans(data: dict):
|
||||
bans = data['bans']
|
||||
name = data['module_name']
|
||||
await PluginPermission.filter(name=name).update(status=True, ban=[])
|
||||
for ban in bans:
|
||||
if ban.startswith('群'):
|
||||
if '.' in ban:
|
||||
group_id = int(ban.split('.')[0][1:])
|
||||
user_id = int(ban.split('.')[1])
|
||||
plugin = await PluginPermission.filter(name=name, session_type='group', session_id=group_id).first()
|
||||
plugin.ban.append(user_id)
|
||||
await plugin.save()
|
||||
else:
|
||||
await PluginPermission.filter(name=name, session_type='group', session_id=int(ban[1:])).update(
|
||||
status=False)
|
||||
else:
|
||||
await PluginPermission.filter(name=name, session_type='user', session_id=int(ban)).update(status=False)
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': '插件权限设置成功'
|
||||
}
|
||||
|
||||
|
||||
@route.post('/set_plugin_detail', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def set_plugin_detail(plugin_info: PluginInfo):
|
||||
plugin_manager.data[plugin_info.module_name] = plugin_info
|
||||
plugin_manager.save()
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': '插件信息设置成功'
|
||||
}
|
||||
|
||||
|
||||
@route.get('/get_config', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_config():
|
||||
config = plugin_manager.config.dict(by_alias=True)
|
||||
config['米游社签到开始时间'] = datetime.datetime(1970, 1, 1, hour=config['米游社签到开始时间(小时)'], minute=config['米游社签到开始时间(分钟)']).strftime('%H:%M')
|
||||
config['米游币开始执行时间'] = datetime.datetime(1970, 1, 1, hour=config['米游币开始执行时间(小时)'], minute=config['米游币开始执行时间(分钟)']).strftime('%H:%M')
|
||||
config['实时便签停止检查时间段'] = (f'0{config["实时便签停止检查开始时间"]}' if config['实时便签停止检查开始时间'] < 10 else str(config['实时便签停止检查开始时间'])) + \
|
||||
':00,' + (f'0{config["实时便签停止检查结束时间"]}' if config['实时便签停止检查结束时间'] < 10 else str(config['实时便签停止检查结束时间'])) + ':00'
|
||||
config['云原神签到开始时间'] = f'0{config["云原神签到时间(小时)"]}' if config['云原神签到时间(小时)'] < 10 else str(config['云原神签到时间(小时)'])
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': 'ok',
|
||||
'data': config
|
||||
}
|
||||
|
||||
|
||||
@route.post('/set_config', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def set_config(data: dict):
|
||||
if '米游社签到开始时间' in data:
|
||||
temp_time = datetime.datetime.strptime(data['米游社签到开始时间'], '%H:%M')
|
||||
data['米游社签到开始时间(小时)'] = temp_time.hour
|
||||
data['米游社签到开始时间(分钟)'] = temp_time.minute
|
||||
if '米游币开始执行时间' in data:
|
||||
temp_time = datetime.datetime.strptime(data['米游币开始执行时间'], '%H:%M')
|
||||
data['米游币开始执行时间(小时)'] = temp_time.hour
|
||||
data['米游币开始执行时间(分钟)'] = temp_time.minute
|
||||
if '实时便签停止检查时间段' in data:
|
||||
temp_time_split = data['实时便签停止检查时间段'].split(',')
|
||||
data['实时便签停止检查开始时间'] = int(temp_time_split[0][:2])
|
||||
data['实时便签停止检查结束时间'] = int(temp_time_split[1][:2])
|
||||
if '云原神签到开始时间' in data:
|
||||
data['云原神签到时间(小时)'] = int(data['云原神签到开始时间'])
|
||||
config = plugin_manager.config.dict(by_alias=True)
|
||||
config.update(**data)
|
||||
plugin_manager.config = Config.parse_obj(config)
|
||||
plugin_manager.save()
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': '保存成功'
|
||||
}
|
||||
|
||||
|
||||
@route.get('/env_config', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def env_config(file_name: str):
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'data': (Path() / file_name).read_text(encoding='utf-8')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@route.post('/env_config', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def env_config(file_name: str, data: dict):
|
||||
with open(Path() / file_name, 'w', encoding='utf-8') as f:
|
||||
f.write(data['editor'])
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': f'{file_name}文件保存成功'
|
||||
}
|
51
LittlePaimon/web/api/status.py
Normal file
51
LittlePaimon/web/api/status.py
Normal file
@ -0,0 +1,51 @@
|
||||
import asyncio
|
||||
|
||||
# from nonebot import logger
|
||||
# from nonebot.log import default_filter, default_format
|
||||
# from LittlePaimon import DRIVER
|
||||
from LittlePaimon.utils.status import get_status
|
||||
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .utils import authentication
|
||||
|
||||
show_logs = []
|
||||
|
||||
|
||||
# @DRIVER.on_startup
|
||||
# async def start_up():
|
||||
#
|
||||
# def record_log(message: str):
|
||||
# show_logs.append(message)
|
||||
#
|
||||
# logger.opt(colors=True, ansi=True).add(record_log, colorize=True, filter=default_filter, format=default_format)
|
||||
|
||||
|
||||
route = APIRouter()
|
||||
|
||||
|
||||
# @route.get('/log', response_class=StreamingResponse)
|
||||
# async def get_log():
|
||||
# async def streaming_logs():
|
||||
# count = 0
|
||||
# while True:
|
||||
# if show_logs:
|
||||
# yield show_logs.pop(0)
|
||||
# count = 0
|
||||
# else:
|
||||
# count += 1
|
||||
# if count > 600:
|
||||
# yield '超过一分钟没有新日志,日志已断开,请刷新页面重新连接\n'
|
||||
# await asyncio.sleep(2)
|
||||
# break
|
||||
# else:
|
||||
# yield '\n'
|
||||
# await asyncio.sleep(0.1)
|
||||
#
|
||||
# return StreamingResponse(streaming_logs())
|
||||
|
||||
|
||||
@route.get('/status', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def status():
|
||||
return await get_status()
|
28
LittlePaimon/web/api/utils.py
Normal file
28
LittlePaimon/web/api/utils.py
Normal file
@ -0,0 +1,28 @@
|
||||
import datetime
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import Header, HTTPException, Depends
|
||||
from jose import jwt
|
||||
from LittlePaimon import SUPERUSERS
|
||||
from LittlePaimon.manager.plugin_manager import plugin_manager
|
||||
|
||||
SECRET_KEY = plugin_manager.config.secret_key
|
||||
ALGORITHM = 'HS256'
|
||||
TOKEN_EXPIRE_MINUTES = 30
|
||||
|
||||
|
||||
def authentication():
|
||||
def inner(token: Optional[str] = Header(...)):
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=ALGORITHM)
|
||||
if not (user_id := payload.get('user_id')) or int(user_id) not in SUPERUSERS:
|
||||
raise HTTPException(status_code=400, detail='登录验证失败或已失效,请重新登录')
|
||||
except (jwt.JWTError, jwt.ExpiredSignatureError, AttributeError):
|
||||
raise HTTPException(status_code=400, detail='登录验证失败或已失效,请重新登录')
|
||||
|
||||
return Depends(inner)
|
||||
|
||||
|
||||
def create_token(user_id: int):
|
||||
data = {'user_id': user_id, 'exp': datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=TOKEN_EXPIRE_MINUTES)}
|
||||
return jwt.encode(data, SECRET_KEY, algorithm=ALGORITHM)
|
3
LittlePaimon/web/pages/__init__.py
Normal file
3
LittlePaimon/web/pages/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .main import admin_app, blank_page
|
||||
from .login import login_page
|
||||
from .bind_cookie import bind_cookie_page
|
23
LittlePaimon/web/pages/bind_cookie.py
Normal file
23
LittlePaimon/web/pages/bind_cookie.py
Normal file
@ -0,0 +1,23 @@
|
||||
from amis import AmisAPI, Collapse, Form, InputNumber, Textarea, Action, LevelEnum, Divider, Page, Html
|
||||
from LittlePaimon import __version__
|
||||
|
||||
|
||||
mk = {
|
||||
"type": "markdown",
|
||||
"value": "**重要提醒**"
|
||||
}
|
||||
|
||||
collapse_text = "<h2>重要提醒:</h2>Cookie的作用相当于账号密码,非常重要,如是非可信任的机器人,请勿绑定!!<br><h2>获取方法:</h2>详见<a href='https://docs.qq.com/doc/DQ3JLWk1vQVllZ2Z1'>Cookie获取教程</a>"
|
||||
api = AmisAPI(method='post', url='/LittlePaimon/api/bind_cookie')
|
||||
collapse = Collapse(header='Cookie说明及获取方法', body=Html(html=collapse_text))
|
||||
form = Form(title='绑定Cookie', api=api, body=[
|
||||
InputNumber(name='user_id', label='QQ号', description='在此处输入你要绑定的QQ号', required=True),
|
||||
Textarea(name='cookie', label='Cookie', description='在此处粘贴你的Cookie', required=True, clearable=True),
|
||||
# Checkboxes(name='function', label='同时开启以下功能', options=[
|
||||
# {'label': '米游社自动签到', 'value': 'sign'},
|
||||
# {'label': '米游币自动获取', 'value': 'coin'}
|
||||
# ], joinValues=False, extractValue=True)
|
||||
], actions=[Action(label='绑定', level=LevelEnum.success, confirmText='我已知晓Cookie的重要性,确认绑定', type='submit'),
|
||||
Action(label='重置', level=LevelEnum.warning, type='reset')])
|
||||
footer = Html(html=f'<div class="p-2 text-center bg-blue-100">Copyright © 2021 - 2022 <a href="https://github.com/CMHopeSunshine/LittlePaimon" target="_blank" class="link-secondary">LittlePaimon v{__version__}</a> X<a target="_blank" href="https://github.com/baidu/amis" class="link-secondary" rel="noopener"> amis v2.2.0</a></div>')
|
||||
bind_cookie_page = Page(title='绑定Cookie', body=[collapse, Divider(), form, footer])
|
348
LittlePaimon/web/pages/config_manage.py
Normal file
348
LittlePaimon/web/pages/config_manage.py
Normal file
@ -0,0 +1,348 @@
|
||||
from amis import Action, Divider, Form, InputText, LevelEnum, Page, PageSchema, Switch, Remark, InputNumber, InputTime, InputTimeRange, Alert, Editor, Tabs, TabsModeEnum, Select
|
||||
|
||||
action_button = [Action(label='保存', level=LevelEnum.success, type='submit'),
|
||||
Action(label='重置', level=LevelEnum.warning, type='reset')]
|
||||
|
||||
cookie_web_form = Form(
|
||||
title='Web端配置',
|
||||
api='/LittlePaimon/api/set_config',
|
||||
visibleOn='${select == 1}',
|
||||
body=[
|
||||
Switch(
|
||||
label='是否启用CookieWeb',
|
||||
name='启用CookieWeb',
|
||||
value='${启用CookieWeb}',
|
||||
labelRemark=Remark(content='是否启用为用户提供的绑定Cookie的网页'),
|
||||
onText='启用',
|
||||
offText='关闭'
|
||||
),
|
||||
InputText(
|
||||
label='CookieWeb地址',
|
||||
name='CookieWeb地址',
|
||||
value='${CookieWeb地址}',
|
||||
labelRemark=Remark(content='只是设置对用户显示的CookieWeb地址,要填写实际的地址')
|
||||
),
|
||||
Switch(
|
||||
label='是否启用Web端',
|
||||
name='启用Web端',
|
||||
value='${启用Web端}',
|
||||
labelRemark=Remark(content='即本Web管理页面,注意,关闭后刷新本页面会及时不能访问'),
|
||||
onText='启用',
|
||||
offText='关闭'
|
||||
),
|
||||
InputText(
|
||||
label='Web端管理员密码',
|
||||
name='Web端管理员密码',
|
||||
value='${Web端管理员密码}',
|
||||
labelRemark=Remark(content='用于超级用户登录该Web端,修改后重启生效')
|
||||
),
|
||||
InputText(
|
||||
label='Web端token密钥',
|
||||
name='Web端token密钥',
|
||||
value='${Web端token密钥}',
|
||||
labelRemark=Remark(content='用于对Web端身份认证的token进行加密,为32位字符串,请不要保持为默认密钥,务必进行修改,修改后重启生效')
|
||||
),
|
||||
],
|
||||
actions=action_button
|
||||
)
|
||||
|
||||
sim_gacha_form = Form(
|
||||
title='模拟抽卡',
|
||||
api='/LittlePaimon/api/set_config',
|
||||
visibleOn='${select == 2}',
|
||||
body=[
|
||||
InputNumber(
|
||||
label='群冷却',
|
||||
name='模拟抽卡群冷却',
|
||||
value='${模拟抽卡群冷却}',
|
||||
labelRemark=Remark(content='每个群在多少秒内只能进行一次抽卡'),
|
||||
displayMode='enhance',
|
||||
suffix='秒',
|
||||
min=0,
|
||||
),
|
||||
InputNumber(
|
||||
label='群员冷却',
|
||||
name='模拟抽卡群员冷却',
|
||||
value='${模拟抽卡群员冷却}',
|
||||
labelRemark=Remark(content='在上一个配置的基础上,每位群员在多少秒内只能进行一次抽卡'),
|
||||
displayMode='enhance',
|
||||
suffix='秒',
|
||||
min=0,
|
||||
),
|
||||
InputNumber(
|
||||
label='单次最多十连数',
|
||||
name='模拟抽卡单次最多十连数',
|
||||
value='${模拟抽卡单次最多十连数}',
|
||||
labelRemark=Remark(content='单次模拟抽卡同时最多的十连数,推荐不超过6次'),
|
||||
displayMode='enhance',
|
||||
suffix='次',
|
||||
min=1
|
||||
)
|
||||
],
|
||||
actions=action_button
|
||||
)
|
||||
|
||||
auto_mys_form = Form(
|
||||
title='自动任务',
|
||||
api='/LittlePaimon/api/set_config',
|
||||
visibleOn='${select == 3}',
|
||||
body=[
|
||||
Switch(
|
||||
label='米游社自动签到开关',
|
||||
name='米游社自动签到开关',
|
||||
value='${米游社自动签到开关}',
|
||||
onText='开启',
|
||||
offText='关闭'
|
||||
),
|
||||
InputTime(
|
||||
label='米游社自动签到开始时间',
|
||||
name='米游社签到开始时间',
|
||||
value='${米游社签到开始时间}',
|
||||
labelRemark=Remark(content='会在每天这个时间点进行米游社自动签到任务,修改后重启生效'),
|
||||
inputFormat='HH时mm分',
|
||||
format='HH:mm'
|
||||
),
|
||||
Divider(),
|
||||
Switch(
|
||||
label='米游币自动获取开关',
|
||||
name='米游币自动获取开关',
|
||||
value='${米游币自动获取开关}',
|
||||
onText='开启',
|
||||
offText='关闭'
|
||||
),
|
||||
InputTime(
|
||||
label='米游币自动获取开始时间',
|
||||
name='米游币开始执行时间',
|
||||
value='${米游币开始执行时间}',
|
||||
labelRemark=Remark(content='会在每天这个时间点进行米游币自动获取任务,修改后重启生效'),
|
||||
inputFormat='HH时mm分',
|
||||
format='HH:mm'
|
||||
),
|
||||
Divider(),
|
||||
Switch(
|
||||
label='云原神自动签到开关',
|
||||
name='云原神自动签到开关',
|
||||
value='${云原神自动签到开关}',
|
||||
onText='开启',
|
||||
offText='关闭'
|
||||
),
|
||||
InputTime(
|
||||
label='云原神签到开始时间',
|
||||
name='云原神签到开始时间',
|
||||
value='${云原神签到开始时间}',
|
||||
labelRemark=Remark(content='会在每天这个时间点进行云原神自动签到,修改后重启生效'),
|
||||
inputFormat='HH时',
|
||||
timeFormat='HH',
|
||||
format='HH'
|
||||
),
|
||||
],
|
||||
actions=action_button)
|
||||
|
||||
ssbq_form = Form(
|
||||
title='实时便签',
|
||||
api='/LittlePaimon/api/set_config',
|
||||
visibleOn='${select == 4}',
|
||||
body=[
|
||||
Switch(
|
||||
label='实时便签检查开关',
|
||||
name='实时便签检查开关',
|
||||
value='${实时便签检查开关}',
|
||||
onText='开启',
|
||||
offText='关闭'
|
||||
),
|
||||
InputTimeRange(
|
||||
label='实时便签停止检查时间段',
|
||||
name='实时便签停止检查时间段',
|
||||
value='${实时便签停止检查时间段}',
|
||||
labelRemark=Remark(
|
||||
content='在这段时间(例如深夜)不进行实时便签检查,注意开始时间不要晚于结束时间,不然会有问题'),
|
||||
timeFormat='HH',
|
||||
format='HH',
|
||||
inputFormat='HH时'
|
||||
),
|
||||
InputNumber(
|
||||
label='实时便签检查间隔',
|
||||
name='实时便签检查间隔',
|
||||
value='${实时便签检查间隔}',
|
||||
labelRemark=Remark(content='每多少分钟检查进行一次实时便签,推荐不快于8分钟,修改后重启生效'),
|
||||
displayMode='enhance',
|
||||
suffix='分钟',
|
||||
min=1,
|
||||
)
|
||||
],
|
||||
actions=action_button
|
||||
)
|
||||
|
||||
ys_form = Form(
|
||||
title='原神信息',
|
||||
api='/LittlePaimon/api/set_config',
|
||||
visibleOn='${select == 5}',
|
||||
body=[
|
||||
Alert(level='info',
|
||||
body='当这些指令原神信息数据最后更新时间距今已超过对应小时数时,就自动进行一次更新后再返回信息'),
|
||||
InputNumber(
|
||||
label='ys自动更新小时数',
|
||||
name='ys自动更新小时',
|
||||
value='${ys自动更新小时}',
|
||||
displayMode='enhance',
|
||||
suffix='小时',
|
||||
min=1,
|
||||
),
|
||||
InputNumber(
|
||||
label='ysa自动更新小时数',
|
||||
name='ysa自动更新小时',
|
||||
value='${ysa自动更新小时}',
|
||||
displayMode='enhance',
|
||||
suffix='小时',
|
||||
min=1,
|
||||
),
|
||||
InputNumber(
|
||||
label='ysd自动更新小时数',
|
||||
name='ysd自动更新小时',
|
||||
value='${ysd自动更新小时}',
|
||||
displayMode='enhance',
|
||||
suffix='小时',
|
||||
min=1,
|
||||
)
|
||||
],
|
||||
actions=action_button
|
||||
)
|
||||
notice_form = Form(
|
||||
title='好友和群事件',
|
||||
api='/LittlePaimon/api/set_config',
|
||||
visibleOn='${select == 6}',
|
||||
body=[
|
||||
Switch(
|
||||
label='启用好友和群请求通知',
|
||||
name='启用好友和群请求通知',
|
||||
value='${启用好友和群请求通知}',
|
||||
labelRemark=Remark(content='开启后,会在机器人收到好友或群添加、拉群等请求时向超管发消息'),
|
||||
onText='开启',
|
||||
offText='关闭'
|
||||
),
|
||||
Switch(
|
||||
label='自动接受好友请求',
|
||||
name='自动接受好友请求',
|
||||
value='${自动接受好友请求}',
|
||||
labelRemark=Remark(content='开启后,机器人会自动接受所有好友请求'),
|
||||
onText='开启',
|
||||
offText='关闭'
|
||||
),
|
||||
Switch(
|
||||
label='自动接受群邀请',
|
||||
name='自动接受群邀请',
|
||||
value='${自动接受群邀请}',
|
||||
labelRemark=Remark(content='开启后,机器人会自动接受所有拉群请求'),
|
||||
onText='开启',
|
||||
offText='关闭'
|
||||
),
|
||||
Switch(
|
||||
label='启用好友和群欢迎消息',
|
||||
name='启用好友和群欢迎消息',
|
||||
value='${启用好友和群欢迎消息}',
|
||||
labelRemark=Remark(content='开启后,会向新添加的好友以及新进入的群发送欢迎消息'),
|
||||
onText='开启',
|
||||
offText='关闭'
|
||||
),
|
||||
],
|
||||
actions=action_button
|
||||
)
|
||||
other_form = Form(
|
||||
title='其他配置',
|
||||
api='/LittlePaimon/api/set_config',
|
||||
visibleOn='${select == 7}',
|
||||
body=[
|
||||
Switch(
|
||||
label='网页截图权限',
|
||||
name='启用网页截图权限',
|
||||
value='${启用网页截图权限}',
|
||||
labelRemark=Remark(content='开启后,任何人都能使用网页截图,关闭后则只有超管能使用'),
|
||||
onText='所有人',
|
||||
offText='仅超级用户'
|
||||
),
|
||||
InputNumber(
|
||||
label='原神猜语音时间',
|
||||
name='原神猜语音时间',
|
||||
value='${原神猜语音时间}',
|
||||
labelRemark=Remark(content='原神猜语音小游戏的持续时间'),
|
||||
displayMode='enhance',
|
||||
suffix='秒',
|
||||
min=5,
|
||||
)
|
||||
],
|
||||
actions=action_button
|
||||
)
|
||||
|
||||
nonebot_form = Form(
|
||||
title='Nonebot配置',
|
||||
initApi='get:/LittlePaimon/api/env_config?file_name=${file_name}',
|
||||
api='post:/LittlePaimon/api/env_config?file_name=${file_name}',
|
||||
visibleOn='${select == 8}',
|
||||
body=[
|
||||
Select(
|
||||
name='file_name',
|
||||
label='选择文件',
|
||||
value='.env.prod',
|
||||
options=[
|
||||
{
|
||||
'label': '.env',
|
||||
'value': '.env'
|
||||
},
|
||||
{
|
||||
'label': '.env.prod',
|
||||
'value': '.env.prod'
|
||||
},
|
||||
{
|
||||
'label': '.env.dev',
|
||||
'value': '.env.dev'
|
||||
}
|
||||
]
|
||||
),
|
||||
Editor(
|
||||
name='editor',
|
||||
label='编辑',
|
||||
value='${data}',
|
||||
placeholder='暂无内容'
|
||||
)
|
||||
],
|
||||
actions=[action_button[0]]
|
||||
)
|
||||
|
||||
select = Select(label='选择配置类',
|
||||
size='sm',
|
||||
name='select',
|
||||
value=1,
|
||||
options=[
|
||||
{
|
||||
'label': 'Web端配置',
|
||||
'value': 1},
|
||||
{
|
||||
'label': '模拟抽卡',
|
||||
'value': 2},
|
||||
{
|
||||
'label': '自动任务',
|
||||
'value': 3
|
||||
},
|
||||
{
|
||||
'label': '实时便签',
|
||||
'value': 4
|
||||
},
|
||||
{
|
||||
'label': '原神信息',
|
||||
'value': 5
|
||||
},
|
||||
{
|
||||
'label': '好友和群事件',
|
||||
'value': 6
|
||||
},
|
||||
{
|
||||
'label': '其他配置',
|
||||
'value': 7
|
||||
},
|
||||
{
|
||||
'label': 'Nonebot配置',
|
||||
'value': 8
|
||||
}
|
||||
])
|
||||
page = PageSchema(url='/configs', label='配置项管理',
|
||||
schema=Page(title='配置项管理', initApi='/LittlePaimon/api/get_config', body=[select, cookie_web_form, sim_gacha_form, auto_mys_form, ssbq_form, ys_form, notice_form, other_form, nonebot_form]))
|
9
LittlePaimon/web/pages/constants.py
Normal file
9
LittlePaimon/web/pages/constants.py
Normal file
@ -0,0 +1,9 @@
|
||||
status_map = {
|
||||
0: '疑似失效',
|
||||
1: '可用',
|
||||
2: '达到每日限制'
|
||||
}
|
||||
status_filter = {
|
||||
'options': [{'label': '疑似失效', 'value': 0},
|
||||
{'label': '可用', 'value': 1},
|
||||
{'label': '达到每日限制', 'value': 2}]}
|
90
LittlePaimon/web/pages/home_page.py
Normal file
90
LittlePaimon/web/pages/home_page.py
Normal file
@ -0,0 +1,90 @@
|
||||
from amis import Page, PageSchema, Html, Property, Service
|
||||
from LittlePaimon import __version__
|
||||
|
||||
logo = Html(html=f'''
|
||||
<p align="center">
|
||||
<a href="https://github.com/CMHopeSunshine/LittlePaimon/">
|
||||
<img src="http://static.cherishmoon.fun/LittlePaimon/readme/logo.png"
|
||||
width="256" height="256" alt="LittlePaimon">
|
||||
</a>
|
||||
</p>
|
||||
<h1 align="center">LittlePaimon 控制台 V{__version__}</h1>
|
||||
<div align="center">
|
||||
<a href="https://github.com/CMHopeSunshine/LittlePaimon" target="_blank">
|
||||
Github仓库</a> ·
|
||||
<a href="https://docs.paimon.cherishmoon.fun/"
|
||||
target="_blank">文档</a>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
''')
|
||||
|
||||
status = Service(
|
||||
api='/LittlePaimon/api/status',
|
||||
body=Property(
|
||||
title='机器人信息',
|
||||
column=2,
|
||||
items=[
|
||||
Property.Item(
|
||||
label='Bot昵称',
|
||||
content='${nickname}'
|
||||
),
|
||||
Property.Item(
|
||||
label='Bot qq号',
|
||||
content='${bot_id}'
|
||||
),
|
||||
Property.Item(
|
||||
label='Bot启动时间',
|
||||
content='${start_time}'
|
||||
),
|
||||
Property.Item(
|
||||
label='系统启动时间',
|
||||
content='${system_start_time}'
|
||||
),
|
||||
Property.Item(
|
||||
label='已接收信息',
|
||||
content='${msg_received}'
|
||||
),
|
||||
Property.Item(
|
||||
label='已发送信息',
|
||||
content='${msg_sent}'
|
||||
),
|
||||
Property.Item(
|
||||
label='CPU占用率',
|
||||
content='${cpu_percent}'
|
||||
),
|
||||
Property.Item(
|
||||
label='RAM占用率',
|
||||
content='${ram_percent}'
|
||||
),
|
||||
Property.Item(
|
||||
label='SWAP占用率',
|
||||
content='${swap_percent}',
|
||||
span=2
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# log_page = Log(
|
||||
# height=500,
|
||||
# autoScroll=True,
|
||||
# placeholder='日志加载中...',
|
||||
# operation=['stop', 'filter'],
|
||||
# source='/LittlePaimon/api/log'
|
||||
# )
|
||||
# log_button = ActionType.Dialog(
|
||||
# label='查看日志',
|
||||
# dialog=Dialog(
|
||||
# title='Nonebot日志',
|
||||
# body=log_page,
|
||||
# size='lg')
|
||||
# )
|
||||
# text = Tpl(tpl='接收消息数:${msg_received} | 发送消息数:${msg_sent}')
|
||||
|
||||
# page_detail = Page(title='主页',
|
||||
# initApi='/LittlePaimon/api/status',
|
||||
# body=[text, log_button])
|
||||
page_detail = Page(title='', body=[logo, status])
|
||||
page = PageSchema(url='/home', label='首页', icon='fa fa-home', isDefaultPage=True, schema=page_detail)
|
38
LittlePaimon/web/pages/login.py
Normal file
38
LittlePaimon/web/pages/login.py
Normal file
@ -0,0 +1,38 @@
|
||||
from LittlePaimon import __version__
|
||||
from amis import Form, InputText, InputPassword, DisplayModeEnum, Horizontal, Remark, Html, Page, Container, AmisAPI
|
||||
|
||||
logo = Html(html=f'''
|
||||
<p align="center">
|
||||
<a href="https://github.com/CMHopeSunshine/LittlePaimon/">
|
||||
<img src="http://static.cherishmoon.fun/LittlePaimon/readme/logo.png"
|
||||
width="256" height="256" alt="LittlePaimon">
|
||||
</a>
|
||||
</p>
|
||||
<h1 align="center">LittlePaimon 控制台 V{__version__}</h1>
|
||||
<div align="center">
|
||||
<a href="https://github.com/CMHopeSunshine/LittlePaimon" target="_blank">
|
||||
Github仓库</a> ·
|
||||
<a href="https://docs.paimon.cherishmoon.fun/"
|
||||
target="_blank">文档</a>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
''')
|
||||
login_api = AmisAPI(
|
||||
url='/LittlePaimon/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=[
|
||||
InputText(name='user_id', label='用户ID', labelRemark=Remark(content='超级用户的QQ号,在.env.prod文件中配置')),
|
||||
InputPassword(name='password', label='密码', labelRemark=Remark(content='默认为admin,可以在paimon_config.json中修改')),
|
||||
# Switch(name='is_admin', label='身份组', onText='管理员', offText='用户', labelRemark=Remark(content='是否以管理员身份登录'))
|
||||
], mode=DisplayModeEnum.horizontal, horizontal=Horizontal(left=3, right=9, offset=5), redirect='/LittlePaimon/admin')
|
||||
body = Container(style={'width': '400px', 'margin': '0 auto'}, body=login_form)
|
||||
login_page = Page(title='', body=[logo, body])
|
55
LittlePaimon/web/pages/main.py
Normal file
55
LittlePaimon/web/pages/main.py
Normal file
@ -0,0 +1,55 @@
|
||||
from LittlePaimon import __version__
|
||||
from amis import App, PageSchema, Tpl, Page, DropDownButton, ActionType, LevelEnum, Flex
|
||||
from .public_cookie import page as public_cookie_page
|
||||
from .private_cookie import page as private_cookie_page
|
||||
from .plugin_manage import page as plugin_manage_page
|
||||
from .home_page import page as home_page
|
||||
from .config_manage import page as config_page
|
||||
|
||||
# from .learning_chat_manage import page as learning_chat_page
|
||||
|
||||
# dropdown = DropDownButton(
|
||||
# buttons=[
|
||||
# ActionType.Dialog(label='用户信息',
|
||||
# dialog=Dialog(title='用户信息',
|
||||
# body='待定')),
|
||||
# ActionType.Url(label='退出登录',
|
||||
# url='/LittlePaimon/api/logout')
|
||||
# ]
|
||||
# )
|
||||
|
||||
action_button = DropDownButton(
|
||||
label='操作',
|
||||
trigger='hover',
|
||||
buttons=[
|
||||
ActionType.Ajax(
|
||||
label='更新',
|
||||
api='/LittlePaimon/api/bot_update',
|
||||
confirmText='该操作将会对Bot进行检查并尝试更新,请在更新完成后重启Bot使更新生效',
|
||||
),
|
||||
ActionType.Ajax(
|
||||
label='重启',
|
||||
api='/LittlePaimon/api/bot_restart',
|
||||
confirmText='该操作将会使Bot重启,在完成重启之前,该页面也将无法访问,请耐心等待重启',
|
||||
)
|
||||
]
|
||||
)
|
||||
github_logo = Tpl(className='w-full',
|
||||
tpl='<div class="flex justify-between"><div></div><div><a href="https://github.com/CMHopeSunshine/LittlePaimon" target="_blank" title="Copyright"><i class="fa fa-github fa-2x"></i></a></div></div>')
|
||||
header = Flex(className='w-full', justify='flex-end', alignItems='flex-end', items=[action_button, github_logo])
|
||||
|
||||
|
||||
admin_app = App(brandName='LittlePaimon',
|
||||
logo='http://static.cherishmoon.fun/LittlePaimon/readme/logo.png',
|
||||
header=header,
|
||||
pages=[{
|
||||
'children': [
|
||||
home_page,
|
||||
PageSchema(label='Cookie管理', url='/cookie', icon='fa fa-key',
|
||||
children=[public_cookie_page, private_cookie_page]),
|
||||
PageSchema(label='机器人配置', url='/config', icon='fa fa-wrench',
|
||||
children=[plugin_manage_page, config_page]),
|
||||
]}],
|
||||
footer=f'<div class="p-2 text-center bg-blue-100">Copyright © 2021 - 2022 <a href="https://github.com/CMHopeSunshine/LittlePaimon" target="_blank" class="link-secondary">LittlePaimon v{__version__}</a> X<a target="_blank" href="https://github.com/baidu/amis" class="link-secondary" rel="noopener"> amis v2.2.0</a></div>')
|
||||
|
||||
blank_page = Page(title='LittlePaimon', body='该页面未开启或不存在')
|
136
LittlePaimon/web/pages/plugin_manage.py
Normal file
136
LittlePaimon/web/pages/plugin_manage.py
Normal file
@ -0,0 +1,136 @@
|
||||
from amis import Form, Transfer, ActionType, Dialog, InputSubForm, DisplayModeEnum, InputText, Textarea, Switch, InputNumber, Alert, Card, Tpl, CardsCRUD, Static, PageSchema, Page
|
||||
|
||||
# -------------插件使用权限设置------------------
|
||||
ban_form = Form(title='',
|
||||
api='post:/LittlePaimon/api/set_plugin_bans',
|
||||
initApi='get:/LittlePaimon/api/get_plugin_bans?module_name=${module_name}',
|
||||
body=[
|
||||
Transfer(
|
||||
type='tabs-transfer',
|
||||
name='bans',
|
||||
value='${bans}',
|
||||
label='',
|
||||
resultTitle='已被禁用列表',
|
||||
selectMode='tree',
|
||||
joinValues=False,
|
||||
extractValue=True,
|
||||
statistics=True,
|
||||
multiple=True,
|
||||
menuTpl='${left_label}',
|
||||
valueTpl='${right_label}',
|
||||
source='get:/LittlePaimon/api/get_groups_and_members',
|
||||
)])
|
||||
|
||||
permission_button = ActionType.Dialog(label='使用权限',
|
||||
icon='fa fa-pencil',
|
||||
# visibleOn='${isLoad}',
|
||||
dialog=Dialog(title='${name}使用权限设置', size='lg', body=ban_form))
|
||||
# -------------插件使用权限设置------------------
|
||||
|
||||
# -------------插件帮助图设置------------------
|
||||
command_form = InputSubForm(name='matchers',
|
||||
label='命令列表',
|
||||
multiple=True,
|
||||
btnLabel='${pm_name}',
|
||||
value='${matchers}',
|
||||
description='该插件下具体的命令使用方法设置',
|
||||
addButtonText='添加命令',
|
||||
form=Form(
|
||||
title='命令信息设置',
|
||||
mode=DisplayModeEnum.horizontal,
|
||||
labelAlign='right',
|
||||
body=[
|
||||
InputText(label='命令标识名称', name='pm_name', value='${pm_name}', required=True,
|
||||
description='仅用于标识命令,不会显示在帮助图中,所以若是本身已有的命令,请勿修改!'),
|
||||
InputText(label='命令用法', name='pm_usage', value='${pm_usage}',
|
||||
description='命令的使用方法,建议不要太长'),
|
||||
Textarea(label='命令详细描述', name='pm_description', value='${pm_description}',
|
||||
description='命令的详细描述,可以用\\n强制换行'),
|
||||
Switch(label='是否展示', name='pm_show', value='${pm_show}',
|
||||
description='是否在帮助图中展示该命令'),
|
||||
InputNumber(label='展示优先级', name='pm_priority', value='${pm_priority}',
|
||||
description='展示优先级,数字越大越靠前', min=0, max=99,
|
||||
displayMode='enhance'),
|
||||
]
|
||||
))
|
||||
detail_form = Form(title='',
|
||||
api='post:/LittlePaimon/api/set_plugin_detail',
|
||||
submitText='保存修改',
|
||||
mode=DisplayModeEnum.horizontal,
|
||||
labelAlign='right',
|
||||
body=[
|
||||
InputText(label='插件名称', name='name', value='${name}', required=True,
|
||||
description='插件显示的名称,建议不要过长'),
|
||||
Static(label='插件模块名', name='module_name', value='${module_name}'),
|
||||
Textarea(label='插件描述', name='description', value='${description}', clearable=True,
|
||||
description='仅用于在本管理页面中显示,不会在帮助图中显示'),
|
||||
Textarea(label='插件使用说明', name='usage', value='${detail}', clearable=True,
|
||||
description='会在该插件没有具体命令的使用说明时,显示在帮助图中'),
|
||||
Switch(label='是否展示', name='show', value='${show}',
|
||||
description='是否在帮助图中展示该插件'),
|
||||
InputNumber(label='展示优先级', name='priority', value='${priority}',
|
||||
description='在帮助图及本管理页面中展示的顺序,数字越小越靠前',
|
||||
min=0, max=99, displayMode='enhance'),
|
||||
command_form
|
||||
])
|
||||
tips_alert = Alert(level='info',
|
||||
body='以下设置用于在本管理页面以及help帮助图中显示插件的信息,不会影响插件的实际使用,你可以修改这些信息来自定义帮助图效果。')
|
||||
detail_button = ActionType.Dialog(label='信息',
|
||||
size='lg',
|
||||
icon='fa fa-pencil',
|
||||
dialog=Dialog(title='${name}信息设置', size='lg', body=[tips_alert, detail_form]))
|
||||
|
||||
card = Card(
|
||||
header=Card.Header(title='$name',
|
||||
subTitle='$module_name',
|
||||
description='$description',
|
||||
avatarText='$name'),
|
||||
actions=[detail_button, permission_button],
|
||||
toolbar=[
|
||||
Tpl(tpl='未加载', className='label label-warning', hiddenOn='${isLoad}'),
|
||||
Switch(name='enable',
|
||||
value='${status}',
|
||||
onText='启用',
|
||||
offText='禁用',
|
||||
visibleOn='${isLoad}',
|
||||
onEvent={
|
||||
'change': {
|
||||
'actions': {
|
||||
'actionType': 'ajax',
|
||||
'args': {
|
||||
'api': {
|
||||
'url': '/LittlePaimon/api/set_plugin_status',
|
||||
'method': 'post'
|
||||
},
|
||||
'messages': {
|
||||
'success': '${name}插件全局开关已设置为${event.data.value}',
|
||||
'failed': '插件设置失败'
|
||||
},
|
||||
'status': '${event.data.value}',
|
||||
'plugin': '${module_name}'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
])
|
||||
|
||||
cards_curd = CardsCRUD(mode='cards',
|
||||
title='',
|
||||
syncLocation=False,
|
||||
api='/LittlePaimon/api/get_plugins',
|
||||
loadDataOnce=True,
|
||||
source='${rows | filter:name:match:keywords_name | filter:description:match:keywords_description | filter:status:match:status}',
|
||||
filter={
|
||||
'body': [
|
||||
InputText(name='keywords_name', label='插件名'),
|
||||
InputText(name='keywords_description', label='插件描述'),
|
||||
Switch(name='status', label='插件状态', onText='启用', offText='禁用')
|
||||
]
|
||||
},
|
||||
perPage=12,
|
||||
autoJumpToTopOnPagerChange=True,
|
||||
placeholder='暂无插件信息',
|
||||
footerToolbar=['switch-per-page', 'pagination'],
|
||||
columnsCount=3,
|
||||
card=card)
|
||||
page = PageSchema(url='/plugins', label='插件管理', schema=Page(title='插件管理', body=cards_curd))
|
41
LittlePaimon/web/pages/private_cookie.py
Normal file
41
LittlePaimon/web/pages/private_cookie.py
Normal file
@ -0,0 +1,41 @@
|
||||
from amis import Page, PageSchema, ActionType, LevelEnum, Dialog, Form, InputNumber, Textarea, Action, Static, TableCRUD, TableColumn, ColumnOperation
|
||||
from .constants import status_map, status_filter
|
||||
|
||||
add_button = ActionType.Dialog(label='添加私人Cookie',
|
||||
level=LevelEnum.primary,
|
||||
dialog=Dialog(title='添加私人Cookie',
|
||||
body=Form(api='post:/LittlePaimon/api/save_private_cookie',
|
||||
body=[InputNumber(name='user_id', label='QQ号', required=True),
|
||||
Textarea(name='cookie', label='Cookie', required=True)])))
|
||||
delete_button = ActionType.Ajax(label='删除', level=LevelEnum.danger,
|
||||
confirmText='确认删除该私人Cookie',
|
||||
api='delete:/LittlePaimon/api/delete_private_cookie?id=${id}')
|
||||
save_action_button = Action(label='保存修改', level=LevelEnum.warning, type='submit')
|
||||
cancel_action_button = Action(label='关闭', level=LevelEnum.default, actionType='close')
|
||||
detail_button = ActionType.Dialog(label='详情',
|
||||
level=LevelEnum.info,
|
||||
dialog=Dialog(title='Cookie详情',
|
||||
body=Form(
|
||||
api='post:/LittlePaimon/api/save_private_cookie',
|
||||
body=[Static(name='id', label='ID'),
|
||||
InputNumber(name='user_id', label='QQ号', required=True),
|
||||
Static(name='uid', label='UID'),
|
||||
Static(name='mys_id', label='米游社ID'),
|
||||
Textarea(name='cookie', label='Cookie', required=True),
|
||||
Static(name='stoken', label='Stoken')]),
|
||||
actions=[cancel_action_button, save_action_button]))
|
||||
table = TableCRUD(mode='table',
|
||||
title='',
|
||||
syncLocation=False,
|
||||
api='/LittlePaimon/api/get_private_cookies',
|
||||
columns=[TableColumn(label='ID', name='id'),
|
||||
TableColumn(label='QQ号', name='user_id', searchable=True),
|
||||
TableColumn(label='UID', name='uid', searchable=True),
|
||||
TableColumn(label='米游社ID', name='mys_id', searchable=True),
|
||||
TableColumn(type='mapping', label='状态', name='status', filterable=status_filter,
|
||||
map=status_map),
|
||||
ColumnOperation(label='操作', buttons=[detail_button, delete_button])],
|
||||
headerToolbar=[add_button],
|
||||
footerToolbar=['switch-per-page', 'pagination'])
|
||||
|
||||
page = PageSchema(url='/private_cookie', label='私人Cookie', schema=Page(title='私人Cookie', body=table))
|
21
LittlePaimon/web/pages/public_cookie.py
Normal file
21
LittlePaimon/web/pages/public_cookie.py
Normal file
@ -0,0 +1,21 @@
|
||||
from amis import Page, PageSchema, ActionType, LevelEnum, Dialog, Form, Textarea, TableCRUD, TableColumn, ColumnOperation
|
||||
from .constants import status_map, status_filter
|
||||
|
||||
add_button = ActionType.Dialog(label='添加公共Cookie',
|
||||
level=LevelEnum.primary,
|
||||
dialog=Dialog(title='添加公共Cookie',
|
||||
body=Form(api='post:/LittlePaimon/api/add_public_cookie',
|
||||
body=[Textarea(name='cookie', label='Cookie', required=True)])))
|
||||
delete_button = ActionType.Ajax(label='删除', level=LevelEnum.danger,
|
||||
confirmText='确认删除该公共Cookie',
|
||||
api='delete:/LittlePaimon/api/delete_public_cookie?id=${id}')
|
||||
table = TableCRUD(mode='table',
|
||||
title='',
|
||||
syncLocation=False,
|
||||
api='/LittlePaimon/api/get_public_cookies',
|
||||
columns=[TableColumn(label='ID', name='id', width='8%'),
|
||||
TableColumn(label='Cookie', name='cookie', width='64%'),
|
||||
TableColumn(type='mapping', label='状态', name='status', filterable=status_filter, map=status_map, width='12%'),
|
||||
ColumnOperation(label='操作', buttons=[delete_button], width='16%')],
|
||||
headerToolbar=[add_button])
|
||||
page = PageSchema(label='公共Cookie', url='public_cookie', schema=Page(title='公共Cookie', body=table))
|
1741
poetry.lock
generated
1741
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -6,15 +6,15 @@ authors = ["惜月 <277073121@qq.com>"]
|
||||
license = "AGPL"
|
||||
|
||||
[[tool.poetry.source]]
|
||||
name = "ali"
|
||||
name = "tsinghua"
|
||||
default = true
|
||||
url = "https://mirrors.aliyun.com/pypi/simple/"
|
||||
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
nonebot2 = "^2.0.0-beta.5"
|
||||
nonebot2 = ">=2.0.0-beta.5"
|
||||
nonebot-adapter-onebot = "^2.1"
|
||||
nonebot-plugin-apscheduler = "^0.1.2"
|
||||
nonebot-plugin-apscheduler = "^0.2.0"
|
||||
nonebot-plugin-htmlrender = "^0.1.1"
|
||||
beautifulsoup4 = "^4.10.0"
|
||||
httpx = "^0.23.0"
|
||||
@ -26,13 +26,14 @@ tqdm = "^4.64.0"
|
||||
"ruamel.yaml" = "^0.17.21"
|
||||
ujson = "^5.4.0"
|
||||
expandvars = "^0.9.0"
|
||||
pywebio = "^1.6.2"
|
||||
jieba = "^0.42.1"
|
||||
scipy = "^1.9.1"
|
||||
scikit-learn = "^1.1.2"
|
||||
shapely = "^1.8.4"
|
||||
gitpython = "^3.1.27"
|
||||
pypinyin = "^0.47.1"
|
||||
python-jose = "^3.3.0"
|
||||
amis-python = "^1.0.5"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
nb-cli = "^0.6.7"
|
||||
|
114
requirements.txt
114
requirements.txt
@ -1,24 +1,92 @@
|
||||
nonebot2>=2.0.0-beta.5
|
||||
nonebot-adapter-onebot>=2.1
|
||||
nonebot-plugin-apscheduler>=0.1.2
|
||||
nonebot-plugin-htmlrender>=0.1.1
|
||||
beautifulsoup4>=4.10.0
|
||||
httpx>=0.23.0
|
||||
lxml>=4.8.0
|
||||
Pillow>=9.1.0
|
||||
matplotlib>=3.5.1
|
||||
aiofiles>=0.8.0
|
||||
tortoise-orm>=0.19.2
|
||||
tqdm>=4.64.0
|
||||
ruamel.yaml>=0.17.21
|
||||
ujson>=5.4.0
|
||||
expandvars>=0.9.0
|
||||
pywebio>=1.6.2
|
||||
jieba>=0.42.1
|
||||
scipy>=1.9.1
|
||||
scikit-learn>=1.1.2
|
||||
shapely>=1.8.4
|
||||
gitpython>=3.1.27
|
||||
pypinyin>=0.47.1
|
||||
|
||||
--index-url https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
|
||||
aiofiles==0.8.0; python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.7.3" and python_full_version < "4.0.0"
|
||||
aiosqlite==0.17.0; python_version >= "3.7" and python_version < "4.0"
|
||||
amis-python==1.0.5; python_version >= "3.7" and python_version < "4.0"
|
||||
anyio==3.6.2; python_full_version >= "3.6.2" and python_version >= "3.8" and python_version < "4.0"
|
||||
apscheduler==3.9.1; python_version >= "3.8" and python_full_version < "3.0.0" and python_version < "4.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.8"
|
||||
backports.zoneinfo==0.2.1; python_version >= "3.8" and python_full_version < "3.0.0" and python_version < "3.9" and (python_version >= "3.8" and python_full_version < "3.0.0" and python_version < "4.0" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.8") or python_full_version >= "3.5.0" and python_version < "3.9" and python_version >= "3.8" and (python_version >= "3.8" and python_full_version < "3.0.0" and python_version < "4.0" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.8")
|
||||
beautifulsoup4==4.11.1; python_full_version >= "3.6.0"
|
||||
certifi==2022.9.24; python_version >= "3.7"
|
||||
click==8.1.3; python_version >= "3.8" and python_version < "4.0"
|
||||
colorama==0.4.6; python_version >= "3.8" and python_full_version < "3.0.0" and platform_system == "Windows" and python_version < "4.0" and sys_platform == "win32" or python_full_version >= "3.7.0" and platform_system == "Windows" and python_version >= "3.8" and python_version < "4.0" and sys_platform == "win32"
|
||||
contourpy==1.0.5; python_version >= "3.8"
|
||||
cycler==0.11.0; python_version >= "3.8"
|
||||
ecdsa==0.18.0; python_version >= "2.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0"
|
||||
expandvars==0.9.0; python_version >= "3.4"
|
||||
fastapi==0.79.1; python_version >= "3.8" and python_version < "4.0" and python_full_version >= "3.7.3" and python_full_version < "4.0.0"
|
||||
fonttools==4.38.0; python_version >= "3.8"
|
||||
gitdb==4.0.9; python_version >= "3.7"
|
||||
gitpython==3.1.29; python_version >= "3.7"
|
||||
greenlet==1.1.3; python_full_version >= "3.7.3" and python_full_version < "4.0.0" and python_version >= "3.7"
|
||||
h11==0.12.0; python_version >= "3.8" and python_version < "4.0"
|
||||
httpcore==0.15.0; python_version >= "3.7"
|
||||
httptools==0.5.0; python_version >= "3.8" and python_version < "4.0" and python_full_version >= "3.5.0"
|
||||
httpx==0.23.0; python_version >= "3.7"
|
||||
idna==3.4
|
||||
importlib-metadata==5.0.0; python_full_version >= "3.7.3" and python_full_version < "4.0.0" and python_version >= "3.7" and python_version < "3.10"
|
||||
iso8601==1.1.0; python_full_version >= "3.6.2" and python_version < "4.0" and python_version >= "3.7"
|
||||
jieba==0.42.1
|
||||
jinja2==3.1.2; python_full_version >= "3.7.3" and python_full_version < "4.0.0" and python_version >= "3.7" and python_version < "4.0"
|
||||
joblib==1.2.0; python_version >= "3.8"
|
||||
kiwisolver==1.4.4; python_version >= "3.8"
|
||||
loguru==0.6.0; python_version >= "3.8" and python_version < "4.0" and python_full_version >= "3.7.3" and python_full_version < "4.0.0"
|
||||
lxml==4.9.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0")
|
||||
markdown==3.4.1; python_full_version >= "3.7.3" and python_full_version < "4.0.0" and python_version >= "3.7"
|
||||
markupsafe==2.1.1; python_full_version >= "3.7.3" and python_full_version < "4.0.0" and python_version >= "3.7" and python_version < "4.0"
|
||||
matplotlib==3.6.1; python_version >= "3.8"
|
||||
msgpack==1.0.4; python_version >= "3.8" and python_version < "4.0"
|
||||
multidict==6.0.2; python_version >= "3.8" and python_version < "4.0"
|
||||
nonebot-adapter-onebot==2.1.5; python_version >= "3.8" and python_version < "4.0"
|
||||
nonebot-plugin-apscheduler==0.2.0; python_version >= "3.8" and python_version < "4.0"
|
||||
nonebot-plugin-htmlrender==0.1.1; python_full_version >= "3.7.3" and python_full_version < "4.0.0"
|
||||
nonebot2==2.0.0rc1; python_version >= "3.8" and python_version < "4.0"
|
||||
numpy==1.23.4; python_version >= "3.8"
|
||||
packaging==21.3; python_version >= "3.8"
|
||||
pillow==9.2.0; python_version >= "3.7"
|
||||
playwright==1.27.1; python_full_version >= "3.7.3" and python_full_version < "4.0.0" and python_version >= "3.7"
|
||||
pyasn1==0.4.8; python_version >= "3.6" and python_version < "4"
|
||||
pydantic==1.9.2; python_version >= "3.8" and python_version < "4.0" and python_full_version >= "3.7.3" and python_full_version < "4.0.0"
|
||||
pyee==8.1.0; python_full_version >= "3.7.3" and python_full_version < "4.0.0" and python_version >= "3.7"
|
||||
pygments==2.13.0; python_full_version >= "3.7.3" and python_full_version < "4.0.0" and python_version >= "3.6"
|
||||
pygtrie==2.5.0; python_version >= "3.8" and python_version < "4.0" and python_full_version >= "3.7.3" and python_full_version < "4.0.0"
|
||||
pymdown-extensions==9.7; python_full_version >= "3.7.3" and python_full_version < "4.0.0" and python_version >= "3.7"
|
||||
pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version >= "3.8"
|
||||
pypika-tortoise==0.1.6; python_version >= "3.7" and python_version < "4.0"
|
||||
pypinyin==0.47.1; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0" and python_version < "4")
|
||||
python-dateutil==2.8.2; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.8"
|
||||
python-dotenv==0.21.0
|
||||
python-jose==3.3.0
|
||||
python-markdown-math==0.8; python_full_version >= "3.7.3" and python_full_version < "4.0.0" and python_version >= "3.6"
|
||||
pytz-deprecation-shim==0.1.0.post0; python_version >= "3.8" and python_full_version < "3.0.0" and python_version < "4.0" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.8"
|
||||
pytz==2022.5; python_version >= "3.7" and python_version < "4.0" and (python_version >= "3.8" and python_full_version < "3.0.0" and python_version < "4.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.8")
|
||||
pyyaml==6.0; python_version >= "3.8" and python_version < "4.0"
|
||||
rfc3986==1.5.0; python_version >= "3.7"
|
||||
rsa==4.9; python_version >= "3.6" and python_version < "4"
|
||||
ruamel.yaml.clib==0.2.7; platform_python_implementation == "CPython" and python_version < "3.11" and python_version >= "3.5"
|
||||
ruamel.yaml==0.17.21; python_version >= "3"
|
||||
scikit-learn==1.1.2; python_version >= "3.8"
|
||||
scipy==1.9.3; python_version >= "3.8"
|
||||
setuptools-scm==7.0.5; python_version >= "3.8"
|
||||
shapely==1.8.5.post1; python_version >= "3.6"
|
||||
six==1.16.0; python_version >= "3.8" and python_full_version < "3.0.0" and python_version < "4.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.8"
|
||||
smmap==5.0.0; python_version >= "3.7"
|
||||
sniffio==1.3.0; python_full_version >= "3.6.2" and python_version >= "3.8" and python_version < "4.0"
|
||||
soupsieve==2.3.2.post1; python_version >= "3.6" and python_full_version >= "3.6.0"
|
||||
starlette==0.19.1; python_version >= "3.8" and python_version < "4.0" and python_full_version >= "3.6.1"
|
||||
threadpoolctl==3.1.0; python_version >= "3.8"
|
||||
tomli==2.0.1; python_version >= "3.8"
|
||||
tomlkit==0.10.2; python_version >= "3.8" and python_version < "4.0" and python_full_version >= "3.7.3" and python_full_version < "4.0.0"
|
||||
tortoise-orm==0.19.2; python_version >= "3.7" and python_version < "4.0"
|
||||
tqdm==4.64.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
|
||||
typing-extensions==4.4.0; python_version >= "3.8" and python_version <= "3.8" and python_full_version >= "3.7.3" and python_full_version < "4.0.0"
|
||||
tzdata==2022.5; python_version >= "3.8" and python_full_version < "3.0.0" and python_version < "4.0" and platform_system == "Windows" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.8" and platform_system == "Windows"
|
||||
tzlocal==4.2; python_version >= "3.8" and python_full_version < "3.0.0" and python_version < "4.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.8"
|
||||
ujson==5.5.0; python_version >= "3.7"
|
||||
uvicorn==0.18.3; python_version >= "3.8" and python_version < "4.0" and python_full_version >= "3.7.3" and python_full_version < "4.0.0"
|
||||
uvloop==0.17.0; sys_platform != "win32" and sys_platform != "cygwin" and platform_python_implementation != "PyPy" and python_version >= "3.8" and python_version < "4.0"
|
||||
watchfiles==0.18.0; python_version >= "3.8" and python_version < "4.0"
|
||||
websockets==10.3; python_version >= "3.8" and python_version < "4.0"
|
||||
win32-setctime==1.1.0; python_version >= "3.8" and python_version < "4.0" and sys_platform == "win32"
|
||||
yarl==1.8.1; python_version >= "3.8" and python_version < "4.0" and python_full_version >= "3.7.3" and python_full_version < "4.0.0"
|
||||
zipp==3.10.0; python_full_version >= "3.7.3" and python_full_version < "4.0.0" and python_version >= "3.7" and python_version < "3.10"
|
||||
|
Loading…
Reference in New Issue
Block a user