Skip to content

Commit

Permalink
Merge pull request #52 from Cassius0924/refactor_send
Browse files Browse the repository at this point in the history
[Refactor] 重构消息发送模块,简化消息发送函数
  • Loading branch information
Cassius0924 authored Feb 5, 2024
2 parents 0e1a54f + 0678033 commit b25f25a
Show file tree
Hide file tree
Showing 48 changed files with 616 additions and 749 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ config.ini
data/

logs/

.sqllsrc.json
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ docker run -d \
--name wxBotWebhook \
-p 3001:3001 \
-e LOGIN_API_TOKEN="<Token>" \
-e RECVD_MSG_API="http://<内网IP>:<接收消息端口>/receive_msg" \
-e RECVD_MSG_API="http(s)://<宿主机IP>:<接收消息端口>/receive_msg" \
dannicool/docker-wechatbot-webhook
```

- `<Token>`:登录令牌(不是密码),自己设置一个好记的。
- `<内网IP>`填入服务器的**内网IP**。如果是在自己电脑,则填入 `127.0.0.1`
- `<接收消息端口>`:设置一个接收消息的端口,此项目中默认为 `4000`
- `<Token>`:登录令牌(可选)
- `<宿主机IP>`填入 Docker 的宿主机地址
- `<接收消息端口>`:设置一个接收消息的端口,默认为 `4000`

3. 登录微信

Expand All @@ -45,7 +45,7 @@ dannicool/docker-wechatbot-webhook
docker logs -f wxBotWebhook
```

### 启动服务器
### 启动 WeChatter

1. 下载源代码

Expand Down
2 changes: 1 addition & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
40 changes: 22 additions & 18 deletions tests/commands/test_food_calories/test_food_calories.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,54 +9,58 @@

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)
with open("tests/commands/test_food_calories/one_food_detail.json") as f:
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")
26 changes: 10 additions & 16 deletions tests/commands/test_gasoline_price/test_gasoline_price.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("广州市")
11 changes: 8 additions & 3 deletions wechatter/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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():
Expand All @@ -28,7 +34,6 @@
async def startup_event():
scheduler.startup()


@app.on_event("shutdown")
async def shutdown_event():
scheduler.shutdown()
2 changes: 1 addition & 1 deletion wechatter/app/routers/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
)
Expand Down
6 changes: 3 additions & 3 deletions wechatter/app/routers/wechat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions wechatter/commands/_commands/bili_hot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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:
Expand Down
39 changes: 17 additions & 22 deletions wechatter/commands/_commands/copilot_gpt4.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 <message>
# 如果没有对话记录,则创建新对话
_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:
Expand All @@ -146,34 +141,34 @@ 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:
wx_id = to.p_id
# 判断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:
Expand Down
8 changes: 4 additions & 4 deletions wechatter/commands/_commands/douyin_hot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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:
Expand Down
Loading

0 comments on commit b25f25a

Please sign in to comment.