Skip to content

Commit 3882c67

Browse files
committed
chore(发版): 更新版本到 v2.0.2
1 parent f87dc1f commit 3882c67

7 files changed

Lines changed: 967 additions & 69 deletions

File tree

XianyuAutoAsync.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,6 +1950,7 @@ def __init__(self, cookies_str=None, cookie_id: str = "default", user_id: int =
19501950
self.last_heartbeat_response = 0
19511951
self.last_sent_heartbeat_mid = None
19521952
self.pending_heartbeat_mids = deque(maxlen=32)
1953+
self.lwp_response_waiters = {}
19531954
self.heartbeat_task = None
19541955
self.ws = None
19551956
self.last_non_heartbeat_message_time = 0
@@ -12169,6 +12170,68 @@ async def init(self, ws):
1216912170
await ws.send(json.dumps(msg))
1217012171
logger.info(f'【{self.cookie_id}】连接注册完成')
1217112172

12173+
async def _ack_lwp_message(self, ws, message: dict) -> None:
12174+
try:
12175+
headers = message.get("headers", {}) if isinstance(message, dict) else {}
12176+
ack = {
12177+
"code": 200,
12178+
"headers": {
12179+
"mid": headers.get("mid", generate_mid()),
12180+
"sid": headers.get("sid", ""),
12181+
}
12182+
}
12183+
if 'app-key' in headers:
12184+
ack["headers"]["app-key"] = headers["app-key"]
12185+
if 'ua' in headers:
12186+
ack["headers"]["ua"] = headers["ua"]
12187+
if 'dt' in headers:
12188+
ack["headers"]["dt"] = headers["dt"]
12189+
await ws.send(json.dumps(ack))
12190+
except Exception:
12191+
pass
12192+
12193+
def _resolve_lwp_response_waiter(self, message_data: dict) -> bool:
12194+
try:
12195+
headers = message_data.get("headers", {}) if isinstance(message_data, dict) else {}
12196+
mid = str(headers.get("mid") or "")
12197+
if not mid:
12198+
return False
12199+
future = self.lwp_response_waiters.pop(mid, None)
12200+
if not future:
12201+
return False
12202+
if not future.done():
12203+
future.set_result(message_data)
12204+
return True
12205+
except Exception as e:
12206+
logger.debug(f"【{self.cookie_id}】分发LWP响应失败: {self._safe_str(e)}")
12207+
return False
12208+
12209+
async def _request_lwp_on_current_ws(self, lwp: str, body: list, timeout: int = 10):
12210+
ws = self.ws
12211+
if not ws or getattr(ws, 'closed', False):
12212+
return {"code": "not_connected", "reason": "账号WebSocket未连接"}
12213+
12214+
mid = generate_mid()
12215+
loop = asyncio.get_running_loop()
12216+
future = loop.create_future()
12217+
self.lwp_response_waiters[mid] = future
12218+
try:
12219+
await ws.send(json.dumps({
12220+
"lwp": lwp,
12221+
"headers": {"mid": mid},
12222+
"body": body,
12223+
}))
12224+
response = await asyncio.wait_for(future, timeout=max(1, int(timeout or 10)))
12225+
return response.get("body", {}) if isinstance(response, dict) else {}
12226+
except asyncio.TimeoutError:
12227+
return {"code": "timeout", "reason": "IM请求超时"}
12228+
except Exception as e:
12229+
return {"code": "request_failed", "reason": self._safe_str(e)}
12230+
finally:
12231+
current = self.lwp_response_waiters.pop(mid, None)
12232+
if current and not current.done():
12233+
current.cancel()
12234+
1217212235
async def list_all_conversations(self, cid: str, page_size: int = 20):
1217312236
"""拉取指定会话的历史消息。"""
1217412237
logger.info(f"【{self.cookie_id}】开始通过独立临时连接拉取历史消息: chat_id={cid}, page_size={page_size}")
@@ -12291,6 +12354,85 @@ async def list_all_conversations(self, cid: str, page_size: int = 20):
1229112354

1229212355
return []
1229312356

12357+
async def list_newest_conversations(self, start_timestamp: int = None, limit: int = 20):
12358+
"""通过当前主WebSocket拉取最近会话列表,避免临时连接挤掉主监听。"""
12359+
if start_timestamp in (None, '', 0, '0'):
12360+
start_timestamp = 9007199254740991
12361+
12362+
try:
12363+
start_timestamp = int(start_timestamp)
12364+
except (TypeError, ValueError):
12365+
start_timestamp = 9007199254740991
12366+
12367+
try:
12368+
limit = max(1, min(int(limit or 20), 100))
12369+
except (TypeError, ValueError):
12370+
limit = 20
12371+
12372+
logger.info(
12373+
f"【{self.cookie_id}】开始通过主连接拉取最近会话: "
12374+
f"cursor={start_timestamp}, limit={limit}"
12375+
)
12376+
12377+
last_body = {}
12378+
for attempt in range(3):
12379+
body = await self._request_lwp_on_current_ws(
12380+
"/r/Conversation/listNewestPagination",
12381+
[start_timestamp, limit],
12382+
timeout=10,
12383+
)
12384+
last_body = body if isinstance(body, dict) else {}
12385+
if isinstance(last_body, dict) and last_body.get("code") == "400600001" and attempt < 2:
12386+
wait_seconds = (attempt + 1) * 2
12387+
logger.warning(
12388+
f"【{self.cookie_id}】最近会话拉取被流控,{wait_seconds}秒后重试 "
12389+
f"({attempt + 1}/3)"
12390+
)
12391+
await asyncio.sleep(wait_seconds)
12392+
continue
12393+
logger.info(
12394+
f"【{self.cookie_id}】最近会话拉取完成: "
12395+
f"count={len(last_body.get('userConvs', [])) if isinstance(last_body, dict) else 0}"
12396+
)
12397+
return last_body
12398+
12399+
return last_body
12400+
12401+
async def list_conversation_messages_page(self, cid: str, start_timestamp: int = None, limit: int = 20):
12402+
"""通过当前主WebSocket分页拉取指定会话消息,避免临时连接挤掉主监听。"""
12403+
normalized_cid = str(cid or '').strip()
12404+
if not normalized_cid:
12405+
return {"code": "missing_cid", "reason": "缺少会话ID"}
12406+
full_cid = normalized_cid if '@goofish' in normalized_cid else f"{normalized_cid}@goofish"
12407+
12408+
if start_timestamp in (None, '', 0, '0'):
12409+
start_timestamp = 9007199254740991
12410+
12411+
try:
12412+
start_timestamp = int(start_timestamp)
12413+
except (TypeError, ValueError):
12414+
start_timestamp = 9007199254740991
12415+
12416+
try:
12417+
limit = max(1, min(int(limit or 20), 100))
12418+
except (TypeError, ValueError):
12419+
limit = 20
12420+
12421+
logger.info(
12422+
f"【{self.cookie_id}】开始通过主连接分页拉取消息: "
12423+
f"chat_id={normalized_cid}, cursor={start_timestamp}, limit={limit}"
12424+
)
12425+
body = await self._request_lwp_on_current_ws(
12426+
"/r/MessageManager/listUserMessages",
12427+
[full_cid, False, start_timestamp, limit, False],
12428+
timeout=10,
12429+
)
12430+
logger.info(
12431+
f"【{self.cookie_id}】分页消息拉取完成: "
12432+
f"chat_id={normalized_cid}, count={len(body.get('userMessageModels', [])) if isinstance(body, dict) else 0}"
12433+
)
12434+
return body if isinstance(body, dict) else {}
12435+
1229412436
async def fetch_conversation_history_once(self, cid: str, page_size: int = 20):
1229512437
"""使用独立临时实例拉取历史消息,避免影响主连接状态。"""
1229612438
isolated_live = XianyuLive(
@@ -16080,6 +16222,10 @@ async def main(self):
1608016222
async for message in websocket:
1608116223
try:
1608216224
message_data = json.loads(message)
16225+
16226+
if self._resolve_lwp_response_waiter(message_data):
16227+
await self._ack_lwp_message(websocket, message_data)
16228+
continue
1608316229

1608416230
# 提取消息标识用于日志追踪(防止异步处理导致日志混乱)
1608516231
msg_id = "unknown"

0 commit comments

Comments
 (0)