mirror of
https://github.com/LifeArchiveProject/WeChatDataAnalysis.git
synced 2026-02-02 22:10:50 +08:00
feat(sns): 增加朋友圈时间线与图片本地缓存接口
- 新增 /api/sns/timeline:优先走 WCDB realtime 读取 sns.db,支持分页/用户过滤/关键字 - 新增 /api/sns/media:本地缓存(cache/.../Sns/Img)解密优先,支持手动 pick/避开重复 - 新增 /api/sns/media_candidates 与 /api/sns/media_picks:候选 key 列表与本机持久化匹配表 - wcdb_realtime 增加 exec_query/get_sns_timeline 封装,并在连接时 set_my_wxid 上下文 - 更新 wcdb_api.dll 并补齐 MSVC runtime 依赖
This commit is contained in:
@@ -18,6 +18,7 @@ from .routers.decrypt import router as _decrypt_router
|
|||||||
from .routers.health import router as _health_router
|
from .routers.health import router as _health_router
|
||||||
from .routers.keys import router as _keys_router
|
from .routers.keys import router as _keys_router
|
||||||
from .routers.media import router as _media_router
|
from .routers.media import router as _media_router
|
||||||
|
from .routers.sns import router as _sns_router
|
||||||
from .routers.wechat_detection import router as _wechat_detection_router
|
from .routers.wechat_detection import router as _wechat_detection_router
|
||||||
from .wcdb_realtime import WCDB_REALTIME, shutdown as _wcdb_shutdown
|
from .wcdb_realtime import WCDB_REALTIME, shutdown as _wcdb_shutdown
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ app.include_router(_media_router)
|
|||||||
app.include_router(_chat_router)
|
app.include_router(_chat_router)
|
||||||
app.include_router(_chat_export_router)
|
app.include_router(_chat_export_router)
|
||||||
app.include_router(_chat_media_router)
|
app.include_router(_chat_media_router)
|
||||||
|
app.include_router(_sns_router)
|
||||||
|
|
||||||
|
|
||||||
class _SPAStaticFiles(StaticFiles):
|
class _SPAStaticFiles(StaticFiles):
|
||||||
|
|||||||
BIN
src/wechat_decrypt_tool/native/msvcp140.dll
Normal file
BIN
src/wechat_decrypt_tool/native/msvcp140.dll
Normal file
Binary file not shown.
BIN
src/wechat_decrypt_tool/native/msvcp140_1.dll
Normal file
BIN
src/wechat_decrypt_tool/native/msvcp140_1.dll
Normal file
Binary file not shown.
BIN
src/wechat_decrypt_tool/native/vcruntime140.dll
Normal file
BIN
src/wechat_decrypt_tool/native/vcruntime140.dll
Normal file
Binary file not shown.
BIN
src/wechat_decrypt_tool/native/vcruntime140_1.dll
Normal file
BIN
src/wechat_decrypt_tool/native/vcruntime140_1.dll
Normal file
Binary file not shown.
Binary file not shown.
1068
src/wechat_decrypt_tool/routers/sns.py
Normal file
1068
src/wechat_decrypt_tool/routers/sns.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -68,6 +68,13 @@ def _load_wcdb_lib() -> ctypes.CDLL:
|
|||||||
lib.wcdb_close_account.argtypes = [ctypes.c_int64]
|
lib.wcdb_close_account.argtypes = [ctypes.c_int64]
|
||||||
lib.wcdb_close_account.restype = ctypes.c_int
|
lib.wcdb_close_account.restype = ctypes.c_int
|
||||||
|
|
||||||
|
# Optional: wcdb_set_my_wxid(handle, wxid)
|
||||||
|
try:
|
||||||
|
lib.wcdb_set_my_wxid.argtypes = [ctypes.c_int64, ctypes.c_char_p]
|
||||||
|
lib.wcdb_set_my_wxid.restype = ctypes.c_int
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
lib.wcdb_get_sessions.argtypes = [ctypes.c_int64, ctypes.POINTER(ctypes.c_char_p)]
|
lib.wcdb_get_sessions.argtypes = [ctypes.c_int64, ctypes.POINTER(ctypes.c_char_p)]
|
||||||
lib.wcdb_get_sessions.restype = ctypes.c_int
|
lib.wcdb_get_sessions.restype = ctypes.c_int
|
||||||
|
|
||||||
@@ -95,6 +102,37 @@ def _load_wcdb_lib() -> ctypes.CDLL:
|
|||||||
lib.wcdb_get_group_members.argtypes = [ctypes.c_int64, ctypes.c_char_p, ctypes.POINTER(ctypes.c_char_p)]
|
lib.wcdb_get_group_members.argtypes = [ctypes.c_int64, ctypes.c_char_p, ctypes.POINTER(ctypes.c_char_p)]
|
||||||
lib.wcdb_get_group_members.restype = ctypes.c_int
|
lib.wcdb_get_group_members.restype = ctypes.c_int
|
||||||
|
|
||||||
|
# Optional: execute arbitrary SQL on a selected database kind/path.
|
||||||
|
# Signature: wcdb_exec_query(handle, kind, path, sql, out_json)
|
||||||
|
try:
|
||||||
|
lib.wcdb_exec_query.argtypes = [
|
||||||
|
ctypes.c_int64,
|
||||||
|
ctypes.c_char_p,
|
||||||
|
ctypes.c_char_p,
|
||||||
|
ctypes.c_char_p,
|
||||||
|
ctypes.POINTER(ctypes.c_char_p),
|
||||||
|
]
|
||||||
|
lib.wcdb_exec_query.restype = ctypes.c_int
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Optional (newer DLLs): wcdb_get_sns_timeline(handle, limit, offset, usernames_json, keyword, start_time, end_time, out_json)
|
||||||
|
try:
|
||||||
|
lib.wcdb_get_sns_timeline.argtypes = [
|
||||||
|
ctypes.c_int64,
|
||||||
|
ctypes.c_int32,
|
||||||
|
ctypes.c_int32,
|
||||||
|
ctypes.c_char_p,
|
||||||
|
ctypes.c_char_p,
|
||||||
|
ctypes.c_int32,
|
||||||
|
ctypes.c_int32,
|
||||||
|
ctypes.POINTER(ctypes.c_char_p),
|
||||||
|
]
|
||||||
|
lib.wcdb_get_sns_timeline.restype = ctypes.c_int
|
||||||
|
except Exception:
|
||||||
|
# Older wcdb_api.dll may not expose this export.
|
||||||
|
pass
|
||||||
|
|
||||||
lib.wcdb_get_logs.argtypes = [ctypes.POINTER(ctypes.c_char_p)]
|
lib.wcdb_get_logs.argtypes = [ctypes.POINTER(ctypes.c_char_p)]
|
||||||
lib.wcdb_get_logs.restype = ctypes.c_int
|
lib.wcdb_get_logs.restype = ctypes.c_int
|
||||||
|
|
||||||
@@ -195,6 +233,30 @@ def open_account(session_db_path: Path, key_hex: str) -> int:
|
|||||||
return int(out_handle.value)
|
return int(out_handle.value)
|
||||||
|
|
||||||
|
|
||||||
|
def set_my_wxid(handle: int, wxid: str) -> bool:
|
||||||
|
"""Best-effort set the "my wxid" context for some WCDB APIs."""
|
||||||
|
try:
|
||||||
|
_ensure_initialized()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
lib = _load_wcdb_lib()
|
||||||
|
fn = getattr(lib, "wcdb_set_my_wxid", None)
|
||||||
|
if not fn:
|
||||||
|
return False
|
||||||
|
|
||||||
|
w = str(wxid or "").strip()
|
||||||
|
if not w:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
rc = int(fn(ctypes.c_int64(int(handle)), w.encode("utf-8")))
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return rc == 0
|
||||||
|
|
||||||
|
|
||||||
def close_account(handle: int) -> None:
|
def close_account(handle: int) -> None:
|
||||||
try:
|
try:
|
||||||
h = int(handle)
|
h = int(handle)
|
||||||
@@ -293,6 +355,93 @@ def get_avatar_urls(handle: int, usernames: list[str]) -> dict[str, str]:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def exec_query(handle: int, *, kind: str, path: Optional[str], sql: str) -> list[dict[str, Any]]:
|
||||||
|
"""Execute raw SQL on a specific db kind/path via WCDB.
|
||||||
|
|
||||||
|
This is primarily used for SNS/other dbs that are not directly exposed by dedicated APIs.
|
||||||
|
"""
|
||||||
|
_ensure_initialized()
|
||||||
|
lib = _load_wcdb_lib()
|
||||||
|
fn = getattr(lib, "wcdb_exec_query", None)
|
||||||
|
if not fn:
|
||||||
|
raise WCDBRealtimeError("Current wcdb_api.dll does not support exec_query.")
|
||||||
|
|
||||||
|
k = str(kind or "").strip()
|
||||||
|
if not k:
|
||||||
|
raise WCDBRealtimeError("Missing kind for exec_query.")
|
||||||
|
|
||||||
|
s = str(sql or "").strip()
|
||||||
|
if not s:
|
||||||
|
return []
|
||||||
|
|
||||||
|
p = None if path is None else str(path or "").strip()
|
||||||
|
|
||||||
|
out_json = _call_out_json(
|
||||||
|
fn,
|
||||||
|
ctypes.c_int64(int(handle)),
|
||||||
|
k.encode("utf-8"),
|
||||||
|
None if p is None else p.encode("utf-8"),
|
||||||
|
s.encode("utf-8"),
|
||||||
|
)
|
||||||
|
decoded = _safe_load_json(out_json)
|
||||||
|
if isinstance(decoded, list):
|
||||||
|
out: list[dict[str, Any]] = []
|
||||||
|
for x in decoded:
|
||||||
|
if isinstance(x, dict):
|
||||||
|
out.append(x)
|
||||||
|
return out
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_sns_timeline(
|
||||||
|
handle: int,
|
||||||
|
*,
|
||||||
|
limit: int = 20,
|
||||||
|
offset: int = 0,
|
||||||
|
usernames: Optional[list[str]] = None,
|
||||||
|
keyword: str | None = None,
|
||||||
|
start_time: int = 0,
|
||||||
|
end_time: int = 0,
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
"""Read Moments (SnsTimeLine) from the live encrypted db_storage via WCDB.
|
||||||
|
|
||||||
|
Requires a newer wcdb_api.dll export: wcdb_get_sns_timeline.
|
||||||
|
"""
|
||||||
|
_ensure_initialized()
|
||||||
|
lib = _load_wcdb_lib()
|
||||||
|
fn = getattr(lib, "wcdb_get_sns_timeline", None)
|
||||||
|
if not fn:
|
||||||
|
raise WCDBRealtimeError("Current wcdb_api.dll does not support sns timeline.")
|
||||||
|
|
||||||
|
lim = max(0, int(limit or 0))
|
||||||
|
off = max(0, int(offset or 0))
|
||||||
|
|
||||||
|
users = [str(u or "").strip() for u in (usernames or []) if str(u or "").strip()]
|
||||||
|
users = list(dict.fromkeys(users))
|
||||||
|
users_json = json.dumps(users, ensure_ascii=False) if users else ""
|
||||||
|
|
||||||
|
kw = str(keyword or "").strip()
|
||||||
|
|
||||||
|
payload = _call_out_json(
|
||||||
|
fn,
|
||||||
|
ctypes.c_int64(int(handle)),
|
||||||
|
ctypes.c_int32(lim),
|
||||||
|
ctypes.c_int32(off),
|
||||||
|
users_json.encode("utf-8"),
|
||||||
|
kw.encode("utf-8"),
|
||||||
|
ctypes.c_int32(int(start_time or 0)),
|
||||||
|
ctypes.c_int32(int(end_time or 0)),
|
||||||
|
)
|
||||||
|
decoded = _safe_load_json(payload)
|
||||||
|
if isinstance(decoded, list):
|
||||||
|
out: list[dict[str, Any]] = []
|
||||||
|
for x in decoded:
|
||||||
|
if isinstance(x, dict):
|
||||||
|
out.append(x)
|
||||||
|
return out
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def shutdown() -> None:
|
def shutdown() -> None:
|
||||||
global _initialized
|
global _initialized
|
||||||
lib = _load_wcdb_lib()
|
lib = _load_wcdb_lib()
|
||||||
@@ -427,6 +576,11 @@ class WCDBRealtimeManager:
|
|||||||
|
|
||||||
session_db_path = _resolve_session_db_path(db_storage_dir)
|
session_db_path = _resolve_session_db_path(db_storage_dir)
|
||||||
handle = open_account(session_db_path, key)
|
handle = open_account(session_db_path, key)
|
||||||
|
# Some WCDB APIs (e.g. exec_query on non-session DBs) may require this context.
|
||||||
|
try:
|
||||||
|
set_my_wxid(handle, account)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
conn = WCDBRealtimeConnection(
|
conn = WCDBRealtimeConnection(
|
||||||
account=account,
|
account=account,
|
||||||
|
|||||||
Reference in New Issue
Block a user