145 lines
5.2 KiB
Python
145 lines
5.2 KiB
Python
from fastapi import APIRouter, HTTPException, Query
|
||
from urllib.parse import quote
|
||
from services.chatlog_client import MessageIndexNotReady, chatlog_client
|
||
from services.message_formatter import extract_quote
|
||
|
||
router = APIRouter(prefix="/api/search", tags=["search"])
|
||
|
||
|
||
@router.get("")
|
||
async def search(
|
||
talker: str = Query(..., description="群/联系人 ID"),
|
||
time: str = Query("", description="时间范围,如 2024-01-01,2024-01-31"),
|
||
sender: str = Query("", description="发送者 ID,可选"),
|
||
keyword: str = Query("", description="关键词,可选"),
|
||
page: int = Query(1, ge=1),
|
||
page_size: int = Query(20, ge=1, le=500),
|
||
):
|
||
"""透传 chatlog /api/v1/chatlog,返回 {"total": N, "items": [...]}"""
|
||
offset = (page - 1) * page_size
|
||
try:
|
||
data = await chatlog_client.get_messages(
|
||
talker,
|
||
time=time,
|
||
sender=sender,
|
||
keyword=keyword,
|
||
limit=page_size,
|
||
offset=offset,
|
||
)
|
||
except MessageIndexNotReady as e:
|
||
raise HTTPException(status_code=503, detail=str(e)) from e
|
||
for item in data.get("items", []) or []:
|
||
contents = item.get("contents") or item.get("Contents") or {}
|
||
if not isinstance(contents, dict):
|
||
contents = {}
|
||
try:
|
||
is_file = int(item.get("type") or item.get("Type") or 0) == 49 and int(
|
||
item.get("subType") or item.get("sub_type") or item.get("SubType") or 0
|
||
) == 6
|
||
except Exception:
|
||
is_file = False
|
||
file_md5 = str(contents.get("md5") or "") if is_file else ""
|
||
item["is_file"] = is_file
|
||
item["file_name"] = (
|
||
contents.get("title") or contents.get("fileName") or contents.get("filename") or ""
|
||
) if is_file else ""
|
||
item["file_md5"] = file_md5
|
||
item["quote"] = item.get("quote") or extract_quote(item)
|
||
file_name = item["file_name"]
|
||
item["file_url"] = f"/api/files/{quote(file_md5, safe='')}?filename={quote(file_name or file_md5, safe='')}" if file_md5 else ""
|
||
return data
|
||
|
||
|
||
@router.get("/chatrooms")
|
||
async def chatrooms(
|
||
keyword: str = Query("", description="关键词搜索"),
|
||
limit: int = Query(100, ge=1, le=500),
|
||
offset: int = Query(0, ge=0),
|
||
):
|
||
"""获取所有可用的微信群聊列表"""
|
||
fetch_limit = min(2000, offset + limit)
|
||
rooms_data = await chatlog_client.get_chatrooms(keyword=keyword, limit=fetch_limit, offset=0)
|
||
if isinstance(rooms_data, list):
|
||
room_items = rooms_data
|
||
total = len(room_items)
|
||
else:
|
||
room_items = rooms_data.get("items") or rooms_data.get("data") or []
|
||
total = rooms_data.get("total", len(room_items))
|
||
|
||
merged = []
|
||
seen = set()
|
||
|
||
def get_room_id(item: dict) -> str:
|
||
return str(item.get("name") or item.get("Name") or item.get("userName") or item.get("UserName") or "")
|
||
|
||
def add_room(item: dict):
|
||
room_id = get_room_id(item)
|
||
if not room_id or not room_id.endswith("@chatroom") or room_id in seen:
|
||
return
|
||
seen.add(room_id)
|
||
merged.append(item)
|
||
|
||
for item in room_items:
|
||
if isinstance(item, dict):
|
||
add_room(item)
|
||
|
||
# Freshly imported phone records may exist in sessions/messages before
|
||
# chatroom metadata is populated. Merge @chatroom sessions as fallback.
|
||
try:
|
||
session_items = await chatlog_client.get_sessions(keyword="", limit=2000)
|
||
except Exception:
|
||
session_items = []
|
||
|
||
lowered_keyword = (keyword or "").lower()
|
||
for session in session_items:
|
||
if not isinstance(session, dict):
|
||
continue
|
||
user_name = str(session.get("userName") or session.get("UserName") or "")
|
||
if not user_name.endswith("@chatroom"):
|
||
continue
|
||
nick_name = session.get("nickName") or session.get("NickName") or ""
|
||
remark = session.get("remark") or session.get("Remark") or ""
|
||
if lowered_keyword:
|
||
haystack = f"{user_name} {nick_name} {remark}".lower()
|
||
if lowered_keyword not in haystack:
|
||
continue
|
||
add_room({
|
||
"name": user_name,
|
||
"nickName": nick_name,
|
||
"remark": remark,
|
||
"source": "session",
|
||
})
|
||
|
||
return {"total": max(total, len(merged)), "items": merged[offset:offset + limit]}
|
||
|
||
@router.get("/avatar")
|
||
async def avatar(wxid: str = Query(...)):
|
||
url = await chatlog_client.get_avatar_url(wxid)
|
||
return {"url": url}
|
||
|
||
|
||
@router.get("/members")
|
||
async def members(
|
||
talker: str = Query(..., description="群 ID"),
|
||
time: str = Query("", description="统计时间范围,可选"),
|
||
):
|
||
"""
|
||
获取群成员列表(按发言量降序)
|
||
返回 {"members": [...], "total": N}
|
||
每个成员:userName, displayName, msgCount, lastSpeakTime
|
||
"""
|
||
return await chatlog_client.get_chatroom_members(talker, time=time)
|
||
|
||
|
||
@router.get("/sessions")
|
||
async def sessions(
|
||
keyword: str = Query("", description="关键词搜索"),
|
||
limit: int = Query(500, ge=1, le=2000),
|
||
):
|
||
"""
|
||
获取所有会话列表,含最新一条消息预览和时间(来自微信原生 Session 表)。
|
||
返回:[{ userName, nickName, remark, content, nTime, nOrder }]
|
||
"""
|
||
items = await chatlog_client.get_sessions(keyword=keyword, limit=limit)
|
||
return items
|