@@ -609,6 +609,13 @@ def _load_wcdb_lib() -> ctypes.CDLL:
609609 lib .wcdb_get_avatar_urls .argtypes = [ctypes .c_int64 , ctypes .c_char_p , ctypes .POINTER (ctypes .c_char_p )]
610610 lib .wcdb_get_avatar_urls .restype = ctypes .c_int
611611
612+ # Optional (newer DLLs): wcdb_get_contact(handle, username, out_json)
613+ try :
614+ lib .wcdb_get_contact .argtypes = [ctypes .c_int64 , ctypes .c_char_p , ctypes .POINTER (ctypes .c_char_p )]
615+ lib .wcdb_get_contact .restype = ctypes .c_int
616+ except Exception :
617+ pass
618+
612619 lib .wcdb_get_group_member_count .argtypes = [ctypes .c_int64 , ctypes .c_char_p , ctypes .POINTER (ctypes .c_int32 )]
613620 lib .wcdb_get_group_member_count .restype = ctypes .c_int
614621
@@ -640,6 +647,50 @@ def _load_wcdb_lib() -> ctypes.CDLL:
640647 except Exception :
641648 pass
642649
650+ # Optional (newer DLLs): native message cursor used by WeFlow for stable
651+ # latest/history paging across message_*.db shards.
652+ try :
653+ lib .wcdb_open_message_cursor .argtypes = [
654+ ctypes .c_int64 ,
655+ ctypes .c_char_p ,
656+ ctypes .c_int32 ,
657+ ctypes .c_int32 ,
658+ ctypes .c_int32 ,
659+ ctypes .c_int32 ,
660+ ctypes .POINTER (ctypes .c_int64 ),
661+ ]
662+ lib .wcdb_open_message_cursor .restype = ctypes .c_int
663+ except Exception :
664+ pass
665+ try :
666+ lib .wcdb_open_message_cursor_lite .argtypes = [
667+ ctypes .c_int64 ,
668+ ctypes .c_char_p ,
669+ ctypes .c_int32 ,
670+ ctypes .c_int32 ,
671+ ctypes .c_int32 ,
672+ ctypes .c_int32 ,
673+ ctypes .POINTER (ctypes .c_int64 ),
674+ ]
675+ lib .wcdb_open_message_cursor_lite .restype = ctypes .c_int
676+ except Exception :
677+ pass
678+ try :
679+ lib .wcdb_fetch_message_batch .argtypes = [
680+ ctypes .c_int64 ,
681+ ctypes .c_int64 ,
682+ ctypes .POINTER (ctypes .c_char_p ),
683+ ctypes .POINTER (ctypes .c_int32 ),
684+ ]
685+ lib .wcdb_fetch_message_batch .restype = ctypes .c_int
686+ except Exception :
687+ pass
688+ try :
689+ lib .wcdb_close_message_cursor .argtypes = [ctypes .c_int64 , ctypes .c_int64 ]
690+ lib .wcdb_close_message_cursor .restype = ctypes .c_int
691+ except Exception :
692+ pass
693+
643694 # Optional (newer DLLs): update a single message content in message db.
644695 # Signature: wcdb_update_message(handle, sessionId, localId, createTime, newContent, outError)
645696 try :
@@ -1086,6 +1137,31 @@ def get_avatar_urls(handle: int, usernames: list[str]) -> dict[str, str]:
10861137 return {}
10871138
10881139
1140+ def get_contact (handle : int , username : str ) -> dict [str , Any ]:
1141+ _ensure_initialized ()
1142+ u = str (username or "" ).strip ()
1143+ if not u :
1144+ return {}
1145+
1146+ if _sidecar_enabled ():
1147+ out_json = _sidecar_payload (
1148+ "get_contact" ,
1149+ {"handle" : int (handle ), "username" : u },
1150+ timeout = 30.0 ,
1151+ )
1152+ decoded = _safe_load_json (out_json )
1153+ return decoded if isinstance (decoded , dict ) else {}
1154+
1155+ lib = _load_wcdb_lib ()
1156+ fn = getattr (lib , "wcdb_get_contact" , None )
1157+ if not fn :
1158+ raise WCDBRealtimeError ("Current wcdb_api.dll does not support get_contact." )
1159+
1160+ out_json = _call_out_json (fn , ctypes .c_int64 (int (handle )), u .encode ("utf-8" ))
1161+ decoded = _safe_load_json (out_json )
1162+ return decoded if isinstance (decoded , dict ) else {}
1163+
1164+
10891165def get_group_members (handle : int , chatroom_id : str ) -> list [dict [str , Any ]]:
10901166 _ensure_initialized ()
10911167 cid = str (chatroom_id or "" ).strip ()
@@ -1200,6 +1276,138 @@ def exec_query(handle: int, *, kind: str, path: Optional[str], sql: str) -> list
12001276 return []
12011277
12021278
1279+ def open_message_cursor (
1280+ handle : int ,
1281+ session_id : str ,
1282+ * ,
1283+ batch_size : int ,
1284+ ascending : bool = False ,
1285+ begin_timestamp : int = 0 ,
1286+ end_timestamp : int = 0 ,
1287+ lite : bool = False ,
1288+ ) -> int :
1289+ """Open the native WeFlow-style message cursor.
1290+
1291+ The cursor API is important for live chat paging because the native layer
1292+ owns cross-`message_*.db` discovery/merge and can see the same live view
1293+ WeFlow uses instead of a single explicit SQL table.
1294+ """
1295+
1296+ _ensure_initialized ()
1297+ sid = str (session_id or "" ).strip ()
1298+ if not sid :
1299+ return 0
1300+ size = max (1 , int (batch_size or 1 ))
1301+ asc = 1 if ascending else 0
1302+ begin = max (0 , int (begin_timestamp or 0 ))
1303+ end = max (0 , int (end_timestamp or 0 ))
1304+
1305+ action = "open_message_cursor_lite" if lite else "open_message_cursor"
1306+ if _sidecar_enabled ():
1307+ result = _sidecar_call (
1308+ action ,
1309+ {
1310+ "handle" : int (handle ),
1311+ "session_id" : sid ,
1312+ "batch_size" : int (size ),
1313+ "ascending" : bool (ascending ),
1314+ "begin_timestamp" : int (begin ),
1315+ "end_timestamp" : int (end ),
1316+ },
1317+ timeout = 30.0 ,
1318+ )
1319+ try :
1320+ return int (result .get ("cursor" ) or 0 )
1321+ except Exception :
1322+ return 0
1323+
1324+ lib = _load_wcdb_lib ()
1325+ fn = getattr (lib , "wcdb_open_message_cursor_lite" if lite else "wcdb_open_message_cursor" , None )
1326+ if not fn :
1327+ if lite :
1328+ fn = getattr (lib , "wcdb_open_message_cursor" , None )
1329+ if not fn :
1330+ return 0
1331+
1332+ out_cursor = ctypes .c_int64 (0 )
1333+ rc = int (
1334+ fn (
1335+ ctypes .c_int64 (int (handle )),
1336+ sid .encode ("utf-8" ),
1337+ ctypes .c_int32 (int (size )),
1338+ ctypes .c_int32 (int (asc )),
1339+ ctypes .c_int32 (int (begin )),
1340+ ctypes .c_int32 (int (end )),
1341+ ctypes .byref (out_cursor ),
1342+ )
1343+ )
1344+ if rc != 0 or int (out_cursor .value ) <= 0 :
1345+ return 0
1346+ return int (out_cursor .value )
1347+
1348+
1349+ def fetch_message_batch (handle : int , cursor : int ) -> tuple [list [dict [str , Any ]], bool ]:
1350+ _ensure_initialized ()
1351+ cur = int (cursor or 0 )
1352+ if cur <= 0 :
1353+ return [], False
1354+
1355+ if _sidecar_enabled ():
1356+ result = _sidecar_call (
1357+ "fetch_message_batch" ,
1358+ {"handle" : int (handle ), "cursor" : int (cur )},
1359+ timeout = 30.0 ,
1360+ )
1361+ decoded = _safe_load_json (str (result .get ("payload" ) or "" ))
1362+ rows = [x for x in decoded if isinstance (x , dict )] if isinstance (decoded , list ) else []
1363+ return rows , bool (result .get ("hasMore" ))
1364+
1365+ lib = _load_wcdb_lib ()
1366+ fn = getattr (lib , "wcdb_fetch_message_batch" , None )
1367+ if not fn :
1368+ return [], False
1369+
1370+ out_json = ctypes .c_char_p ()
1371+ out_has_more = ctypes .c_int32 (0 )
1372+ rc = int (fn (ctypes .c_int64 (int (handle )), ctypes .c_int64 (cur ), ctypes .byref (out_json ), ctypes .byref (out_has_more )))
1373+ try :
1374+ if rc != 0 or not out_json .value :
1375+ return [], False
1376+ payload = out_json .value .decode ("utf-8" , errors = "replace" )
1377+ finally :
1378+ try :
1379+ if out_json .value :
1380+ lib .wcdb_free_string (out_json )
1381+ except Exception :
1382+ pass
1383+ decoded = _safe_load_json (payload )
1384+ rows = [x for x in decoded if isinstance (x , dict )] if isinstance (decoded , list ) else []
1385+ return rows , bool (int (out_has_more .value or 0 ) == 1 )
1386+
1387+
1388+ def close_message_cursor (handle : int , cursor : int ) -> None :
1389+ _ensure_initialized ()
1390+ cur = int (cursor or 0 )
1391+ if cur <= 0 :
1392+ return
1393+
1394+ if _sidecar_enabled ():
1395+ try :
1396+ _sidecar_call ("close_message_cursor" , {"handle" : int (handle ), "cursor" : int (cur )}, timeout = 5.0 )
1397+ except Exception :
1398+ pass
1399+ return
1400+
1401+ lib = _load_wcdb_lib ()
1402+ fn = getattr (lib , "wcdb_close_message_cursor" , None )
1403+ if not fn :
1404+ return
1405+ try :
1406+ fn (ctypes .c_int64 (int (handle )), ctypes .c_int64 (cur ))
1407+ except Exception :
1408+ return
1409+
1410+
12031411def update_message (handle : int , * , session_id : str , local_id : int , create_time : int , new_content : str ) -> None :
12041412 """Update a single message content in the live encrypted db_storage via WCDB.
12051413
0 commit comments