nonebot-plugin-l4d2-server 0.6.6__py3-none-any.whl → 1.0.0a1__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.
Files changed (186) hide show
  1. nonebot_plugin_l4d2_server/__init__.py +9 -92
  2. nonebot_plugin_l4d2_server/__main__.py +90 -0
  3. nonebot_plugin_l4d2_server/config.py +30 -0
  4. nonebot_plugin_l4d2_server/data/L4D2/image/head/head.png +0 -0
  5. nonebot_plugin_l4d2_server/data/L4D2/image/header/logo.png +0 -0
  6. nonebot_plugin_l4d2_server/data/L4D2/image/header/player1.jpg +0 -0
  7. nonebot_plugin_l4d2_server/data/L4D2/image/template/anne.html +50 -42
  8. nonebot_plugin_l4d2_server/data/L4D2/image/template/back.png +0 -0
  9. nonebot_plugin_l4d2_server/data/L4D2/image/template/group_ip.html +1 -2
  10. nonebot_plugin_l4d2_server/data/L4D2/image/template/help.html +1 -2
  11. nonebot_plugin_l4d2_server/data/L4D2/image/template/help_dack.html +259 -221
  12. nonebot_plugin_l4d2_server/data/L4D2/image/template/ip.html +1 -2
  13. nonebot_plugin_l4d2_server/data/font/loli.ttf +0 -0
  14. nonebot_plugin_l4d2_server/data/icon//344/273/213/347/273/215.png +0 -0
  15. nonebot_plugin_l4d2_server/data/icon//344/273/273/345/212/241.png +0 -0
  16. nonebot_plugin_l4d2_server/data/icon//344/277/241/346/201/257.png +0 -0
  17. nonebot_plugin_l4d2_server/data/icon//345/205/254/345/221/212.png +0 -0
  18. nonebot_plugin_l4d2_server/data/icon//345/210/200/345/211/221.png +0 -0
  19. nonebot_plugin_l4d2_server/data/icon//345/210/207/346/215/242.png +0 -0
  20. nonebot_plugin_l4d2_server/data/icon//345/210/240/351/231/244.png +0 -0
  21. nonebot_plugin_l4d2_server/data/icon//345/210/267/346/226/260.png +0 -0
  22. nonebot_plugin_l4d2_server/data/icon//345/215/241/347/273/204.png +0 -0
  23. nonebot_plugin_l4d2_server/data/icon//345/223/252/351/207/214.png +0 -0
  24. nonebot_plugin_l4d2_server/data/icon//345/234/260/345/233/276.png +0 -0
  25. nonebot_plugin_l4d2_server/data/icon//345/257/274/345/205/245.png +0 -0
  26. nonebot_plugin_l4d2_server/data/icon//345/257/274/345/207/272.png +0 -0
  27. nonebot_plugin_l4d2_server/data/icon//345/275/261.png +0 -0
  28. nonebot_plugin_l4d2_server/data/icon//346/213/274/345/233/276.png +0 -0
  29. nonebot_plugin_l4d2_server/data/icon//346/216/242/347/264/242.png +0 -0
  30. nonebot_plugin_l4d2_server/data/icon//346/216/250/351/200/201.png +0 -0
  31. nonebot_plugin_l4d2_server/data/icon//346/224/266/351/233/206.png +0 -0
  32. nonebot_plugin_l4d2_server/data/icon//346/224/273/347/225/245.png +0 -0
  33. nonebot_plugin_l4d2_server/data/icon//346/233/264/346/226/260.png +0 -0
  34. nonebot_plugin_l4d2_server/data/icon//346/235/220/346/226/231.png +0 -0
  35. nonebot_plugin_l4d2_server/data/icon//346/237/245/350/257/242.png +0 -0
  36. nonebot_plugin_l4d2_server/data/icon//346/240/241/351/252/214.png +0 -0
  37. nonebot_plugin_l4d2_server/data/icon//346/257/217/346/234/210.png +0 -0
  38. nonebot_plugin_l4d2_server/data/icon//346/267/261/346/270/212.png +0 -0
  39. nonebot_plugin_l4d2_server/data/icon//346/267/273/345/212/240.png +0 -0
  40. nonebot_plugin_l4d2_server/data/icon//346/270/205/351/231/244.png +0 -0
  41. nonebot_plugin_l4d2_server/data/icon//347/212/266/346/200/201.png +0 -0
  42. nonebot_plugin_l4d2_server/data/icon//347/255/276/345/210/260.png +0 -0
  43. nonebot_plugin_l4d2_server/data/icon//347/273/221/345/256/232.png +0 -0
  44. nonebot_plugin_l4d2_server/data/icon//350/241/250.png +0 -0
  45. nonebot_plugin_l4d2_server/data/icon//350/241/250/346/203/205.png +0 -0
  46. nonebot_plugin_l4d2_server/data/icon//350/247/222/350/211/262.png +0 -0
  47. nonebot_plugin_l4d2_server/data/icon//350/256/260/345/275/225.png +0 -0
  48. nonebot_plugin_l4d2_server/data/icon//351/205/215/347/275/256.png +0 -0
  49. nonebot_plugin_l4d2_server/data/icon//351/207/215/345/220/257.png +0 -0
  50. nonebot_plugin_l4d2_server/data/img/l4d2.png +0 -0
  51. nonebot_plugin_l4d2_server/data/img/linux.png +0 -0
  52. nonebot_plugin_l4d2_server/data/img/vac.png +0 -0
  53. nonebot_plugin_l4d2_server/data/img/white.png +0 -0
  54. nonebot_plugin_l4d2_server/l4_help/Help.json +65 -0
  55. nonebot_plugin_l4d2_server/l4_help/__init__.py +57 -0
  56. nonebot_plugin_l4d2_server/l4_help/draw.py +206 -0
  57. nonebot_plugin_l4d2_server/l4_help/icon//344/273/213/347/273/215.png +0 -0
  58. nonebot_plugin_l4d2_server/l4_help/icon//344/273/273/345/212/241.png +0 -0
  59. nonebot_plugin_l4d2_server/l4_help/icon//344/277/241/346/201/257.png +0 -0
  60. nonebot_plugin_l4d2_server/l4_help/icon//345/205/254/345/221/212.png +0 -0
  61. nonebot_plugin_l4d2_server/l4_help/icon//345/210/200/345/211/221.png +0 -0
  62. nonebot_plugin_l4d2_server/l4_help/icon//345/210/207/346/215/242.png +0 -0
  63. nonebot_plugin_l4d2_server/l4_help/icon//345/210/240/351/231/244.png +0 -0
  64. nonebot_plugin_l4d2_server/l4_help/icon//345/210/267/346/226/260.png +0 -0
  65. nonebot_plugin_l4d2_server/l4_help/icon//345/215/241/347/273/204.png +0 -0
  66. nonebot_plugin_l4d2_server/l4_help/icon//345/223/252/351/207/214.png +0 -0
  67. nonebot_plugin_l4d2_server/l4_help/icon//345/234/260/345/233/276.png +0 -0
  68. nonebot_plugin_l4d2_server/l4_help/icon//345/257/274/345/205/245.png +0 -0
  69. nonebot_plugin_l4d2_server/l4_help/icon//345/257/274/345/207/272.png +0 -0
  70. nonebot_plugin_l4d2_server/l4_help/icon//345/275/261.png +0 -0
  71. nonebot_plugin_l4d2_server/l4_help/icon//346/213/274/345/233/276.png +0 -0
  72. nonebot_plugin_l4d2_server/l4_help/icon//346/216/242/347/264/242.png +0 -0
  73. nonebot_plugin_l4d2_server/l4_help/icon//346/216/250/351/200/201.png +0 -0
  74. nonebot_plugin_l4d2_server/l4_help/icon//346/224/266/351/233/206.png +0 -0
  75. nonebot_plugin_l4d2_server/l4_help/icon//346/224/273/347/225/245.png +0 -0
  76. nonebot_plugin_l4d2_server/l4_help/icon//346/233/264/346/226/260.png +0 -0
  77. nonebot_plugin_l4d2_server/l4_help/icon//346/235/220/346/226/231.png +0 -0
  78. nonebot_plugin_l4d2_server/l4_help/icon//346/237/245/350/257/242.png +0 -0
  79. nonebot_plugin_l4d2_server/l4_help/icon//346/240/241/351/252/214.png +0 -0
  80. nonebot_plugin_l4d2_server/l4_help/icon//346/257/217/346/234/210.png +0 -0
  81. nonebot_plugin_l4d2_server/l4_help/icon//346/267/261/346/270/212.png +0 -0
  82. nonebot_plugin_l4d2_server/l4_help/icon//346/267/273/345/212/240.png +0 -0
  83. nonebot_plugin_l4d2_server/l4_help/icon//346/270/205/351/231/244.png +0 -0
  84. nonebot_plugin_l4d2_server/l4_help/icon//347/212/266/346/200/201.png +0 -0
  85. nonebot_plugin_l4d2_server/l4_help/icon//347/255/276/345/210/260.png +0 -0
  86. nonebot_plugin_l4d2_server/l4_help/icon//347/273/221/345/256/232.png +0 -0
  87. nonebot_plugin_l4d2_server/l4_help/icon//350/241/250.png +0 -0
  88. nonebot_plugin_l4d2_server/l4_help/icon//350/241/250/346/203/205.png +0 -0
  89. nonebot_plugin_l4d2_server/l4_help/icon//350/247/222/350/211/262.png +0 -0
  90. nonebot_plugin_l4d2_server/l4_help/icon//350/256/260/345/275/225.png +0 -0
  91. nonebot_plugin_l4d2_server/l4_help/icon//351/205/215/347/275/256.png +0 -0
  92. nonebot_plugin_l4d2_server/l4_help/icon//351/207/215/345/220/257.png +0 -0
  93. nonebot_plugin_l4d2_server/l4_help/texture2d/badge.png +0 -0
  94. nonebot_plugin_l4d2_server/l4_help/texture2d/banner.png +0 -0
  95. nonebot_plugin_l4d2_server/l4_help/texture2d/bg.jpg +0 -0
  96. nonebot_plugin_l4d2_server/l4_help/texture2d/button.png +0 -0
  97. nonebot_plugin_l4d2_server/l4_help/texture2d/icon.png +0 -0
  98. nonebot_plugin_l4d2_server/l4_image/__init__.py +16 -0
  99. nonebot_plugin_l4d2_server/l4_image/convert.py +175 -0
  100. nonebot_plugin_l4d2_server/{l4d2_image → l4_image}/download.py +6 -30
  101. nonebot_plugin_l4d2_server/{l4d2_image/__init__.py → l4_image/html_img.py} +54 -45
  102. nonebot_plugin_l4d2_server/l4_image/image_tools.py +468 -0
  103. nonebot_plugin_l4d2_server/l4_image/img/anne/anne.html +60 -0
  104. nonebot_plugin_l4d2_server/l4_image/img/anne/back.png +0 -0
  105. nonebot_plugin_l4d2_server/l4_image/img/anne/group_ip.html +259 -0
  106. nonebot_plugin_l4d2_server/l4_image/img/anne/ip.html +55 -0
  107. nonebot_plugin_l4d2_server/l4_image/img/head/head.png +0 -0
  108. nonebot_plugin_l4d2_server/l4_image/img/header/logo.png +0 -0
  109. nonebot_plugin_l4d2_server/l4_image/img/header/player1.jpg +0 -0
  110. nonebot_plugin_l4d2_server/l4_image/img/template/Bocchi_The_Rock.html +312 -0
  111. nonebot_plugin_l4d2_server/l4_image/img/template/Bocchi_The_Rock.png +0 -0
  112. nonebot_plugin_l4d2_server/l4_image/img/template/HYPixel11pxU-2.ttf +0 -0
  113. nonebot_plugin_l4d2_server/l4_image/img/template/Pixel.html +350 -0
  114. nonebot_plugin_l4d2_server/l4_image/img/template/Pixel.png +0 -0
  115. nonebot_plugin_l4d2_server/l4_image/img/template/Rainbow.html +321 -0
  116. nonebot_plugin_l4d2_server/l4_image/img/template/Rainbow.png +0 -0
  117. nonebot_plugin_l4d2_server/l4_image/img/template/Tutumianhuatang-Bold-2.ttf +0 -0
  118. nonebot_plugin_l4d2_server/l4_image/img/template/bilibili.svg +1 -0
  119. nonebot_plugin_l4d2_server/l4_image/img/template/fingerprint.svg +15 -0
  120. nonebot_plugin_l4d2_server/l4_image/img/template/github.svg +1 -0
  121. nonebot_plugin_l4d2_server/l4_image/img/template/l.svg +9 -0
  122. nonebot_plugin_l4d2_server/l4_image/img/template/m.svg +1 -0
  123. nonebot_plugin_l4d2_server/l4_image/img/template/normal.html +257 -0
  124. nonebot_plugin_l4d2_server/l4_image/img/template/vue.css +531 -0
  125. nonebot_plugin_l4d2_server/l4_image/img/template/w.svg +1 -0
  126. nonebot_plugin_l4d2_server/l4_image/img/template//345/276/256/350/275/257/351/233/205/351/273/221.ttf +0 -0
  127. nonebot_plugin_l4d2_server/l4_image/model.py +15 -0
  128. nonebot_plugin_l4d2_server/{l4d2_image → l4_image}/vtfs.py +2 -0
  129. nonebot_plugin_l4d2_server/l4_request/__init__.py +84 -0
  130. nonebot_plugin_l4d2_server/l4_request/draw_msg.py +88 -0
  131. nonebot_plugin_l4d2_server/utils/api/api.py +4 -0
  132. nonebot_plugin_l4d2_server/utils/api/models.py +29 -0
  133. nonebot_plugin_l4d2_server/utils/api/request.py +119 -0
  134. nonebot_plugin_l4d2_server/utils/database/models.py +29 -0
  135. nonebot_plugin_l4d2_server/{l4d2_utils → utils}/utils.py +52 -55
  136. {nonebot_plugin_l4d2_server-0.6.6.dist-info → nonebot_plugin_l4d2_server-1.0.0a1.dist-info}/METADATA +47 -64
  137. nonebot_plugin_l4d2_server-1.0.0a1.dist-info/RECORD +144 -0
  138. {nonebot_plugin_l4d2_server-0.6.6.dist-info → nonebot_plugin_l4d2_server-1.0.0a1.dist-info}/WHEEL +1 -1
  139. nonebot_plugin_l4d2_server/l4d2_anne/__init__.py +0 -95
  140. nonebot_plugin_l4d2_server/l4d2_anne/analysis.py +0 -54
  141. nonebot_plugin_l4d2_server/l4d2_anne/anne_telecom.py +0 -79
  142. nonebot_plugin_l4d2_server/l4d2_anne/server.py +0 -47
  143. nonebot_plugin_l4d2_server/l4d2_anne/startand.py +0 -17
  144. nonebot_plugin_l4d2_server/l4d2_anne/utils.py +0 -294
  145. nonebot_plugin_l4d2_server/l4d2_data/__init__.py +0 -105
  146. nonebot_plugin_l4d2_server/l4d2_data/config.py +0 -18
  147. nonebot_plugin_l4d2_server/l4d2_data/database.py +0 -0
  148. nonebot_plugin_l4d2_server/l4d2_data/players.py +0 -100
  149. nonebot_plugin_l4d2_server/l4d2_data/serverip.py +0 -40
  150. nonebot_plugin_l4d2_server/l4d2_file/__init__.py +0 -222
  151. nonebot_plugin_l4d2_server/l4d2_file/ayromote.py +0 -64
  152. nonebot_plugin_l4d2_server/l4d2_file/input_json.py +0 -77
  153. nonebot_plugin_l4d2_server/l4d2_file/remote.py +0 -86
  154. nonebot_plugin_l4d2_server/l4d2_file/utils.py +0 -104
  155. nonebot_plugin_l4d2_server/l4d2_image/htmlimg.py +0 -18
  156. nonebot_plugin_l4d2_server/l4d2_image/images.py +0 -92
  157. nonebot_plugin_l4d2_server/l4d2_image/one.py +0 -44
  158. nonebot_plugin_l4d2_server/l4d2_image/send_image_tool.py +0 -32
  159. nonebot_plugin_l4d2_server/l4d2_image/steam.py +0 -63
  160. nonebot_plugin_l4d2_server/l4d2_push/__init__.py +0 -225
  161. nonebot_plugin_l4d2_server/l4d2_queries/__init__.py +0 -326
  162. nonebot_plugin_l4d2_server/l4d2_queries/himi.py +0 -113
  163. nonebot_plugin_l4d2_server/l4d2_queries/local_ip.py +0 -41
  164. nonebot_plugin_l4d2_server/l4d2_queries/qqgroup.py +0 -370
  165. nonebot_plugin_l4d2_server/l4d2_queries/send_msg.py +0 -131
  166. nonebot_plugin_l4d2_server/l4d2_queries/utils.py +0 -212
  167. nonebot_plugin_l4d2_server/l4d2_server/__init__.py +0 -118
  168. nonebot_plugin_l4d2_server/l4d2_server/index.py +0 -0
  169. nonebot_plugin_l4d2_server/l4d2_server/rcon.py +0 -53
  170. nonebot_plugin_l4d2_server/l4d2_server/workshop.py +0 -82
  171. nonebot_plugin_l4d2_server/l4d2_update/__init__.py +0 -137
  172. nonebot_plugin_l4d2_server/l4d2_update/draw_update_log.py +0 -45
  173. nonebot_plugin_l4d2_server/l4d2_update/restart.py +0 -69
  174. nonebot_plugin_l4d2_server/l4d2_update/update.py +0 -53
  175. nonebot_plugin_l4d2_server/l4d2_utils/classcal.py +0 -53
  176. nonebot_plugin_l4d2_server/l4d2_utils/command.py +0 -23
  177. nonebot_plugin_l4d2_server/l4d2_utils/config.py +0 -201
  178. nonebot_plugin_l4d2_server/l4d2_utils/message.py +0 -59
  179. nonebot_plugin_l4d2_server/l4d2_utils/rule.py +0 -35
  180. nonebot_plugin_l4d2_server/l4d2_utils/seach.py +0 -43
  181. nonebot_plugin_l4d2_server/l4d2_utils/txt_to_img.py +0 -32
  182. nonebot_plugin_l4d2_server/l4d2_web/web.py +0 -277
  183. nonebot_plugin_l4d2_server/l4d2_web/webUI.py +0 -506
  184. nonebot_plugin_l4d2_server/l4d2_web/webUI_s.py +0 -94
  185. nonebot_plugin_l4d2_server-0.6.6.dist-info/RECORD +0 -70
  186. {nonebot_plugin_l4d2_server-0.6.6.dist-info → nonebot_plugin_l4d2_server-1.0.0a1.dist-info}/licenses/LICENSE +0 -0
