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
RUN python3 -m pip install poetry && poetry config virtualenvs.create false
WORKDIR /app
@ -6,5 +11,6 @@ RUN poetry install --no-root --no-dev
# RUN PYPPETEER_DOWNLOAD_HOST='http://npm.taobao.org/mirrors' pyppeteer-install
ADD src /app/src
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
CMD ["python", "bot.py"]

View File

@ -1,5 +1,5 @@
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';
export async function getGlobalConf(): Promise<GlobalConf> {
@ -22,7 +22,7 @@ export async function getTargetName(platformName: string, target: string): Promi
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}})
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 { 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 { SettingOutlined, BugOutlined, DeleteOutlined, CopyOutlined } from '@ant-design/icons';
import { getSubscribe, getTargetName, addSubscribe, delSubscribe } from '../api/config';
@ -74,7 +74,9 @@ function ConfigPage(prop: ConfigPageProp) {
onConfirm={handleDelete(groupNumber, config.platformName, config.target || 'default')}>
<Tooltip title="删除" ><DeleteOutlined /></Tooltip>
</Popconfirm>,
<Tooltip title="添加到其他群"><CopyOutlined /></Tooltip>
<TargetGroupSelection config={config} groups={configData}>
<Tooltip title="添加到其他群"><CopyOutlined /></Tooltip>
</TargetGroupSelection>
]}>
<Form labelCol={{ span: 6 }}>
<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 {
value?: Array<string>,
onChange?: (value: Array<string>) => void,

View File

@ -19,7 +19,7 @@ export type LoginContextType = {
export interface SubscribeConfig {
platformName: string
target?: string
target: string
targetName: string
cats: Array<number>
tags: Array<string>
@ -66,11 +66,3 @@ export interface SubscribeResp {
export interface TargetNameResp {
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"
aiofiles = "^0.7.0"
python-socketio = "^5.4.0"
jinja2 = "^3.0.1"
[tool.poetry.dev-dependencies]
ipdb = "^0.13.4"

View File

@ -1,5 +1,7 @@
from dataclasses import dataclass
from pathlib import Path
import os
from typing import Union
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
@ -31,6 +33,17 @@ TEST_URL = f'{URL_BASE}test'
sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*")
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):
from fastapi.security import OAuth2PasswordBearer
@ -61,7 +74,7 @@ def register_router_fastapi(driver: Driver, socketio):
tags: list[str]
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(GLOBAL_CONF_URL)(get_global_conf)
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):
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)
@app.delete(SUBSCRIBE_URL, dependencies=[Depends(check_group_permission)])
async def _del_group_subs(groupNumber: str, target: str, platformName: str):
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}}')
async def serve_sap(request: Request, rest_path: str):
return templates.TemplateResponse("index.html", {"request": request})
app.mount(URL_BASE, SinglePageApplication(directory=static_path), name="bison")
def init():
@ -108,10 +118,6 @@ get_token = on_command('后台管理', rule=to_me(), priority=5)
@get_token.handle()
async def send_token(bot: "Bot", event: PrivateMessageEvent, state: T_State):
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))
await get_token.finish(f'请访问: {plugin_config.bison_outer_url}auth/{token}')
token = tm.get_user_token((event.get_user_id(), event.sender.nickname))
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 }
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):
if qq_tuple := token_manager.get_user(token):
@ -41,6 +52,18 @@ async def auth(token: str):
'token': pack_jwt(jwt_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:
return { 'status': 400, 'type': '', 'name': '', 'id': '', 'token': '' }
else: