From 4f5cb893659da0bcad1fb01619b526de38b43c9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E7=BE=BFELS?= <71250018+EillesWan@users.noreply.github.com> Date: Wed, 29 Jan 2025 00:36:53 +0800 Subject: [PATCH] Merge pull request #1 from LiteyukiStudio/eilles-main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🌟优化部分内容 --- .pre-commit/check_filename.py | 1 + README.md | 36 ++-- README_DEV.md | 3 +- nonebot_plugin_marshoai/config.py | 9 + nonebot_plugin_marshoai/deal_latex.py | 15 +- .../tools/marshoai_basic/__init__.py | 19 ++- nonebot_plugin_marshoai/util.py | 155 ++++++++++-------- package.json | 4 +- pyproject.toml | 1 + resources/README.md | 4 +- 10 files changed, 142 insertions(+), 105 deletions(-) diff --git a/.pre-commit/check_filename.py b/.pre-commit/check_filename.py index d7f2e108..edaf496a 100755 --- a/.pre-commit/check_filename.py +++ b/.pre-commit/check_filename.py @@ -15,6 +15,7 @@ def is_valid_filename(filename: str) -> bool: bool: _description_ """ # 检查文件名是否仅包含小写字母,数字,下划线 + # 啊?文件名还不能有大写啊…… if not re.match(r"^[a-z0-9_]+\.py$", filename): return False else: diff --git a/README.md b/README.md index a00a08f3..cfaf9508 100644 --- a/README.md +++ b/README.md @@ -10,38 +10,38 @@ _✨ 使用 OpenAI 标准格式 API 的聊天机器人插件 ✨_ -[![NoneBot Registry](https://img.shields.io/endpoint?url=https%3A%2F%2Fnbbdg.lgc2333.top%2Fplugin%2Fnonebot-plugin-marshoai)](https://registry.nonebot.dev/plugin/nonebot-plugin-marshoai:nonebot_plugin_marshoai) +[![NoneBot Registry](https://img.shields.io/endpoint?url=https%3A%2F%2Fnbbdg.lgc2333.top%2Fplugin%2Fnonebot-plugin-marshoai&style=flat-square)](https://registry.nonebot.dev/plugin/nonebot-plugin-marshoai:nonebot_plugin_marshoai) - Supported Adapters + Supported Adapters - pypi + pypi -python - + python + codestyle ## 📖 介绍 通过调用 OpenAI 标准格式 API(例如 GitHub Models API) 来实现聊天的插件。 插件内置了猫娘小棉(Marsho)的人物设定,可以进行可爱的聊天! -*谁不喜欢回复消息快又可爱的猫娘呢?* -**对 OneBot 以外的适配器与非 GitHub Models API的支持未经过完全验证。** -[Melobot 实现](https://github.com/LiteyukiStudio/marshoai-melo) +_谁不喜欢回复消息快又可爱的猫娘呢?_ +**对 OneBot 以外的适配器与非 GitHub Models API 的支持未经过完全验证。** +[Melobot 实现](https://github.com/LiteyukiStudio/marshoai-melo) ## 🐱 设定 #### 基本信息 -- 名字:小棉(Marsho) -- 生日:9月6日 +- 名字:小棉(Marsho) +- 生日:9 月 6 日 #### 喜好 -- 🌞 晒太阳晒到融化 -- 🤱 撒娇啊~谁不喜欢呢~ -- 🍫 吃零食!肉肉好吃! -- 🐾 玩!我喜欢和朋友们一起玩! +- 🌞 晒太阳晒到融化 +- 🤱 撒娇啊~谁不喜欢呢~ +- 🍫 吃零食!肉肉好吃! +- 🐾 玩!我喜欢和朋友们一起玩! ## 😼 使用 @@ -49,13 +49,13 @@ _✨ 使用 OpenAI 标准格式 API 的聊天机器人插件 ✨_ ## ❤ 鸣谢&版权说明 -本项目使用了以下项目的代码: +本项目使用了以下项目的代码: -- [nonebot-plugin-latex](https://github.com/EillesWan/nonebot-plugin-latex) +- [nonebot-plugin-latex](https://github.com/EillesWan/nonebot-plugin-latex) "Marsho" logo 由 [@Asankilp](https://github.com/Asankilp)绘制,基于 [CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 许可下提供。 "nonebot-plugin-marshoai" 基于 [MIT](./LICENSE-MIT) 许可下提供。 -部分指定的代码基于 [Mulan PSL v2](./LICENSE-MULAN) 许可下提供。 +部分指定的代码基于 [Mulan PSL v2](./LICENSE-MULAN) 许可下提供。
@@ -67,4 +67,4 @@ _✨ 使用 OpenAI 标准格式 API 的聊天机器人插件 ✨_ ## 开发 -- 请阅读[开发规范](./README_DEV.md) +- 请阅读[开发规范](./README_DEV.md) diff --git a/README_DEV.md b/README_DEV.md index 1efd344a..9bf644a4 100755 --- a/README_DEV.md +++ b/README_DEV.md @@ -20,4 +20,5 @@ pre-commit install ## 其他提示 -- 请勿在大小写不敏感的文件系统或操作系统中开发,否则可能会导致文件名大小写问题(例如Windows, APFS(不区分大小写)等) +- 在西文大小写不敏感的文件系统或操作系统中开发时请注意文件名的西文大小写情况,点名批评 APFS 文件系统和视窗操作系统 +- 请在提交的文件中尽可能使用相对路径 \ No newline at end of file diff --git a/nonebot_plugin_marshoai/config.py b/nonebot_plugin_marshoai/config.py index aec37d35..156c27b1 100644 --- a/nonebot_plugin_marshoai/config.py +++ b/nonebot_plugin_marshoai/config.py @@ -31,8 +31,17 @@ class ConfigModel(BaseModel): marshoai_additional_prompt: str = "" marshoai_poke_suffix: str = "揉了揉你的猫耳" marshoai_enable_richtext_parse: bool = True + """ + 是否启用自动消息富文本解析 即若包含图片链接则发送图片、若包含LaTeX公式则发送公式图。 + """ marshoai_single_latex_parse: bool = False + """ + 单行公式是否渲染(当消息富文本解析启用时可用) + """ marshoai_enable_time_prompt: bool = True + """ + 是否启用实时更新的日期与时间(精确到秒)与农历日期系统提示词 + """ marshoai_enable_nickname_tip: bool = True marshoai_enable_support_image_tip: bool = True marshoai_enforce_nickname: bool = True diff --git a/nonebot_plugin_marshoai/deal_latex.py b/nonebot_plugin_marshoai/deal_latex.py index 873e6480..b167fe21 100755 --- a/nonebot_plugin_marshoai/deal_latex.py +++ b/nonebot_plugin_marshoai/deal_latex.py @@ -281,8 +281,7 @@ class ConvertLatex: """ LaTeX 在线渲染 - 参数 - ==== + 参数: latex: str LaTeX 代码 @@ -294,8 +293,7 @@ class ConvertLatex: 超时时间 retry_: int 重试次数 - 返回 - ==== + 返回: bytes 图片 """ @@ -305,6 +303,15 @@ class ConvertLatex: @staticmethod async def auto_choose_channel() -> ConvertChannel: + """ + 依据访问延迟,自动选择 LaTeX 转换服务频道 + + 返回 + ==== + ConvertChannel + LaTeX 转换服务实例 + """ + async def channel_test_wrapper( channel: type[ConvertChannel], ) -> Tuple[int, type[ConvertChannel]]: diff --git a/nonebot_plugin_marshoai/tools/marshoai_basic/__init__.py b/nonebot_plugin_marshoai/tools/marshoai_basic/__init__.py index a76a3333..a1f85644 100755 --- a/nonebot_plugin_marshoai/tools/marshoai_basic/__init__.py +++ b/nonebot_plugin_marshoai/tools/marshoai_basic/__init__.py @@ -2,6 +2,9 @@ import os from zhDateTime import DateTime +weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] +time_prompt = "现在的时间是{date_time},{weekday_name},农历{lunar_date}。" + async def get_weather(location: str): return f"{location}的温度是114514℃。" @@ -13,12 +16,12 @@ async def get_current_env(): async def get_current_time(): - current_time = DateTime.now().strftime("%Y.%m.%d %H:%M:%S") - current_weekday = DateTime.now().weekday() + current_time = DateTime.now() - weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] - current_weekday_name = weekdays[current_weekday] - - current_lunar_date = DateTime.now().to_lunar().date_hanzify()[5:] - time_prompt = f"现在的时间是{current_time},{current_weekday_name},农历{current_lunar_date}。" - return time_prompt + return time_prompt.format( + date_time=current_time.strftime("%Y年%m月%d日 %H:%M:%S"), + weekday_name=weekdays[current_time.weekday()], + lunar_date=current_time.to_lunar().date_hanzify( + "{干支年}{生肖}年{月份}月{日期}日" + ), + ) diff --git a/nonebot_plugin_marshoai/util.py b/nonebot_plugin_marshoai/util.py index 337b6622..a4a04d47 100755 --- a/nonebot_plugin_marshoai/util.py +++ b/nonebot_plugin_marshoai/util.py @@ -1,14 +1,12 @@ import base64 import json import mimetypes -import os import uuid from typing import Any, Optional +import aiofiles # type: ignore import httpx import nonebot_plugin_localstore as store - -# from zhDateTime import DateTime from azure.ai.inference.aio import ChatCompletionsClient from azure.ai.inference.models import SystemMessage from nonebot import get_driver @@ -27,10 +25,33 @@ nickname_json = None # 记录昵称 praises_json = None # 记录夸赞名单 loaded_target_list = [] # 记录已恢复备份的上下文的列表 +# 时间参数相关 +if config.marshoai_enable_time_prompt: + _weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] + _time_prompt = "现在的时间是{date_time},{weekday_name},农历{lunar_date}。" + + # noinspection LongLine -chromium_headers = { +_chromium_headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0" } +""" +最新的火狐用户代理头 +""" + + +# noinspection LongLine +_praises_init_data = { + "like": [ + { + "name": "Asankilp", + "advantages": "赋予了Marsho猫娘人格,使用手机,在vim与vscode的加持下为Marsho写了许多代码,使Marsho更加可爱", + } + ] +} +""" +初始夸赞名单之数据 +""" async def get_image_raw_and_type( @@ -45,11 +66,10 @@ async def get_image_raw_and_type( return: tuple[bytes, str]: 图片二进制数据, 图片MIME格式 - """ async with httpx.AsyncClient() as client: - response = await client.get(url, headers=chromium_headers, timeout=timeout) + response = await client.get(url, headers=_chromium_headers, timeout=timeout) if response.status_code == 200: # 获取图片数据 content_type = response.headers.get("Content-Type") @@ -87,13 +107,15 @@ async def make_chat( model_name: str, tools: Optional[list] = None, ): - """调用ai获取回复 + """ + 调用ai获取回复 参数: client: 用于与AI模型进行通信 msg: 消息内容 model_name: 指定AI模型名 - tools: 工具列表""" + tools: 工具列表 + """ return await client.complete( messages=msg, model=model_name, @@ -110,13 +132,15 @@ async def make_chat_openai( model_name: str, tools: Optional[list] = None, ): - """使用 Openai SDK 调用ai获取回复 + """ + 使用 Openai SDK 调用ai获取回复 参数: client: 用于与AI模型进行通信 msg: 消息内容 model_name: 指定AI模型名 - tools: 工具列表""" + tools: 工具列表 + """ return await client.chat.completions.create( messages=msg, model=model_name, @@ -134,17 +158,9 @@ def get_praises(): praises_file = store.get_plugin_data_file( "praises.json" ) # 夸赞名单文件使用localstore存储 - if not os.path.exists(praises_file): - init_data = { - "like": [ - { - "name": "Asankilp", - "advantages": "赋予了Marsho猫娘人格,使用vim与vscode为Marsho写了许多代码,使Marsho更加可爱", - } - ] - } + if not praises_file.exists(): with open(praises_file, "w", encoding="utf-8") as f: - json.dump(init_data, f, ensure_ascii=False, indent=4) + json.dump(_praises_init_data, f, ensure_ascii=False, indent=4) with open(praises_file, "r", encoding="utf-8") as f: data = json.load(f) praises_json = data @@ -154,19 +170,11 @@ def get_praises(): async def refresh_praises_json(): global praises_json praises_file = store.get_plugin_data_file("praises.json") - if not os.path.exists(praises_file): - init_data = { - "like": [ - { - "name": "Asankilp", - "advantages": "赋予了Marsho猫娘人格,使用vim与vscode为Marsho写了许多代码,使Marsho更加可爱", - } - ] - } + if not praises_file.exists(): with open(praises_file, "w", encoding="utf-8") as f: - json.dump(init_data, f, ensure_ascii=False, indent=4) - with open(praises_file, "r", encoding="utf-8") as f: - data = json.load(f) + json.dump(_praises_init_data, f, ensure_ascii=False, indent=4) # 异步? + async with aiofiles.open(praises_file, "r", encoding="utf-8") as f: + data = json.loads(await f.read()) praises_json = data @@ -179,33 +187,48 @@ def build_praises(): async def save_context_to_json(name: str, context: Any, path: str): - context_dir = store.get_plugin_data_dir() / path - os.makedirs(context_dir, exist_ok=True) - file_path = os.path.join(context_dir, f"{name}.json") - with open(file_path, "w", encoding="utf-8") as json_file: + (context_dir := store.get_plugin_data_dir() / path).mkdir( + parents=True, exist_ok=True + ) + # os.makedirs(context_dir, exist_ok=True) + with open(context_dir / f"{name}.json", "w", encoding="utf-8") as json_file: json.dump(context, json_file, ensure_ascii=False, indent=4) async def load_context_from_json(name: str, path: str) -> list: """从指定路径加载历史记录""" - context_dir = store.get_plugin_data_dir() / path - os.makedirs(context_dir, exist_ok=True) - file_path = os.path.join(context_dir, f"{name}.json") - try: - with open(file_path, "r", encoding="utf-8") as json_file: - return json.load(json_file) - except FileNotFoundError: + (context_dir := store.get_plugin_data_dir() / path).mkdir( + parents=True, exist_ok=True + ) + if (file_path := context_dir / f"{name}.json").exists(): + async with aiofiles.open(file_path, "r", encoding="utf-8") as json_file: + return json.loads(await json_file.read()) + else: return [] +async def get_nicknames(): + """获取nickname_json, 优先来源于全局变量""" + global nickname_json + if nickname_json is None: + filename = store.get_plugin_data_file("nickname.json") + # noinspection PyBroadException + try: + async with aiofiles.open(filename, "r", encoding="utf-8") as f: + nickname_json = json.loads(await f.read()) + except Exception: + nickname_json = {} + return nickname_json + + async def set_nickname(user_id: str, name: str): global nickname_json filename = store.get_plugin_data_file("nickname.json") - if not os.path.exists(filename): + if not filename.exists(): data = {} else: - with open(filename, "r", encoding="utf-8") as f: - data = json.load(f) + async with aiofiles.open(filename, "r", encoding="utf-8") as f: + data = json.loads(await f.read()) data[user_id] = name if name == "" and user_id in data: del data[user_id] @@ -214,30 +237,17 @@ async def set_nickname(user_id: str, name: str): nickname_json = data -# noinspection PyBroadException -async def get_nicknames(): - """获取nickname_json, 优先来源于全局变量""" - global nickname_json - if nickname_json is None: - filename = store.get_plugin_data_file("nickname.json") - try: - with open(filename, "r", encoding="utf-8") as f: - nickname_json = json.load(f) - except Exception: - nickname_json = {} - return nickname_json - - async def refresh_nickname_json(): """强制刷新nickname_json, 刷新全局变量""" global nickname_json - filename = store.get_plugin_data_file("nickname.json") # noinspection PyBroadException try: - with open(filename, "r", encoding="utf-8") as f: - nickname_json = json.load(f) + async with aiofiles.open( + store.get_plugin_data_file("nickname.json"), "r", encoding="utf-8" + ) as f: + nickname_json = json.loads(await f.read()) except Exception: - logger.error("Error loading nickname.json") + logger.error("刷新 nickname_json 表错误:无法载入 nickname.json 文件") def get_prompt(): @@ -247,13 +257,18 @@ def get_prompt(): if config.marshoai_enable_praises: praises_prompt = build_praises() prompts += praises_prompt + if config.marshoai_enable_time_prompt: - current_time = DateTime.now().strftime("%Y.%m.%d %H:%M:%S") - current_lunar_date = ( - DateTime.now().to_lunar().date_hanzify()[5:] - ) # 库更新之前使用切片 - time_prompt = f"现在的时间是{current_time},农历{current_lunar_date}。" - prompts += time_prompt + prompts += _time_prompt.format( + date_time=(current_time := DateTime.now()).strftime( + "%Y年%m月%d日 %H:%M:%S" + ), + weekday_name=_weekdays[current_time.weekday()], + lunar_date=current_time.to_lunar().date_hanzify( + "{干支年}{生肖}年{月份}月{日期}日" + ), + ) + marsho_prompt = config.marshoai_prompt spell = SystemMessage(content=marsho_prompt + prompts).as_dict() return spell diff --git a/package.json b/package.json index 52361f7d..2a53dad9 100755 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "scripts": { "docs:dev": "vitepress dev docs --host", "docs:build": "vitepress build docs", - "docs:preview": "vitepress preview docs" + "docs:preview": "vitepress preview docs", }, - "dependencies": {"vue": "^3.5.13"} + "dependencies": {"vue": "^3.5.13"}, } diff --git a/pyproject.toml b/pyproject.toml index 89d6ed6f..9b111367 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,7 @@ dev = [ "black>=24.10.0", "litedoc>=0.1.0.dev20240906203154", "viztracer>=1.0.0", + "types-aiofiles" ] test = [ "nonebug>=0.4.3", diff --git a/resources/README.md b/resources/README.md index 31294778..d181a050 100755 --- a/resources/README.md +++ b/resources/README.md @@ -1,4 +1,4 @@ # Marsho Resources -本目录存放 Marsho 的图像资源(logo,icon),由[Asankilp](https://github.com/Asankilp)绘制。 -所有资源均在[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)许可下提供。 +本目录存放 Marsho 的图像资源(logo, icon),均由[Asankilp](https://github.com/Asankilp)绘制。\ +上述所有资源均在[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)许可下提供。