nonebot-plugin-l4d2-server 0.3.4a2__py3-none-any.whl → 0.3.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- README.md +23 -3
- nonebot_plugin_l4d2_server/__init__.py +75 -20
- nonebot_plugin_l4d2_server/command.py +5 -4
- nonebot_plugin_l4d2_server/config.py +26 -11
- nonebot_plugin_l4d2_server/data/L4D2/image/template/anne.html +7 -4
- nonebot_plugin_l4d2_server/data/L4D2/image/template/back.png +0 -0
- nonebot_plugin_l4d2_server/data/L4D2/image/template/help - /321/205/320/231/320/277/321/206/320/254/320/274.html" +232 -0
- nonebot_plugin_l4d2_server/data/L4D2/image/template/help.html +1 -1
- nonebot_plugin_l4d2_server/data/L4D2/image/template/help_dack.html +231 -0
- nonebot_plugin_l4d2_server/data/L4D2/image/template/ip.html +1 -1
- nonebot_plugin_l4d2_server/data/L4D2/l4d2.json +27 -139
- nonebot_plugin_l4d2_server/l4d2_anne/__init__.py +40 -17
- nonebot_plugin_l4d2_server/l4d2_anne/analysis.py +40 -0
- nonebot_plugin_l4d2_server/l4d2_anne/anne_telecom.py +70 -70
- nonebot_plugin_l4d2_server/l4d2_anne/server.py +2 -1
- nonebot_plugin_l4d2_server/l4d2_anne/startand.py +18 -0
- nonebot_plugin_l4d2_server/l4d2_file/__init__.py +28 -2
- nonebot_plugin_l4d2_server/l4d2_image/__init__.py +8 -11
- nonebot_plugin_l4d2_server/l4d2_queries/__init__.py +4 -0
- nonebot_plugin_l4d2_server/l4d2_queries/maps.py +27 -0
- nonebot_plugin_l4d2_server/l4d2_queries/qqgroup.py +12 -7
- nonebot_plugin_l4d2_server/l4d2_server/web.py +221 -0
- nonebot_plugin_l4d2_server/l4d2_server/webUI.py +353 -0
- nonebot_plugin_l4d2_server/l4d2_server/workshop.py +24 -5
- nonebot_plugin_l4d2_server/seach.py +2 -1
- nonebot_plugin_l4d2_server/utils.py +5 -7
- {nonebot_plugin_l4d2_server-0.3.4a2.dist-info → nonebot_plugin_l4d2_server-0.3.5.dist-info}/METADATA +28 -4
- nonebot_plugin_l4d2_server-0.3.5.dist-info/RECORD +49 -0
- nonebot_plugin_l4d2_server/data/L4D2/image/template/back2.jpg +0 -0
- nonebot_plugin_l4d2_server-0.3.4a2.dist-info/RECORD +0 -43
- {nonebot_plugin_l4d2_server-0.3.4a2.dist-info → nonebot_plugin_l4d2_server-0.3.5.dist-info}/LICENSE +0 -0
- {nonebot_plugin_l4d2_server-0.3.4a2.dist-info → nonebot_plugin_l4d2_server-0.3.5.dist-info}/WHEEL +0 -0
@@ -1,76 +1,76 @@
|
|
1
|
-
from ..l4d2_image.steam import url_to_byte
|
2
|
-
from bs4 import BeautifulSoup
|
3
|
-
from typing import List
|
1
|
+
# from ..l4d2_image.steam import url_to_byte
|
2
|
+
# from bs4 import BeautifulSoup
|
3
|
+
# from typing import List
|
4
4
|
|
5
|
-
#
|
6
|
-
class ANNE_API:
|
5
|
+
# 暂时废弃
|
6
|
+
# class ANNE_API:
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
8
|
+
# async def __init__(
|
9
|
+
# self,
|
10
|
+
# STEAMID:str,
|
11
|
+
# tag:str
|
12
|
+
# ):
|
13
|
+
# self.STEAMID = STEAMID
|
14
|
+
# if tag == '中':
|
15
|
+
# msg1 = await self.anne_msg()
|
16
|
+
# elif tag == '长':
|
17
|
+
# msg1 = await self.anne_msg()
|
18
|
+
# msg1.update(await self.anne_map())
|
19
19
|
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
21
|
+
# async def anne_msg(self):
|
22
|
+
# """个人资料表"""
|
23
|
+
# data_bytes = await url_to_byte('https://sb.trygek.com/l4d_stats/ranking/player.php?steamid={self.STEAMID}')
|
24
|
+
# data_bs = BeautifulSoup(data_bytes, 'html.parser')
|
25
|
+
# data_fom = data_bs.find_all('table')
|
26
|
+
# n = 0
|
27
|
+
# data_dict = {}
|
28
|
+
# while n < 2:
|
29
|
+
# data_list:List[dict] = []
|
30
|
+
# detail2 = data_fom[n]
|
31
|
+
# tr = detail2.find_all('tr')
|
32
|
+
# for i in tr:
|
33
|
+
# title = i.find('td', {'class': 'w-50'})
|
34
|
+
# value = title.find_next_sibling('td')
|
35
|
+
# new_dict = {title.text:value.text}
|
36
|
+
# data_dict.update(new_dict)
|
37
|
+
# data_list.append(data_dict)
|
38
|
+
# n += 1
|
39
|
+
# # 获取头像
|
40
|
+
# element:str = data_fom.find_all(attrs={"style": "cursor:pointer"})[0].get("onclick")
|
41
|
+
# player_url = element.split("'")[1]
|
42
|
+
# data_list[0].update({"个人资料":player_url})
|
43
|
+
# # 获取一言
|
44
|
+
# message = data_fom.select("html body div.content.text-center.text-md-left div.container.text-left div.col-md-12.h-100 div.card-body.worldmap.d-flex.flex-column.justify-content-center.text-center span")
|
45
|
+
# msg_list = []
|
46
|
+
# for i in message:
|
47
|
+
# msg_list.append(i.text)
|
48
|
+
# data_list[0].update({"一言":msg_list})
|
49
|
+
# return data_list
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
51
|
+
# async def anne_map(self):
|
52
|
+
# """个人地图表"""
|
53
|
+
# data_dict = {}
|
54
|
+
# data_bytes = await url_to_byte('https://sb.trygek.com/l4d_stats/ranking/timedmaps.php?steamid={self.STEAMID}')
|
55
|
+
# data_bs = BeautifulSoup(data_bytes, 'html.parser')
|
56
|
+
# tbody = data_bs.select('tbody')
|
57
|
+
# for tr in tbody:
|
58
|
+
# tds = tr.select('td')
|
59
|
+
# n = 0
|
60
|
+
# for td in tds:
|
61
|
+
# n += 1
|
62
|
+
# title:str = td['data-title'][:-1]
|
63
|
+
# data_text = td.text
|
64
|
+
# if title == '特感数量':
|
65
|
+
# special_amount = data_text
|
66
|
+
# elif title == '刷新间隔':
|
67
|
+
# refresh_interval = data_text
|
68
|
+
# else:
|
69
|
+
# if title in data_dict:
|
70
|
+
# data_dict[title].append(data_text)
|
71
|
+
# else:
|
72
|
+
# data_dict[title] = [data_text]
|
73
|
+
# if special_amount and refresh_interval:
|
74
|
+
# data_dict['刷特时间'] = special_amount + refresh_interval
|
75
75
|
|
76
|
-
|
76
|
+
# return data_dict
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
NUMBER_MAP = [4,5,4,5,5,3,3,5,2,5,5,5,4,2]
|
3
|
+
SAVE_MAP = [
|
4
|
+
"c1m4_atrium",
|
5
|
+
"c2m5_concert",
|
6
|
+
"c3m4_plantation",
|
7
|
+
"c4m5_milltown_escape",
|
8
|
+
"c5m5_bridge",
|
9
|
+
"c6m3_port",
|
10
|
+
"c7m3_port",
|
11
|
+
"c8m5_rooftop",
|
12
|
+
"c9m2_lots",
|
13
|
+
"c10m5_houseboat",
|
14
|
+
"c11m5_runway",
|
15
|
+
"c12m5_cornfield",
|
16
|
+
"c13m4_cutthroatcreek",
|
17
|
+
"c14m2_lighthouse"
|
18
|
+
]
|
@@ -3,6 +3,8 @@ from zipfile import ZipFile
|
|
3
3
|
from time import sleep
|
4
4
|
import sys
|
5
5
|
import os
|
6
|
+
import io
|
7
|
+
from typing import List
|
6
8
|
|
7
9
|
from ..utils import get_file,get_vpk
|
8
10
|
from ..config import systems
|
@@ -39,7 +41,7 @@ def open_packet(name:str,down_file:Path):
|
|
39
41
|
if systems == 'win':
|
40
42
|
if name.endswith('.zip'):
|
41
43
|
mes = 'zip文件已下载,正在解压'
|
42
|
-
with ZipFile(down_file, 'r') as z:
|
44
|
+
with support_gbk(ZipFile(down_file, 'r')) as z:
|
43
45
|
z.extractall(down_path)
|
44
46
|
os.remove(down_file)
|
45
47
|
elif name.endswith('.7z'):
|
@@ -99,4 +101,28 @@ def support_gbk(zip_file):
|
|
99
101
|
del name_to_info[name]
|
100
102
|
name_to_info[real_name] = info
|
101
103
|
zip_file = rar_file
|
102
|
-
return zip_file
|
104
|
+
return zip_file
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
async def all_zip_to_one(data_list:List[bytes]):
|
110
|
+
"""多压缩包文件合并"""
|
111
|
+
file_list = []
|
112
|
+
for data in data_list:
|
113
|
+
# 将每个bytes对象解压缩成文件对象
|
114
|
+
# 将文件对象存储在一个列表中
|
115
|
+
file_list.append(io.BytesIO(data))
|
116
|
+
|
117
|
+
# 创建一个新的BytesIO对象
|
118
|
+
data_file = io.BytesIO()
|
119
|
+
|
120
|
+
# 使用zipfile将列表中的文件对象添加到zipfile中
|
121
|
+
with ZipFile(data_file, mode='w') as zf:
|
122
|
+
for i, file in enumerate(file_list):
|
123
|
+
# 将文件名设置为"file{i}.zip",i为文件在列表中的索引
|
124
|
+
filename = f"file{i}.zip"
|
125
|
+
zf.writestr(filename, file.getvalue())
|
126
|
+
|
127
|
+
# 获取zipfile的bytes对象
|
128
|
+
return data_file.getvalue()
|
@@ -4,11 +4,12 @@ from nonebot.log import logger
|
|
4
4
|
from nonebot_plugin_htmlrender import html_to_pic
|
5
5
|
from typing import List,Optional
|
6
6
|
# from .htmlimg import dict_to_dict_img
|
7
|
-
from ..l4d2_anne.anne_telecom import ANNE_API
|
7
|
+
# from ..l4d2_anne.anne_telecom import ANNE_API
|
8
8
|
from ..config import TEXT_PATH
|
9
9
|
from .download import get_head_by_user_id_and_save
|
10
10
|
from .send_image_tool import convert_img
|
11
11
|
import jinja2
|
12
|
+
from ..config import l4_style
|
12
13
|
template_path = TEXT_PATH/"template"
|
13
14
|
|
14
15
|
env = jinja2.Environment(
|
@@ -50,6 +51,7 @@ async def dict_to_html(usr_id,DETAIL_MAP:dict,soup:BeautifulSoup):
|
|
50
51
|
DETAIL_right['playtimes'] = DETAIL_MAP['游玩地图数量:']
|
51
52
|
DETAIL_right['url'] = DETAIL_MAP['个人资料']
|
52
53
|
DETAIL_right['one_msg'] = DETAIL_MAP['一言']
|
54
|
+
DETAIL_right['last_one'] = DETAIL_MAP['救援关']
|
53
55
|
# html_text = soup.prettify()
|
54
56
|
# for key, value in DETAIL_right.items():
|
55
57
|
# html_text = html_text.replace(key,value)
|
@@ -80,22 +82,17 @@ async def server_ip_pic(msg_list:List[dict]):
|
|
80
82
|
server_info['Players'] = players_list
|
81
83
|
print(server_info['Players'])
|
82
84
|
|
83
|
-
# 返回更新后的 msg_list
|
84
|
-
# template_path = TEXT_PATH/"template"
|
85
|
-
# template = env.get_template('ip.html')
|
86
85
|
pic = await get_help_img(msg_list)
|
87
|
-
|
88
|
-
# pic = await html_to_pic(
|
89
|
-
# html,
|
90
|
-
# wait=0,
|
91
|
-
# viewport={"width": 1080, "height": 400},
|
92
|
-
# template_path=f"file://{template_path.absolute()}",)
|
86
|
+
|
93
87
|
return pic
|
94
88
|
|
95
89
|
|
96
90
|
async def get_help_img(plugins: List[dict]) -> Optional[bytes]:
|
97
91
|
try:
|
98
|
-
|
92
|
+
if l4_style == 'black':
|
93
|
+
template = env.get_template("help_dack.html")
|
94
|
+
else:
|
95
|
+
template = env.get_template("help.html")
|
99
96
|
content = await template.render_async(plugins=plugins)
|
100
97
|
return await html_to_pic(
|
101
98
|
content,
|
@@ -7,6 +7,7 @@ async def queries(ip:str,port:int):
|
|
7
7
|
message = 'ip:' + msg_dict['ip'] + '\n'
|
8
8
|
message += '名称:' + msg_dict['name'] + '\n'
|
9
9
|
message += f"地图:{msg_dict['map_']}\n"
|
10
|
+
message += f"延迟:{msg_dict['ping']}\n"
|
10
11
|
message += f"玩家:{msg_dict['players']} / {msg_dict['max_players']}\n"
|
11
12
|
return message
|
12
13
|
|
@@ -23,10 +24,12 @@ async def queries_dict(ip:str,port:int) -> dict:
|
|
23
24
|
msg_dict['players'] = msg.player_count
|
24
25
|
msg_dict['max_players'] = msg.max_players
|
25
26
|
msg_dict['ip'] = str(ip) + ':' +str(port)
|
27
|
+
msg_dict['ping'] = f"{msg.ping*1000:.0f}ms"
|
26
28
|
if msg_dict['players'] < msg_dict['max_players']:
|
27
29
|
msg_dict['enabled'] = True
|
28
30
|
else:
|
29
31
|
msg_dict['enabled'] = False
|
32
|
+
print(msg_dict)
|
30
33
|
return msg_dict
|
31
34
|
|
32
35
|
async def player_queries_anne_dict(ip:str,port:int):
|
@@ -73,6 +76,7 @@ async def msg_ip_to_list(message_list:list):
|
|
73
76
|
max_duration_len = max([len(str(i['Duration'])) for i in message_list])
|
74
77
|
max_score_len = max([len(str(i['Score'])) for i in message_list])
|
75
78
|
for i in message_list:
|
79
|
+
print(i)
|
76
80
|
n += 1
|
77
81
|
name = i['name']
|
78
82
|
Score = i['Score']
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import httpx
|
2
|
+
|
3
|
+
|
4
|
+
async def seach_map(msg:str,qq:str,key:str):
|
5
|
+
url = "http://106.13.207.45:4015/l4d2"
|
6
|
+
json = {
|
7
|
+
"mode":"zh",
|
8
|
+
"map_name":msg,
|
9
|
+
"qq":qq,
|
10
|
+
"key":key
|
11
|
+
}
|
12
|
+
print(json)
|
13
|
+
file = httpx.post(url,json=json)
|
14
|
+
if file.status_code == 200:
|
15
|
+
return file.json()
|
16
|
+
elif file.status_code == 204:
|
17
|
+
return "没有结果"
|
18
|
+
elif file.status_code == 406:
|
19
|
+
return "参数错误"
|
20
|
+
elif file.status_code == 401:
|
21
|
+
return file.json()
|
22
|
+
|
23
|
+
async def map_dict_to_str(data:dict):
|
24
|
+
msg = ""
|
25
|
+
for key,value in data[0].items():
|
26
|
+
msg += f"{key}:{value}\n"
|
27
|
+
return msg
|
@@ -54,6 +54,7 @@ async def qq_ip_queries(msg:List[tuple]):
|
|
54
54
|
async def qq_ip_queries_pic(msg:list):
|
55
55
|
"""输入一个ip的三元元组组成的列表,返回一个输出消息的图片"""
|
56
56
|
msg_list = []
|
57
|
+
pic = None
|
57
58
|
if msg != []:
|
58
59
|
for i in msg:
|
59
60
|
try:
|
@@ -71,6 +72,8 @@ async def qq_ip_queries_pic(msg:list):
|
|
71
72
|
except errors:
|
72
73
|
continue
|
73
74
|
pic = await server_ip_pic(msg_list)
|
75
|
+
if pic == None:
|
76
|
+
return None
|
74
77
|
return pic
|
75
78
|
|
76
79
|
async def get_tan_jian(msg:List[tuple],mode:int):
|
@@ -247,10 +250,12 @@ async def write_json(data_str:str):
|
|
247
250
|
return '删除成功喵'
|
248
251
|
return '序号不正确,请输入【求生更新 删除 腐竹 序号】'
|
249
252
|
return '腐竹名不存在,请输入【求生更新 删除 腐竹 序号】'
|
250
|
-
|
251
|
-
|
252
|
-
ips = ALL_HOST['云']
|
253
|
-
ip_anne_list = []
|
254
|
-
for one_ip in ips:
|
255
|
-
|
256
|
-
|
253
|
+
ip_anne_list=[]
|
254
|
+
try:
|
255
|
+
ips = ALL_HOST['云']
|
256
|
+
ip_anne_list = []
|
257
|
+
for one_ip in ips:
|
258
|
+
host,port = split_maohao(one_ip['ip'])
|
259
|
+
ip_anne_list.append((one_ip['id'],host,port))
|
260
|
+
except KeyError:
|
261
|
+
pass
|
@@ -0,0 +1,221 @@
|
|
1
|
+
import datetime
|
2
|
+
from typing import Optional, Union
|
3
|
+
|
4
|
+
from fastapi import FastAPI
|
5
|
+
from fastapi import Header, HTTPException, Depends
|
6
|
+
from fastapi.responses import JSONResponse, HTMLResponse, RedirectResponse
|
7
|
+
from jose import jwt
|
8
|
+
from nonebot import get_bot, get_app
|
9
|
+
from pydantic import BaseModel
|
10
|
+
from typing import List, Dict
|
11
|
+
from pathlib import Path
|
12
|
+
|
13
|
+
from pydantic import BaseModel, Field
|
14
|
+
|
15
|
+
from nonebot import get_driver, logger
|
16
|
+
from ruamel import yaml
|
17
|
+
|
18
|
+
CONFIG_PATH = Path() / 'data' / 'L4D2' / 'l4d2.yml'
|
19
|
+
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
20
|
+
|
21
|
+
driver = get_driver()
|
22
|
+
|
23
|
+
from .webUI import login_page, admin_app
|
24
|
+
|
25
|
+
requestAdaptor = '''
|
26
|
+
requestAdaptor(api) {
|
27
|
+
api.headers["token"] = localStorage.getItem("token");
|
28
|
+
return api;
|
29
|
+
},
|
30
|
+
'''
|
31
|
+
responseAdaptor = '''
|
32
|
+
responseAdaptor(api, payload, query, request, response) {
|
33
|
+
if (response.data.detail == '登录验证失败或已失效,请重新登录') {
|
34
|
+
window.location.href = '/l4d2/login'
|
35
|
+
window.localStorage.clear()
|
36
|
+
window.sessionStorage.clear()
|
37
|
+
window.alert('登录验证失败或已失效,请重新登录')
|
38
|
+
}
|
39
|
+
return payload
|
40
|
+
},
|
41
|
+
'''
|
42
|
+
|
43
|
+
|
44
|
+
def authentication():
|
45
|
+
def inner(token: Optional[str] = Header(...)):
|
46
|
+
try:
|
47
|
+
payload = jwt.decode(token, config_manager.config.web_secret_key, algorithms='HS256')
|
48
|
+
if not (username := payload.get('username')) or username != config_manager.config.web_username:
|
49
|
+
raise HTTPException(status_code=400, detail='登录验证失败或已失效,请重新登录')
|
50
|
+
except (jwt.JWTError, jwt.ExpiredSignatureError, AttributeError):
|
51
|
+
raise HTTPException(status_code=400, detail='登录验证失败或已失效,请重新登录')
|
52
|
+
|
53
|
+
return Depends(inner)
|
54
|
+
|
55
|
+
|
56
|
+
COMMAND_START = driver.config.command_start.copy()
|
57
|
+
if '' in COMMAND_START:
|
58
|
+
COMMAND_START.remove('')
|
59
|
+
|
60
|
+
|
61
|
+
class ChatGroupConfig(BaseModel):
|
62
|
+
enable: bool = Field(True, alias='群聊学习开关')
|
63
|
+
ban_words: List[str] = Field([], alias='屏蔽词')
|
64
|
+
ban_users: List[int] = Field([], alias='屏蔽用户')
|
65
|
+
answer_threshold: int = Field(4, alias='回复阈值')
|
66
|
+
answer_threshold_weights: List[int] = Field([10, 30, 60], alias='回复阈值权重')
|
67
|
+
repeat_threshold: int = Field(3, alias='复读阈值')
|
68
|
+
break_probability: float = Field(0.25, alias='打断复读概率')
|
69
|
+
speak_enable: bool = Field(True, alias='主动发言开关')
|
70
|
+
speak_threshold: int = Field(5, alias='主动发言阈值')
|
71
|
+
speak_min_interval: int = Field(300, alias='主动发言最小间隔')
|
72
|
+
speak_continuously_probability: float = Field(0.5, alias='连续主动发言概率')
|
73
|
+
speak_continuously_max_len: int = Field(3, alias='最大连续主动发言句数')
|
74
|
+
speak_poke_probability: float = Field(0.5, alias='主动发言附带戳一戳概率')
|
75
|
+
|
76
|
+
def update(self, **kwargs):
|
77
|
+
for key, value in kwargs.items():
|
78
|
+
if key in self.__fields__:
|
79
|
+
self.__setattr__(key, value)
|
80
|
+
|
81
|
+
|
82
|
+
class ChatConfig(BaseModel):
|
83
|
+
total_enable: bool = Field(True, alias='群聊学习总开关')
|
84
|
+
enable_web: bool = Field(True, alias='启用后台管理')
|
85
|
+
web_username: str = Field('chat', alias='后台管理用户名')
|
86
|
+
web_password: str = Field('admin', alias='后台管理密码')
|
87
|
+
web_secret_key: str = Field('49c294d32f69b732ef6447c18379451ce1738922a75cd1d4812ef150318a2ed0',
|
88
|
+
alias='后台管理token密钥')
|
89
|
+
ban_words: List[str] = Field([], alias='全局屏蔽词')
|
90
|
+
ban_users: List[int] = Field([], alias='全局屏蔽用户')
|
91
|
+
KEYWORDS_SIZE: int = Field(3, alias='单句关键词分词数量')
|
92
|
+
cross_group_threshold: int = Field(3, alias='跨群回复阈值')
|
93
|
+
learn_max_count: int = Field(6, alias='最高学习次数')
|
94
|
+
dictionary: List[str] = Field([], alias='自定义词典')
|
95
|
+
group_config: Dict[int, ChatGroupConfig] = Field({}, alias='分群配置')
|
96
|
+
|
97
|
+
def update(self, **kwargs):
|
98
|
+
for key, value in kwargs.items():
|
99
|
+
if key in self.__fields__:
|
100
|
+
self.__setattr__(key, value)
|
101
|
+
|
102
|
+
class ChatConfigManager:
|
103
|
+
|
104
|
+
def __init__(self):
|
105
|
+
self.file_path = CONFIG_PATH
|
106
|
+
if self.file_path.exists():
|
107
|
+
self.config = ChatConfig.parse_obj(
|
108
|
+
yaml.load(self.file_path.read_text(encoding='utf-8'), Loader=yaml.Loader))
|
109
|
+
else:
|
110
|
+
self.config = ChatConfig()
|
111
|
+
self.save()
|
112
|
+
|
113
|
+
def get_group_config(self, group_id: int) -> ChatGroupConfig:
|
114
|
+
if group_id not in self.config.group_config:
|
115
|
+
self.config.group_config[group_id] = ChatGroupConfig()
|
116
|
+
self.save()
|
117
|
+
return self.config.group_config[group_id]
|
118
|
+
|
119
|
+
@property
|
120
|
+
def config_list(self) -> List[str]:
|
121
|
+
return list(self.config.dict(by_alias=True).keys())
|
122
|
+
|
123
|
+
def save(self):
|
124
|
+
with self.file_path.open('w', encoding='utf-8') as f:
|
125
|
+
yaml.dump(
|
126
|
+
self.config.dict(by_alias=True),
|
127
|
+
f,
|
128
|
+
indent=2,
|
129
|
+
Dumper=yaml.RoundTripDumper,
|
130
|
+
allow_unicode=True)
|
131
|
+
|
132
|
+
config_manager = ChatConfigManager()
|
133
|
+
|
134
|
+
class UserModel(BaseModel):
|
135
|
+
username: str
|
136
|
+
password: str
|
137
|
+
|
138
|
+
|
139
|
+
@driver.on_startup
|
140
|
+
async def init_web():
|
141
|
+
if not config_manager.config.enable_web:
|
142
|
+
return
|
143
|
+
app: FastAPI = get_app()
|
144
|
+
|
145
|
+
@app.post('/l4d2/api/login', response_class=JSONResponse)
|
146
|
+
async def login(user: UserModel):
|
147
|
+
if user.username != config_manager.config.web_username or user.password != config_manager.config.web_password:
|
148
|
+
return {
|
149
|
+
'status': -100,
|
150
|
+
'msg': '登录失败,请确认用户ID和密码无误'
|
151
|
+
}
|
152
|
+
token = jwt.encode({'username': user.username,
|
153
|
+
'exp': datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
|
154
|
+
minutes=30)}, config_manager.config.web_secret_key, algorithm='HS256')
|
155
|
+
return {
|
156
|
+
'status': 0,
|
157
|
+
'msg': '登录成功',
|
158
|
+
'data': {
|
159
|
+
'token': token
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
@app.get('/l4d2/api/get_group_list', response_class=JSONResponse, dependencies=[authentication()])
|
164
|
+
async def get_group_list_api():
|
165
|
+
try:
|
166
|
+
group_list = await get_bot().get_group_list()
|
167
|
+
group_list = [{'label': f'{group["group_name"]}({group["group_id"]})', 'value': group['group_id']} for group
|
168
|
+
in group_list]
|
169
|
+
return {
|
170
|
+
'status': 0,
|
171
|
+
'msg': 'ok',
|
172
|
+
'data': {
|
173
|
+
'group_list': group_list
|
174
|
+
}
|
175
|
+
}
|
176
|
+
except ValueError:
|
177
|
+
return {
|
178
|
+
'status': -100,
|
179
|
+
'msg': '获取群和好友列表失败,请确认已连接GOCQ'
|
180
|
+
}
|
181
|
+
|
182
|
+
@app.get('/l4d2/api/chat_global_config', response_class=JSONResponse, dependencies=[authentication()])
|
183
|
+
async def get_chat_global_config():
|
184
|
+
try:
|
185
|
+
bot = get_bot()
|
186
|
+
groups = await bot.get_group_list()
|
187
|
+
member_list = []
|
188
|
+
for group in groups:
|
189
|
+
members = await bot.get_group_member_list(group_id=group['group_id'])
|
190
|
+
member_list.extend(
|
191
|
+
[{'label': f'{member["nickname"] or member["card"]}({member["user_id"]})',
|
192
|
+
'value': member['user_id']}
|
193
|
+
for
|
194
|
+
member in members])
|
195
|
+
config = config_manager.config.dict(exclude={'group_config'})
|
196
|
+
config['member_list'] = member_list
|
197
|
+
return config
|
198
|
+
except ValueError:
|
199
|
+
return {
|
200
|
+
'status': -100,
|
201
|
+
'msg': '获取群和好友列表失败,请确认已连接GOCQ'
|
202
|
+
}
|
203
|
+
|
204
|
+
|
205
|
+
|
206
|
+
@app.get('/l4d2', response_class=RedirectResponse)
|
207
|
+
async def redirect_page():
|
208
|
+
return RedirectResponse('/l4d2/login')
|
209
|
+
|
210
|
+
@app.get('/l4d2/login', response_class=HTMLResponse)
|
211
|
+
async def login_page_app():
|
212
|
+
return login_page.render(site_title='登录 | Learning-Chat 后台管理',
|
213
|
+
theme='ang')
|
214
|
+
|
215
|
+
@app.get('/l4d2/admin', response_class=HTMLResponse)
|
216
|
+
async def admin_page_app():
|
217
|
+
return admin_app.render(site_title='Learning-Chat 后台管理',
|
218
|
+
theme='ang',
|
219
|
+
requestAdaptor=requestAdaptor,
|
220
|
+
responseAdaptor=responseAdaptor)
|
221
|
+
|