@@ -3,15 +3,16 @@ import hashlib
3
3
  import io
4
4
  import os
5
5
  import random
6
+ from pathlib import Path
6
7
 
7
8
  import httpx
8
9
  from nonebot.log import logger
9
10
  from PIL import Image, ImageDraw
10
11
  from PIL.Image import Image as ImageS
11
12
 
12
- from ..l4d2_utils.config import PLAYERSDATA, TEXT_PATH
13
+ from ..config import DATAOUT
13
14
 
14
- # from .steam import web_player
15
+ TEXT_PATH = Path(__file__).parent
15
16
 
16
17
 
17
18
  async def download_url(url: str) -> bytes:
@@ -53,7 +54,7 @@ async def get_head_by_user_id_and_save(user_id):
53
54
  """qq转头像"""
54
55
  user_id = str(user_id)
55
56
 
56
- user_head_path = PLAYERSDATA / user_id / "HEAD.png"
57
+ user_head_path = DATAOUT / user_id / "HEAD.png"
57
58
  default_header_path = TEXT_PATH / "header"
58
59
  default_head_path = TEXT_PATH / "head"
59
60
  default_header = default_header_path / random.choice(
@@ -77,8 +78,8 @@ async def get_head_by_user_id_and_save(user_id):
77
78
  .resize((280, 280))
78
79
  .convert("RGBA")
79
80
  )
