nonebot-plugin-l4d2-server 1.0.2__py3-none-any.whl → 1.0.4__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.
- nonebot_plugin_l4d2_server/__main__.py +96 -40
- nonebot_plugin_l4d2_server/config.py +56 -3
- nonebot_plugin_l4d2_server/l4_help/__init__.py +1 -1
- nonebot_plugin_l4d2_server/l4_request/__init__.py +251 -101
- nonebot_plugin_l4d2_server/l4_request/draw_msg.py +178 -35
- nonebot_plugin_l4d2_server/utils/api/models.py +1 -1
- nonebot_plugin_l4d2_server/utils/api/request.py +29 -10
- nonebot_plugin_l4d2_server/utils/utils.py +11 -2
- {nonebot_plugin_l4d2_server-1.0.2.dist-info → nonebot_plugin_l4d2_server-1.0.4.dist-info}/METADATA +20 -15
- {nonebot_plugin_l4d2_server-1.0.2.dist-info → nonebot_plugin_l4d2_server-1.0.4.dist-info}/RECORD +13 -15
- nonebot_plugin_l4d2_server/l4_anne/__init__.py +0 -125
- nonebot_plugin_l4d2_server/l4_anne/ranne.py +0 -18
- {nonebot_plugin_l4d2_server-1.0.2.dist-info → nonebot_plugin_l4d2_server-1.0.4.dist-info}/WHEEL +0 -0
- {nonebot_plugin_l4d2_server-1.0.2.dist-info → nonebot_plugin_l4d2_server-1.0.4.dist-info}/entry_points.txt +0 -0
- {nonebot_plugin_l4d2_server-1.0.2.dist-info → nonebot_plugin_l4d2_server-1.0.4.dist-info}/licenses/LICENSE +0 -0
@@ -1,11 +1,12 @@
|
|
1
1
|
import random
|
2
|
-
from typing import Dict, List, Optional, cast
|
2
|
+
from typing import Dict, List, Optional, Tuple, Union, cast
|
3
3
|
|
4
4
|
from nonebot.log import logger
|
5
|
+
from nonebot_plugin_alconna import UniMessage
|
5
6
|
|
6
7
|
from ..config import server_all_path
|
7
8
|
from ..l4_image import msg_to_image
|
8
|
-
from ..utils.api.models import AllServer, NserverOut
|
9
|
+
from ..utils.api.models import AllServer, NserverOut
|
9
10
|
from ..utils.api.request import L4API
|
10
11
|
from ..utils.utils import split_maohao
|
11
12
|
from .draw_msg import convert_duration, draw_one_ip, get_much_server
|
@@ -23,6 +24,16 @@ COMMAND = set()
|
|
23
24
|
|
24
25
|
|
25
26
|
async def get_all_server_detail():
|
27
|
+
"""
|
28
|
+
获取所有服务器的详细信息。
|
29
|
+
|
30
|
+
Args:
|
31
|
+
无
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
str: 包含所有服务器详细信息的字符串。
|
35
|
+
|
36
|
+
"""
|
26
37
|
out_list: List[AllServer] = []
|
27
38
|
for group in ALLHOST:
|
28
39
|
msg_list = await get_group_detail(group)
|
@@ -58,40 +69,138 @@ async def get_all_server_detail():
|
|
58
69
|
|
59
70
|
|
60
71
|
async def get_server_detail(
|
61
|
-
command: str,
|
72
|
+
command: str = "",
|
62
73
|
_id: Optional[str] = None,
|
63
74
|
is_img: bool = True,
|
64
75
|
):
|
65
|
-
|
76
|
+
"""
|
77
|
+
异步获取服务器详细信息。
|
78
|
+
|
79
|
+
Args:
|
80
|
+
command (str): 服务器组名。
|
81
|
+
_id (Optional[str], optional): 服务器ID。默认为None。
|
82
|
+
is_img (bool, optional): 是否返回图片格式的信息。默认为True。
|
83
|
+
return_host_port (bool, optional): 是否返回host和port值。默认为False。
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
Union[bytes, str, None, Tuple[str, int]]:
|
87
|
+
如果return_host_port为True且_id不为None,返回(host, port)元组;
|
88
|
+
否则返回服务器详细信息(图片格式返回bytes,文本格式返回str);
|
89
|
+
未找到服务器组返回None。
|
90
|
+
"""
|
91
|
+
server_json = _get_server_json(command)
|
66
92
|
logger.info(server_json)
|
67
93
|
if server_json is None:
|
68
94
|
logger.warning("未找到这个组")
|
69
95
|
return None
|
70
96
|
|
71
97
|
if _id is None:
|
72
|
-
|
73
|
-
logger.info(f"正在请求组服务器信息 {command}")
|
74
|
-
server_dict = await get_much_server(server_json, command)
|
75
|
-
if is_img:
|
76
|
-
out_msg = await msg_to_image(server_dict)
|
77
|
-
else:
|
78
|
-
out_msg = server_dict
|
79
|
-
return out_msg
|
98
|
+
return await _handle_group_info(server_json, command, is_img)
|
80
99
|
|
81
|
-
|
82
|
-
|
83
|
-
|
100
|
+
_ip = await get_single_server_info(server_json, _id)
|
101
|
+
if _ip is None:
|
102
|
+
logger.warning("未找到这个服务器")
|
103
|
+
return None
|
104
|
+
|
105
|
+
out_msg = await _handle_single_server(server_json, _id, is_img)
|
106
|
+
if isinstance(out_msg, bytes):
|
107
|
+
return UniMessage.image(raw=out_msg) + UniMessage.text(
|
108
|
+
f"connect {_ip[0]}:{_ip[1]}",
|
109
|
+
)
|
110
|
+
if isinstance(out_msg, str):
|
111
|
+
return UniMessage.text(out_msg)
|
112
|
+
return None
|
113
|
+
|
114
|
+
|
115
|
+
def _get_server_json(command: str) -> Optional[list]:
|
116
|
+
"""
|
117
|
+
根据命令获取服务器JSON列表
|
118
|
+
|
119
|
+
Args:
|
120
|
+
command (str): 服务器组名
|
121
|
+
|
122
|
+
Returns:
|
123
|
+
Optional[list]: 服务器JSON列表,未找到组时返回None
|
124
|
+
"""
|
125
|
+
if command:
|
126
|
+
return ALLHOST.get(command)
|
127
|
+
server_json = []
|
128
|
+
for servers in ALLHOST.values():
|
129
|
+
server_json.extend(servers)
|
130
|
+
return server_json
|
131
|
+
|
132
|
+
|
133
|
+
async def _handle_group_info(
|
134
|
+
server_json: list,
|
135
|
+
command: str,
|
136
|
+
is_img: bool,
|
137
|
+
) -> Union[bytes, str, None]:
|
138
|
+
"""
|
139
|
+
处理服务器组信息请求
|
140
|
+
|
141
|
+
Args:
|
142
|
+
server_json (list): 服务器JSON列表
|
143
|
+
command (str): 服务器组名
|
144
|
+
is_img (bool): 是否返回图片格式
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
Union[bytes, list, None]: 图片格式返回bytes,否则返回服务器列表
|
148
|
+
"""
|
149
|
+
logger.info(f"正在请求组服务器信息 {command}")
|
150
|
+
server_dict = await get_much_server(server_json, command)
|
151
|
+
if is_img:
|
152
|
+
return await msg_to_image(server_dict)
|
153
|
+
return str(server_dict)
|
154
|
+
|
155
|
+
|
156
|
+
async def get_single_server_info(
|
157
|
+
server_json: list,
|
158
|
+
_id: str,
|
159
|
+
) -> Optional[Tuple[str, int]]:
|
160
|
+
"""
|
161
|
+
获取单个服务器的host和port信息
|
162
|
+
|
163
|
+
Args:
|
164
|
+
server_json (list): 服务器JSON列表
|
165
|
+
_id (str): 服务器ID
|
166
|
+
|
167
|
+
Returns:
|
168
|
+
Optional[Tuple[str, int]]: 返回(host, port)元组,未找到返回None
|
169
|
+
"""
|
170
|
+
logger.info("正在获取单服务器信息")
|
84
171
|
for i in server_json:
|
85
172
|
if str(_id) == str(i["id"]):
|
86
|
-
|
87
|
-
if is_img:
|
88
|
-
return cast(bytes, out_msg)
|
89
|
-
if not is_img:
|
90
|
-
return cast(List[OutServer], out_msg)
|
91
|
-
# print(out_msg)
|
173
|
+
return i["host"], i["port"]
|
92
174
|
return None
|
93
175
|
|
94
176
|
|
177
|
+
async def _handle_single_server(
|
178
|
+
server_json: list,
|
179
|
+
_id: str,
|
180
|
+
is_img: bool,
|
181
|
+
) -> Union[bytes, str, None]:
|
182
|
+
"""
|
183
|
+
处理单个服务器信息请求
|
184
|
+
|
185
|
+
Args:
|
186
|
+
server_json (list): 服务器JSON列表
|
187
|
+
_id (str): 服务器ID
|
188
|
+
is_img (bool): 是否返回图片格式
|
189
|
+
|
190
|
+
Returns:
|
191
|
+
Union[bytes, str, None]: 找到服务器时返回信息,否则返回None
|
192
|
+
"""
|
193
|
+
server_info = await get_single_server_info(server_json, _id)
|
194
|
+
if server_info is None:
|
195
|
+
return None
|
196
|
+
|
197
|
+
host, port = server_info
|
198
|
+
out_msg = await draw_one_ip(host, port)
|
199
|
+
if is_img:
|
200
|
+
return cast(bytes, out_msg)
|
201
|
+
return out_msg
|
202
|
+
|
203
|
+
|
95
204
|
async def get_group_detail(
|
96
205
|
command: str,
|
97
206
|
):
|
@@ -116,98 +225,139 @@ def reload_ip():
|
|
116
225
|
# print("正在读取json文件")
|
117
226
|
group_ip = []
|
118
227
|
for item in server_all_path.iterdir():
|
119
|
-
if item.is_file():
|
120
|
-
|
121
|
-
|
122
|
-
group_server = cast(Dict[str, List[NserverOut]], json_data)
|
123
|
-
|
124
|
-
for group, group_ip in group_server.items():
|
125
|
-
# 处理ip,host,port关系
|
126
|
-
for one_ip in group_ip:
|
127
|
-
if one_ip.get("ip"):
|
128
|
-
if one_ip.get("host") and one_ip.get("port"):
|
129
|
-
pass
|
130
|
-
if one_ip.get("host") and not one_ip.get("port"):
|
131
|
-
one_ip["port"] = 20715
|
132
|
-
if not one_ip.get("host"):
|
133
|
-
one_ip["host"], one_ip["port"] = split_maohao(
|
134
|
-
one_ip.get("ip"),
|
135
|
-
)
|
136
|
-
else:
|
137
|
-
if one_ip.get("host") and one_ip.get("port"):
|
138
|
-
one_ip["ip"] = f'{one_ip["host"]}:{one_ip["port"]}'
|
139
|
-
if one_ip.get("host") and not one_ip.get("port"):
|
140
|
-
one_ip["ip"] = f'{one_ip["host"]}:20715'
|
141
|
-
else:
|
142
|
-
logger.warning(f"{one_ip} 没有ip")
|
228
|
+
if item.is_file() and item.name.endswith("json"):
|
229
|
+
json_data = json.loads(item.read_text(encoding="utf-8"))
|
230
|
+
group_server = cast(Dict[str, List[NserverOut]], json_data)
|
143
231
|
|
144
|
-
|
145
|
-
|
146
|
-
|
232
|
+
for group, group_ip in group_server.items():
|
233
|
+
# 处理ip,host,port关系
|
234
|
+
for one_ip in group_ip:
|
235
|
+
if one_ip.get("ip"):
|
236
|
+
if one_ip.get("host") and one_ip.get("port"):
|
237
|
+
pass
|
238
|
+
if one_ip.get("host") and not one_ip.get("port"):
|
239
|
+
one_ip["port"] = 20715
|
240
|
+
if not one_ip.get("host"):
|
241
|
+
one_ip["host"], one_ip["port"] = split_maohao(
|
242
|
+
one_ip.get("ip"),
|
243
|
+
)
|
244
|
+
else:
|
245
|
+
if one_ip.get("host") and one_ip.get("port"):
|
246
|
+
one_ip["ip"] = f'{one_ip["host"]}:{one_ip["port"]}'
|
247
|
+
if one_ip.get("host") and not one_ip.get("port"):
|
248
|
+
one_ip["ip"] = f'{one_ip["host"]}:20715'
|
249
|
+
else:
|
250
|
+
logger.warning(f"{one_ip} 没有ip")
|
147
251
|
|
148
|
-
|
149
|
-
|
252
|
+
ALLHOST.update({group: group_ip})
|
253
|
+
COMMAND.add(group)
|
254
|
+
logger.success(f"成功加载 {item.name.split('.')[0]} {len(group_ip)}个")
|
150
255
|
|
151
256
|
|
152
257
|
async def tj_request(command: str = "云", tj="tj"):
|
258
|
+
map_type = "普通药役"
|
153
259
|
server_json = ALLHOST.get(command)
|
154
260
|
logger.info(server_json)
|
155
261
|
if server_json is None:
|
156
262
|
logger.warning("未找到这个组")
|
157
263
|
return None
|
158
|
-
|
159
|
-
logger.info("
|
264
|
+
|
265
|
+
logger.info("正在获取电信服务器信息")
|
160
266
|
player_msg = ""
|
161
267
|
right_ip = []
|
162
|
-
|
163
|
-
|
268
|
+
|
269
|
+
async def _filter_servers(servers: list, tj_mode: str) -> list:
|
270
|
+
"""筛选符合条件的服务器
|
271
|
+
Args:
|
272
|
+
servers: 服务器列表
|
273
|
+
tj_mode: 筛选模式('tj'或'zl')
|
274
|
+
Returns:
|
275
|
+
符合条件的服务器列表
|
276
|
+
"""
|
277
|
+
filtered = []
|
278
|
+
for i in servers:
|
279
|
+
ser_list = await L4API.a2s_info([(i["host"], i["port"])], is_player=True)
|
280
|
+
if not ser_list:
|
281
|
+
continue
|
282
|
+
|
283
|
+
srv = ser_list[0][0]
|
284
|
+
players = ser_list[0][1]
|
285
|
+
|
286
|
+
if tj_mode == "tj" and map_type in srv.map_name:
|
287
|
+
score = sum(p.score for p in players[:4])
|
288
|
+
t = srv.map_name.split("[")[-1].split("特")[0]
|
289
|
+
if t.isdigit() and int(t) * 50 < score:
|
290
|
+
logger.info(
|
291
|
+
f"符合TJ条件的服务器: {i['host']}:{i['port']}, 地图: {srv.map_name}, 分数: {score}",
|
292
|
+
)
|
293
|
+
filtered.append(i)
|
294
|
+
elif tj_mode == "zl" and map_type in srv.map_name and len(players) <= 4:
|
295
|
+
logger.info(
|
296
|
+
f"符合ZL条件的服务器: {i['host']}:{i['port']}, 地图: {srv.map_name}, 玩家数: {len(players)}",
|
297
|
+
)
|
298
|
+
filtered.append(i)
|
299
|
+
return filtered
|
300
|
+
|
301
|
+
async def _format_players(player_list: list) -> str:
|
302
|
+
"""格式化玩家信息
|
303
|
+
Args:
|
304
|
+
player_list: 玩家对象列表
|
305
|
+
Returns:
|
306
|
+
格式化后的玩家信息字符串
|
307
|
+
"""
|
308
|
+
durations = [await convert_duration(p.duration) for p in player_list]
|
309
|
+
max_duration_len = max(len(str(d)) for d in durations)
|
310
|
+
max_score_len = max(len(str(p.score)) for p in player_list)
|
311
|
+
return "\n".join(
|
312
|
+
f"[{p.score:>{max_score_len}}] | {durations[i]:^{max_duration_len}} | {p.name[0]}***{p.name[-1]}"
|
313
|
+
for i, p in enumerate(player_list)
|
314
|
+
)
|
315
|
+
|
316
|
+
def _build_message(srv_info, players_msg: str, selected_srv: dict) -> str:
|
317
|
+
"""构建服务器信息消息
|
318
|
+
Args:
|
319
|
+
srv_info: 服务器信息对象
|
320
|
+
players_msg: 格式化后的玩家信息
|
321
|
+
selected_srv: 选中的服务器信息
|
322
|
+
Returns:
|
323
|
+
完整的消息字符串
|
324
|
+
"""
|
325
|
+
msg = f"""*{srv_info.server_name}*
|
326
|
+
游戏: {srv_info.folder}
|
327
|
+
地图: {srv_info.map_name}
|
328
|
+
人数: {srv_info.player_count}/{srv_info.max_players}"""
|
329
|
+
if srv_info.ping is not None:
|
330
|
+
msg += f"\nping: {srv_info.ping * 1000:.0f}ms\n{players_msg}"
|
331
|
+
if config.l4_show_ip:
|
332
|
+
msg += f"\nconnect {selected_srv['host']}:{selected_srv['port']}"
|
333
|
+
return msg
|
334
|
+
|
335
|
+
try:
|
336
|
+
right_ip = await _filter_servers(server_json, tj)
|
337
|
+
|
338
|
+
if not right_ip:
|
339
|
+
logger.warning("没有找到符合条件的服务器")
|
340
|
+
return "没有符合条件的服务器"
|
341
|
+
|
342
|
+
logger.info(
|
343
|
+
f"符合条件的服务器列表: {[f'{ip['host']}:{ip['port']}' for ip in right_ip]}",
|
344
|
+
)
|
345
|
+
s = random.choice(right_ip)
|
346
|
+
logger.info(f"最终选择的服务器: {s['host']}:{s['port']}")
|
347
|
+
ser_list = await L4API.a2s_info([(s["host"], s["port"])], is_player=True)
|
348
|
+
if not ser_list:
|
349
|
+
return "获取服务器信息失败"
|
350
|
+
|
164
351
|
one_server = ser_list[0][0]
|
165
352
|
one_player = ser_list[0][1]
|
166
353
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
right_ip.append(i)
|
178
|
-
if tj == "zl" and "普通药役" in one_server.map_name and len(one_player) <= 4:
|
179
|
-
right_ip.append(i)
|
180
|
-
|
181
|
-
if not right_ip:
|
182
|
-
return "没有符合条件的服务器"
|
183
|
-
|
184
|
-
s = random.choice(right_ip)
|
185
|
-
ser_list = await L4API.a2s_info([(s["host"], s["port"])], is_player=True)
|
186
|
-
one_server = ser_list[0][0]
|
187
|
-
one_player = ser_list[0][1]
|
188
|
-
if len(one_player):
|
189
|
-
max_duration_len = max(
|
190
|
-
[len(str(await convert_duration(i.duration))) for i in one_player],
|
191
|
-
)
|
192
|
-
max_score_len = max(len(str(i.score)) for i in one_player)
|
193
|
-
|
194
|
-
for player in one_player:
|
195
|
-
soc = "[{:>{}}]".format(player.score, max_score_len)
|
196
|
-
chines_dur = await convert_duration(player.duration)
|
197
|
-
dur = "{:^{}}".format(chines_dur, max_duration_len)
|
198
|
-
name = f"{player.name[0]}***{player.name[-1]}"
|
199
|
-
player_msg += f"{soc} | {dur} | {name} \n"
|
200
|
-
else:
|
201
|
-
player_msg = "服务器感觉很安静啊"
|
202
|
-
msg = f"""*{one_server.server_name}*
|
203
|
-
游戏: {one_server.folder}
|
204
|
-
地图: {one_server.map_name}
|
205
|
-
人数: {one_server.player_count}/{one_server.max_players}"""
|
206
|
-
if one_server.ping is not None:
|
207
|
-
msg += f"""
|
208
|
-
ping: {one_server.ping * 1000:.0f}ms
|
209
|
-
{player_msg}"""
|
210
|
-
if config.l4_show_ip:
|
211
|
-
msg += f"""
|
212
|
-
connect {s["host"]}:{s["port"]}"""
|
213
|
-
return msg
|
354
|
+
if one_player:
|
355
|
+
player_msg = await _format_players(one_player)
|
356
|
+
else:
|
357
|
+
player_msg = "服务器感觉很安静啊"
|
358
|
+
|
359
|
+
return _build_message(one_server, player_msg, s)
|
360
|
+
|
361
|
+
except Exception as e:
|
362
|
+
logger.error(f"tj_request error: {e}")
|
363
|
+
return "获取服务器信息时出错"
|
@@ -1,56 +1,193 @@
|
|
1
1
|
import asyncio
|
2
2
|
|
3
3
|
# from logging import log
|
4
|
+
import io
|
5
|
+
from pathlib import Path
|
4
6
|
from typing import List, Tuple
|
5
7
|
|
8
|
+
from nonebot.log import logger
|
9
|
+
from PIL import Image, ImageDraw, ImageFont
|
10
|
+
|
6
11
|
from ..config import config
|
7
12
|
from ..utils.api.models import NserverOut, OutServer
|
8
13
|
from ..utils.api.request import L4API
|
9
14
|
|
10
15
|
|
11
|
-
async def draw_one_ip(host: str, port: int):
|
16
|
+
async def draw_one_ip(host: str, port: int, is_img: bool = config.l4_image):
|
12
17
|
"""输出单个ip"""
|
13
|
-
# 先用文字凑合
|
14
18
|
try:
|
15
19
|
ser_list = await L4API.a2s_info([(host, port)], is_player=True)
|
16
20
|
except asyncio.exceptions.TimeoutError:
|
17
21
|
return "服务器无响应"
|
18
|
-
player_msg = ""
|
19
22
|
one_server = ser_list[0][0]
|
20
23
|
one_player = ser_list[0][1]
|
21
24
|
|
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
|
-
|
50
|
-
|
51
|
-
|
25
|
+
async def format_player_info(players: list) -> str:
|
26
|
+
"""格式化玩家信息
|
27
|
+
Args:
|
28
|
+
players: 玩家对象列表
|
29
|
+
Returns:
|
30
|
+
格式化后的玩家信息字符串
|
31
|
+
"""
|
32
|
+
player_msg = ""
|
33
|
+
if len(players):
|
34
|
+
max_duration_len = max(
|
35
|
+
[len(str(await convert_duration(i.duration))) for i in players],
|
36
|
+
)
|
37
|
+
max_score_len = max(len(str(i.score)) for i in players)
|
38
|
+
|
39
|
+
for player in players:
|
40
|
+
soc = "[{:>{}}]".format(player.score, max_score_len)
|
41
|
+
chines_dur = await convert_duration(player.duration)
|
42
|
+
dur = "{:^{}}".format(chines_dur, max_duration_len)
|
43
|
+
name_leg = len(player.name)
|
44
|
+
if name_leg > 2:
|
45
|
+
xing = ":)" * (name_leg - 2)
|
46
|
+
name = f"{player.name[0]}{xing}{player.name[-1]}"
|
47
|
+
else:
|
48
|
+
name = player.name
|
49
|
+
player_msg += f"{soc} | {dur} | {name} \n"
|
50
|
+
else:
|
51
|
+
player_msg = "服务器感觉很安静啊"
|
52
|
+
return player_msg
|
53
|
+
|
54
|
+
def build_server_message(server, player_info: str) -> str:
|
55
|
+
"""构建服务器信息消息
|
56
|
+
Args:
|
57
|
+
server: 服务器对象
|
58
|
+
player_info: 格式化后的玩家信息字符串
|
59
|
+
Returns:
|
60
|
+
完整的服务器信息字符串
|
61
|
+
"""
|
62
|
+
msg = f"""-{server.server_name}-
|
63
|
+
游戏: {server.folder}
|
64
|
+
地图: {server.map_name}
|
65
|
+
人数: {server.player_count}/{server.max_players}"""
|
66
|
+
if server.ping is not None:
|
67
|
+
msg += f"""
|
68
|
+
ping: {server.ping * 1000:.0f}ms
|
69
|
+
{player_info}"""
|
70
|
+
if config.l4_show_ip:
|
71
|
+
msg += f"""
|
52
72
|
connect {host}:{port}"""
|
53
|
-
|
73
|
+
return msg
|
74
|
+
|
75
|
+
def draw_text_on_image(
|
76
|
+
text: str,
|
77
|
+
font: ImageFont.FreeTypeFont,
|
78
|
+
draw: ImageDraw.ImageDraw,
|
79
|
+
) -> Image.Image:
|
80
|
+
"""在图片上绘制文本
|
81
|
+
Args:
|
82
|
+
text: 要绘制的文本
|
83
|
+
font: PIL字体对象
|
84
|
+
draw: PIL绘图对象
|
85
|
+
Returns:
|
86
|
+
包含绘制文本的PIL图片对象
|
87
|
+
"""
|
88
|
+
try:
|
89
|
+
# 分割文本为标题行和内容行
|
90
|
+
lines = text.split("\n")
|
91
|
+
if not lines:
|
92
|
+
return Image.new("RGB", (600, 400), color=(73, 109, 137))
|
93
|
+
|
94
|
+
title = lines[0]
|
95
|
+
content = "\n".join(lines[1:]) if len(lines) > 1 else ""
|
96
|
+
|
97
|
+
# 计算各部分文字尺寸
|
98
|
+
title_bbox = font.getbbox(title)
|
99
|
+
title_width = title_bbox[2] - title_bbox[0]
|
100
|
+
title_height = title_bbox[3] - title_bbox[1]
|
101
|
+
|
102
|
+
content_width = 0
|
103
|
+
content_height = 0
|
104
|
+
if content:
|
105
|
+
content_lines = content.split("\n")
|
106
|
+
line_height = font.getbbox("A")[3] - font.getbbox("A")[1]
|
107
|
+
content_height = len(content_lines) * line_height
|
108
|
+
content_width = max(
|
109
|
+
font.getbbox(line)[2] - font.getbbox(line)[0]
|
110
|
+
for line in content_lines
|
111
|
+
)
|
112
|
+
|
113
|
+
# 计算图片尺寸
|
114
|
+
margin = 20
|
115
|
+
line_spacing = 5
|
116
|
+
img_width = max(title_width, content_width) + 2 * margin
|
117
|
+
content_lines_count = len(content.split("\n")) if content else 0
|
118
|
+
img_height = max(
|
119
|
+
title_height
|
120
|
+
+ content_height
|
121
|
+
+ (line_spacing + 1) * max(0, content_lines_count - 1)
|
122
|
+
+ 2 * margin,
|
123
|
+
300,
|
124
|
+
)
|
125
|
+
|
126
|
+
# 加载背景图片
|
127
|
+
bg_path = (
|
128
|
+
Path(__file__).parent.parent / "l4_image" / "img" / "anne" / "back.png"
|
129
|
+
)
|
130
|
+
try:
|
131
|
+
img = Image.open(bg_path)
|
132
|
+
# 调整背景图尺寸
|
133
|
+
img = img.resize((int(img_width), int(img_height)))
|
134
|
+
logger.info(f"图片像素大小: {img.width}x{img.height}")
|
135
|
+
draw = ImageDraw.Draw(img)
|
136
|
+
|
137
|
+
title_x = (img_width - title_width) // 2
|
138
|
+
title_y = margin
|
139
|
+
draw.text((title_x, title_y), title, font=font, fill=(255, 255, 255))
|
140
|
+
|
141
|
+
if content:
|
142
|
+
content_x = margin
|
143
|
+
content_y = title_y + title_height + margin
|
144
|
+
draw.text(
|
145
|
+
(content_x, content_y),
|
146
|
+
content,
|
147
|
+
font=font,
|
148
|
+
fill=(255, 255, 255),
|
149
|
+
spacing=line_spacing,
|
150
|
+
)
|
151
|
+
|
152
|
+
return img
|
153
|
+
except Exception as e:
|
154
|
+
logger.error(f"加载背景图片失败: {e}")
|
155
|
+
img = Image.new(
|
156
|
+
"RGB",
|
157
|
+
(int(img_width), int(img_height)),
|
158
|
+
color=(73, 109, 137),
|
159
|
+
)
|
160
|
+
draw = ImageDraw.Draw(img)
|
161
|
+
draw.text((title_x, title_y), title, font=font, fill=(255, 255, 255))
|
162
|
+
if content:
|
163
|
+
draw.text(
|
164
|
+
(content_x, content_y),
|
165
|
+
content,
|
166
|
+
font=font,
|
167
|
+
fill=(255, 255, 255),
|
168
|
+
spacing=line_spacing,
|
169
|
+
)
|
170
|
+
return img
|
171
|
+
except Exception as e:
|
172
|
+
logger.error(f"绘制图片时出错: {e}")
|
173
|
+
error_img = Image.new("RGB", (600, 400), color=(255, 0, 0))
|
174
|
+
error_draw = ImageDraw.Draw(error_img)
|
175
|
+
error_draw.text((10, 10), "图片生成失败", fill=(255, 255, 255))
|
176
|
+
return error_img
|
177
|
+
|
178
|
+
player_info = await format_player_info(one_player)
|
179
|
+
server_message = build_server_message(one_server, player_info)
|
180
|
+
|
181
|
+
if is_img:
|
182
|
+
# 加载字体
|
183
|
+
font_path = Path(__file__).parent.parent / "data" / "font" / "loli.ttf"
|
184
|
+
font = ImageFont.truetype(str(font_path), 18)
|
185
|
+
draw = ImageDraw.Draw(Image.new("RGB", (600, 400), color=(73, 109, 137)))
|
186
|
+
img = draw_text_on_image(server_message, font, draw)
|
187
|
+
img_byte_arr = io.BytesIO()
|
188
|
+
img.save(img_byte_arr, format="PNG")
|
189
|
+
return img_byte_arr.getvalue()
|
190
|
+
return server_message
|
54
191
|
|
55
192
|
|
56
193
|
async def get_much_server(server_json: List[NserverOut], command: str):
|
@@ -64,7 +201,7 @@ async def get_much_server(server_json: List[NserverOut], command: str):
|
|
64
201
|
for index, i in enumerate(all_server):
|
65
202
|
out_server.append(
|
66
203
|
{
|
67
|
-
"server": i[0],
|
204
|
+
"server": i[0], # type: ignore
|
68
205
|
"player": i[1],
|
69
206
|
"host": server_json[index]["host"],
|
70
207
|
"port": server_json[index]["port"],
|
@@ -77,6 +214,12 @@ async def get_much_server(server_json: List[NserverOut], command: str):
|
|
77
214
|
|
78
215
|
|
79
216
|
async def convert_duration(duration: float) -> str:
|
217
|
+
"""Convert duration in seconds to human-readable string format (e.g. '1h 30m 15s')
|
218
|
+
Args:
|
219
|
+
duration: Duration in seconds
|
220
|
+
Returns:
|
221
|
+
Formatted time string
|
222
|
+
"""
|
80
223
|
minutes, seconds = divmod(duration, 60)
|
81
224
|
hours, minutes = divmod(minutes, 60)
|
82
225
|
time_str = ""
|