133 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
轻量的 QQ Bot 客户端(用于发送私聊文本和媒体)
使用环境变量:
- QQ_BOT_URL: bot 接收地址(例如 http://localhost:30000/send_private_msg
- QQ_BOT_TARGET_ID: 默认目标 QQ id可被函数参数覆盖
示例:
from qq import send_msg, send_media_msg
send_msg('hello')
# 发送图片时将自动转换为 "base64://..." 格式
send_media_msg('figures/btcusd_interval_pred_60m.png', type='image')
"""
from __future__ import annotations
import os
import json
from typing import Optional
import base64
try:
import requests
except Exception: # pragma: no cover - runtime dependency
requests = None
def _bot_url() -> str:
return os.getenv("QQ_BOT_URL", "http://localhost:30000/send_private_msg")
def _default_target() -> Optional[str]:
return os.getenv("QQ_BOT_TARGET_ID")
def _file_to_base64_uri(path: str) -> str:
"""将本地文件读取为 base64 uri 字符串(前缀 base64://)。"""
with open(path, "rb") as f:
data = f.read()
b64 = base64.b64encode(data).decode("ascii")
return f"base64://{b64}"
def send_msg(msg: str, target_id: Optional[str] = None, timeout: float = 8.0):
"""发送文本消息到 QQ 机器人。返回 requests.Response 或 None失败"""
if target_id is None:
target_id = _default_target()
if target_id is None:
raise ValueError("target_id 未提供,且环境变量 QQ_BOT_TARGET_ID 未设置")
payload = {
"user_id": str(target_id),
"message": [
{"type": "text", "data": {"text": str(msg)}}
],
}
url = _bot_url()
print(f"[QQ] 发送文本到 {target_id}: {msg}")
if requests is None:
print("requests 未安装,无法发送消息")
return None
try:
resp = requests.post(url, json=payload, timeout=timeout)
try:
# 非必要:打印返回的简短信息
print(f"[QQ] 返回: {resp.status_code} {resp.text[:200]}")
except Exception:
pass
return resp
except Exception as e:
print(f"[QQ] 发送文本消息出错: {e}")
return None
def send_media_msg(file_path: str, target_id: Optional[str] = None, type: str = "image", timeout: float = 15.0):
"""发送媒体消息image 或 video
- 当 type == "image" 时:读取文件并以 "base64://..." 形式发送。
- 当 type == "video" 时:仍使用 "file://" 本地文件引用(保持原有行为)。
"""
if type not in ("image", "video"):
raise ValueError("type 必须为 'image''video'")
if target_id is None:
target_id = _default_target()
if target_id is None:
raise ValueError("target_id 未提供,且环境变量 QQ_BOT_TARGET_ID 未设置")
# 根据类型构造 file 字段
if type == "image":
try:
file_field = _file_to_base64_uri(file_path)
except Exception as e:
print(f"[QQ] 读取图片失败: {e}")
return None
else: # video
file_field = f"file://{file_path}"
payload = {
"user_id": str(target_id),
"message": [
{"type": type, "data": {"file": file_field}}
],
}
url = _bot_url()
print(f"[QQ] 发送媒体 {type}{target_id}: {file_path}")
if requests is None:
print("requests 未安装,无法发送媒体消息")
return None
try:
resp = requests.post(url, json=payload, timeout=timeout)
try:
print(f"[QQ] 返回: {resp.status_code} {resp.text[:200]}")
except Exception:
pass
return resp
except Exception as e:
print(f"[QQ] 发送媒体消息出错: {e}")
return None
def send_prediction_result(summary: str, png_path: Optional[str] = None, target_id: Optional[str] = None):
"""便捷函数:先发送 summary 文本,再发送 png若提供"""
r1 = send_msg(summary, target_id=target_id)
r2 = None
if png_path:
r2 = send_media_msg(png_path, target_id=target_id, type="image")
return r1, r2