Initial upload for secondary development

This commit is contained in:
2026-06-08 19:00:03 +08:00
commit b913b8c78c
81 changed files with 27139 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
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