diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd958c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ + +*.png diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7ff0725 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "api"] + path = mc_status_api + url = https://github.com/Murasame-Dev/McStatus-API.git diff --git a/MiSans-Bold.ttf b/MiSans-Bold.ttf new file mode 100644 index 0000000..eda4bea Binary files /dev/null and b/MiSans-Bold.ttf differ diff --git a/create_image.py b/create_image.py new file mode 100644 index 0000000..5a6f8be --- /dev/null +++ b/create_image.py @@ -0,0 +1,76 @@ +from PIL import Image, ImageDraw, ImageFont, ImageFilter +from io import BytesIO +from motd_formatter import foramt_motd +from PIL.ImageColor import getrgb + +def create_background(input: bytes, width: int, height: int): + background = Image.open(BytesIO(input)) + w1, h1, w2, h2 = background.getbbox() + midw = (w1 + w2) // 2 + midh = (h1 + h2) // 2 + rheight = int(height / width * w2) + rwidth = w2 + if rheight > h2: + rwidth = int(width / height * h2) + rheight = h2 + + background = background.crop((midw - int(rwidth / 2), midh - int(rheight / 2), midw + int(rwidth / 2), midh + int(rheight / 2))) + background = background.resize((width, height), Image.Resampling.LANCZOS) + + blurred_background = background.filter(ImageFilter.GaussianBlur(radius=5)) + + image = blurred_background + return image + +def draw_text_with_shadow(image: Image.Image, text: str, posx: int, posy: int): + font_size = 14 + draw = ImageDraw.Draw(image) + font = ImageFont.truetype("./MiSans-Bold.ttf", font_size) + draw.text((posx + 2, posy + 2), text, font=font, fill='black') + draw.text((posx, posy), text, font=font, fill='white') + +def draw_motd_text_with_shadow(image: Image.Image, text: str, posx: int, posy: int): + font_size = 10 + draw = ImageDraw.Draw(image) + font = ImageFont.truetype("./MiSans-Bold.ttf", font_size) + w1, _, w2, _ = draw.textbbox((0, 0), text, font=font) + weight = w2 - w1 + motd_list = foramt_motd(text, weight) + for pos, color, text in motd_list: + draw.text((posx + pos + 1, posy + 1), text, font=font, fill='black') + draw.text((posx + pos, posy), text, font=font, fill=getrgb(color)) + +def create_image(background: bytes, icon: str | None, text_list: list[str], motd_list: list[str]): + # 图片尺寸 + width, height = 600, 200 + if (len(text_list) + len(motd_list)) * 20 + 20 > height: + height = len(text_list) * 20 + 20 + + try: + image = create_background(background, width, height) + except FileNotFoundError: + image = Image.new('RGB', (width, height), color='orange') + + # 添加半透明蒙版层以增强文字可读性 + overlay = Image.new('RGBA', (width, height), (0, 0, 0, 80)) # 半透明黑色蒙版 + image.paste(overlay, (0, 0), overlay) + + if width // 2 > height: + small_size = int(height * 0.8) + else: + small_size = width // 3 + if icon == None: + small_image = Image.new('RGBA', (small_size, small_size), color='gray') + else: + small_image = Image.open(BytesIO(icon)).resize((small_size, small_size), Image.Resampling.LANCZOS) + + image.paste(small_image, (30, height // 2 - small_size // 2)) + + text_list_size = len(text_list) + motd_list_size = len(motd_list) + for i in range(text_list_size): + draw_text_with_shadow(image, text_list[i], width // 2.5, 10 + 20 * i) + for i in range(motd_list_size): + draw_motd_text_with_shadow(image, motd_list[i], width // 2.5, 10 + 20 * (i + text_list_size)) + + return image diff --git a/BackgroundsGet.py b/get_background.py similarity index 51% rename from BackgroundsGet.py rename to get_background.py index 46ab7b4..2a95676 100644 --- a/BackgroundsGet.py +++ b/get_background.py @@ -1,7 +1,6 @@ import httpx -import os -def download_image_with_httpx_auto_redirect(url, save_path): +def download_image_with_httpx_auto_redirect(url:str): """ 使用httpx库自动处理重定向下载图片 @@ -16,17 +15,7 @@ def download_image_with_httpx_auto_redirect(url, save_path): # 检查状态码 if response.status_code == 200: - # 确保保存目录存在 - os.makedirs(os.path.dirname(save_path), exist_ok=True) - - # 写入文件 - with open(save_path, 'wb') as f: - f.write(response.content) - Background = response.content - - print(f"图片已成功保存到: {save_path}") - print(f"最终URL: {response.url}") # 显示最终重定向后的URL return Background else: print(f"请求失败,状态码: {response.status_code}") @@ -35,9 +24,3 @@ def download_image_with_httpx_auto_redirect(url, save_path): except Exception as e: print(f"下载失败: {e}") return False - -# 使用示例 -if __name__ == "__main__": - image_url = "https://www.loliapi.com/acg/" - save_path = "images/downloaded_image.jpg" - download_image_with_httpx_auto_redirect(image_url, save_path) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..f878cb5 --- /dev/null +++ b/main.py @@ -0,0 +1,44 @@ +from get_background import download_image_with_httpx_auto_redirect +from create_image import create_image + +# Java版查询模块 +from mc_status_api.JavaServerStatus import java_status +# 基岩版查询模块 +from mc_status_api.BedrockServerStatus import bedrock_status +# 此API优先解析 srv 记录 +from mc_status_api.dnslookup import dns_lookup +# 格式化文本 +from mc_status_api.FormatData import format_java_data, format_bedrock_data, format_index, format_java_index, format_bedrock_index + +import base64 + +BACKGROUND_URL = "https://www.loliapi.com/acg/" + +def generate_java_status_image(addr: str, output_image_path: str): + try: + ip, type = dns_lookup(addr) + status = java_status(ip) + data = format_java_data(ip, type, status) + except Exception as e: + print(f"查询服务器时出错: {e}") + return + + background_data = download_image_with_httpx_auto_redirect(BACKGROUND_URL) + if not background_data: + background_data = None + + motd_list = data['motd'].split("\n") + text_list = [ + f"ip: {data["ip"]}", + f"type: {data['type']}", + f"version: {data['version']}", + f"latency: {data['latency']} ms", + f"players: {data['players']['online']}/{data['players']['max']}", + ] + + image = create_image(background_data, base64.b64decode(status.icon.split(",")[1]), text_list, motd_list) + image.save(output_image_path) + + +if __name__ == "__main__": + generate_java_status_image("mc.hypixel.net", "output_image.png") \ No newline at end of file diff --git a/mc_status_api b/mc_status_api new file mode 160000 index 0000000..7346b1f --- /dev/null +++ b/mc_status_api @@ -0,0 +1 @@ +Subproject commit 7346b1f7729c7d0884f5ba705420f8a6aeb643b8 diff --git a/motd_formatter.py b/motd_formatter.py new file mode 100644 index 0000000..76ccccb --- /dev/null +++ b/motd_formatter.py @@ -0,0 +1,69 @@ +def format_color(color_code: str) -> str: + """ + 将Minecraft颜色代码转换为对应的颜色名称 + + Args: + color_code (str): Minecraft颜色代码(单个字符) + + Returns: + str: 对应的颜色名称 + """ + color_map = { + '0': '#000000', + '1': '#0000AA', + '2': '#00AA00', + '3': '#00AAAA', + '4': '#AA0000', + '5': '#AA00AA', + '6': '#FFAA00', + '7': '#AAAAAA', + '8': '#555555', + '9': '#5555FF', + 'a': '#55FF55', + 'b': '#55FFFF', + 'c': '#FF5555', + 'd': '#FF55FF', + 'e': '#FFFF55', + 'f': '#FFFFFF', + 'g': '#DDD605', + 'r': '#FFFFFF' + } + return color_map.get(color_code.lower(), 'white') + +def foramt_motd(data: str, weight: int) -> list[tuple[int, str, str]]: + """ + 格式化 MOTD 文本,去除多余的空格和换行符 + + Args: + data (str): 原始 MOTD 文本 + + Returns: + list: 格式化后的 MOTD 文本 + """ + iter = 0 + character_count = 0 + motd_list = [] + color_state = "#FFFFFF" + data_size = len(data) + while iter < data_size: + if data[iter] == "§": + if iter + 1 < data_size: + color = data[iter + 1] + text = "" + iter += 2 + character_count += 1 + while iter < data_size and data[iter] != "§" and data[iter] != "\n": + text += data[iter] + iter += 1 + if color != "l": + color_state = format_color(color) + motd_list.append(((iter - character_count * 2) / data_size * weight, color_state, text)) + else: + iter += 1 + else: + text = "" + while iter < data_size and data[iter] != "§" and data[iter] != "\n": + text += data[iter] + iter += 1 + motd_list.append((len(text), "white", text)) + return motd_list diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5be5beb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pillow +mcstatus >= 12.0.5