diff --git a/.gitignore b/.gitignore index 62eb116..c0ecedd 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ config.ini data/ logs/ + +.sqllsrc.json diff --git a/README.md b/README.md index 0861f04..4387bb5 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,13 @@ docker run -d \ --name wxBotWebhook \ -p 3001:3001 \ -e LOGIN_API_TOKEN="" \ --e RECVD_MSG_API="http://<内网IP>:<接收消息端口>/receive_msg" \ +-e RECVD_MSG_API="http(s)://<宿主机IP>:<接收消息端口>/receive_msg" \ dannicool/docker-wechatbot-webhook ``` -- ``:登录令牌(不是密码),自己设置一个好记的。 -- `<内网IP>`:填入服务器的**内网IP**。如果是在自己电脑,则填入 `127.0.0.1`。 -- `<接收消息端口>`:设置一个接收消息的端口,此项目中默认为 `4000`。 +- ``:登录令牌(可选) +- `<宿主机IP>`:填入 Docker 的宿主机地址。 +- `<接收消息端口>`:设置一个接收消息的端口,默认为 `4000`。 3. 登录微信 @@ -45,7 +45,7 @@ dannicool/docker-wechatbot-webhook docker logs -f wxBotWebhook ``` -### 启动服务器 +### 启动 WeChatter 1. 下载源代码 diff --git a/main.py b/main.py index 18c991e..f2acd08 100644 --- a/main.py +++ b/main.py @@ -31,7 +31,7 @@ def main(): sqlite_manager = SqliteManager("data/wechatter.sqlite") sqlite_manager.excute_folder("wechatter/sqlite/sqls") - logger.info("Wechatter 启动成功!") + logger.info("WeChatter 启动成功!") # 启动uvicorn uvicorn.run(app, host="0.0.0.0", port=config.wechatter_port) diff --git a/tests/commands/test_food_calories/test_food_calories.py b/tests/commands/test_food_calories/test_food_calories.py index 38d83e7..cbdb82a 100644 --- a/tests/commands/test_food_calories/test_food_calories.py +++ b/tests/commands/test_food_calories/test_food_calories.py @@ -9,15 +9,17 @@ class TestFoodCaloriesCommand(unittest.TestCase): def setUp(self): - with open('tests/commands/test_food_calories/food_calories_response.html.test') as f: + with open( + "tests/commands/test_food_calories/food_calories_response.html.test" + ) as f: r_html = f.read() self.food_calories_response = Response() - self.food_calories_response._content = r_html.encode('utf-8') + self.food_calories_response._content = r_html.encode("utf-8") with open("tests/commands/test_food_calories/one_food_response.html.test") as f: one_food_r_html = f.read() self.one_food_response = Response() - self.one_food_response._content = one_food_r_html.encode('utf-8') - with open('tests/commands/test_food_calories/food_href_list.json') as f: + self.one_food_response._content = one_food_r_html.encode("utf-8") + with open("tests/commands/test_food_calories/food_href_list.json") as f: self.food_href_list = json.load(f) with open("tests/commands/test_food_calories/food_detail_list.json") as f: self.food_detail_list = json.load(f) @@ -25,38 +27,40 @@ def setUp(self): self.one_food_detail = json.load(f) def test_parse_food_href_list_response_success(self): - result = food_calories.parse_food_href_list_response(self.food_calories_response) + result = food_calories._parse_food_href_list_response( + self.food_calories_response + ) self.assertEqual(result, self.food_href_list) def test_parse_food_href_list_response_failure(self): with self.assertRaises(Bs4ParsingError): - food_calories.parse_food_href_list_response(Response()) - - def test_get_food_detail_list_success(self): - result = food_calories.get_food_detail_list(self.food_href_list) - self.assertEqual(result, self.food_detail_list) + food_calories._parse_food_href_list_response(Response()) def test_get_food_detail_list_failure(self): with self.assertRaises(Exception): - food_calories.get_food_detail_list([]) + food_calories._get_food_detail_list([]) def test_parse_food_detail_response_success(self): - result = food_calories.parse_food_detail_response(self.one_food_response, "牛肉丸,又叫火锅牛肉丸子,火锅牛肉丸") + result = food_calories._parse_food_detail_response( + self.one_food_response, "牛肉丸,又叫火锅牛肉丸子,火锅牛肉丸" + ) self.assertEqual(result, self.one_food_detail) def test_parse_food_detail_response_failure(self): with self.assertRaises(Bs4ParsingError): - food_calories.parse_food_detail_response(Response(), "牛肉丸,又叫火锅牛肉丸子,火锅牛肉丸") + food_calories._parse_food_detail_response( + Response(), "牛肉丸,又叫火锅牛肉丸子,火锅牛肉丸" + ) def test_generate_food_message_success(self): - result = food_calories.generate_food_message(self.food_detail_list) - true_result = "✨=====食物列表=====✨\n1. 牛肉(肥瘦),又叫肥牛\n 🍲热量(大卡): 125.00\n 🍞碳水(克): 2.00\n 🥓脂肪(克): 4.20\n 🍗蛋白质(克): 19.90\n \U0001f966纤维素(克): 一\n2. 牛肉(精瘦),又叫牛肉,瘦牛肉\n 🍲热量(大卡): 113.00\n 🍞碳水(克): 1.30\n 🥓脂肪(克): 2.50\n 🍗蛋白质(克): 21.30\n \U0001f966纤维素(克): 0.00\n3. 牛腩,又叫牛肉(牛腩)、牛腩\n 🍲热量(大卡): 332.00\n 🍞碳水(克): 0.00\n 🥓脂肪(克): 29.30\n 🍗蛋白质(克): 17.10\n \U0001f966纤维素(克): 0.00\n4. 肥牛卷,又叫肥牛、火锅牛肉卷、牛肉卷\n 🍲热量(大卡): 250.00\n 🍞碳水(克): 0.02\n 🥓脂肪(克): 18.73\n 🍗蛋白质(克): 19.06\n \U0001f966纤维素(克): 0.00\n5. 牛肉丸,又叫火锅牛肉丸子,火锅牛肉丸\n 🍲热量(大卡): 115.00\n 🍞碳水(克): 9.00\n 🥓脂肪(克): 3.70\n 🍗蛋白质(克): 11.20\n \U0001f966纤维素(克): 0.00\n🔵====含量(100克)====🔵' not found in '✨=====食物列表=====✨\n1. 牛肉(肥瘦),又叫肥牛\n🍲热量(大卡): 125.00\n🍞碳水(克): 2.00\n🥓脂肪(克): 4.20\n🍗蛋白质(克): 19.90\n\U0001f966纤维素(克): 一\n2. 牛肉(精瘦),又叫牛肉,瘦牛肉\n🍲热量(大卡): 113.00\n🍞碳水(克): 1.30\n🥓脂肪(克): 2.50\n🍗蛋白质(克): 21.30\n\U0001f966纤维素(克): 0.00\n3. 牛腩,又叫牛肉(牛腩)、牛腩\n🍲热量(大卡): 332.00\n🍞碳水(克): 0.00\n🥓脂肪(克): 29.30\n🍗蛋白质(克): 17.10\n\U0001f966纤维素(克): 0.00\n4. 肥牛卷,又叫肥牛、火锅牛肉卷、牛肉卷\n🍲热量(大卡): 250.00\n🍞碳水(克): 0.02\n🥓脂肪(克): 18.73\n🍗蛋白质(克): 19.06\n\U0001f966纤维素(克): 0.00\n5. 牛肉丸,又叫火锅牛肉丸子,火锅牛肉丸\n🍲热量(大卡): 115.00\n🍞碳水(克): 9.00\n🥓脂肪(克): 3.70\n🍗蛋白质(克): 11.20\n\U0001f966纤维素(克): 0.00\n🔵====含量(100克)====🔵" - self.assertIn(result, true_result) + result = food_calories._generate_food_message(self.food_detail_list) + true_result = "✨=====食物列表=====✨\n1. 牛肉(肥瘦),又叫肥牛\n 🍲热量(大卡): 125.00\n 🍞碳水(克): 2.00\n 🥓脂肪(克): 4.20\n 🍗蛋白质(克): 19.90\n 🥦纤维素(克): 一\n2. 牛肉(精瘦),又叫牛肉,瘦牛肉\n 🍲热量(大卡): 113.00\n 🍞碳水(克): 1.30\n 🥓脂肪(克): 2.50\n 🍗蛋白质(克): 21.30\n 🥦纤维素(克): 0.00\n3. 牛腩,又叫牛肉(牛腩)、牛腩\n 🍲热量(大卡): 332.00\n 🍞碳水(克): 0.00\n 🥓脂肪(克): 29.30\n 🍗蛋白质(克): 17.10\n 🥦纤维素(克): 0.00\n4. 肥牛卷,又叫肥牛、火锅牛肉卷、牛肉卷\n 🍲热量(大卡): 250.00\n 🍞碳水(克): 0.02\n 🥓脂肪(克): 18.73\n 🍗蛋白质(克): 19.06\n 🥦纤维素(克): 0.00\n5. 牛肉丸,又叫火锅牛肉丸子,火锅牛肉丸\n 🍲热量(大卡): 115.00\n 🍞碳水(克): 9.00\n 🥓脂肪(克): 3.70\n 🍗蛋白质(克): 11.20\n 🥦纤维素(克): 0.00\n🔵====含量(100克)====🔵" + self.assertEqual(result, true_result) def test_generate_food_message_failure(self): with self.assertRaises(Exception): - food_calories.generate_food_message([]) + food_calories._generate_food_message([]) def test_get_url_encoding_success(self): - result = food_calories.get_url_encoding("牛肉丸") + result = food_calories._get_url_encoding("牛肉丸") self.assertEqual(result, "%E7%89%9B%E8%82%89%E4%B8%B8") diff --git a/tests/commands/test_gasoline_price/test_gasoline_price.py b/tests/commands/test_gasoline_price/test_gasoline_price.py index 6a56247..26e6db8 100644 --- a/tests/commands/test_gasoline_price/test_gasoline_price.py +++ b/tests/commands/test_gasoline_price/test_gasoline_price.py @@ -7,39 +7,33 @@ class TestGasolinePriceCommand(unittest.TestCase): - def setUp(self): - with open("tests/commands/test_gasoline_price/gasoline_price_response_html.test") as f: + with open( + "tests/commands/test_gasoline_price/gasoline_price_response_html.test" + ) as f: r_html = f.read() self.response = Response() - self.response._content = r_html.encode('utf-8') + self.response._content = r_html.encode("utf-8") with open("tests/commands/test_gasoline_price/gasoline_price_data") as f: self.gasoline_price = f.read() - def test_get_gasoline_price_str_success(self): - result = gasoline_price.get_gasoline_price_str('广州') - self.assertIn('✨广州石化92汽油指导价✨', result) - def test_get_gasoline_price_str_failure(self): with self.assertRaises(KeyError): - gasoline_price.get_gasoline_price_str('广州市') + gasoline_price.get_gasoline_price_str("广州市") def test_parse_gasoline_price_response_success(self): result = gasoline_price._parse_gasoline_price_response(self.response) - self.assertIn('广州中国石化92号汽油最新指导价格为', result) + true_result = "2024年02月04日,广州中国石化92号汽油最新指导价格为:7.84元每升,调整时间为2024-02-01,相对上次调整时间2024-01-18的油价涨了0.16元,涨幅达2.08%" + self.assertEqual(result, true_result) def test_parse_gasoline_price_response_failure(self): with self.assertRaises(Bs4ParsingError): gasoline_price._parse_gasoline_price_response(Response()) def test_get_city_id_success(self): - result = gasoline_price._get_city_id('广州') - self.assertEqual(result, '440100') + result = gasoline_price._get_city_id("广州") + self.assertEqual(result, "440100") def test_get_city_id_failure(self): with self.assertRaises(KeyError): - gasoline_price._get_city_id('广州市') - - def test_generate_gasoline_price_message_success(self): - result = gasoline_price._generate_gasoline_price_message(self.gasoline_price, '广州') - self.assertIn('✨广州石化92汽油指导价✨', result) + gasoline_price._get_city_id("广州市") diff --git a/wechatter/app/app.py b/wechatter/app/app.py index 85fd7c8..b9bbbb5 100644 --- a/wechatter/app/app.py +++ b/wechatter/app/app.py @@ -2,7 +2,10 @@ import wechatter.app.routers as routers import wechatter.config as config -from wechatter.config.parsers import parse_weather_cron_rule_list, parse_gasoline_price_cron_rule_list +from wechatter.config.parsers import ( + parse_gasoline_price_cron_rule_list, + parse_weather_cron_rule_list, +) from wechatter.scheduler import Scheduler app = FastAPI() @@ -12,12 +15,15 @@ if config.github_webhook_enabled: app.include_router(routers.github_router) +# 定时任务 if config.weather_cron_enabled: cron_tasks = parse_weather_cron_rule_list(config.weather_cron_rule_list) Scheduler.add_cron_tasks(cron_tasks) if config.gasoline_price_cron_enable: - cron_tasks = parse_gasoline_price_cron_rule_list(config.gasoline_price_cron_rule_list) + cron_tasks = parse_gasoline_price_cron_rule_list( + config.gasoline_price_cron_rule_list + ) Scheduler.add_cron_tasks(cron_tasks) if not Scheduler.is_cron_tasks_empty(): @@ -28,7 +34,6 @@ async def startup_event(): scheduler.startup() - @app.on_event("shutdown") async def shutdown_event(): scheduler.shutdown() diff --git a/wechatter/app/routers/github.py b/wechatter/app/routers/github.py index 0a78c10..42488f3 100644 --- a/wechatter/app/routers/github.py +++ b/wechatter/app/routers/github.py @@ -22,7 +22,7 @@ async def recv_github_webhook(request: Request): try: handler(data) except ValueError as e: - logger.error(f"GitHub Webhook 处理失败: {e}") + logger.error(f"GitHub Webhook 处理失败: {str(e)}") return JSONResponse( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content={"detail": str(e)} ) diff --git a/wechatter/app/routers/wechat.py b/wechatter/app/routers/wechat.py index fe78b7e..788f007 100644 --- a/wechatter/app/routers/wechat.py +++ b/wechatter/app/routers/wechat.py @@ -10,7 +10,7 @@ from wechatter.message import MessageHandler from wechatter.message_forwarder import MessageForwarder from wechatter.models.message import Message -from wechatter.sender import notify_logged_in, notify_logged_out +from wechatter.sender import notifier from wechatter.sqlite.sqlite_manager import SqliteManager router = APIRouter() @@ -79,10 +79,10 @@ def handle_system_event(content: str) -> None: # 判断是否为机器人登录消息 if content_dict["event"] == "login": print("机器人登录成功") - notify_logged_in() + notifier.notify_logged_in() elif content_dict["event"] == "logout": print("机器人已退出登录") - notify_logged_out() + notifier.notify_logged_out() elif content_dict["event"] == "error": pass else: diff --git a/wechatter/commands/_commands/bili_hot.py b/wechatter/commands/_commands/bili_hot.py index b03fea1..e7cebc9 100644 --- a/wechatter/commands/_commands/bili_hot.py +++ b/wechatter/commands/_commands/bili_hot.py @@ -3,8 +3,8 @@ from loguru import logger from wechatter.commands.handlers import command -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils import get_request_json @@ -19,9 +19,9 @@ def bili_hot_command_handler(to: SendTo, message: str = "") -> None: except Exception as e: error_message = f"获取Bilibili热搜失败,错误信息: {str(e)}" logger.error(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + sender.send_msg(to, error_message) else: - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) + sender.send_msg(to, result) def get_bili_hot_str() -> str: diff --git a/wechatter/commands/_commands/copilot_gpt4.py b/wechatter/commands/_commands/copilot_gpt4.py index 32aefbe..cc98ffa 100644 --- a/wechatter/commands/_commands/copilot_gpt4.py +++ b/wechatter/commands/_commands/copilot_gpt4.py @@ -5,8 +5,8 @@ import wechatter.config as config import wechatter.utils.path_manager as pm from wechatter.commands.handlers import command -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.sqlite.sqlite_manager import SqliteManager from wechatter.utils import post_request_json from wechatter.utils.time import get_current_timestamp @@ -94,45 +94,40 @@ def gpt4_remove_command_handler(to: SendTo, message: str = "") -> None: pass -def _send_text_msg(to: SendTo, message: str = "") -> None: - """封装发送文本消息""" - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, message)) - - def _gptx(model: str, to: SendTo, message: str = "") -> None: wx_id = to.p_id # 获取文件夹下最新的对话记录 chat_info = CopilotGPT4.get_chating_chat_info(wx_id, model) if message == "": # /gpt4 # 判断对话是否有效 - _send_text_msg(to, "正在创建新对话...") + sender.send_msg(to, "正在创建新对话...") if chat_info is None or CopilotGPT4.is_chat_valid(chat_info): CopilotGPT4.create_chat(wx_id=wx_id, model=model) logger.info("创建新对话成功") - _send_text_msg(to, "创建新对话成功") + sender.send_msg(to, "创建新对话成功") return logger.info("对话未开始,继续上一次对话") - _send_text_msg(to, "对话未开始,继续上一次对话") + sender.send_msg(to, "对话未开始,继续上一次对话") else: # /gpt4 # 如果没有对话记录,则创建新对话 - _send_text_msg(to, f"正在调用 {model} 进行对话...") + sender.send_msg(to, f"正在调用 {model} 进行对话...") if chat_info is None: chat_info = CopilotGPT4.create_chat(wx_id=wx_id, model=model) logger.info("无历史对话记录,创建新对话成功") - _send_text_msg(to, "无历史对话记录,创建新对话成功") + sender.send_msg(to, "无历史对话记录,创建新对话成功") try: response = CopilotGPT4.chat(chat_info, message) logger.info(response) - _send_text_msg(to, response) + sender.send_msg(to, response) except Exception as e: - error_message = f"调用Copilot-GPT4-Server失败,错误信息:{e}" + error_message = f"调用Copilot-GPT4-Server失败,错误信息:{str(e)}" logger.error(error_message) - _send_text_msg(to, error_message) + sender.send_msg(to, error_message) def _gptx_chats(model: str, to: SendTo, message: str = "") -> None: response = CopilotGPT4.get_chat_list_str(to.p_id, model) - _send_text_msg(to, response) + sender.send_msg(to, response) def _gptx_record(model: str, to: SendTo, message: str = "") -> None: @@ -146,11 +141,11 @@ def _gptx_record(model: str, to: SendTo, message: str = "") -> None: chat_info = CopilotGPT4.get_chat_info(wx_id, model, int(message)) if chat_info is None: logger.waring("对话不存在") - _send_text_msg(to, "对话不存在") + sender.send_msg(to, "对话不存在") return response = CopilotGPT4.get_brief_conversation_str(chat_info) logger.info(response) - _send_text_msg(to, response) + sender.send_msg(to, response) def _gptx_continue(model: str, to: SendTo, message: str = "") -> None: @@ -158,22 +153,22 @@ def _gptx_continue(model: str, to: SendTo, message: str = "") -> None: # 判断message是否为数字 if not message.isdigit(): logger.info("请输入对话记录编号") - _send_text_msg(to, "请输入对话记录编号") + sender.send_msg(to, "请输入对话记录编号") return - _send_text_msg(to, f"正在切换到对话记录 {message}...") + sender.send_msg(to, f"正在切换到对话记录 {message}...") chat_info = CopilotGPT4.continue_chat( wx_id=wx_id, model=model, chat_index=int(message) ) if chat_info is None: waring_message = "选择历史对话失败,对话不存在" logger.waring(waring_message) - _send_text_msg(to, waring_message) + sender.send_msg(to, waring_message) return response = CopilotGPT4.get_brief_conversation_str(chat_info) response += "====================\n" response += "对话已选中,输入命令继续对话" logger.info(response) - _send_text_msg(to, response) + sender.send_msg(to, response) class ChatInfo: diff --git a/wechatter/commands/_commands/douyin_hot.py b/wechatter/commands/_commands/douyin_hot.py index 08e2cdf..301e031 100644 --- a/wechatter/commands/_commands/douyin_hot.py +++ b/wechatter/commands/_commands/douyin_hot.py @@ -3,8 +3,8 @@ from loguru import logger from wechatter.commands.handlers import command -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils import get_request_json @@ -19,9 +19,9 @@ def douyin_hot_command_handler(to: SendTo, message: str = "") -> None: except Exception as e: error_message = f"获取抖音热搜失败,错误信息: {str(e)}" logger.error(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + sender.send_msg(to, error_message) else: - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) + sender.send_msg(to, result) def get_douyin_hot_str() -> str: diff --git a/wechatter/commands/_commands/food_calories.py b/wechatter/commands/_commands/food_calories.py index 3e83ac4..274e45e 100644 --- a/wechatter/commands/_commands/food_calories.py +++ b/wechatter/commands/_commands/food_calories.py @@ -3,11 +3,12 @@ import requests from bs4 import BeautifulSoup +from loguru import logger from wechatter.commands.handlers import command from wechatter.exceptions import Bs4ParsingError -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils import get_request @@ -18,20 +19,26 @@ ) def food_calories_command_handler(to: SendTo, message: str = "") -> None: try: - response = get_request( - url=f"https://www.boohee.com/food/search?keyword={message}" - ) - food_href_list = parse_food_href_list_response(response) - food_detail_list = get_food_detail_list(food_href_list) - result = generate_food_message(food_detail_list) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) + result = get_food_calories_str(message) except Exception as e: - error_message = f"获取食物热量失败,错误信息:{e}" - print(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + error_message = f"获取食物热量失败,错误信息:{str(e)}" + logger.error(error_message) + sender.send_msg(to, error_message) + else: + sender.send_msg(to, result) + + +def get_food_calories_str(message: str) -> str: + if not message: + return "查询失败,请输入食物名称" + response = get_request(url=f"https://www.boohee.com/food/search?keyword={message}") + food_href_list = _parse_food_href_list_response(response) + food_detail_list = _get_food_detail_list(food_href_list) + result = _generate_food_message(food_detail_list) + return result -def get_food_detail_list(food_href_list: List) -> List: +def _get_food_detail_list(food_href_list: List) -> List: """ 获取食物详情列表 :param food_href_list: 食物详情链接列表 @@ -42,7 +49,7 @@ def get_food_detail_list(food_href_list: List) -> List: food_name = food.get("name") food_all_name = food.get("all_name") food_href = food.get("href") - keyword = get_url_encoding(food_name) # 获取URL编码 + keyword = _get_url_encoding(food_name) # 获取URL编码 headers = { "referer": f"https://www.boohee.com/food/search?keyword={keyword}", } @@ -50,22 +57,25 @@ def get_food_detail_list(food_href_list: List) -> List: url=f"https://www.boohee.com{food_href}", headers=headers ) if not food_response: - raise Exception(f"获取食物详情失败,食物名称:{food_name}") - food_detail = parse_food_detail_response(food_response, food_all_name) + logger.error(f"获取食物详情失败,食物名称:{food_name}") + raise ValueError(f"获取食物详情失败,食物名称:{food_name}") + food_detail = _parse_food_detail_response(food_response, food_all_name) food_detail_list.append(food_detail) if not food_detail_list: - raise Exception("获取食物详情失败,为空列表") + logger.error("获取食物详情失败,为空列表") + raise ValueError("获取食物详情失败,为空列表") return food_detail_list -def generate_food_message(food_detail_list: List) -> str: +def _generate_food_message(food_detail_list: List) -> str: """ 生成食物信息 :param food_detail_list: 食物详情列表 :return: 食物信息 """ if not food_detail_list: - raise Exception("食物详情列表为空") + logger.error("食物详情列表为空") + raise ValueError("食物详情列表为空") food_str = "✨=====食物列表=====✨\n" for i, food_detail in enumerate(food_detail_list): @@ -89,7 +99,9 @@ def generate_food_message(food_detail_list: List) -> str: # 这里也是 parse部分 -def parse_food_detail_response(response: requests.Response, food_all_name: str) -> Dict: +def _parse_food_detail_response( + response: requests.Response, food_all_name: str +) -> Dict: """ 解析食物详情 :param response: 请求响应 @@ -105,13 +117,14 @@ def parse_food_detail_response(response: requests.Response, food_all_name: str) if name and value: food_detail[name] = value if not food_detail: + logger.error("解析食物列表失败") raise Bs4ParsingError("解析食物列表失败") food_detail["food_all_name"] = food_all_name return food_detail -def parse_food_href_list_response(response: requests.Response) -> List: +def _parse_food_href_list_response(response: requests.Response) -> List: """ 解析食物详情链接列表 :param response: 请求响应 @@ -121,6 +134,7 @@ def parse_food_href_list_response(response: requests.Response) -> List: href_list = [] articles = soup.select("div.text-box") if not articles: + logger.error("解析食物详情链接失败") raise Bs4ParsingError("解析食物详情链接失败") for article in articles: href_list_item = {} @@ -137,12 +151,13 @@ def parse_food_href_list_response(response: requests.Response) -> List: href_list.append(href_list_item) if not href_list: + logger.error("解析食物详情链接失败") raise Bs4ParsingError("解析食物详情链接失败") return href_list -def get_url_encoding(message: str) -> str: +def _get_url_encoding(message: str) -> str: """ 获取URL编码 :param message: 消息 diff --git a/wechatter/commands/_commands/gasoline_price.py b/wechatter/commands/_commands/gasoline_price.py index bc9c80a..9bd4693 100644 --- a/wechatter/commands/_commands/gasoline_price.py +++ b/wechatter/commands/_commands/gasoline_price.py @@ -5,8 +5,8 @@ import wechatter.utils.path_manager as pm from wechatter.commands.handlers import command from wechatter.exceptions import Bs4ParsingError -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils import get_request, load_json @@ -18,11 +18,12 @@ def gasoline_price_command_handler(to: SendTo, message: str = "") -> None: try: result = get_gasoline_price_str(message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) except Exception as e: - error_message = f"获取汽油价格失败,错误信息:{e}" + error_message = f"获取汽油价格失败,错误信息:{str(e)}" logger.error(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + sender.send_msg(to, error_message) + else: + sender.send_msg(to, result) # TODO:查询其他类型的油价,如95,97柴油等,例子:查询95号汽油只需改成{city_id}_4_1.html @@ -54,17 +55,17 @@ def _parse_gasoline_price_response(response: requests.Response) -> str: :param response: 响应 :return: 汽油价格 """ - soup = BeautifulSoup(response.text, 'html.parser') + soup = BeautifulSoup(response.text, "html.parser") article_body_div = soup.select_one("div.articlebody") if article_body_div: # 找到div内的第二个p元素 - second_p_element = article_body_div.find_all('p')[1] + second_p_element = article_body_div.find_all("p")[1] if second_p_element: # 提取第二个p元素的文本内容 text_content = second_p_element.get_text() - desired_text = text_content.split(',若需要计算')[0] + desired_text = text_content.split(",若需要计算")[0] else: logger.error("找不到第二个p元素") @@ -93,4 +94,4 @@ def _get_city_id(city_name: str) -> str: def _generate_gasoline_price_message(gasoline_price: str, message: str) -> str: - return f"✨{message}石化92汽油指导价✨\n\n{gasoline_price}\n\n油价数据仅供参考,实际在售油价可能有小幅偏差。" + return f"✨{message}石化92汽油指导价✨\n{gasoline_price}\n\n油价数据仅供参考,实际在售油价可能有小幅偏差。" diff --git a/wechatter/commands/_commands/github_trending.py b/wechatter/commands/_commands/github_trending.py index 1dd3aed..f5a12d3 100644 --- a/wechatter/commands/_commands/github_trending.py +++ b/wechatter/commands/_commands/github_trending.py @@ -6,15 +6,15 @@ from wechatter.commands.handlers import command from wechatter.exceptions import Bs4ParsingError -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils import get_request @command( command="github-trending", - keys=["GitHub趋势", "github-trending"], - desc="获取GitHub趋势。", + keys=["github趋势", "github-trending"], + desc="获取 GitHub 趋势。", ) def github_trending_command_handler(to: SendTo, message: str = "") -> None: try: @@ -22,9 +22,9 @@ def github_trending_command_handler(to: SendTo, message: str = "") -> None: except Exception as e: error_message = f"获取GitHub趋势失败,错误信息: {str(e)}" logger.error(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + sender.send_msg(to, error_message) else: - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) + sender.send_msg(to, result) def get_github_trending_str() -> str: diff --git a/wechatter/commands/_commands/help.py b/wechatter/commands/_commands/help.py index 13b810d..596e69f 100644 --- a/wechatter/commands/_commands/help.py +++ b/wechatter/commands/_commands/help.py @@ -3,7 +3,7 @@ from wechatter.commands import commands from wechatter.commands.handlers import command from wechatter.models.message import SendTo -from wechatter.sender import Sender +from wechatter.sender import sender from wechatter.utils.text_to_image import text_to_image @@ -18,7 +18,7 @@ def help_command_handler(to: SendTo, message: str = "") -> None: help_msg = get_help_msg() response = text_to_image(help_msg) if response: - Sender.send_localfile_msg(to, response) + sender.send_localfile_msg(to, response) def get_help_msg() -> str: diff --git a/wechatter/commands/_commands/pai_post.py b/wechatter/commands/_commands/pai_post.py index f5385f6..759b18d 100644 --- a/wechatter/commands/_commands/pai_post.py +++ b/wechatter/commands/_commands/pai_post.py @@ -6,8 +6,8 @@ from wechatter.commands.handlers import command from wechatter.exceptions import Bs4ParsingError -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils import get_request @@ -22,9 +22,9 @@ def pai_post_command_handler(to: SendTo, message: str = "") -> None: except Exception as e: error_message = f"获取少数派早报失败,错误信息:{str(e)}" logger.error(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + sender.send_msg(to, error_message) else: - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) + sender.send_msg(to, result) def get_pai_post_str() -> str: diff --git a/wechatter/commands/_commands/people_daily.py b/wechatter/commands/_commands/people_daily.py index a23b424..1c348ee 100644 --- a/wechatter/commands/_commands/people_daily.py +++ b/wechatter/commands/_commands/people_daily.py @@ -1,8 +1,8 @@ from loguru import logger from wechatter.commands.handlers import command -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils.time import get_current_ymd @@ -13,25 +13,7 @@ ) def people_daily_command_handler(to: SendTo, message: str = "") -> None: """发送人民日报url""" - # 发送当天01版本的人民日报PDF - if message == "": - try: - url = get_today_people_daliy_url() - except Exception as e: - error_message = f"获取今日人民日报失败,错误信息:{e}" - logger.error(error_message) - _send_text_msg(to, error_message) - else: - _send_file_url_msg(to, url) - else: - try: - url = get_people_daily_url(message) - except Exception as e: - error_message = f"输入的日期版本号不符合要求,请重新输入,错误信息:{e}\n若要获取2021年1月2日03版的人民日报的PDF,请输入:\n/people 2021010203" - logger.error(error_message) - _send_text_msg(to, error_message) - else: - _send_file_url_msg(to, url) + _send_people_daily(to, message, type="fileUrl") @command( @@ -41,26 +23,29 @@ def people_daily_command_handler(to: SendTo, message: str = "") -> None: ) def people_daily_url_command_handler(to: SendTo, message: str = "") -> None: """发送人民日报url""" - # 获取今天 + _send_people_daily(to, message, type="text") + + +def _send_people_daily(to: SendTo, message: str, type: str) -> None: if message == "": try: url = get_today_people_daliy_url() except Exception as e: - error_message = f"获取今天的人民日报失败,错误信息:{e}" + error_message = f"获取今天的人民日报失败,错误信息:{str(e)}" logger.error(error_message) - _send_text_msg(to, error_message) + sender.send_msg(to, error_message) else: - _send_text_msg(to, url) + sender.send_msg(to, url, type=type) # 获取指定日期 else: try: url = get_people_daily_url(message) except Exception as e: - error_message = f"输入的日期版本号不符合要求,请重新输入,错误信息:{e}\n若要获取2021年1月2日03版的人民日报的URL,请输入:\n/people-url 2021010203" + error_message = f"输入的日期版本号不符合要求,请重新输入,错误信息:{str(e)}\n若要获取2021年1月2日03版的人民日报的URL,请输入:\n/people-url 2021010203" logger.error(error_message) - _send_text_msg(to, error_message) + sender.send_msg(to, error_message) else: - _send_text_msg(to, url) + sender.send_msg(to, url, type=type) def get_people_daily_url(date_version: str) -> str: @@ -86,13 +71,3 @@ def get_today_people_daliy_url() -> str: version = "01" today_version = f"{yearmonthday}{version}" return get_people_daily_url(today_version) - - -def _send_file_url_msg(to: SendTo, message: str = "") -> None: - """封装发送文件URL消息""" - Sender.send_msg(to, SendMessage(SendMessageType.FILE_URL, message)) - - -def _send_text_msg(to: SendTo, message: str = "") -> None: - """封装发送文本消息""" - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, message)) diff --git a/wechatter/commands/_commands/qrcode.py b/wechatter/commands/_commands/qrcode.py index fd79134..c2ffaa2 100644 --- a/wechatter/commands/_commands/qrcode.py +++ b/wechatter/commands/_commands/qrcode.py @@ -5,8 +5,8 @@ import wechatter.utils.path_manager as pm from wechatter.commands.handlers import command -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils.time import get_current_datetime @@ -18,16 +18,27 @@ def qrcode_command_handler(to: SendTo, message: str = "") -> None: # 获取二维码 try: - datetime_str = get_current_datetime() - path = pm.get_abs_path(f"data/qrcodes/{datetime_str}.png") - img = generate_qrcode(message) - save_qrcode(img, path) + path = get_qrcode_saved_path(message) except Exception as e: error_message = f"生成二维码失败,错误信息:{str(e)}" logger.error(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + sender.send_msg(to, error_message) else: - Sender.send_localfile_msg(to, path) + sender.send_localfile_msg(to, path) + + +# TODO: 发送后删除二维码 + + +def get_qrcode_saved_path(data: str) -> str: + if not data: + raise ValueError("请输入文本或链接") + """获取二维码保存路径""" + datetime_str = get_current_datetime() + path = pm.get_abs_path(f"data/qrcodes/{datetime_str}.png") + img = generate_qrcode(data) + save_qrcode(img, path) + return path def generate_qrcode(data: str): diff --git a/wechatter/commands/_commands/today_in_history.py b/wechatter/commands/_commands/today_in_history.py index b54f162..7f7acea 100644 --- a/wechatter/commands/_commands/today_in_history.py +++ b/wechatter/commands/_commands/today_in_history.py @@ -3,8 +3,8 @@ from loguru import logger from wechatter.commands.handlers import command -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils import get_request_json @@ -18,11 +18,11 @@ def today_in_history_command_handler(to: SendTo, message: str = "") -> None: try: result = get_today_in_history_str() except Exception as e: - error_message = f"获取历史上的今天失败,错误信息:{e}" + error_message = f"获取历史上的今天失败,错误信息:{str(e)}" logger.error(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + sender.send_msg(to, error_message) else: - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) + sender.send_msg(to, result) def get_today_in_history_str() -> str: diff --git a/wechatter/commands/_commands/todo.py b/wechatter/commands/_commands/todo.py index e7ea9af..3d3d4be 100644 --- a/wechatter/commands/_commands/todo.py +++ b/wechatter/commands/_commands/todo.py @@ -6,8 +6,8 @@ import wechatter.utils.path_manager as pm from wechatter.commands.handlers import command -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils import load_json, save_json @@ -22,17 +22,17 @@ def todo_command_handler(to: SendTo, message: str = "") -> None: if message == "": # 获取待办事项 result = view_todos(to.p_id, to.p_name) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) + sender.send_msg(to, result) else: # 添加待办事项 try: add_todo_task(to.p_id, message) result = view_todos(to.p_id, to.p_name) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) + sender.send_msg(to, result) except Exception as e: - error_message = f"添加待办事项失败,错误信息:{e}" + error_message = f"添加待办事项失败,错误信息:{str(e)}" logger.error(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + sender.send_msg(to, error_message) @command( @@ -47,21 +47,19 @@ def remove_todo_command_handler(to: SendTo, message: str = "") -> None: if idx.strip().isdigit() ] if not indices: - Sender.send_msg( - to, SendMessage(SendMessageType.TEXT, "请输入有效数字来删除待办事项") - ) + sender.send_msg(to, "输入有效数字来删除待办事项") return try: remove_result = remove_todo_task(to.p_id, indices) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, remove_result)) + sender.send_msg(to, remove_result) except Exception as e: - error_message = f"删除待办事项失败,错误信息:{e}" + error_message = f"删除待办事项失败,错误信息:{str(e)}" logger.error(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + sender.send_msg(to, error_message) else: result = view_todos(to.p_id, to.p_name) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) + sender.send_msg(to, result) def _load_todos(person_id: str) -> List[str]: diff --git a/wechatter/commands/_commands/translate.py b/wechatter/commands/_commands/translate.py index 9d6d84e..4eb3ad6 100644 --- a/wechatter/commands/_commands/translate.py +++ b/wechatter/commands/_commands/translate.py @@ -6,8 +6,8 @@ from loguru import logger from wechatter.commands.handlers import command -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils import get_request, get_request_json @@ -24,7 +24,7 @@ def word_command_handler(to: SendTo, message: str = "") -> None: if from_lang == "": error_message = "翻译失败,无法检测文本语言" logger.error(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + sender.send_msg(to, error_message) return from_lang, to_lang = _auto_translate(from_lang, to_lang) @@ -35,9 +35,9 @@ def word_command_handler(to: SendTo, message: str = "") -> None: except Exception as e: error_message = f"翻译失败,错误信息: {str(e)}" logger.error(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + sender.send_msg(to, error_message) else: - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) + sender.send_msg(to, result) # 翻译语言字典(何种语言对应何种语言) diff --git a/wechatter/commands/_commands/trivia.py b/wechatter/commands/_commands/trivia.py index bf1ef2c..f493299 100644 --- a/wechatter/commands/_commands/trivia.py +++ b/wechatter/commands/_commands/trivia.py @@ -1,5 +1,5 @@ -from typing import List import random +from typing import List import requests from bs4 import BeautifulSoup @@ -7,8 +7,8 @@ from wechatter.commands.handlers import command from wechatter.exceptions import Bs4ParsingError -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils import get_request @@ -25,11 +25,11 @@ def trivia_command_handler(to: SendTo, message: str = "") -> None: ) trivia_list = _parse_trivia_response(response) result = _generate_trivia_message(trivia_list, random_number) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) + sender.send_msg(to, result) except Exception as e: - error_message = f"获取冷知识失败,错误信息:{e}" - print(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + error_message = f"获取冷知识失败,错误信息:{str(e)}" + logger.error(error_message) + sender.send_msg(to, error_message) def _parse_trivia_response(response: requests.Response) -> List: diff --git a/wechatter/commands/_commands/weather.py b/wechatter/commands/_commands/weather.py index 82efbbd..18d261b 100644 --- a/wechatter/commands/_commands/weather.py +++ b/wechatter/commands/_commands/weather.py @@ -8,8 +8,8 @@ import wechatter.utils.path_manager as pm from wechatter.commands.handlers import command from wechatter.exceptions import Bs4ParsingError -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils import get_request, load_json from wechatter.utils.time import get_current_hour, get_current_minute, get_current_ymdh @@ -25,9 +25,9 @@ def weather_command_handler(to: SendTo, message: str = "") -> None: except Exception as e: error_message = f"获取天气预报失败,错误信息:{str(e)}" logger.error(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + sender.send_msg(to, error_message) else: - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) + sender.send_msg(to, result) # class WeatherTip: @@ -120,8 +120,8 @@ def _get_city_id(city_name: str) -> int: city_ids = load_json(CITY_IDS_PATH) if city_name not in city_ids.keys(): - logger.error(f"未找到城市 {city_name}。") - raise KeyError(f"未找到城市 {city_name}。") + logger.error(f"未找到城市 {city_name}") + raise KeyError(f"未找到城市 {city_name}") return city_ids[city_name] diff --git a/wechatter/commands/_commands/weibo_hot.py b/wechatter/commands/_commands/weibo_hot.py index 6cc29fc..8670ad0 100644 --- a/wechatter/commands/_commands/weibo_hot.py +++ b/wechatter/commands/_commands/weibo_hot.py @@ -3,8 +3,8 @@ from loguru import logger from wechatter.commands.handlers import command -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils import get_request_json @@ -19,9 +19,9 @@ def weibo_hot_command_handler(to: SendTo, message: str = "") -> None: except Exception as e: error_message = f"获取微博热搜失败,错误信息: {str(e)}" logger.error(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + sender.send_msg(to, error_message) else: - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) + sender.send_msg(to, result) def get_weibo_hot_str() -> str: diff --git a/wechatter/commands/_commands/zhihu_hot.py b/wechatter/commands/_commands/zhihu_hot.py index 9876739..0d7a1c9 100644 --- a/wechatter/commands/_commands/zhihu_hot.py +++ b/wechatter/commands/_commands/zhihu_hot.py @@ -3,8 +3,8 @@ from loguru import logger from wechatter.commands.handlers import command -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender from wechatter.utils import get_request_json @@ -19,9 +19,9 @@ def zhihu_hot_command_handler(to: SendTo, message: str = "") -> None: except Exception as e: error_message = f"获取知乎热搜失败,错误信息: {str(e)}" logger.error(error_message) - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, error_message)) + sender.send_msg(to, error_message) else: - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, result)) + sender.send_msg(to, result) def get_zhihu_hot_str() -> str: diff --git a/wechatter/config/parsers/gasoline_price_cron.py b/wechatter/config/parsers/gasoline_price_cron.py index 5328011..0db4d89 100644 --- a/wechatter/config/parsers/gasoline_price_cron.py +++ b/wechatter/config/parsers/gasoline_price_cron.py @@ -3,13 +3,12 @@ from apscheduler.triggers.cron import CronTrigger from wechatter.commands._commands.gasoline_price import get_gasoline_price_str -from wechatter.models.message import SendMessage, SendMessageType from wechatter.models.scheduler import CronTask -from wechatter.sender import Sender +from wechatter.sender import sender def parse_gasoline_price_cron_rule_list( - gasoline_price_cron_rule_list: List, + gasoline_price_cron_rule_list: List, ) -> List[CronTask]: """ 解析汽油价格定时任务规则 @@ -36,14 +35,11 @@ def parse_gasoline_price_cron_rule_list( def func(): for task in tasks: - to_persons = task["to_persons"] - to_groups = task["to_groups"] - send_message = SendMessage( - type=SendMessageType.TEXT, - content=get_gasoline_price_str(task["city"]) - ) - Sender.send_msg_ps(to_p_names=to_persons, message=send_message) - Sender.send_msg_gs(to_g_names=to_groups, message=send_message) + message = get_gasoline_price_str(task["city"]) + if task["to_persons"]: + sender.mass_send_msg(task["to_persons"], message) + if task["to_groups"]: + sender.mass_send_msg(task["to_groups"], message, is_group=True) cron_tasks.append( CronTask( diff --git a/wechatter/config/parsers/weather_cron.py b/wechatter/config/parsers/weather_cron.py index 3019cd9..c23d7c5 100644 --- a/wechatter/config/parsers/weather_cron.py +++ b/wechatter/config/parsers/weather_cron.py @@ -3,13 +3,12 @@ from apscheduler.triggers.cron import CronTrigger from wechatter.commands._commands.weather import get_weather_str -from wechatter.models.message import SendMessage, SendMessageType from wechatter.models.scheduler import CronTask -from wechatter.sender import Sender +from wechatter.sender import sender def parse_weather_cron_rule_list( - weather_cron_rule_list: List, + weather_cron_rule_list: List, ) -> List[CronTask]: """ 解析天气定时任务规则 @@ -36,14 +35,11 @@ def parse_weather_cron_rule_list( def func(): for task in tasks: - to_persons = task["to_persons"] - to_groups = task["to_groups"] - send_message = SendMessage( - type=SendMessageType.TEXT, - content=get_weather_str(task["city"]), - ) - Sender.send_msg_ps(to_p_names=to_persons, message=send_message) - Sender.send_msg_gs(to_g_names=to_groups, message=send_message) + message = get_weather_str(task["city"]) + if task["to_persons"]: + sender.mass_send_msg(task["to_persons"], message) + if task["to_groups"]: + sender.mass_send_msg(task["to_groups"], message, is_group=True) cron_tasks.append( CronTask( diff --git a/wechatter/message/message_parser.py b/wechatter/message/message_parser.py index ee62461..3761ba6 100644 --- a/wechatter/message/message_parser.py +++ b/wechatter/message/message_parser.py @@ -37,7 +37,7 @@ def handle_message(self, message: Message) -> None: logger.debug("该消息为群消息,但未@机器人,不处理") return - to = SendTo(message.source) + to = SendTo.from_message_source(message.source) # 是命令消息 # 开始处理命令 diff --git a/wechatter/message_forwarder/message_forwarder.py b/wechatter/message_forwarder/message_forwarder.py index 430bdcb..6ef8cd6 100644 --- a/wechatter/message_forwarder/message_forwarder.py +++ b/wechatter/message_forwarder/message_forwarder.py @@ -2,8 +2,8 @@ from loguru import logger -from wechatter.models.message import Message, SendMessage, SendMessageType -from wechatter.sender import Sender +from wechatter.models.message import Message +from wechatter.sender import sender class MessageForwarder: @@ -30,12 +30,12 @@ def forward_message(self, message: Message): if from_name in rule["froms"]: # 构造转发消息 msg = self.__construct_forwarding_message(message) - for p_name in rule["to_persons"]: - logger.info(f"转发消息:{from_name} -> {p_name}") - Sender.send_msg_p(p_name, SendMessage(SendMessageType.TEXT, msg)) - for g_name in rule["to_groups"]: - logger.info(f"转发消息:{from_name} -> {g_name}") - Sender.send_msg_g(g_name, SendMessage(SendMessageType.TEXT, msg)) + logger.info( + f"转发消息:{from_name} -> {rule['to_persons']}\n" + f"转发消息:{from_name} -> {rule['to_groups']}" + ) + sender.mass_send_msg(rule["to_persons"], msg) + sender.mass_send_msg(rule["to_groups"], msg, is_group=True) def __construct_forwarding_message(self, message: Message) -> str: """构造转发消息""" diff --git a/wechatter/models/message/__init__.py b/wechatter/models/message/__init__.py index 9c69f2b..8c1717f 100644 --- a/wechatter/models/message/__init__.py +++ b/wechatter/models/message/__init__.py @@ -1,11 +1,8 @@ from .message import Message, MessageSource -from .send_message import SendMessage, SendMessageList, SendMessageType, SendTo +from .send_message import SendTo __all__ = [ "Message", "MessageSource", - "SendMessage", - "SendMessageList", - "SendMessageType", "SendTo", ] diff --git a/wechatter/models/message/group_info.py b/wechatter/models/message/group_info.py index 58ff875..3db01d4 100644 --- a/wechatter/models/message/group_info.py +++ b/wechatter/models/message/group_info.py @@ -1,66 +1,86 @@ +from dataclasses import dataclass, field from typing import List +@dataclass class GroupMemberInfo: """群成员类""" - def __init__( - self, - id: str, - name: str, - alias: str = "", - ): - self.id = id - self.name = name - self.alias = alias - - def __str__(self) -> str: - return f"微信ID:{self.id}\n昵称:{self.name}\n备注:{self.alias}" + id: str + name: str + alias: str = "" +@dataclass class GroupInfo: """群消息类""" - def __init__( - self, - id: str, - name: str, - admin_id_list: List[str] = [], - member_list: List[dict] = [], - ): - self.id = id - self.name = name - self.admin_id_list = admin_id_list - self.member_list = member_list - - @property - def admin_id_list(self) -> List[str]: - """获取管理员ID列表""" - return self.__admin_id_list - - @admin_id_list.setter - def admin_id_list(self, admin_id_list: List[str]): - self.__admin_id_list = admin_id_list - - @property - def member_list(self) -> List[GroupMemberInfo]: - """获取群成员列表""" - return self.__member_list - - @member_list.setter - def member_list(self, member_list: List[dict]): - self.__member_list = [] - for m in member_list: - self.__member_list.append( - GroupMemberInfo( - id=m["id"], - name=m["name"], - alias=m["alias"], - ) - ) - - def __str__(self) -> str: - # 群成员数量过多,不打印 - # member_list_str = "[\n{}\n]".format('\n'.join([str(m) for m in self.member_list])) - # return f"群ID: {self.id}\n群名:{self.name}\n管理员:{self.admin_id_list}\n成员:{member_list_str}" - return f"群ID: {self.id}\n群名:{self.name}\n管理员:{self.admin_id_list}\n成员:{str(self.member_list)}" + id: str + name: str + admin_id_list: List[str] = field(default_factory=list) + member_list: List[GroupMemberInfo] = field(default_factory=list) + + +# class GroupMemberInfo: +# """群成员类""" + +# def __init__( +# self, +# id: str, +# name: str, +# alias: str = "", +# ): +# self.id = id +# self.name = name +# self.alias = alias + +# def __str__(self) -> str: +# return f"微信ID:{self.id}\n昵称:{self.name}\n备注:{self.alias}" + + +# class GroupInfo: +# """群消息类""" + +# def __init__( +# self, +# id: str, +# name: str, +# admin_id_list: List[str] = [], +# member_list: List[dict] = [], +# ): +# self.id = id +# self.name = name +# self.admin_id_list = admin_id_list +# self.member_list = member_list + +# @property +# def admin_id_list(self) -> List[str]: +# """获取管理员ID列表""" +# return self.__admin_id_list + +# @admin_id_list.setter +# def admin_id_list(self, admin_id_list: List[str]): +# self.__admin_id_list = admin_id_list + +# @property +# def member_list(self) -> List[GroupMemberInfo]: +# """获取群成员列表""" +# return self.__member_list + +# @member_list.setter +# def member_list(self, member_list: List[dict]): +# self.__member_list = [] +# for m in member_list: +# self.__member_list.append( +# GroupMemberInfo( +# id=m["id"], +# name=m["name"], +# alias=m["alias"], +# ) +# ) + +# def __str__(self) -> str: +# # 群成员数量过多,不打印 +# # member_list_str = "[\n{}\n]".format('\n'.join([str(m) for m in self.member_list])) +# # return f"群ID: {self.id}\n群名:{self.name}\n管理员:{self.admin_id_list}\n成员:{member_list_str}" +# return f"群ID: {self.id}\n群名:{self.name}\n管理员:{self.admin_id_list}\n成员:{str(self.member_list)}" diff --git a/wechatter/models/message/person_info.py b/wechatter/models/message/person_info.py index 14e8c01..4ad5025 100644 --- a/wechatter/models/message/person_info.py +++ b/wechatter/models/message/person_info.py @@ -1,30 +1,17 @@ +from dataclasses import dataclass, field from typing import List +@dataclass class PersonInfo: """个人消息类""" - def __init__( - self, - id: str, - name: str, - alias: str = "", - gender: int = -1, - signature: str = "", - province: str = "", - city: str = "", - phone_list: List[str] = [], - is_star: bool = False, - ): - self.id = id - self.name = name - self.alias = alias - self.gender = gender - self.signature = signature - self.province = province - self.city = city - self.phone_list = phone_list - self.is_star = is_star - - def __str__(self) -> str: - return f"ID: {self.id}\n昵称:{self.name}\n备注:{self.alias}\n性别:{self.gender}\n签名:{self.signature}\n手机:{self.phone_list}\n星标:{self.is_star}" + id: str + name: str + alias: str = "" + gender: int = -1 + signature: str = "" + province: str = "" + city: str = "" + phone_list: List[str] = field(default_factory=list) + is_star: bool = False diff --git a/wechatter/models/message/send_message.py b/wechatter/models/message/send_message.py index 20ba6c4..0204b41 100644 --- a/wechatter/models/message/send_message.py +++ b/wechatter/models/message/send_message.py @@ -1,45 +1,29 @@ -from enum import Enum -from typing import List +from dataclasses import dataclass from wechatter.models.message.message import MessageSource +@dataclass class SendTo: """发送对象类""" - def __init__(self, source: MessageSource): - self.p_id = "" - self.p_name = "" - self.g_id = "" - self.g_name = "" + p_id: str + p_name: str + # p_alias: str + g_id: str + g_name: str + + @classmethod + def from_message_source(cls, source: MessageSource): + p_id = "" + p_name = "" + # p_alias = "" + g_id = "" + g_name = "" if source.p_info is not None: - self.p_id = source.p_info.id - self.p_name = source.p_info.name + p_id = source.p_info.id + p_name = source.p_info.name if source.g_info is not None: - self.g_id = source.g_info.id - self.g_name = source.g_info.name - - -class SendMessageType(Enum): - """发送消息类型枚举""" - - TEXT = "text" - FILE_URL = "fileUrl" - - -class SendMessage: - """发送消息类""" - - def __init__(self, type: SendMessageType, content: str): - self.type = type.value - self.content = content - - -class SendMessageList: - """发送消息列表类,用于给同一个对象发送多条消息""" - - def __init__(self): - self.messages: List[SendMessage] = [] - - def add(self, message: SendMessage): - self.messages.append(message) + g_id = source.g_info.id + g_name = source.g_info.name + return cls(p_id, p_name, g_id, g_name) diff --git a/wechatter/sender/__init__.py b/wechatter/sender/__init__.py index 28ded72..b4c2099 100644 --- a/wechatter/sender/__init__.py +++ b/wechatter/sender/__init__.py @@ -1,6 +1,3 @@ -# isort: off -from .sender import Sender -from .notifier import notify_logged_in, notify_logged_out, notify_received -# isort: on +from . import notifier, sender -__all__ = ["Sender", "notify_received", "notify_logged_in", "notify_logged_out"] +__all__ = ["sender", "notifier"] diff --git a/wechatter/sender/notifier.py b/wechatter/sender/notifier.py index e7de822..37aab05 100644 --- a/wechatter/sender/notifier.py +++ b/wechatter/sender/notifier.py @@ -1,23 +1,23 @@ # 消息通知器 -from wechatter.models.message import SendMessage, SendMessageType, SendTo -from wechatter.sender import Sender +from wechatter.models.message import SendTo +from wechatter.sender import sender def notify_received(to: SendTo) -> None: """通知收到命令请求""" msg = "收到命令请求" - Sender.send_msg(to, SendMessage(SendMessageType.TEXT, msg)) + sender.send_msg(to, msg) # 机器人登录登出通知,若是登录(登出)则发送登录(登出)消息给所有管理员 def notify_logged_in() -> None: """通知登录成功""" msg = "微信机器人启动成功" - Sender.send_msg_to_admins(msg) + sender.mass_send_msg_to_admins(msg) # FIXME: 登出消息发送不出去,因为发消息时候,机器人已经退出登录了 def notify_logged_out() -> None: """通知已退出登录""" msg = "微信机器人已退出" - Sender.send_msg_to_admins(msg) + sender.mass_send_msg_to_admins(msg) diff --git a/wechatter/sender/sender.py b/wechatter/sender/sender.py index 119fbd7..9539a01 100644 --- a/wechatter/sender/sender.py +++ b/wechatter/sender/sender.py @@ -1,4 +1,5 @@ import json +from functools import singledispatch from typing import List import requests @@ -7,12 +8,7 @@ import wechatter.config as config import wechatter.utils.http_request as http_request -from wechatter.models.message import ( - SendMessage, - SendMessageList, - SendMessageType, - SendTo, -) +from wechatter.models.message import SendTo # 对retry装饰器重新包装,增加日志输出 @@ -48,29 +44,30 @@ def _post_request( def _log(response: requests.Response) -> bool: """检查发送状态""" - # if response.status_code != 200: - # logger.error(f"发送消息失败,状态码:{response.status_code}") - # return False r_json = response.json() - # 即使code为200,也需要检查success字段 - task = r_json.get("task", None) - if task is not None: - if not r_json["success"] or not task["successCount"] == task["totalCount"]: - logger.error(f"发送消息失败,错误信息:{r_json['message']}") - return False - # 部分成功 - if task["successCount"] > 0 and task["successCount"] < task["totalCount"]: - logger.warning(f"发送消息部分成功,成功数:{task['successCount']}") - return True + # https://github.com/danni-cool/wechatbot-webhook?tab=readme-ov-file#%E8%BF%94%E5%9B%9E%E5%80%BC-response-%E7%BB%93%E6%9E%84 + if r_json["message"].startswith("Message"): + logger.info("发送消息成功") + elif r_json["message"].startswith("Some"): + logger.error("发送消息失败,参数校验不通过") + elif r_json["message"].startswith("All"): + logger.error("发送消息失败,所有消息均发送失败") + return False + elif r_json["message"].startswith("Part"): + logger.warning("发送消息失败,部分消息发送成功") + return False + + if "task" not in r_json: + return False try: data = json.loads(response.request.body.decode("utf-8")) except UnicodeDecodeError: # 本地文件发送无法解码 - logger.info("发送图片成功") + # logger.info("发送图片成功") return True - except json.JSONDecodeError: - logger.error(f"发送消息失败,错误信息:{r_json['message']}") + except json.JSONDecodeError as e: + logger.error(f"发送消息失败,错误信息:{str(e)}") return False if isinstance(data, list): @@ -83,340 +80,233 @@ def _log(response: requests.Response) -> bool: return True -class Sender: - """v2 版本 api 消息发送类""" +URL = f"{config.wx_webhook_host}:{config.wx_webhook_port}/webhook/msg/v2" +V1_URL = f"{config.wx_webhook_host}:{config.wx_webhook_port}/webhook/msg" - url = f"{config.wx_webhook_host}:{config.wx_webhook_port}/webhook/msg/v2" - v1_url = f"{config.wx_webhook_host}:{config.wx_webhook_port}/webhook/msg" - # 发送文本消息或链接文件 +def _validate(fn): """ - curl --location 'http://localhost:3001/webhook/msg/v2' \ - --header 'Content-Type: application/json' \ - --data '{ - "to": "testUser", - "ioRoom": false, - "data": { - "type": "text", - "content": "你好👋" - } - }' - curl --location --request POST 'http://localhost:3001/webhook/msg/v2' \ - --header 'Content-Type: application/json' \ - --data-raw '{ - "to": "testGroup", - "type": "fileUrl", - "content": "https://samplelib.com/lib/preview/mp3/sample-3s.mp3", - "isRoom": true - }' + 验证接收者和消息内容是否为空 """ - @staticmethod - def send_msg(to: SendTo, message: SendMessage) -> bool: - """发送消息(文本或链接文件)""" - # 群消息 - if to.g_name != "": - if message.type == SendMessageType.TEXT.value: - message.content = f"@{to.p_name}\n{message.content}" - return Sender.send_msg_g(to.g_name, message) - # 个人消息 - else: - return Sender.send_msg_p(to.p_name, message) - - @staticmethod - def send_msg_p(to_p_name: str, message: SendMessage) -> bool: - """发送给个人""" - headers = {"Content-Type": "application/json"} - data = { - "to": to_p_name, - "isRoom": False, - "data": {"type": message.type, "content": message.content}, - } - _log(_post_request(Sender.url, headers=headers, json=data)) - - @staticmethod - def send_msg_g(to_g_name: str, message: SendMessage) -> bool: - """发送给群组""" - headers = {"Content-Type": "application/json"} - data = { - "to": to_g_name, - "isRoom": True, - "data": {"type": message.type, "content": message.content}, - } - _log(_post_request(Sender.url, headers=headers, json=data)) - - # 给同一个对象发送多条消息 + def wrapper(n, m, *args, **kwargs): + if not n: + logger.error("发送消息失败,接收者为空") + return + if not m: + logger.error("发送消息失败,消息内容为空") + return + + return fn(n, m, *args, **kwargs) + + return wrapper + + +@singledispatch +def send_msg(): """ - curl --location 'http://localhost:3001/webhook/msg' \ - --header 'Content-Type: application/json' \ - --data '{ - "to": "testUser", - "data": [ - { - "type": "text", - "content": "你好👋" - }, - { - "type": "fileUrl", - "content": "https://samplelib.com/lib/preview/mp3/sample-3s.mp3" - } - ] - }' + 发送消息 + + 当传入的第一个参数是字符串时,is_group 默认为 False。 + 当传入的第一个参数是 SendTo 对象时,is_group 默认为 True。 + + :param to: 接收对象的名字或SendTo对象 + :param message: 消息内容 + :param is_group: 是否为群组(默认值根据 to 的类型而定) + :param type: 消息类型,可选 text、fileUrl(默认值为 text) + """ + pass + + +@send_msg.register(str) +@_validate +def _send_msg1( + name: str, message: str, is_group: bool = False, type: str = "text" +) -> None: """ + 发送消息 + :param name: 接收者 + :param message: 消息内容 + :param is_group: 是否为群组(默认为个人,False) + :param type: 消息类型(text、fileUrl) + """ + data = { + "to": name, + "isRoom": is_group, + "data": {"type": type, "content": message}, + } + _log(_post_request(URL, json=data)) + - @staticmethod - def send_msgs(to: SendTo, messages: SendMessageList) -> bool: - if to.g_name != "": - return Sender.send_msgs_g(to.g_name, messages) - else: - return Sender.send_msgs_p(to.p_name, messages) - - # 给同一个人发送多条消息 - @staticmethod - def send_msgs_p(to_p_name: str, messages: SendMessageList) -> bool: - headers = {"Content-Type": "application/json"} - data = {"to": to_p_name, "isRoom": False, "data": []} - for message in messages.messages: - msg = {"type": message.type, "content": message.content} - data["data"].append(msg) - _log(_post_request(Sender.url, headers=headers, json=data)) - - # 给同一个群组发送多条消息 - @staticmethod - def send_msgs_g(to_g_name: str, messages: SendMessageList) -> bool: - headers = {"Content-Type": "application/json"} - data = {"to": to_g_name, "isRoom": True, "data": []} - for message in messages.messages: - msg = {"type": message.type, "content": message.content} - data["data"].append(msg) - _log(_post_request(Sender.url, headers=headers, json=data)) - - # 给多个人发送一条消息(群发) +@send_msg.register(SendTo) +def _send_msg2(to: SendTo, message: str, is_group: bool = True, type: str = "text"): """ - curl --location 'http://localhost:3001/webhook/msg/v2' \ - --header 'Content-Type: application/json' \ - --data '[ - { - "to": "testUser1", - "data": { - "content": "你好👋" - } - }, - { - "to": "testUser2", - "data": { - "content": "你好👋" - }, - } - ]' + 发送消息 + :param to: SendTo 对象 + :param message: 消息内容 + :param is_group: 是否为群组(默认为群组,True) + :param type: 消息类型(text、fileUrl) """ + if not is_group: + return _send_msg1(to.p_name, message, is_group=False, type=type) - @staticmethod - def send_msg_ps(to_p_names: List[str], message: SendMessage) -> bool: - """给多个人发送一条消息""" - if to_p_names == []: - return False - headers = {"Content-Type": "application/json"} - data = [] - for to_p_name in to_p_names: - msg = { - "to": to_p_name, - "isRoom": False, - "data": {"type": message.type, "content": message.content}, - } - data.append(msg) - _log(_post_request(Sender.url, headers=headers, json=data)) - - @staticmethod - def send_msg_gs(to_g_names: List[str], message: SendMessage) -> bool: - """给多个群组发送一条消息""" - if to_g_names == []: - return False - headers = {"Content-Type": "application/json"} - data = [] - for to_g_name in to_g_names: - msg = { - "to": to_g_name, - "isRoom": True, - "data": {"type": message.type, "content": message.content}, + if to.g_name != "": + return _send_msg1(to.g_name, message, is_group=True, type=type) + elif to.p_name != "": + return _send_msg1(to.p_name, message, is_group=False, type=type) + else: + logger.error("发送消息失败,接收者为空") + + +@singledispatch +def send_msg_list(): + """ + 发送多条消息,消息类型相同 + :param name: 接收者 + :param message_list: 消息内容列表 + :param is_group: 是否为群组 + :param type: 消息类型(text、fileUrl) + """ + pass + + +@send_msg_list.register(str) +@_validate +def _send_msg_list1( + name: str, message_list: List[str], is_group: bool = False, type: str = "text" +): + """ + 发送多条消息,消息类型相同 + :param name: 接收者 + :param message_list: 消息内容列表 + :param is_group: 是否为群组 + :param type: 消息类型(text、fileUrl) + """ + data = {"to": name, "isRoom": is_group, "data": []} + for message in message_list: + data["data"].append({"type": type, "content": message}) + _log(_post_request(URL, json=data)) + + +@send_msg_list.register(SendTo) +def _send_msg_list2( + to: SendTo, message_list: List[str], is_group: bool = True, type: str = "text" +): + """ + 发送多条消息,消息类型相同 + :param to: SendTo 对象 + :param message_list: 消息内容列表 + :param is_group: 是否为群组 + :param type: 消息类型(text、fileUrl) + """ + if not is_group: + return _send_msg_list1(to.p_name, message_list, is_group=False, type=type) + + if to.g_name != "": + return _send_msg_list1(to.g_name, message_list, is_group=True, type=type) + elif to.p_name != "": + return _send_msg_list1(to.p_name, message_list, is_group=False, type=type) + else: + logger.error("发送消息失败,接收者为空") + + +@_validate +def mass_send_msg( + name_list: List[str], message: str, is_group: bool = False, type: str = "text" +): + """ + 群发消息,给多个人发送一条消息 + :param name_list: 接收者列表 + :param message: 消息内容 + :param is_group: 是否为群组 + :param type: 消息类型(text、fileUrl) + """ + data = [] + for name in name_list: + data.append( + { + "to": name, + "isRoom": is_group, + "data": {"type": type, "content": message}, } - data.append(msg) - _log(_post_request(Sender.url, headers=headers, json=data)) + ) + _log(_post_request(URL, json=data)) - # TODO: 给多个人发送多条消息 - # 本地文件发送 +@singledispatch +def send_localfile_msg(): """ - curl --location --request POST 'http://localhost:3001/webhook/msg' \ - --form 'to=testGroup' \ - --form content=@"$HOME/demo.jpg" \ - --form 'isRoom=1' + 发送本地文件 + :param name: 接收者 + :param file_path: 文件路径 + :param is_group: 是否为群组 """ + pass - @staticmethod - def send_localfile_msg(to: SendTo, file_path: str) -> bool: - """发送本地文件""" - if to.g_name != "": - return Sender.send_localfile_msg_g(to.g_name, file_path) - else: - return Sender.send_localfile_msg_p(to.p_name, file_path) - - @staticmethod - def send_localfile_msg_p(to_p_name: str, file_path: str) -> bool: - """发送本地文件给个人""" - data = {"to": to_p_name, "isRoom": 0} - files = {"content": open(file_path, "rb")} - _log(_post_request(Sender.v1_url, data=data, files=files)) - - @staticmethod - def send_localfile_msg_g(to_g_name: str, file_path: str) -> bool: - """发送本地文件给群组""" - data = {"to": to_g_name, "isRoom": 1} - files = {"content": open(file_path, "rb")} - _log(_post_request(Sender.v1_url, data=data, files=files)) - - @staticmethod - def send_msg_to_admins(message: str) -> None: - """发送消息给所有管理员""" - if len(config.admin_list) == 0: - logger.warning("管理员列表为空") - else: - Sender.send_msg_ps( - config.admin_list, SendMessage(SendMessageType.TEXT, message) - ) - if len(config.admin_group_list) == 0: - logger.warning("管理员群列表为空") - else: - Sender.send_msg_gs( - config.admin_group_list, SendMessage(SendMessageType.TEXT, message) - ) - @staticmethod - def send_msg_to_github_webhook_receivers(message: str) -> None: - """发送消息给所有 GitHub Webhook 接收者""" - if len(config.github_webhook_receiver_list) == 0: - logger.warning("GitHub Webhook 接收者列表为空") - else: - Sender.send_msg_ps( - config.github_webhook_receiver_list, - SendMessage(SendMessageType.TEXT, message), - ) - if len(config.github_webhook_receive_group_list) == 0: - logger.warning("GitHub Webhook 接收群列表为空") - else: - Sender.send_msg_gs( - config.github_webhook_receive_group_list, - SendMessage(SendMessageType.TEXT, message), - ) +@send_localfile_msg.register(str) +@_validate +def _send_localfile_msg1(name: str, file_path: str, is_group: bool = False): + """ + 发送本地文件 + :param name: 接收者 + :param file_path: 文件路径 + :param is_group: 是否为群组 + """ + data = {"to": name, "isRoom": int(is_group)} + files = {"content": open(file_path, "rb")} + _log(_post_request(V1_URL, data=data, files=files)) + + +@send_localfile_msg.register(SendTo) +def _send_localfile_msg2(to: SendTo, file_path: str, is_group: bool = True): + """ + 发送本地文件 + :param to: SendTo 对象 + :param file_path: 文件路径 + :param is_group: 是否为群组 + """ + if not is_group: + return _send_localfile_msg1(to.p_name, file_path, is_group=False) + if to.g_name != "": + return _send_localfile_msg1(to.g_name, file_path, is_group=True) + elif to.p_name != "": + return _send_localfile_msg1(to.p_name, file_path, is_group=False) + else: + logger.error("发送消息失败,接收者为空") -# class SenderV1: -# """v1 版本 api 消息发送类""" - -# # url = f"{config.wx_webhook_host}:{config.wx_webhook_port}/webhook/msg" - -# # 发送文本消息 -# """ -# curl --location --request POST 'http://localhost:3001/webhook/msg' \ -# --header 'Content-Type: application/json' \ -# --data-raw '{ -# "to": "testUser", -# "type": "text", -# "content": "Hello World!" -# }' -# """ - -# @staticmethod -# def send_text_msg(to: SendTo, message: str) -> None: -# """发送文本消息""" -# # 群消息 -# if to.g_name != "": -# message = f"@{to.p_name}\n{message}" -# SenderV1.send_text_msg_g(to.g_name, message) -# # 个人消息 -# else: -# SenderV1.send_text_msg_p(to.p_name, message) - -# @staticmethod -# def send_text_msg_p(to_p_name: str, message: str) -> None: -# """发送文本消息给个人""" -# url = "http://localhost:3001/webhook/msg" -# headers = {"Content-Type": "application/json"} -# data = {"to": to_p_name, "type": "text", "content": message} -# _post_request(url, headers=headers, json=data) - -# @staticmethod -# def send_text_msg_g(to_g_name: str, message: str) -> None: -# """发送文本消息给群组""" -# url = "http://localhost:3001/webhook/msg" -# headers = {"Content-Type": "application/json"} -# data = {"to": to_g_name, "isRoom": True, "type": "text", "content": message} -# _post_request(url, headers=headers, json=data) - -# # 通过文件URL发送文件 -# """ -# curl --location --request POST 'http://localhost:3001/webhook/msg' \ -# --header 'Content-Type: application/json' \ -# --data-raw '{ -# "to": "testGroup", -# "type": "fileUrl", -# "content": "https://samplelib.com/lib/preview/mp3/sample-3s.mp3", -# "isRoom": true -# }' -# """ - -# @staticmethod -# def send_urlfile_msg(to: SendTo, file_path: str) -> None: -# """通过文件URL发送文件""" -# if to.g_name != "": -# SenderV1.send_urlfile_msg_g(to.g_name, file_path) -# else: -# SenderV1.send_urlfile_msg_p(to.p_name, file_path) - -# @staticmethod -# def send_urlfile_msg_p(to_p_name: str, file_url: str) -> None: -# """通过文件URL发送文件给个人""" -# url = "http://localhost:3001/webhook/msg" -# headers = {"Content-Type": "application/json"} -# data = {"to": to_p_name, "type": "fileUrl", "content": file_url} -# _post_request(url, headers=headers, json=data) - -# @staticmethod -# def send_urlfile_msg_g(to_g_name: str, file_url: str) -> None: -# """通过文件URL发送文件给群组""" -# url = "http://localhost:3001/webhook/msg" -# headers = {"Content-Type": "application/json"} -# data = {"to": to_g_name, "isRoom": True, "type": "fileUrl", "content": file_url} -# _post_request(url, headers=headers, json=data) - -# # 本地文件发送 -# """ -# curl --location --request POST 'http://localhost:3001/webhook/msg' \ -# --form 'to=testGroup' \ -# --form content=@"$HOME/demo.jpg" \ -# --form 'isRoom=1' -# """ - -# @staticmethod -# def send_localfile_msg(to: SendTo, file_path: str) -> None: -# """发送本地文件""" -# if to.g_name != "": -# SenderV1.send_localfile_msg_g(to.g_name, file_path) -# else: -# SenderV1.send_localfile_msg_p(to.p_name, file_path) - -# @staticmethod -# def send_localfile_msg_p(to_p_name: str, file_path: str) -> None: -# """发送本地文件给个人""" -# url = "http://localhost:3001/webhook/msg" -# data = {"to": to_p_name, "isRoom": 0} -# files = {"content": open(file_path, "rb")} -# _post_request(url, data=data, files=files) - -# @staticmethod -# def send_localfile_msg_g(to_g_name: str, file_path: str) -> None: -# """发送本地文件给群组""" -# url = "http://localhost:3001/webhook/msg" -# data = {"to": to_g_name, "isRoom": 1} -# files = {"content": open(file_path, "rb")} -# _post_request(url, data=data, files=files) + +def mass_send_msg_to_admins(message: str, type: str = "text"): + """ + 群发消息给所有管理员 + :param message: 消息内容 + """ + if len(config.admin_list) == 0: + logger.warning("管理员列表为空") + else: + mass_send_msg(config.admin_list, message, type=type) + if len(config.admin_group_list) == 0: + logger.warning("管理员群列表为空") + else: + mass_send_msg(config.admin_group_list, message, is_group=True, type=type) + + +def mass_send_msg_to_github_webhook_receivers(message: str): + """ + 群发消息给所有 GitHub Webhook 接收者 + :param message: 消息内容 + """ + if len(config.github_webhook_receiver_list) == 0: + logger.warning("GitHub Webhook 接收者列表为空") + else: + mass_send_msg(config.github_webhook_receiver_list, message, type="text") + if len(config.github_webhook_receive_group_list) == 0: + logger.warning("GitHub Webhook 接收群列表为空") + else: + mass_send_msg( + config.github_webhook_receive_group_list, + message, + is_group=True, + type="text", + ) diff --git a/wechatter/utils/http_request.py b/wechatter/utils/http_request.py index 8468964..90954fc 100644 --- a/wechatter/utils/http_request.py +++ b/wechatter/utils/http_request.py @@ -25,22 +25,22 @@ def get_request( response.encoding = "utf-8" response.raise_for_status() # 如果响应状态码不是 200,就主动抛出异常 except requests.ConnectionError as e: - logger.error(f"请求 {url} 失败,连接错误:{e}") + logger.error(f"请求 {url} 失败,连接错误:{str(e)}") raise except requests.HTTPError as e: - logger.error(f"请求 {url} 失败,HTTP错误:{e}") + logger.error(f"请求 {url} 失败,HTTP错误:{str(e)}") raise except requests.Timeout as e: - logger.error(f"请求 {url} 失败,请求超时:{e}") + logger.error(f"请求 {url} 失败,请求超时:{str(e)}") raise except requests.URLRequired as e: - logger.error(f"请求 {url} 失败,无效的URL:{e}") + logger.error(f"请求 {url} 失败,无效的URL:{str(e)}") raise except requests.TooManyRedirects as e: - logger.error(f"请求 {url} 失败,重定向次数过多:{e}") + logger.error(f"请求 {url} 失败,重定向次数过多:{str(e)}") raise except Exception as e: - logger.error(f"请求 {url} 失败,未知错误:{e}") + logger.error(f"请求 {url} 失败,未知错误:{str(e)}") raise else: return response @@ -83,22 +83,22 @@ def post_request( ) response.raise_for_status() except requests.ConnectionError as e: - logger.error(f"请求 {url} 失败,连接错误:{e}") + logger.error(f"请求 {url} 失败,连接错误:{str(e)}") raise except requests.HTTPError as e: - logger.error(f"请求 {url} 失败,HTTP错误:{e}") + logger.error(f"请求 {url} 失败,HTTP错误:{str(e)}") raise except requests.Timeout as e: - logger.error(f"请求 {url} 失败,请求超时:{e}") + logger.error(f"请求 {url} 失败,请求超时:{str(e)}") raise except requests.URLRequired as e: - logger.error(f"请求 {url} 失败,无效的URL:{e}") + logger.error(f"请求 {url} 失败,无效的URL:{str(e)}") raise except requests.TooManyRedirects as e: - logger.error(f"请求 {url} 失败,重定向次数过多:{e}") + logger.error(f"请求 {url} 失败,重定向次数过多:{str(e)}") raise except Exception as e: - logger.error(f"请求 {url} 失败,未知错误:{e}") + logger.error(f"请求 {url} 失败,未知错误:{str(e)}") raise else: response.encoding = "utf-8" diff --git a/wechatter/utils/json_manager.py b/wechatter/utils/json_manager.py index 5d3b3f9..8faf678 100644 --- a/wechatter/utils/json_manager.py +++ b/wechatter/utils/json_manager.py @@ -15,8 +15,8 @@ def save_json(file_path, data): logger.error(f"写入文件 {file_path} 时权限被拒绝。") raise PermissionError(f"写入文件 {file_path} 时权限被拒绝。") except Exception as e: - logger.error(f"保存 JSON 数据时发生未知错误: {e}") - raise Exception(f"保存 JSON 数据时发生未知错误: {e}") + logger.error(f"保存 JSON 数据时发生未知错误: {str(e)}") + raise Exception(f"保存 JSON 数据时发生未知错误: {str(e)}") def load_json(file_path) -> Dict: @@ -33,5 +33,5 @@ def load_json(file_path) -> Dict: logger.error(f"文件 {file_path} 中的 JSON 数据无效。") raise json.JSONDecodeError(f"文件 {file_path} 中的 JSON 数据无效。") except Exception as e: - logger.error(f"加载 JSON 数据时发生未知错误: {e}") - raise Exception(f"加载 JSON 数据时发生未知错误: {e}") + logger.error(f"加载 JSON 数据时发生未知错误: {str(e)}") + raise Exception(f"加载 JSON 数据时发生未知错误: {str(e)}") diff --git a/wechatter/webhook_handlers/github/create_handler.py b/wechatter/webhook_handlers/github/create_handler.py index 3a5015e..55035c8 100644 --- a/wechatter/webhook_handlers/github/create_handler.py +++ b/wechatter/webhook_handlers/github/create_handler.py @@ -1,7 +1,7 @@ from loguru import logger from wechatter.models.github import GithubCreateWebhook -from wechatter.sender import Sender +from wechatter.sender import sender from wechatter.webhook_handlers.hanlders import github_webhook_handler @@ -18,7 +18,7 @@ def handle_create(data: dict): f"🧑‍💻 创建者:{payload.sender.login}\n" f"🔗 查看详情:{payload.repository.html_url}" ) - Sender.send_msg_to_github_webhook_receivers(message) + sender.send_msg_to_github_webhook_receivers(message) elif payload.ref_type == "tag": logger.info(f"Tag {payload.ref} was created by {payload.sender.login}.") message = ( @@ -29,4 +29,4 @@ def handle_create(data: dict): f"🧑‍💻 创建者:{payload.sender.login}\n" f"🔗 查看详情:{payload.repository.html_url}" ) - Sender.send_msg_to_github_webhook_receivers(message) + sender.send_msg_to_github_webhook_receivers(message) diff --git a/wechatter/webhook_handlers/github/delete_handler.py b/wechatter/webhook_handlers/github/delete_handler.py index d2e2b78..736c8f7 100644 --- a/wechatter/webhook_handlers/github/delete_handler.py +++ b/wechatter/webhook_handlers/github/delete_handler.py @@ -1,7 +1,7 @@ from loguru import logger from wechatter.models.github import GithubDeleteWebhook -from wechatter.sender import Sender +from wechatter.sender import sender from wechatter.webhook_handlers.hanlders import github_webhook_handler @@ -18,7 +18,7 @@ def handle_delete(data: dict): f"🧑‍💻 创建者:{payload.sender.login}\n" f"🔗 查看详情:{payload.repository.html_url}" ) - Sender.send_msg_to_github_webhook_receivers(message) + sender.send_msg_to_github_webhook_receivers(message) elif payload.ref_type == "tag": logger.info(f"Tag {payload.ref} was deleted by {payload.sender.login}") message = ( @@ -29,4 +29,4 @@ def handle_delete(data: dict): f"🧑‍💻 创建者:{payload.sender.login}\n" f"🔗 查看详情:{payload.repository.html_url}" ) - Sender.send_msg_to_github_webhook_receivers(message) + sender.send_msg_to_github_webhook_receivers(message) diff --git a/wechatter/webhook_handlers/github/fork_handler.py b/wechatter/webhook_handlers/github/fork_handler.py index b6dac20..77d4c3a 100644 --- a/wechatter/webhook_handlers/github/fork_handler.py +++ b/wechatter/webhook_handlers/github/fork_handler.py @@ -1,7 +1,7 @@ from loguru import logger from wechatter.models.github import GithubForkWebhook -from wechatter.sender import Sender +from wechatter.sender import sender from wechatter.webhook_handlers.hanlders import github_webhook_handler @@ -15,4 +15,4 @@ def handle_fork(data: dict): "==== GitHub Fork 事件 ====\n" f"🍴 {payload.repository.full_name} 有新的 Fork!🆙\n" ) - Sender.send_msg_to_github_webhook_receivers(message) + sender.send_msg_to_github_webhook_receivers(message) diff --git a/wechatter/webhook_handlers/github/issue_comment_handler.py b/wechatter/webhook_handlers/github/issue_comment_handler.py index e1ab556..deb8285 100644 --- a/wechatter/webhook_handlers/github/issue_comment_handler.py +++ b/wechatter/webhook_handlers/github/issue_comment_handler.py @@ -1,7 +1,7 @@ from loguru import logger from wechatter.models.github import GithubIssueCommentWebhook -from wechatter.sender.sender import Sender +from wechatter.sender import sender from wechatter.webhook_handlers.hanlders import github_webhook_handler @@ -25,4 +25,4 @@ def handle_issue_comment(data: dict): f"🧑‍💻 创建者:{payload.issue.user.login}\n" f"🔗 查看详情:{payload.issue.html_url}" ) - Sender.send_msg_to_github_webhook_receivers(message) + sender.send_msg_to_github_webhook_receivers(message) diff --git a/wechatter/webhook_handlers/github/issue_handler.py b/wechatter/webhook_handlers/github/issue_handler.py index e75ac1a..94a3e7d 100644 --- a/wechatter/webhook_handlers/github/issue_handler.py +++ b/wechatter/webhook_handlers/github/issue_handler.py @@ -1,7 +1,7 @@ from loguru import logger from wechatter.models.github import GithubIssueWebhook -from wechatter.sender.sender import Sender +from wechatter.sender import sender from wechatter.webhook_handlers.hanlders import github_webhook_handler @@ -19,4 +19,4 @@ def handle_issue(data: dict): f"🧑‍💻 创建者:{payload.issue.user.login}\n" f"🔗 查看详情:{payload.issue.html_url}" ) - Sender.send_msg_to_github_webhook_receivers(message) + sender.send_msg_to_github_webhook_receivers(message) diff --git a/wechatter/webhook_handlers/github/ping_handler.py b/wechatter/webhook_handlers/github/ping_handler.py index 7c3b0df..3b5a476 100644 --- a/wechatter/webhook_handlers/github/ping_handler.py +++ b/wechatter/webhook_handlers/github/ping_handler.py @@ -1,7 +1,7 @@ from loguru import logger from wechatter.models.github import GithubPingWebhook -from wechatter.sender import Sender +from wechatter.sender import sender from wechatter.webhook_handlers.hanlders import github_webhook_handler @@ -16,4 +16,4 @@ def handle_ping(data: dict): f"📚 仓库:{payload.repository.full_name}\n" f"🧑‍💻 触发者:{payload.sender.login}\n" ) - Sender.send_msg_to_github_webhook_receivers(message) + sender.send_msg_to_github_webhook_receivers(message) diff --git a/wechatter/webhook_handlers/github/pr_handler.py b/wechatter/webhook_handlers/github/pr_handler.py index 4f0241d..7f4ae0c 100644 --- a/wechatter/webhook_handlers/github/pr_handler.py +++ b/wechatter/webhook_handlers/github/pr_handler.py @@ -1,7 +1,7 @@ from loguru import logger from wechatter.models.github import GithubPrWebhook -from wechatter.sender import Sender +from wechatter.sender import sender from wechatter.webhook_handlers.hanlders import github_webhook_handler @@ -22,7 +22,7 @@ def handle_pr(data: dict): f"🔀 '{payload.pull_request.base.ref}' ⬅ '{payload.pull_request.head.ref}'\n" f"🔗 查看详情:{payload.pull_request.html_url}" ) - Sender.send_msg_to_github_webhook_receivers(message) + sender.send_msg_to_github_webhook_receivers(message) return message = ( "==== GitHub Pull Request 事件 ====\n" @@ -33,4 +33,4 @@ def handle_pr(data: dict): f"🧑‍💻 创建者:{payload.pull_request.user.login}\n" f"🔗 查看详情:{payload.pull_request.html_url}" ) - Sender.send_msg_to_github_webhook_receivers(message) + sender.send_msg_to_github_webhook_receivers(message) diff --git a/wechatter/webhook_handlers/github/pr_review_handler.py b/wechatter/webhook_handlers/github/pr_review_handler.py index 4665ca7..7bdb684 100644 --- a/wechatter/webhook_handlers/github/pr_review_handler.py +++ b/wechatter/webhook_handlers/github/pr_review_handler.py @@ -1,7 +1,7 @@ from loguru import logger from wechatter.models.github import GithubPrReviewWebhook -from wechatter.sender import Sender +from wechatter.sender import sender from wechatter.webhook_handlers.hanlders import github_webhook_handler @@ -19,4 +19,4 @@ def handle_pr_review(data: dict): f"🧑‍💻 创建者:{payload.pull_request.user.login}\n" f"🔗 查看详情:{payload.pull_request.html_url}" ) - Sender.send_msg_to_github_webhook_receivers(message) + sender.send_msg_to_github_webhook_receivers(message) diff --git a/wechatter/webhook_handlers/github/push_handler.py b/wechatter/webhook_handlers/github/push_handler.py index 5c7814a..8928f53 100644 --- a/wechatter/webhook_handlers/github/push_handler.py +++ b/wechatter/webhook_handlers/github/push_handler.py @@ -1,7 +1,7 @@ from loguru import logger from wechatter.models.github import GithubPushWebhook -from wechatter.sender import Sender +from wechatter.sender import sender from wechatter.webhook_handlers.hanlders import github_webhook_handler @@ -26,4 +26,4 @@ def handle_push(data: dict): message += f"📃 提交信息:{payload.commits.pop().message}\n" message += f"🔗 查看详情:{branch_url}" - Sender.send_msg_to_github_webhook_receivers(message) + sender.send_msg_to_github_webhook_receivers(message) diff --git a/wechatter/webhook_handlers/github/star_handler.py b/wechatter/webhook_handlers/github/star_handler.py index d81029c..76c7cf6 100644 --- a/wechatter/webhook_handlers/github/star_handler.py +++ b/wechatter/webhook_handlers/github/star_handler.py @@ -1,7 +1,7 @@ from loguru import logger from wechatter.models.github import GithubStarWebhook -from wechatter.sender import Sender +from wechatter.sender import sender from wechatter.webhook_handlers.hanlders import github_webhook_handler @@ -14,10 +14,10 @@ def handle_star(data: dict): "==== GitHub Star 事件 ====\n" f"⭐️ {payload.repository.full_name} 的 Star 数量 +1 🆙!\n" ) - Sender.send_msg_to_github_webhook_receivers(message) + sender.send_msg_to_github_webhook_receivers(message) else: message = ( "==== GitHub Star 事件 ====\n" f"⭐️ {payload.repository.full_name} 的 Star 数量 -1 🔽!\n" ) - Sender.send_msg_to_github_webhook_receivers(message) + sender.send_msg_to_github_webhook_receivers(message)