80
- if not (PLAYERSDATA / user_id).exists(): # 用户文件夹不存在
81
- (PLAYERSDATA / user_id).mkdir(parents=True, exist_ok=True)
81
+ if not (DATAOUT / user_id).exists(): # 用户文件夹不存在
82
+ (DATAOUT / user_id).mkdir(parents=True, exist_ok=True)
82
83
  im.save(user_head_path, "PNG")
83
84
  except Exception:
84
85
  logger.error("获取失败")
@@ -91,28 +92,3 @@ async def get_head_by_user_id_and_save(user_id):
91
92
  im3.paste(im, (75, 75), mask=a1)
92
93
  im3.paste(im2, mask=a2)
93
94
  return im3
94
-
95
-
96
- # async def get_head_steam_and_save(user_id:str,urls):
97
- # """保存steam头像"""
98
- # user_head_path = PLAYERSDATA / str(user_id) / 'HEAD.png'
99
- # # default_head_path = TEXT_PATH / "template/player.jpg"
100
- # ## im头像 im2头像框 im3合成
101
- # if os.path.exists(user_head_path):
102
- # logger.info("使用本地头像")
103
- # im = Image.open(user_head_path).resize((280, 280)).convert("RGBA")
104
- # else:
105
- # # if user_id == "1145149191810":
106
- # # logger.info("使用默认头像")
107
- # # im = Image.open(default_head_path).resize((300, 300)).convert("RGBA")
108
- # # else:
109
- # try:
110
- # logger.info("正在下载头像")
111
- # image_bytes = await web_player(urls)
112
- # im = Image.open(io.BytesIO(image_bytes)).resize((280, 280)).convert("RGBA") # noqa: E501
113
- # if not os.path.exists(PLAYERSDATA / user_id):#用户文件夹不存在
114
- # os.makedirs(PLAYERSDATA / user_id)
115
- # im.save(user_head_path, "PNG")
116
- # except TimeoutError:
117
- # logger.error("获取失败")
118
- # return im
@@ -1,18 +1,20 @@
1
+ import random
2
+ from pathlib import Path
1
3
  from typing import List, Optional
