735 lines
27 KiB
Python
735 lines
27 KiB
Python
import datetime
|
||
import json
|
||
import os.path
|
||
from typing import List, Tuple
|
||
import logging
|
||
import os
|
||
import threading
|
||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||
|
||
import requests
|
||
from requests.adapters import HTTPAdapter
|
||
from urllib3.util.retry import Retry
|
||
import pytz
|
||
|
||
from tools import DatabaseManager
|
||
from file import ObjectFileManager, LocalFileManager
|
||
|
||
TZ = pytz.timezone('Asia/Shanghai')
|
||
|
||
|
||
def convert_to_utc(time_str, time_format='%Y-%m-%d %H:%M:%S', source_timezone=None):
|
||
# 解析时间字符串为datetime对象
|
||
local_dt = datetime.datetime.strptime(time_str, time_format)
|
||
|
||
# 如果指定了源时区,则将本地时间转换为UTC时间
|
||
if source_timezone:
|
||
tz = pytz.timezone(source_timezone)
|
||
local_dt = tz.localize(local_dt)
|
||
utc_dt = local_dt.astimezone(pytz.UTC)
|
||
else:
|
||
# 如果没有指定源时区,则假设时间字符串已经是UTC时间
|
||
utc_dt = pytz.UTC.localize(local_dt)
|
||
|
||
return utc_dt
|
||
|
||
|
||
class AIAgent:
|
||
"""AI代理类,整合了各种AI功能"""
|
||
|
||
def __init__(self, deepseek_api_key: str = "sk-c2f04b4d635b4df0aace0432f3c6d6f4",
|
||
max_retries: int = 3, timeout: int = 30):
|
||
"""初始化AI助手
|
||
|
||
Args:
|
||
deepseek_api_key: DeepSeek API密钥
|
||
max_retries: 最大重试次数
|
||
timeout: 请求超时时间(秒)
|
||
"""
|
||
self.deepseek_api_key = deepseek_api_key
|
||
self.timeout = timeout
|
||
|
||
# 配置日志
|
||
self._setup_logging()
|
||
|
||
# 创建带重试机制的会话
|
||
self.session = self._create_session(max_retries)
|
||
|
||
# 配置常量
|
||
self.BASE_REPORT_URL = "https://a.cmdp.cn/basiceg/v1/json/tpldev"
|
||
self.CACHE_DIR = "cache"
|
||
|
||
def _setup_logging(self):
|
||
"""配置日志"""
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||
)
|
||
self.logger = logging.getLogger(__name__)
|
||
|
||
@staticmethod
|
||
def _create_session(max_retries: int) -> requests.Session:
|
||
"""创建带重试机制的会话
|
||
|
||
Args:
|
||
max_retries: 最大重试次数
|
||
|
||
Returns:
|
||
配置好的会话对象
|
||
"""
|
||
session = requests.Session()
|
||
|
||
# 配置重试策略
|
||
retry_strategy = Retry(
|
||
total=max_retries,
|
||
status_forcelist=[429, 500, 502, 503, 504],
|
||
backoff_factor=1
|
||
)
|
||
|
||
# 为HTTP和HTTPS适配器添加重试机制
|
||
adapter = HTTPAdapter(max_retries=retry_strategy)
|
||
session.mount("http://", adapter)
|
||
session.mount("https://", adapter)
|
||
|
||
return session
|
||
|
||
def _make_request(self, method: str, url: str, **kwargs):
|
||
"""发送HTTP请求(带错误处理)
|
||
|
||
Args:
|
||
method: HTTP方法
|
||
url: 请求URL
|
||
**kwargs: 其他请求参数
|
||
|
||
Returns:
|
||
JSON响应或None
|
||
"""
|
||
try:
|
||
# 设置默认超时
|
||
if 'timeout' not in kwargs:
|
||
kwargs['timeout'] = self.timeout
|
||
|
||
response = self.session.request(method, url, **kwargs)
|
||
response.raise_for_status()
|
||
|
||
self.logger.info(f"请求成功: {url}")
|
||
return response.json()
|
||
|
||
except requests.exceptions.RequestException as e:
|
||
self.logger.error(f"请求失败: {url} - {str(e)}")
|
||
return {}
|
||
except json.JSONDecodeError as e:
|
||
self.logger.error(f"JSON解析失败: {url} - {str(e)}")
|
||
return {}
|
||
|
||
def chat_deepseek(self, message: str, role: str = "", model: str = "deepseek-chat") -> str:
|
||
"""与DeepSeek聊天
|
||
|
||
Args:
|
||
message: 用户消息
|
||
role: 系统角色
|
||
model: 使用的模型
|
||
|
||
Returns:
|
||
AI回复内容
|
||
"""
|
||
url = "https://api.deepseek.com/chat/completions"
|
||
headers = {
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {self.deepseek_api_key}"
|
||
}
|
||
|
||
messages = []
|
||
if role:
|
||
messages.append({"role": "system", "content": role})
|
||
messages.append({"role": "user", "content": message})
|
||
|
||
body = {
|
||
"model": model,
|
||
"messages": messages,
|
||
"stream": False
|
||
}
|
||
|
||
result = self._make_request("POST", url, json=body, headers=headers)
|
||
if result and "choices" in result:
|
||
return result.get('choices', [{}])[0]['message']['content']
|
||
|
||
self.logger.warning("DeepSeek API调用失败或返回空结果")
|
||
return ""
|
||
|
||
|
||
agent = AIAgent(max_retries=1, timeout=180)
|
||
|
||
|
||
class ChatSessionManager:
|
||
"""聊天会话管理器类"""
|
||
|
||
def __init__(self, db_manager: DatabaseManager):
|
||
"""
|
||
初始化聊天会话管理器
|
||
|
||
Args:
|
||
db_manager: 数据库管理工具
|
||
"""
|
||
self.files_dir = os.path.join("", "files")
|
||
self.messages_dir = os.path.join("", "messages")
|
||
self.cache_path = None
|
||
# 初始化文件管理器
|
||
self.obj = ObjectFileManager()
|
||
self.lfm = LocalFileManager(self.files_dir)
|
||
self.dbm = db_manager
|
||
self.users = []
|
||
self.groups = []
|
||
self.members = {}
|
||
self.messages = []
|
||
self.files = {}
|
||
|
||
# 确保目录存在
|
||
os.makedirs(self.files_dir, exist_ok=True)
|
||
os.makedirs(self.messages_dir, exist_ok=True)
|
||
|
||
def execute_query(self, query: str, params=None, fetch_all=True):
|
||
result = []
|
||
if fetch_all:
|
||
rows = self.dbm.execute_query(query, params)
|
||
for row in rows:
|
||
result.append(list(row))
|
||
else:
|
||
rows = self.dbm.execute_query(query, params, False)
|
||
result = list(rows)
|
||
return result
|
||
|
||
def execute_event(self, query: str, params=None):
|
||
return self.dbm.execute_event(query, params)
|
||
|
||
def status(self):
|
||
|
||
row = self.dbm.execute_query("SELECT datetime FROM information order by datetime desc limit 1", fetch_all=False)
|
||
|
||
if row:
|
||
return row[0]
|
||
else:
|
||
return None
|
||
|
||
def _download_file(self, file_info, files_dir, history, lock):
|
||
"""
|
||
下载单个文件的线程函数
|
||
|
||
Args:
|
||
file_info: 文件信息
|
||
files_dir: 文件保存目录
|
||
history: 聊天历史字典
|
||
lock: 线程锁
|
||
"""
|
||
try:
|
||
path = file_info["path"].replace("s3://", "")
|
||
file_content = self.obj.get_file(path)
|
||
|
||
if path.endswith(".json"):
|
||
data = json.loads(file_content)
|
||
with lock:
|
||
history[data["msgid"]] = data
|
||
else:
|
||
# 保存非JSON文件
|
||
filename = path.split("/")[-1]
|
||
with open(os.path.join(files_dir, filename), "wb") as f:
|
||
f.write(file_content)
|
||
|
||
return path, True
|
||
except Exception as e:
|
||
return path, False
|
||
|
||
def download_chat_history(self, year: int, month: int, day: int, hour: int, max_workers: int = 5) -> bool:
|
||
"""
|
||
下载指定时间的聊天历史(多线程版本)
|
||
|
||
Args:
|
||
year: 年份
|
||
month: 月份
|
||
day: 日期
|
||
hour: 小时
|
||
max_workers: 最大线程数
|
||
|
||
Returns:
|
||
下载是否成功
|
||
"""
|
||
message_history_name = f"{year}_{month:02d}_{day:02d}_{hour:02d}.json"
|
||
print(f"Downloading chat history for {year}-{month:02d}-{day:02d}-{hour:02d} (多线程模式)")
|
||
|
||
try:
|
||
file_list = self.obj.get_file_list(f"zysl-log/wework/chat_msg/{year}/{month}/{day}/{hour}/")
|
||
print(f"共计{len(file_list)}个文件,使用{max_workers}个线程下载")
|
||
history = {}
|
||
failed_files = []
|
||
if file_list:
|
||
lock = threading.Lock()
|
||
|
||
# 使用线程池下载文件
|
||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||
# 提交所有下载任务
|
||
future_to_file = {
|
||
executor.submit(self._download_file, file_info, self.files_dir, history, lock): file_info
|
||
for file_info in file_list
|
||
}
|
||
|
||
# 处理完成的任务
|
||
completed_count = 0
|
||
total_count = len(file_list)
|
||
|
||
for future in as_completed(future_to_file):
|
||
file_info = future_to_file[future]
|
||
completed_count += 1
|
||
|
||
try:
|
||
path, success = future.result()
|
||
if success:
|
||
print(f"[{completed_count}/{total_count}] 下载成功: {path}")
|
||
else:
|
||
print(f"[{completed_count}/{total_count}] 下载失败: {path}")
|
||
failed_files.append(path)
|
||
except Exception as e:
|
||
print(f"[{completed_count}/{total_count}] 处理异常: {file_info['path']} - {str(e)}")
|
||
failed_files.append(file_info['path'])
|
||
|
||
# 检查是否有失败的文件
|
||
if failed_files:
|
||
print(f"警告: {len(failed_files)}个文件下载失败")
|
||
print("失败文件列表:", failed_files)
|
||
|
||
# 保存聊天历史JSON文件
|
||
output_path = os.path.join(self.messages_dir, message_history_name)
|
||
with open(output_path, "w", encoding="utf-8") as f:
|
||
json.dump(history, f, ensure_ascii=False, indent=2)
|
||
|
||
print(f"Downloading chat history for {year}-{month:02d}-{day:02d}-{hour:02d} complete")
|
||
print(f"成功下载: {len(file_list) - len(failed_files)}/{len(file_list)} 个文件")
|
||
|
||
# 如果有失败的文件,返回False
|
||
return len(failed_files) == 0
|
||
|
||
except Exception as e:
|
||
error_file = os.path.join(self.messages_dir, f"{message_history_name}.unfinished")
|
||
with open(error_file, "w", encoding="utf-8") as f:
|
||
f.write(str(e))
|
||
print(
|
||
f"Downloading chat history for {year}-{month:02d}-{day:02d}-{hour:02d} failed: please check the {message_history_name}.unfinished")
|
||
return False
|
||
|
||
def _parse_message(self, data, is_mixed: bool = False, idx: int = 0):
|
||
msg_type = data["msgtype"]
|
||
if msg_type == "text":
|
||
content = data['text']["content"]
|
||
|
||
elif msg_type in ["file", "image", "video", "voice", "emotion", "meeting_voice_call"]:
|
||
file_data = data[msg_type]
|
||
suffixs = [f"_{idx}", ".png", ".jpg", ".gif", ".mp4", "_.png", "_.jpg", "_.gif", "_.mp4",
|
||
("_" + file_data["filename"]) if msg_type == "file" else ""]
|
||
file_path = ""
|
||
for suffix in suffixs:
|
||
temp_path = f"{data['msgid']}_mixed{suffix}" if is_mixed else f"{data['msgid']}_{msg_type}{suffix}"
|
||
if self.lfm.exists(temp_path):
|
||
file_path = temp_path
|
||
break
|
||
if file_path != "":
|
||
ext_mapping = {
|
||
"file": (file_data.get('fileext', ''), "filesize"),
|
||
"image": ("png", file_data.get("filesize", 0)),
|
||
"video": ("mp4", file_data.get("filesize", 0)),
|
||
"voice": ("wav", file_data.get("voice_size", 0)),
|
||
"emotion": ("gif" if file_data.get("type", 0) == 1 else "png", file_data.get("imagesize", 0)),
|
||
"meeting_voice_call": ("wav", self.lfm.get_size(file_path)),
|
||
}
|
||
file_info = {
|
||
"id": file_data.get("md5sum", self.lfm.get_md5(file_path)),
|
||
"size": ext_mapping[msg_type][1],
|
||
"extension": ext_mapping[msg_type][0],
|
||
"content": file_path,
|
||
"original": self.cache_path + file_path,
|
||
"type": msg_type
|
||
}
|
||
self.files[file_info["id"]] = file_info
|
||
content = file_info["id"]
|
||
else:
|
||
file_path = f"{data['msgid']}_{msg_type}_0"
|
||
file_info = {
|
||
"id": file_data.get("md5sum", file_path),
|
||
"size": file_data.get('filesize', 0),
|
||
"extension": file_data.get('fileext', "unknown"),
|
||
"content": file_path,
|
||
"original": self.cache_path + file_path,
|
||
"type": msg_type
|
||
}
|
||
self.files[file_info["id"]] = file_info
|
||
content = file_info["id"]
|
||
elif msg_type == "link":
|
||
data = data[msg_type]
|
||
content = f'{data["description"]}{data["title"]}({data["link_url"]})'
|
||
else:
|
||
print(f"未知消息类型: {msg_type}")
|
||
content = json.dumps(data, ensure_ascii=False)
|
||
return content
|
||
|
||
def _parse_mixed_message(self, param, idx: int):
|
||
tp = param["msgtype"]
|
||
content = self._parse_message(param, True, idx)
|
||
|
||
if tp in ["file", "image", "video", "voice", "emotion", "meeting_voice_call"]:
|
||
self.files[content]["content"] = f'{param["msgid"]}_mixed_{idx}'
|
||
idx += 1
|
||
return idx, content
|
||
|
||
def parse_chat_history(self, year: int, month: int, day: int, hour: int):
|
||
"""
|
||
解析聊天历史数据
|
||
|
||
Args:
|
||
year: 年份
|
||
month: 月份
|
||
day: 日期
|
||
hour: 小时
|
||
|
||
Returns:
|
||
(用户列表, 群组列表, 成员映射, 消息列表, 文件映射)
|
||
"""
|
||
message_history_name = f"{year}_{month:02d}_{day:02d}_{hour:02d}.json"
|
||
history_path = os.path.join(self.messages_dir, message_history_name)
|
||
|
||
# 如果文件不存在,先下载
|
||
if not os.path.exists(history_path):
|
||
self.download_chat_history(year, month, day, hour,10)
|
||
|
||
with open(history_path, "r", encoding="utf-8") as f:
|
||
message_history = json.load(f)
|
||
|
||
try:
|
||
|
||
for message_data in message_history.values():
|
||
# 只处理发送的消息
|
||
if message_data.get("action") != "send":
|
||
continue
|
||
|
||
# 提取基本信息
|
||
from_user = message_data["from"]
|
||
to_users = message_data["tolist"]
|
||
room_id = message_data.get("roomid", "")
|
||
|
||
# 更新用户列表
|
||
self.users.extend([from_user] + to_users)
|
||
self.users = list(set(self.users))
|
||
|
||
# 确定接收者类型
|
||
to_type = 1 if room_id else 0
|
||
receiver = room_id if to_type == 1 else to_users[0]
|
||
|
||
# 处理群组信息
|
||
if room_id:
|
||
if room_id not in self.members:
|
||
self.members[room_id] = to_users + [from_user]
|
||
else:
|
||
self.members[room_id].extend(to_users + [from_user])
|
||
self.members[room_id] = list(set(self.members[room_id]))
|
||
|
||
if room_id not in self.groups:
|
||
self.groups.append(room_id)
|
||
|
||
# 构建消息对象
|
||
message = {
|
||
"id": message_data["msgid"],
|
||
"type": message_data["msgtype"],
|
||
"time": message_data["msgtime"],
|
||
"toType": to_type,
|
||
"from": from_user,
|
||
"to": receiver
|
||
}
|
||
|
||
# 根据消息类型处理内容
|
||
msg_type = message_data["msgtype"]
|
||
if msg_type == "mixed":
|
||
temp = []
|
||
idx = 0
|
||
for t in message_data["mixed"]["item"]:
|
||
idx, content = self._parse_mixed_message({"msgid": message_data["msgid"],
|
||
"msgtype": t["type"],
|
||
t["type"]: json.loads(t["content"])}, idx)
|
||
temp.append([t["type"], content])
|
||
message["content"] = json.dumps(temp, ensure_ascii=False)
|
||
else:
|
||
message["content"] = self._parse_message(message_data)
|
||
self.messages.append(message)
|
||
|
||
|
||
except Exception as e:
|
||
print(f"解析聊天记录失败: {e}")
|
||
raise e
|
||
|
||
def save_to_sqlite(self, sql: str, data: List[Tuple]) -> None:
|
||
"""
|
||
批量保存数据到SQLite数据库
|
||
|
||
Args:
|
||
sql: SQL语句
|
||
data: 数据列表
|
||
"""
|
||
if not data:
|
||
return
|
||
|
||
print(f"开始插入 {len(data)} 条记录")
|
||
self.dbm.execute_event(sql, data)
|
||
print(f"成功插入 {len(data)} 条记录")
|
||
|
||
def save_information_to_sqlite(self, date_time: str, message_num: int, file_num: int, user_num: int,
|
||
group_num: int) -> None:
|
||
"""
|
||
保存会话信息到SQLite数据库
|
||
|
||
Args:
|
||
date_time: 会话时间
|
||
message_num: 消息数量
|
||
file_num: 文件数量
|
||
user_num: 用户数量
|
||
group_num: 群组数量
|
||
"""
|
||
self.save_to_sqlite('''
|
||
INSERT OR IGNORE INTO "information"
|
||
(datetime, message, file, user, "group")
|
||
VALUES (?, ?, ?, ?, ?)
|
||
''', [(date_time, message_num, file_num, user_num, group_num)])
|
||
|
||
def save_all_to_sqlite(self) -> None:
|
||
"""
|
||
保存所有聊天数据到SQLite数据库
|
||
"""
|
||
# 保存文件信息
|
||
file_data = []
|
||
for file_info in self.files.values():
|
||
file_data.append((
|
||
file_info.get('id'),
|
||
file_info.get('type'),
|
||
file_info.get('content'),
|
||
file_info.get('original', ''),
|
||
file_info.get('extension', ''),
|
||
file_info.get('size', 0)
|
||
))
|
||
|
||
self.save_to_sqlite('''
|
||
INSERT OR IGNORE INTO file
|
||
(id, type, content, original, extension, size)
|
||
VALUES (?, ?, ?, ?, ?, ?)
|
||
''', file_data)
|
||
|
||
# 保存用户信息
|
||
user_data = [(user, user) for user in self.users]
|
||
self.save_to_sqlite('''
|
||
INSERT OR IGNORE INTO user
|
||
(id, name)
|
||
VALUES (?, ?)
|
||
''', user_data)
|
||
|
||
# 保存消息信息
|
||
message_data = []
|
||
for message in self.messages:
|
||
message_data.append((
|
||
message.get('id'),
|
||
message.get('type'),
|
||
message.get('time'),
|
||
message.get('toType'),
|
||
message.get('from'),
|
||
message.get('to'),
|
||
message.get('content')
|
||
))
|
||
|
||
self.save_to_sqlite('''
|
||
INSERT OR IGNORE INTO message
|
||
(id, type, time, toType, sender, receiver, content)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||
''', message_data)
|
||
|
||
# 保存群组信息
|
||
group_data = [(group, group) for group in self.groups]
|
||
self.save_to_sqlite('''
|
||
INSERT OR IGNORE INTO "group"
|
||
(id, name)
|
||
VALUES (?, ?)
|
||
''', group_data)
|
||
|
||
# 保存成员信息
|
||
member_data = []
|
||
for group_id, group_users in self.members.items():
|
||
for user in group_users:
|
||
member_data.append((group_id, user))
|
||
|
||
self.save_to_sqlite('''
|
||
INSERT OR IGNORE INTO "member"
|
||
(groupId, userId)
|
||
VALUES (?, ?)
|
||
''', member_data)
|
||
|
||
def process_chat_session(self, year: int, month: int, day: int, hour: int) -> bool:
|
||
"""
|
||
处理完整的聊天会话流程
|
||
|
||
Args:
|
||
year: 年份
|
||
month: 月份
|
||
day: 日期
|
||
hour: 小时
|
||
|
||
Returns:
|
||
处理是否成功
|
||
"""
|
||
self.users = []
|
||
self.groups = []
|
||
self.members = {}
|
||
self.messages = []
|
||
self.files = {}
|
||
try:
|
||
self.cache_path = f"zysl-log/wework/chat_msg/{year}/{month}/{day}/{hour}/"
|
||
# 解析聊天历史
|
||
self.parse_chat_history(year, month, day, hour)
|
||
|
||
# 保存到数据库
|
||
self.save_all_to_sqlite()
|
||
self.save_information_to_sqlite(f"{year}-{month:02d}-{day:02d}-{hour:02d}", len(self.messages),
|
||
len(self.files), len(self.users), len(self.groups))
|
||
print(f"成功处理 {year}-{month:02d}-{day:02d}-{hour:02d} 的聊天会话")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"处理聊天会话失败: {e}")
|
||
return False
|
||
|
||
def get_chat_history(self, start_time_str: str, end_time_str: str) -> list:
|
||
"""
|
||
获取指定时间的聊天历史记录
|
||
|
||
Args:
|
||
start_time_str: 开始时间,格式为 "YYYY-MM-DD HH:MM:SS"
|
||
end_time_str: 结束时间,格式为 "YYYY-MM-DD HH:MM:SS"
|
||
|
||
Returns:
|
||
聊天历史记录列表
|
||
"""
|
||
start_time = convert_to_utc(start_time_str, source_timezone="Asia/Shanghai").timestamp()
|
||
end_time = convert_to_utc(end_time_str, source_timezone="Asia/Shanghai").timestamp()
|
||
|
||
res = self.dbm.execute_query('''
|
||
SELECT message.time, message.content
|
||
FROM message
|
||
WHERE (message.type = 'text'
|
||
or message.type = 'mixed')
|
||
AND message.time >= ?
|
||
AND message.time <= ?
|
||
''', (str(start_time).replace(".0", "000"), str(end_time).replace(".0", "000")))
|
||
return res
|
||
|
||
@staticmethod
|
||
def analyze_qa_content(group_messages) -> dict:
|
||
group_qa = {}
|
||
for group_id in group_messages:
|
||
print(f"群ID: {group_id}")
|
||
print(f"消息数量: {len(group_messages[group_id])}")
|
||
content = ""
|
||
if len(group_messages[group_id]) <= 2:
|
||
continue
|
||
else:
|
||
message_mapping = {}
|
||
for msg in sorted(group_messages[group_id], key=lambda i: i[1]):
|
||
message_mapping[msg[4]] = msg[2]
|
||
content += json.dumps({
|
||
"user": msg[1],
|
||
"time": msg[2],
|
||
"content": msg[3],
|
||
"message_id": msg[4]
|
||
}, ensure_ascii=False) + ",\n"
|
||
|
||
template = f"""
|
||
以下是群中的消息记录
|
||
|
||
```json
|
||
[
|
||
{content}
|
||
]
|
||
```
|
||
""" + """
|
||
找到其中的问题和回答,如果没有回答就只整理问题,问题是与产品填报系统,上市公司,证券政策相关的问题
|
||
问题可能不只由一句话构成,回答也可能不只由一句话构成
|
||
多个问题需要分开放置
|
||
不需要任何解释说明只返回json格式
|
||
输出格式
|
||
|
||
```json
|
||
[
|
||
{
|
||
"question": ["message_id",...],
|
||
"answer": ["message_id",...]
|
||
}
|
||
]
|
||
```
|
||
"""
|
||
answer = agent.chat_deepseek(template)
|
||
answer = answer.replace("```json", "").replace("```", "") if "```json" in answer else answer
|
||
try:
|
||
answer = json.loads(answer)
|
||
except:
|
||
print(answer)
|
||
answer = []
|
||
for ans in answer:
|
||
time_list = [message_mapping.get(a, "") for a in ans["answer"]] + [message_mapping.get(q, "") for q
|
||
in ans["question"]]
|
||
start_time = min(time_list)
|
||
end_time = max(time_list)
|
||
ans["start_time"] = start_time
|
||
ans["end_time"] = end_time
|
||
group_qa[group_id] = answer
|
||
return group_qa
|
||
|
||
def save_qa_to_sqlite(self, group_qa: dict):
|
||
"""
|
||
将问答数据保存到 SQLite 数据库
|
||
|
||
Args:
|
||
group_qa: 问答数据字典,键为群ID,值为问答列表
|
||
"""
|
||
data = []
|
||
index_id = self.execute_query(f"SELECT id FROM qa order by id desc limit 1", fetch_all=False)[0]
|
||
|
||
for r in group_qa:
|
||
qas = group_qa.get(r, {})
|
||
for qa in qas:
|
||
Q = qa["question"]
|
||
A = qa["answer"]
|
||
index_id += 1
|
||
data.append((index_id, r, json.dumps(Q, ensure_ascii=False), json.dumps(A, ensure_ascii=False),
|
||
qa["start_time"],
|
||
qa["end_time"]))
|
||
self.execute_event('''
|
||
insert into qa (id, groupId, question, answer, start_time, end_time)
|
||
values (?, ?, ?, ?, ?, ?)
|
||
''', data)
|
||
|
||
def update_qa_data(self, start_time_str: str, end_time_str: str):
|
||
"""
|
||
更新指定时间范围内的问答内容
|
||
|
||
Args:
|
||
start_time_str: 开始时间,格式为 "YYYY-MM-DD HH:MM:SS"
|
||
end_time_str: 结束时间,格式为 "YYYY-MM-DD HH:MM:SS"
|
||
"""
|
||
start_time = convert_to_utc(start_time_str, source_timezone="Asia/Shanghai").timestamp()
|
||
end_time = convert_to_utc(end_time_str, source_timezone="Asia/Shanghai").timestamp()
|
||
data = self.execute_query('''
|
||
SELECT receiver, sender, time, content, id
|
||
FROM message
|
||
WHERE message.time >= ?
|
||
AND message.time <= ?
|
||
AND toType = 1
|
||
AND type in ('text', 'mixed', 'chatrecord')
|
||
''', (str(start_time).replace(".0", "000"), str(end_time).replace(".0", "000")))
|
||
group_messages = {}
|
||
for i in data:
|
||
group_id = i[0]
|
||
if group_id not in group_messages:
|
||
group_messages[group_id] = []
|
||
group_messages[group_id].append(i)
|
||
group_qa = self.analyze_qa_content(group_messages)
|
||
self.save_qa_to_sqlite(group_qa) |