diff --git a/model/command/command.py b/model/command/command.py index c02dea6c9..40289b1cd 100644 --- a/model/command/command.py +++ b/model/command/command.py @@ -306,22 +306,22 @@ class Command: notice = json.loads(resp)["notice"] except BaseException as e: notice = "" - msg = '' + msg = "# Help Center\n## 指令列表\n" # msg = "Github项目名QQChannelChatGPT, 有问题提交issue, 欢迎Star\n【指令列表】\n" for key, value in commands.items(): - msg += key + ": " + value + "\n" + msg += f"`{key}` - {value}\n" # plugins if cached_plugins != None: - plugin_list_info = "\n".join([f"{k}: \n名称: {v['info']['name']}\n简介: {v['info']['desc']}\n" for k, v in cached_plugins.items()]) + plugin_list_info = "\n".join([f"`{k}` {v['info']['name']}\n简介: \n{v['info']['desc']}\n" for k, v in cached_plugins.items()]) if plugin_list_info.strip() != "": - msg += "\n【插件列表】\n" + msg += "## 插件列表\n> 使用plugin v 插件名 查看插件帮助\n" msg += plugin_list_info - msg += "\n*使用plugin v 插件名 查看插件帮助" msg += notice if platform == gu.PLATFORM_GOCQ: try: - p = gu.create_text_image("【Help Center】", msg) + # p = gu.create_text_image("【Help Center】", msg) + p = gu.create_markdown_image(msg) return [Image.fromFileSystem(p)] except BaseException as e: gu.log(str(e)) diff --git a/model/platform/qq.py b/model/platform/qq.py index fdcaa4f8c..e0f6f6f88 100644 --- a/model/platform/qq.py +++ b/model/platform/qq.py @@ -31,7 +31,6 @@ class QQ: source, res, image_mode: bool = False): - # image_mode parameter is [deprecated]. if not self.is_start: raise Exception("管理员未启动GOCQ平台") diff --git a/util/general_utils.py b/util/general_utils.py index c00016368..1aa67c4bd 100644 --- a/util/general_utils.py +++ b/util/general_utils.py @@ -3,6 +3,8 @@ import time import socket from PIL import Image, ImageDraw, ImageFont import os +import re +import requests PLATFORM_GOCQ = 'gocq' PLATFORM_QQCHAN = 'qqchan' @@ -138,6 +140,219 @@ def word2img(title: str, text: str, max_width=30, font_size=20): return image +def word2img_markdown(markdown: str, max_width=35, font_size=25): + + if os.path.exists("resources/fonts/genshin.ttf"): + font_path = "resources/fonts/genshin.ttf" + elif os.path.exists("QQChannelChatGPT/resources/fonts/genshin.ttf"): + font_path = "QQChannelChatGPT/resources/fonts/genshin.ttf" + elif os.path.exists("C:/Windows/Fonts/simhei.ttf"): + font_path = "C:/Windows/Fonts/simhei.ttf" + elif os.path.exists("/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"): + font_path = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc" + else: + raise Exception("找不到字体文件") + + # try to render markdown + + + +def render_markdown(markdown_text, image_width=800, image_height=600, font_size=16, font_color=(0, 0, 0), bg_color=(255, 255, 255)): + + # pre_process, get height of each line + pre_lines = markdown_text.split('\n') + height = 0 + pre_in_code = False + # pre_codes = [] + for line in pre_lines: + line = line.strip() + if pre_in_code and not line.startswith("```"): + height += font_size+2 + # pre_codes.append(line) + continue + if line.startswith("#"): + header_level = line.count("#") + height += 58 - header_level * 4 + elif line.startswith("-"): + height += font_size+5 + elif line.startswith(">"): + height += font_size+10 + elif line.startswith("```"): + if pre_in_code: + pre_in_code = False + # pre_codes = [] + height += 10 + else: + pre_in_code = True + height += 5 + elif re.search(r"`(.*?)`", line): + height += font_size+20 + else: + height += font_size+5 + + print("Pre process done, height: ", height) + image_height = height + + + # 创建空白图像 + image = Image.new('RGB', (image_width, image_height), bg_color) + draw = ImageDraw.Draw(image) + + + # # get all the emojis unicode in the markdown text + # unicode_text = markdown_text.encode('unicode_escape').decode() + # # print(unicode_text) + # unicode_emojis = re.findall(r'\\U\w{8}', unicode_text) + # emoji_base_url = "https://abs.twimg.com/emoji/v1/72x72/{unicode_emoji}.png" + + + if os.path.exists("resources/fonts/genshin.ttf"): + font_path = "resources/fonts/genshin.ttf" + elif os.path.exists("QQChannelChatGPT/resources/fonts/genshin.ttf"): + font_path = "QQChannelChatGPT/resources/fonts/genshin.ttf" + elif os.path.exists("C:/Windows/Fonts/simhei.ttf"): + font_path = "C:/Windows/Fonts/simhei.ttf" + elif os.path.exists("/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"): + font_path = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc" + else: + raise Exception("找不到字体文件") + + # backup + if os.path.exists("resources/fonts/simhei.ttf"): + font_path1 = "resources/fonts/simhei.ttf" + elif os.path.exists("QQChannelChatGPT/resources/fonts/simhei.ttf"): + font_path1 = "QQChannelChatGPT/resources/fonts/simhei.ttf" + else: + font_path1 = font_path + + + # 加载字体 + font = ImageFont.truetype(font_path, font_size) + + # 设置初始位置 + x, y = 10, 10 + + # 解析Markdown文本 + lines = markdown_text.split("\n") + + in_code_block = False + code_block_start_y = 0 + code_block_codes = [] + + for line in lines: + if in_code_block and not line.startswith("```"): + code_block_codes.append(line) + y += font_size + 2 + continue + line = line.strip() + + # y += 28 - header_level * 4 + 20 + + + if line.startswith("#"): + # unicode_emojis = re.findall(r'\\U0001\w{4}', line) + # for unicode_emoji in unicode_emojis: + # line = line.replace(unicode_emoji, "") + # unicode_emoji = "" + # if len(unicode_emojis) > 0: + # unicode_emoji = unicode_emojis[0] + + # 处理标题 + header_level = line.count("#") + line = line.strip("#").strip() + font_size = 28 - header_level * 4 + + # if unicode_emoji != "": + # emoji_url = emoji_base_url.format(unicode_emoji=unicode_emoji[-5:]) + # emoji = Image.open(requests.get(emoji_url, stream=True).raw) + # emoji = emoji.resize((font_size, font_size)) + # image.paste(emoji, (x, y)) + # x += font_size + + font = ImageFont.truetype(font_path, font_size) + draw.text((x, y), line, font=font, fill=font_color) + + # material design color: blue 500 + draw.line((x, y + font_size + 8, image_width - 10, y + font_size + 8), fill=(230, 230, 230), width=3) + y += font_size + 30 + + # y += font_size + 10 + elif line.startswith(">"): + # 处理引用 + quote_text = line.strip(">") + # quote_width = image_width - 20 # 引用框的宽度为图像宽度减去左右边距 + # quote_height = font_size + 10 # 引用框的高度为字体大小加上上下边距 + # quote_box = (x, y, x + quote_width, y + quote_height) + # draw.rounded_rectangle(quote_box, radius=5, fill=(230, 230, 230), width=2) # 使用灰色填充矩形框作为引用背景 + + draw.line((x, y, x, y + font_size + 10), fill=(230, 230, 230), width=5) + font = ImageFont.truetype(font_path, font_size) + draw.text((x + 5, y + 5), quote_text, font=font, fill=(180, 180, 180)) + y += font_size + 10 + + # y += 16+5 + elif line.startswith("-"): + # 处理列表 + list_text = line.strip("-").strip() + font_size = 16 + font = ImageFont.truetype(font_path, font_size) + draw.text((x, y), " · " + list_text, font=font, fill=font_color) + y += font_size + 5 + + # y += 5+10+font_size*line.count + elif line.startswith("```"): + if not in_code_block: + code_block_start_y = y+5 + in_code_block = True + else: + # print(code_block_codes) + in_code_block = False + codes = "\n".join(code_block_codes) + code_block_codes = [] + + draw.rounded_rectangle((x, code_block_start_y, image_width - 10, y+15), radius=5, fill=(240, 240, 240), width=2) + font = ImageFont.truetype(font_path1, 16) + draw.text((x + 10, code_block_start_y + 5), codes, font=font, fill=font_color) + + y += 15 + # y += font_size+10 + elif re.search(r"`(.*?)`", line): + # 处理行内代码 + code_regex = r"`(.*?)`" + parts_inline = re.findall(code_regex, line) + # print(parts_inline) + parts = re.split(code_regex, line) + # print(parts) + for part in parts: + # the judge has a tiny bug. + # when line is like "hi`hi`". all the parts will be in parts_inline. + if part in parts_inline: + code_text = part.strip("`") + font_size = 16 + code_width = font.getsize(code_text)[0] + 10 + code_height = font_size + 10 + code_box = (x, y-5, x + code_width, y + code_height) + font = ImageFont.truetype(font_path, font_size) + draw.rounded_rectangle(code_box, radius=5, fill=(230, 230, 230), width=2) # 使用灰色填充矩形框作为引用背景 + draw.text((x+5, y), code_text, font=font, fill=font_color) + x += code_width + else: + font_size = 16 + font = ImageFont.truetype(font_path, font_size) + draw.text((x, y), part, font=font, fill=font_color) + x += font.getsize(part)[0] + y += font_size + 20 + x = 10 + else: + # 处理普通文本 + font_size = 16 + font = ImageFont.truetype(font_path, font_size) + draw.text((x, y), line, font=font, fill=font_color) + y += font_size + 5 + + return image + + def save_temp_img(img: Image) -> str: if not os.path.exists("temp"): os.makedirs("temp") @@ -175,4 +390,38 @@ def create_text_image(title: str, text: str, max_width=30, font_size=20): p = save_temp_img(img) return p except Exception as e: - raise e \ No newline at end of file + raise e + +def create_markdown_image(text: str): + ''' + markdown文本转图片。 + 返回:文件路径 + ''' + try: + img = render_markdown(text) + p = save_temp_img(img) + return p + except Exception as e: + raise e + +def test_markdown(): + # 示例使用 + markdown_text = """ + # Help Center + ## 指令列表 + `/help` - 显示帮助中心 + `/start` - 开始使用 + `/about` - 关于 + `/feedback` - 反馈 + + ## Plugins列表 + `/plugins` - 显示插件列表 + `/plugins enable ` - 启用插件 + `/plugins disable ` - 禁用插件 + + > Hi, thanks for using this bot. If you have any questions, please contact me. + + """ + + image = render_markdown(markdown_text) + image.show() # 显示渲染后的图像