2
4
 
3
5
  import jinja2
4
6
  from nonebot.log import logger
5
7
  from nonebot_plugin_htmlrender import html_to_pic
6
8
 
7
- from ..l4d2_utils.classcal import PlayerInfo, ServerGroup, ServerStatus
9
+ from ..config import config
10
+ from ..utils.api.models import OutServer
11
+ from .convert import convert_img
8
12
 
9
13
  # from .htmlimg import dict_to_dict_img
10
14
  # from ..l4d2_anne.anne_telecom import ANNE_API
11
- from ..l4d2_utils.config import TEXT_PATH, l4_config
12
15
  from .download import get_head_by_user_id_and_save
13
- from .send_image_tool import convert_img
14
16
 
15
- template_path = TEXT_PATH / "template"
17
+ template_path = Path(__file__).parent / "img/template"
16
18
 
17
19
  env = jinja2.Environment(
18
20
  loader=jinja2.FileSystemLoader(template_path),
@@ -66,25 +68,24 @@ async def dict_to_html(usr_id, detail_map: dict):
66
68
  return data_list
67
69
 
68
70
 
69
- async def server_ip_pic(msg_list: List[ServerStatus]):
71
+ async def server_ip_pic(server_dict: List[OutServer]):
70
72
  """
71
73
  输入一个字典列表,输出图片
72
74
  msg_dict:folder/name/map_/players/max_players/Players/[Name]
73
75
  """
74
- for server_info in msg_list:
75
- server_info.rank_players = f"{server_info.players}/{server_info.max_players}"
76
- players_list: List[PlayerInfo] = []
77
- # logger.info(server_info.name)
78
- max_number = l4_config.l4_img_name
79
- sorted_players = sorted(server_info.Players, key=lambda x: x.Score)[:max_number]
80
- for player_info in sorted_players:
81
- # player_str = f"{player_info.name} | {player_info.Duration}"
82
- players_list.append(player_info)
83
- while len(players_list) < max_number:
84
- players_list.append(PlayerInfo())
85
- server_info.Players = players_list
86
- # logger.info(server_info.Players)
87
- pic = await get_help_img(msg_list)
76
+ for server_info in server_dict:
77
+ max_number = config.l4_players
78
+ if server_info.get("player"):
79
+ sorted_players = sorted(server_info["player"], key=lambda x: x.score)[
80
+ :max_number
81
+ ]
82
+ server_info["player"] = sorted_players
83
+ else:
84
+ server_info["player"] = []
85
+
86
+ # server_info["server"].server_type= f"{server_info['server'].server_type}.svg"
87
+
88
+ pic = await get_server_img(server_dict)
88
89
  if pic:
89
90
  logger.success("正在输出图片")
90
91
  else:
@@ -92,34 +93,42 @@ async def server_ip_pic(msg_list: List[ServerStatus]):
92
93
  return pic
93
94
 
94
95
 
95
- async def get_help_img(plugins: List[ServerStatus]) -> Optional[bytes]:
96
- try:
97
- if l4_config.l4_style == "black":
98
- template = env.get_template("help_dack.html")
99
- else:
100
- template = env.get_template("help.html")
101
- content = await template.render_async(plugins=plugins)
102
- return await html_to_pic(
103
- content,
104
- wait=0,
105
- viewport={"width": 100, "height": 100},
106
- template_path=f"file://{template_path.absolute()}",
107
- )
108
- except Exception as e:
109
- logger.warning(f"Error in get_help_img: {e}")
110
- return None
111
-
96
+ async def get_server_img(plugins: List[OutServer]) -> Optional[bytes]:
97
+ # try:
112
98
 
113
- async def server_group_ip_pic(msg_list: List[ServerGroup]):
114
- """
115
- 输入一个群组字典列表,输出图片
116
- msg_dict:folder/name/map_/players/max_players/Players/[Name]
117
- """
118
- template = env.get_template("group_ip.html")
119
- html = await template.render_async(plugins=msg_list)
99
+ if config.l4_style == "孤独摇滚":
100
+ template = env.get_template("Bocchi_The_Rock.html")
101
+ elif config.l4_style == "电玩像素":
102
+ template = env.get_template("Pixel.html")
103
+ elif config.l4_style == "缤纷彩虹":
104
+ template = env.get_template("Rainbow.html")
105
+ elif config.l4_style == "随机":
106
+ html_files = [str(f.name) for f in template_path.rglob("*.html") if f.is_file()]
107
+ template = env.get_template(random.choice(html_files))
108
+ else:
109
+ template = env.get_template("normal.html")
110
+ content = await template.render_async(plugins=plugins, max_count=config.l4_players)
120
111
  return await html_to_pic(
121
- html=html,
112
+ content,
122
113
  wait=0,
123
- viewport={"width": 1100, "height": 800},
114
+ viewport={"width": 100, "height": 100},
124
115
  template_path=f"file://{template_path.absolute()}",
125
116
  )
117
+ # except Exception as e:
118
+ # logger.warning(f"Error in get_help_img: {e}")
119
+ return None
120
+
121
+
122
+ # async def server_group_ip_pic(msg_list: List[ServerGroup]) -> bytes:
123
+ # """
124
+ # 输入一个群组字典列表,输出图片
125
+ # msg_dict:folder/name/map_/players/max_players/Players/[Name]
126
+ # """
127
+ # template = env.get_template("group_ip.html")
128
+ # html = await template.render_async(plugins=msg_list)
129
+ # return await html_to_pic(
130
+ # html=html,
131
+ # wait=0,
132
+ # viewport={"width": 1100, "height": 800},
133
+ # template_path=f"file://{template_path.absolute()}",
134
+ # )
@@ -0,0 +1,468 @@
1
+ import math
2
+ import random
3
+ from io import BytesIO
4
+ from pathlib import Path
5
+ from typing import Optional, Tuple, Union
6
+
7
+ import httpx
8
+ from httpx import get
9
+ from PIL import Image, ImageDraw, ImageFilter, ImageFont
10
+
11
+ TEXT_PATH = Path(__file__).parent / "texture2d"
12
+ BG_PATH = Path(__file__).parents[1] / "default_bg"
13
+
14
+
15
+ def get_div():
16
+ return Image.open(TEXT_PATH / "div.png")
17
+
18
+
19
+ async def sget(url: str):
20
+ async with httpx.AsyncClient(timeout=None) as client:
21
+ return await client.get(url=url)
22
+
23
+
24
+ def get_status_icon(status: Union[int, bool]) -> Image.Image:
25
+ if status:
26
+ img = Image.open(TEXT_PATH / "yes.png")
27
+ else:
28
+ img = Image.open(TEXT_PATH / "no.png")
29
+ return img
30
+
31
+
32
+ def get_v4_footer():
33
+ return Image.open(TEXT_PATH / "footer.png")
34
+
35
+
36
+ def get_v4_bg(w: int, h: int, is_dark: bool = False, is_blur: bool = False):
37
+ ci_img = CustomizeImage(BG_PATH)
38
+ img = ci_img.get_image(None, w, h)
39
+ if is_blur:
40
+ img = img.filter(ImageFilter.GaussianBlur(radius=20))
41
+ if is_dark:
42
+ black_img = Image.new("RGBA", (w, h), (0, 0, 0, 180))
43
+ img.paste(black_img, (0, 0), black_img)
44
+ return img.convert("RGBA")
45
+
46
+
47
+ async def shift_image_hue(img: Image.Image, angle: float = 30) -> Image.Image:
48
+ alpha = img.getchannel("A")
49
+ img = img.convert("HSV")
50
+
51
+ pixels = img.load()
52
+ assert pixels is not None
53
+ hue_shift = angle
54
+
55
+ for y in range(img.height):
56
+ for x in range(img.width):
57
+ h, s, v = pixels[x, y] # type: ignore
58
+ h = (h + hue_shift) % 360
59
+ pixels[x, y] = (h, s, v) # type: ignore
60
+
61
+ img = img.convert("RGBA")
62
+ img.putalpha(alpha)
63
+ return img
64
+
65
+
66
+ async def get_pic(url, size: Optional[Tuple[int, int]] = None) -> Image.Image:
67
+ """
68
+ 从网络获取图片, 格式化为RGBA格式的指定尺寸
69
+ """
70
+ async with httpx.AsyncClient(timeout=None) as client:
71
+ resp = await client.get(url=url)
72
+ if resp.status_code != 200:
73
+ if size is None:
74
+ size = (960, 600)
75
+ return Image.new("RGBA", size)
76
+ pic = Image.open(BytesIO(resp.read()))
77
+ pic = pic.convert("RGBA")
78
+ if size is not None:
79
+ pic = pic.resize(size)
80
+ return pic
81
+
82
+
83
+ def draw_center_text_by_line(
84
+ img: ImageDraw.ImageDraw,
85
+ pos: Tuple[int, int],
86
+ text: str,
87
+ font: ImageFont.FreeTypeFont,
88
+ fill: Union[Tuple[int, int, int, int], str],
89
+ max_length: float,
90
+ not_center: bool = False,
91
+ ) -> float:
92
+ pun = "。!?;!?…"
93
+ gun = "。!?;!?」』"
94
+ x, y = pos
95
+
96
+ if hasattr(font, "getsize"):
97
+ _, h = font.getsize("X") # type: ignore
98
+ else:
99
+ bbox = font.getbbox("X")
100
+ _, h = 0, bbox[3] - bbox[1]
101
+
102
+ line = ""
103
+ lenth = 0
104
+ anchor = "la" if not_center else "mm"
105
+ for index, char in enumerate(text):
106
+ if hasattr(font, "getsize"):
107
+ # 获取当前字符的宽度
108
+ size, _ = font.getsize(char) # type: ignore
109
+ else:
110
+ bbox = font.getbbox(char)
111
+ size, _ = bbox[2] - bbox[0], bbox[3] - bbox[1]
112
+ lenth += size
113
+ line += char
114
+ if lenth < max_length and char not in pun and char != "\n":
115
+ pass
116
+ else:
117
+ if index + 1 < len(text) and text[index + 1] in gun:
118
+ pass
119
+ else:
120
+ line = line.replace("\n", "")
121
+ img.text((x, y), line, fill, font, anchor)
122
+ line, lenth = "", 0
123
+ y += h * 2.5
124
+ else:
125
+ img.text((x, y), line, fill, font, anchor)
126
+ return y
127
+
128
+
129
+ def draw_text_by_line(
130
+ img: Image.Image,
131
+ pos: Tuple[int, int],
132
+ text: str,
133
+ font: ImageFont.FreeTypeFont,
134
+ fill: Union[Tuple[int, int, int, int], str],
135
+ max_length: float,
136
+ center=False,
137
+ line_space: Optional[float] = None,
138
+ ) -> float:
139
+ """
140
+ 在图片上写长段文字, 自动换行
141
+ max_length单行最大长度, 单位像素
142
+ line_space 行间距, 单位像素, 默认是字体高度的0.3倍
143
+ """
144
+ x, y = pos
145
+
146
+ if hasattr(font, "getsize"):
147
+ _, h = font.getsize("X") # type: ignore
148
+ else:
149
+ bbox = font.getbbox("X")
150
+ _, h = 0, bbox[3] - bbox[1]
151
+
152
+ y_add = math.ceil(1.3 * h) if line_space is None else math.ceil(h + line_space)
153
+ draw = ImageDraw.Draw(img)
154
+ row = "" # 存储本行文字
155
+ length = 0 # 记录本行长度
156
+ for character in text:
157
+ # 获取当前字符的宽度
158
+ if hasattr(font, "getsize"):
159
+ w, h = font.getsize(character) # type: ignore
160
+ else:
161
+ bbox = font.getbbox("X")
162
+ w, h = bbox[2] - bbox[0], bbox[3] - bbox[1]
163
+
164
+ if length + w * 2 <= max_length:
165
+ row += character
166
+ length += w
167
+ else:
168
+ row += character
169
+ if center:
170
+ if hasattr(font, "getsize"):
171
+ font_size = font.getsize(row) # type: ignore
172
+ else:
173
+ bbox = font.getbbox(character)
174
+ font_size = bbox[2] - bbox[0], bbox[3] - bbox[1]
175
+ x = math.ceil((img.size[0] - font_size[0]) / 2)
176
+ draw.text((x, y), row, font=font, fill=fill)
177
+ row = ""
178
+ length = 0
179
+ y += y_add
180
+ if row != "":
181
+ if center:
182
+ if hasattr(font, "getsize"):
183
+ font_size = font.getsize(row) # type: ignore
184
+ else:
185
+ bbox = font.getbbox(row)
186
+ font_size = bbox[2] - bbox[0], bbox[3] - bbox[1]
187
+ x = math.ceil((img.size[0] - font_size[0]) / 2)
188
+ draw.text((x, y), row, font=font, fill=fill)
189
+ return y
190
+
191
+
192
+ def easy_paste(
193
+ im: Image.Image,
194
+ im_paste: Image.Image,
195
+ pos=(0, 0),
196
+ direction="lt",
197
+ ):
198
+ """
199
+ inplace method
200
+ 快速粘贴, 自动获取被粘贴图像的坐标。
201
+ pos应当是粘贴点坐标,direction指定粘贴点方位,例如lt为左上
202
+ """
203
+ x, y = pos
204
+ size_x, size_y = im_paste.size
205
+ if "d" in direction:
206
+ y = y - size_y
207
+ if "r" in direction:
208
+ x = x - size_x
209
+ if "c" in direction:
210
+ x = x - int(0.5 * size_x)
211
+ y = y - int(0.5 * size_y)
212
+ im.paste(im_paste, (x, y, x + size_x, y + size_y), im_paste)
213
+
214
+
215
+ def easy_alpha_composite(
216
+ im: Image.Image,
217
+ im_paste: Image.Image,
218
+ pos=(0, 0),
219
+ direction="lt",
220
+ ) -> Image.Image:
221
+ """
222
+ 透明图像快速粘贴
223
+ """
224
+ base = Image.new("RGBA", im.size)
225
+ easy_paste(base, im_paste, pos, direction)
226
+ return Image.alpha_composite(im, base)
227
+
228
+
229
+ async def get_qq_avatar(
230
+ qid: Optional[Union[int, str]] = None,
231
+ avatar_url: Optional[str] = None,
232
+ ) -> Image.Image:
233
+ if qid:
234
+ avatar_url = f"http://q1.qlogo.cn/g?b=qq&nk={qid}&s=640"
235
+ elif avatar_url is None:
236
+ avatar_url = "https://q1.qlogo.cn/g?b=qq&nk=3399214199&s=640"
237
+ return Image.open(BytesIO((await sget(avatar_url)).content)).convert(
238
+ "RGBA",
239
+ )
240
+
241
+
242
+ async def draw_pic_with_ring(
243
+ pic: Image.Image,
244
+ size: int,
245
+ bg_color: Optional[Tuple[int, int, int]] = None,
246
+ is_ring: bool = True,
247
+ ):
248
+ """
249
+ :说明:
250
+ 绘制一张带白色圆环的1:1比例图片。
251
+
252
+ :参数:
253
+ * pic: `Image.Image`: 要修改的图片。
254
+ * size: `int`: 最后传出图片的大小(1:1)。
255
+ * bg_color: `Optional[Tuple[int, int, int]]`: 是否指定圆环内背景颜色。
256
+
257
+ :返回:
258
+ * img: `Image.Image`: 图片对象
259
+ """
260
+ ring_pic = Image.open(TEXT_PATH / "ring.png")
261
+ mask_pic = Image.open(TEXT_PATH / "mask.png")
262
+ img = Image.new("RGBA", (size, size))
263
+ mask = mask_pic.resize((size, size))
264
+ resize_pic = crop_center_img(pic, size, size)
265
+ if bg_color:
266
+ img_color = Image.new("RGBA", (size, size), bg_color)
267
+ img_color.paste(resize_pic, (0, 0), resize_pic)
268
+ img.paste(img_color, (0, 0), mask)
269
+ else:
270
+ img.paste(resize_pic, (0, 0), mask)
271
+
272
+ if is_ring:
273
+ ring = ring_pic.resize((size, size))
274
+ img.paste(ring, (0, 0), ring)
275
+
276
+ return img
277
+
278
+
279
+ def crop_center_img(
280
+ img: Image.Image,
281
+ based_w: int,
282
+ based_h: int,
283
+ ) -> Image.Image:
284
+ # 确定图片的长宽
285
+ based_scale = "%.3f" % (based_w / based_h)
286
+ w, h = img.size
287
+ scale_f = "%.3f" % (w / h)
288
+ new_w = math.ceil(based_h * float(scale_f))
289
+ new_h = math.ceil(based_w / float(scale_f))
290
+ if scale_f > based_scale:
291
+ resize_img = img.resize((new_w, based_h), Image.Resampling.LANCZOS)
292
+ x1 = int(new_w / 2 - based_w / 2)
293
+ y1 = 0
294
+ x2 = int(new_w / 2 + based_w / 2)
295
+ y2 = based_h
296
+ else:
297
+ resize_img = img.resize((based_w, new_h), Image.Resampling.LANCZOS)
298
+ x1 = 0
299
+ y1 = int(new_h / 2 - based_h / 2)
300
+ x2 = based_w
301
+ y2 = int(new_h / 2 + based_h / 2)
302
+ return resize_img.crop((x1, y1, x2, y2))
303
+
304
+
305
+ async def get_color_bg(
306
+ based_w: int,
307
+ based_h: int,
308
+ bg_path: Optional[Path] = None,
309
+ without_mask: bool = False,
310
+ is_full: bool = False,
311
+ color: Optional[Tuple[int, int, int]] = None,
312
+ full_opacity: int = 200,
313
+ ) -> Image.Image:
314
+ ci_img = CustomizeImage(bg_path) # type: ignore
315
+ img = ci_img.get_image(None, based_w, based_h)
316
+ if color is None:
317
+ color = ci_img.get_bg_color(img)
318
+ if is_full:
319
+ color_img = Image.new("RGBA", (based_w, based_h), color)
320
+ mask = Image.new(
321
+ "RGBA",
322
+ (based_w, based_h),
323
+ (255, 255, 255, full_opacity),
324
+ )
325
+ img.paste(color_img, (0, 0), mask)
326
+ elif not without_mask:
327
+ color_mask = Image.new("RGBA", (based_w, based_h), color)
328
+ enka_mask = Image.open(TEXT_PATH / "bg_mask.png").resize(
329
+ (based_w, based_h),
330
+ )
331
+ img.paste(color_mask, (0, 0), enka_mask)
332
+ return img
333
+
334
+
335
+ class CustomizeImage:
336
+ def __init__(self, bg_path: Path) -> None:
337
+ self.bg_path = bg_path
338
+
339
+ def get_image(
340
+ self,
341
+ image: Union[str, Image.Image, None],
342
+ based_w: int,
343
+ based_h: int,
344
+ ) -> Image.Image:
345
+ # 获取背景图片
346
+ if isinstance(image, Image.Image):
347
+ edit_bg = image
348
+ elif image:
349
+ edit_bg = Image.open(BytesIO(get(image).content)).convert("RGBA")
350
+ else:
351
+ _lst = list(self.bg_path.iterdir())
352
+ if _lst:
353
+ path = random.choice(list(self.bg_path.iterdir()))
354
+ else:
355
+ path = random.choice(list(BG_PATH.iterdir()))
356
+ edit_bg = Image.open(path).convert("RGBA")
357
+
358
+ # 确定图片的长宽
359
+ return crop_center_img(edit_bg, based_w, based_h)
360
+
361
+ @staticmethod
362
+ def get_dominant_color(pil_img: Image.Image) -> Tuple[int, int, int]:
363
+ img = pil_img.copy()
364
+ img = img.convert("RGBA")
365
+ img = img.resize((1, 1), resample=0)
366
+ return img.getpixel((0, 0)) # type: ignore
367
+
368
+ @staticmethod
369
+ def get_bg_color(
370
+ edit_bg: Image.Image,
371
+ is_light: Optional[bool] = False,
372
+ ) -> Tuple[int, int, int]:
373
+ # 获取背景主色
374
+ color = 8
375
+ q = edit_bg.quantize(colors=color, method=Image.Quantize.FASTOCTREE)
376
+ bg_color = (0, 0, 0)
377
+ based_light = 195 if is_light else 120
378
+ temp = 9999
379
+ for i in range(color):
380
+ bg = tuple(
381
+ q.getpalette()[ # type:ignore
382
+ i * 3 : (i * 3) + 3 # noqa:E203
383
+ ],
384
+ )
385
+ light_value = bg[0] * 0.3 + bg[1] * 0.6 + bg[2] * 0.1
386
+ if abs(light_value - based_light) < temp: # noqa:E203
387
+ bg_color = bg
388
+ temp = abs(light_value - based_light)
389
+ return bg_color # type:ignore
390
+
391
+ @staticmethod
392
+ def get_text_color(bg_color: Tuple[int, int, int]) -> Tuple[int, int, int]:
393
+ # 通过背景主色(bg_color)确定文字主色
394
+ r = 125
395
+ if max(*bg_color) > 255 - r:
396
+ r *= -1
397
+ return (
398
+ math.floor(bg_color[0] + r if bg_color[0] + r <= 255 else 255),
399
+ math.floor(bg_color[1] + r if bg_color[1] + r <= 255 else 255),
400
+ math.floor(bg_color[2] + r if bg_color[2] + r <= 255 else 255),
401
+ )
402
+
403
+ @staticmethod
404
+ def get_char_color(bg_color: Tuple[int, int, int]) -> Tuple[int, int, int]:
405
+ r = 140
406
+ if max(*bg_color) > 255 - r:
407
+ r *= -1
408
+ return (
409
+ math.floor(bg_color[0] + 5 if bg_color[0] + r <= 255 else 255),
410
+ math.floor(bg_color[1] + 5 if bg_color[1] + r <= 255 else 255),
411
+ math.floor(bg_color[2] + 5 if bg_color[2] + r <= 255 else 255),
412
+ )
413
+
414
+ @staticmethod
415
+ def get_char_high_color(
416
+ bg_color: Tuple[int, int, int],
417
+ ) -> Tuple[int, int, int]:
418
+ r = 140
419
+ d = 20
420
+ if max(*bg_color) > 255 - r:
421
+ r *= -1
422
+ return (
423
+ math.floor(bg_color[0] + d if bg_color[0] + r <= 255 else 255),
424
+ math.floor(bg_color[1] + d if bg_color[1] + r <= 255 else 255),
425
+ math.floor(bg_color[2] + d if bg_color[2] + r <= 255 else 255),
426
+ )
427
+
428
+ @staticmethod
429
+ def get_bg_detail_color(
430
+ bg_color: Tuple[int, int, int],
431
+ ) -> Tuple[int, int, int]:
432
+ r = 140
433
+ if max(*bg_color) > 255 - r:
434
+ r *= -1
435
+ return (
436
+ math.floor(bg_color[0] - 20 if bg_color[0] + r <= 255 else 255),
437
+ math.floor(bg_color[1] - 20 if bg_color[1] + r <= 255 else 255),
438
+ math.floor(bg_color[2] - 20 if bg_color[2] + r <= 255 else 255),
439
+ )
440
+
441
+ @staticmethod
442
+ def get_highlight_color(
443
+ color: Tuple[int, int, int],
444
+ ) -> Tuple[int, int, int]:
445
+ red_color = color[0]
446
+ green_color = color[1]
447
+ blue_color = color[2]
448
+
449
+ highlight_color = {
450
+ "red": red_color - 127 if red_color > 127 else 127,
451
+ "green": green_color - 127 if green_color > 127 else 127,
452
+ "blue": blue_color - 127 if blue_color > 127 else 127,
453
+ }
454
+
455
+ max_color = max(highlight_color.values())
456
+
457
+ name = ""
458
+ for _highlight_color in highlight_color:
459
+ if highlight_color[_highlight_color] == max_color:
460
+ name = str(_highlight_color)
461
+
462
+ if name == "red":
463
+ return red_color, highlight_color["green"], highlight_color["blue"]
464
+ if name == "green":
465
+ return highlight_color["red"], green_color, highlight_color["blue"]
466
+ if name == "blue":
467
+ return highlight_color["red"], highlight_color["green"], blue_color
468
+ return 0, 0, 0 # Error