This commit is contained in:
felinae98 2021-11-22 16:46:46 +08:00
parent 098c610167
commit 03576a0ab6
No known key found for this signature in database
GPG Key ID: 00C8B010587FF610
7 changed files with 92 additions and 27 deletions

View File

@ -1,3 +1,8 @@
FROM node:16 as frontend
ADD . /app
WORKDIR /app/admin-frontend
RUN yarn && yarn build
FROM python:3.9 FROM python:3.9
RUN python3 -m pip install poetry && poetry config virtualenvs.create false RUN python3 -m pip install poetry && poetry config virtualenvs.create false
WORKDIR /app WORKDIR /app
@ -6,5 +11,6 @@ RUN poetry install --no-root --no-dev
# RUN PYPPETEER_DOWNLOAD_HOST='http://npm.taobao.org/mirrors' pyppeteer-install # RUN PYPPETEER_DOWNLOAD_HOST='http://npm.taobao.org/mirrors' pyppeteer-install
ADD src /app/src ADD src /app/src
ADD bot.py /app/ ADD bot.py /app/
COPY --from=frontend /app/src/plugins/nonebot_bison/admin_page/dist /app/src/plugins/nonebot_bison/admin_page/dist
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0
CMD ["python", "bot.py"] CMD ["python", "bot.py"]

View File

@ -1,5 +1,5 @@
import axios from "axios"; import axios from "axios";
import { GlobalConf, TokenResp, SubscribeResp, TargetNameResp, CreateSubscribeReq } from "../utils/type"; import { GlobalConf, TokenResp, SubscribeResp, TargetNameResp, SubscribeConfig } from "../utils/type";
import { baseUrl } from './utils'; import { baseUrl } from './utils';
export async function getGlobalConf(): Promise<GlobalConf> { export async function getGlobalConf(): Promise<GlobalConf> {
@ -22,7 +22,7 @@ export async function getTargetName(platformName: string, target: string): Promi
return res.data; return res.data;
} }
export async function addSubscribe(groupNumber: string, req: CreateSubscribeReq) { export async function addSubscribe(groupNumber: string, req: SubscribeConfig) {
const res = await axios.post(`${baseUrl}subs`, req, {params: {groupNumber}}) const res = await axios.post(`${baseUrl}subs`, req, {params: {groupNumber}})
return res.data; return res.data;
} }

View File

