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