@ -1,7 +1,7 @@
import React, { ReactElement, useContext, useEffect, useState } from "react"; import React, { FC, ReactElement, ReactNode, useContext, useEffect, useState } from "react";
import { LoginContext, GlobalConfContext } from "../utils/context"; import { LoginContext, GlobalConfContext } from "../utils/context";
import { Layout, Menu, Empty, Collapse, Card, Tag, Row, Col, Form, Tooltip, Button, Modal, Select, import { Layout, Menu, Empty, Collapse, Card, Tag, Row, Col, Form, Tooltip, Button, Modal, Select,
Input, Popconfirm} from 'antd'; Input, Popconfirm, message } from 'antd';
import { SubscribeConfig, SubscribeResp, PlatformConfig, CategoryConfig } from '../utils/type'; import { SubscribeConfig, SubscribeResp, PlatformConfig, CategoryConfig } from '../utils/type';
import { SettingOutlined, BugOutlined, DeleteOutlined, CopyOutlined } from '@ant-design/icons'; import { SettingOutlined, BugOutlined, DeleteOutlined, CopyOutlined } from '@ant-design/icons';
import { getSubscribe, getTargetName, addSubscribe, delSubscribe } from '../api/config'; import { getSubscribe, getTargetName, addSubscribe, delSubscribe } from '../api/config';
@ -74,7 +74,9 @@ function ConfigPage(prop: ConfigPageProp) {
onConfirm={handleDelete(groupNumber, config.platformName, config.target || 'default')}> onConfirm={handleDelete(groupNumber, config.platformName, config.target || 'default')}>
<Tooltip title="删除" ><DeleteOutlined /></Tooltip> <Tooltip title="删除" ><DeleteOutlined /></Tooltip>
</Popconfirm>, </Popconfirm>,
<TargetGroupSelection config={config} groups={configData}>
<Tooltip title="添加到其他群"><CopyOutlined /></Tooltip> <Tooltip title="添加到其他群"><CopyOutlined /></Tooltip>
</TargetGroupSelection>
]}> ]}>
<Form labelCol={{ span: 6 }}> <Form labelCol={{ span: 6 }}>
<Form.Item label="订阅类型"> <Form.Item label="订阅类型">
@ -119,6 +121,43 @@ function ConfigPage(prop: ConfigPageProp) {
} }
} }
interface TargetGroupSelectionProp {
config: SubscribeConfig,
groups: SubscribeResp
children: ReactNode
}
function TargetGroupSelection({ config, groups, children }: TargetGroupSelectionProp) {
let [ selectedGroups, setSelectGroups ] = useState<Array<string>>([]);
const submitCopy = () => {
let promise = null
for (let selectGroup of selectedGroups) {
if (! promise) {
promise = addSubscribe(selectGroup, config)
} else {
promise = promise.then(() => addSubscribe(selectGroup, config))
}
}
if (promise) {
promise.then(() => message.success("复制订阅成功"))
}
return promise;
}
return <>
<Popconfirm title={
<Select mode="multiple" onChange={(value: Array<string>) => setSelectGroups(value)}>
{
Object.keys(groups).map((groupNumber) =>
<Select.Option value={groupNumber} key={groupNumber}>
{`${groupNumber} - ${groups[groupNumber].name}`}
</Select.Option>)
}
</Select>
} onConfirm={submitCopy} >
{ children }
</Popconfirm>
</>
}
interface InputTagCustomProp { interface InputTagCustomProp {
value?: Array<string>, value?: Array<string>,
onChange?: (value: Array<string>) => void, onChange?: (value: Array<string>) => void,

View File

@ -19,7 +19,7 @@ export type LoginContextType = {
export interface SubscribeConfig { export interface SubscribeConfig {
platformName: string platformName: string
target?: string target: string
targetName: string targetName: string
cats: Array<number> cats: Array<number>
tags: Array<string> tags: Array<string>
@ -66,11 +66,3 @@ export interface SubscribeResp {
export interface TargetNameResp { export interface TargetNameResp {
targetName: string targetName: string
} }
export interface CreateSubscribeReq {
platformName: string,
targetName: string,
target: string,
categories: Array<string>,
tags: Array<string>
}

View File

@ -33,7 +33,6 @@ expiringdict = "^1.2.1"
pyjwt = "^2.1.0" pyjwt = "^2.1.0"
aiofiles = "^0.7.0" aiofiles = "^0.7.0"
python-socketio = "^5.4.0" python-socketio = "^5.4.0"
jinja2 = "^3.0.1"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
ipdb = "^0.13.4" ipdb = "^0.13.4"

View File

@ -1,5 +1,7 @@
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
import os
from typing import Union
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
@ -31,6 +33,17 @@ TEST_URL = f'{URL_BASE}test'
sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*") sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*")
socket_app = socketio.ASGIApp(sio, socketio_path="socket") socket_app = socketio.ASGIApp(sio, socketio_path="socket")
class SinglePageApplication(StaticFiles):
def __init__(self, directory: os.PathLike, index='index.html'):
self.index = index
super().__init__(directory=directory, packages=None, html=True, check_dir=True)
async def lookup_path(self, path: str) -> tuple[str, Union[os.stat_result, None]]:
full_path, stat_res = await super().lookup_path(path)
if stat_res is None:
return await super().lookup_path(self.index)
return (full_path, stat_res)
def register_router_fastapi(driver: Driver, socketio): def register_router_fastapi(driver: Driver, socketio):
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
@ -61,7 +74,7 @@ def register_router_fastapi(driver: Driver, socketio):
tags: list[str] tags: list[str]
app = driver.server_app app = driver.server_app
static_path = str((Path(__file__).parent / "dist").resolve()) static_path = (Path(__file__).parent / "dist").resolve()
app.get(TEST_URL)(test) app.get(TEST_URL)(test)
app.get(GLOBAL_CONF_URL)(get_global_conf) app.get(GLOBAL_CONF_URL)(get_global_conf)
app.get(AUTH_URL)(auth) app.get(AUTH_URL)(auth)
@ -76,15 +89,12 @@ def register_router_fastapi(driver: Driver, socketio):
async def _add_group_subs(groupNumber: str, req: AddSubscribeReq): async def _add_group_subs(groupNumber: str, req: AddSubscribeReq):
return await add_group_sub(group_number=groupNumber, platform_name=req.platformName, return await add_group_sub(group_number=groupNumber, platform_name=req.platformName,
target=req.target, target_name=req.targetName, cats=req.categories, tags=req.tags) target=req.target, target_name=req.targetName, cats=req.categories, tags=req.tags)
@app.delete(SUBSCRIBE_URL, dependencies=[Depends(check_group_permission)]) @app.delete(SUBSCRIBE_URL, dependencies=[Depends(check_group_permission)])
async def _del_group_subs(groupNumber: str, target: str, platformName: str): async def _del_group_subs(groupNumber: str, target: str, platformName: str):
return await del_group_sub(groupNumber, platformName, target) return await del_group_sub(groupNumber, platformName, target)
app.mount(URL_BASE, StaticFiles(directory=static_path, html=True), name="bison")
templates = Jinja2Templates(directory=static_path)
@app.get(f'{URL_BASE}{{rest_path:path}}') app.mount(URL_BASE, SinglePageApplication(directory=static_path), name="bison")
async def serve_sap(request: Request, rest_path: str):
return templates.TemplateResponse("index.html", {"request": request})
def init(): def init():
@ -108,10 +118,6 @@ get_token = on_command('后台管理', rule=to_me(), priority=5)
@get_token.handle() @get_token.handle()
async def send_token(bot: "Bot", event: PrivateMessageEvent, state: T_State): async def send_token(bot: "Bot", event: PrivateMessageEvent, state: T_State):
driver = nonebot.get_driver() driver = nonebot.get_driver()
superusers = driver.config.superusers
if event.get_user_id() not in superusers:
await get_token.finish('你不是管理员')
else:
token = tm.get_user_token((event.get_user_id(), event.sender.nickname)) token = tm.get_user_token((event.get_user_id(), event.sender.nickname))
await get_token.finish(f'请访问: {plugin_config.bison_outer_url}auth/{token}') await get_token.finish(f'请访问: {plugin_config.bison_outer_url}auth/{token}')

View File

@ -20,6 +20,17 @@ async def get_global_conf():
} }
return { 'platformConf': res } return { 'platformConf': res }
async def get_admin_groups(qq: int):
bot = nonebot.get_bot()
groups = await bot.call_api('get_group_list')
res = []
for group in groups:
group_id = group['group_id']
users = await bot.call_api('get_group_member_list', group_id=group_id)
for user in users:
if user['user_id'] == qq and user['role'] in ('owner', 'admin'):
res.append({'id': group_id, 'name': group['group_name']})
return res
async def auth(token: str): async def auth(token: str):
if qq_tuple := token_manager.get_user(token): if qq_tuple := token_manager.get_user(token):
@ -41,6 +52,18 @@ async def auth(token: str):
'token': pack_jwt(jwt_obj) 'token': pack_jwt(jwt_obj)
} }
return { 'status': 200, **ret_obj } return { 'status': 200, **ret_obj }
if admin_groups := await get_admin_groups(int(qq)):
jwt_obj = {
'id': str(qq),
'groups': admin_groups
}
ret_obj = {
'type': 'user',
'name': nickname,
'id': str(qq),
'token': pack_jwt(jwt_obj)
}
return { 'status': 200, **ret_obj }
else: else:
return { 'status': 400, 'type': '', 'name': '', 'id': '', 'token': '' } return { 'status': 400, 'type': '', 'name': '', 'id': '', 'token': '' }
else: else: