From ee097e1fda300a3ee94c9bf4585ab9a567b2d19b Mon Sep 17 00:00:00 2001 From: yml2213 Date: Thu, 6 Aug 2026 17:42:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(douyu):=20=E5=92=8C=E5=B9=B3=E5=B0=8F?= =?UTF-8?q?=E5=BA=97=E7=BB=91=E5=AE=9A=E4=BA=8C=E7=BB=B4=E7=A0=81=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=BC=B9=E5=87=BA=20+=20=E6=89=AB=E7=A0=81=E5=90=8E?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9B=B4=E6=96=B0=E8=A7=92=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端: get_xpd_bind_qr 生成二维码后保持 running, 每 5s 轮询 bindInfo(最长 300s), 检测到绑定即回写角色并自动结束 - 前端: hasBindQrcode 纳入 get_xpd_bind_qr → 新二维码自动弹出; 弹窗显示等待扫码/绑定成功状态与角色; 对 xpd 任务隐藏 不适用的"查询角色/确认绑定"按钮 --- web/backend/services/douyu_runner.py | 83 ++++++++++++++++++++++- web/frontend/src/pages/DouyuTasksPage.tsx | 63 +++++++++-------- 2 files changed, 117 insertions(+), 29 deletions(-) diff --git a/web/backend/services/douyu_runner.py b/web/backend/services/douyu_runner.py index a5e914f..92afbdf 100644 --- a/web/backend/services/douyu_runner.py +++ b/web/backend/services/douyu_runner.py @@ -29,6 +29,8 @@ from .douyu_service import ( DOUYU_LEGACY_BIND_ACT_ALIAS = "20250213NQCYX" DOUYU_BIND_ROLE_POLL_SECONDS = 65 DOUYU_BIND_ROLE_POLL_INTERVAL = 5 +DOUYU_XPD_BIND_POLL_SECONDS = 300 +DOUYU_XPD_BIND_POLL_INTERVAL = 5 DOUYU_PAYMENT_POLL_SECONDS = 600 DOUYU_PAYMENT_POLL_INTERVAL = 5 DOUYU_GIFT_POINTS_REFRESH_TIMES = 3 @@ -1084,7 +1086,7 @@ class DouyuBatchRunner: cookie: str, config: dict, ): - """生成和平小店绑定二维码(微信扫码在小程序内绑定角色)。""" + """生成和平小店绑定二维码并轮询等待微信扫码绑定完成。""" client = self._client(cookie) act_alias = str(config.get("xpd_act_alias") or "").strip() if not act_alias: @@ -1094,7 +1096,84 @@ class DouyuBatchRunner: account.xpd_bind_status = "xpd_bind_qr_ready" account.updated_at = datetime.now(timezone.utc) db.commit() - self._mark_task(db, task, "success", "小店绑定二维码已生成", result) + result["bind_polling"] = True + self._update_task_progress( + db, + task, + "running", + "二维码已生成,请微信扫码在小程序中绑定角色", + result, + ) + bound = self._wait_xpd_bind(db, task, account, client, act_alias, result) + if self._stop.is_set(): + result["bind_polling"] = False + self._mark_task(db, task, "stopped", "任务已停止", result) + return + if bound: + account.xpd_bind_status = "xpd_bound" + account.updated_at = datetime.now(timezone.utc) + db.commit() + role_text = str(result.get("role_name") or account.xpd_game_name or "-") + self._mark_task(db, task, "success", f"小店绑定成功: {role_text}", result) + return + result["bind_polling"] = False + self._mark_task( + db, + task, + "failed", + "未检测到小店绑定(二维码仍有效,可再次生成后扫码)", + result, + ) + + def _wait_xpd_bind( + self, + db: Session, + task: DouyuTask, + account: Account, + client: DouyuActivityClient, + act_alias: str, + result: dict, + ) -> bool: + """轮询 bindInfo,检测到绑定角色即回写账号并返回 True。""" + deadline = time.monotonic() + DOUYU_XPD_BIND_POLL_SECONDS + poll_count = 0 + while not self._stop.is_set() and time.monotonic() <= deadline: + try: + info = client.xpd_bind_info(act_alias=act_alias) + poll_count += 1 + result["bind_poll_count"] = poll_count + result["bind_polling"] = True + role_name = str(info.get("role_name") or "") + if info.get("bind_role") and role_name: + result.update({key: value for key, value in info.items() if key != "raw"}) + result["bind_polling"] = False + result["xpd_bound"] = True + account.xpd_game_name = role_name + account.updated_at = datetime.now(timezone.utc) + db.commit() + return True + self._update_task_progress( + db, + task, + "running", + f"等待扫码绑定(第 {poll_count} 次)", + result, + ) + except Exception as exc: + poll_count += 1 + result["bind_poll_count"] = poll_count + result["bind_poll_error"] = str(exc) + self._update_task_progress( + db, + task, + "running", + f"等待扫码绑定: {exc}", + result, + ) + if self._stop.wait(DOUYU_XPD_BIND_POLL_INTERVAL): + break + result["bind_polling"] = False + return False def _execute_query_xpd_bind_info( self, diff --git a/web/frontend/src/pages/DouyuTasksPage.tsx b/web/frontend/src/pages/DouyuTasksPage.tsx index 873290f..165be49 100644 --- a/web/frontend/src/pages/DouyuTasksPage.tsx +++ b/web/frontend/src/pages/DouyuTasksPage.tsx @@ -176,7 +176,8 @@ function bindReadyForConfirm(task: DouyuTaskItem | null | undefined): boolean { } function hasBindQrcode(task: DouyuTaskItem | null | undefined): boolean { - if (!task || task.task_type !== 'get_bind_qr') return false; + if (!task) return false; + if (!['get_bind_qr', 'get_xpd_bind_qr'].includes(task.task_type)) return false; // 有二维码,或已有待确认角色(无需再扫码)都应可展示绑定面板 return Boolean(resultText(task.result, 'url')) || bindReadyForConfirm(task) @@ -1053,10 +1054,12 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) ) ), ); - const qrBindReady = bindReadyForConfirm(qrTask) || qrQueryPending; + const isXpdBindTask = qrTask?.task_type === 'get_xpd_bind_qr'; + const qrXpdBound = Boolean(isXpdBindTask && resultFlag(qrResult, 'xpd_bound')); + const qrBindReady = bindReadyForConfirm(qrTask) || qrQueryPending || qrXpdBound; const qrRoleSourceResult = qrQueryPending ? qrQueryResult - : (bindReadyForConfirm(qrTask) ? qrResult : null); + : (bindReadyForConfirm(qrTask) || qrXpdBound ? qrResult : null); // 待确认角色优先用 pending_* 字段(已绑定角色之外的扫码新角色) const qrRoleName = resultText(qrRoleSourceResult, 'pending_role_name') || resultText(qrRoleSourceResult, 'role_name'); const qrAreaName = resultText(qrRoleSourceResult, 'pending_area_name') || resultText(qrRoleSourceResult, 'area_name'); @@ -1090,12 +1093,16 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) : ''; const qrBindSummary = resultText(qrResult, 'bind_summary') || resultText(qrQueryResult, 'bind_summary'); const qrStatusText = qrBindReady - ? '已识别角色,待确认' - : qrQueryRunning - ? '查询角色中' - : qrQueryTask && qrQueryTask.id > (qrTask?.id || 0) && qrQueryTask.status === 'success' && qrQueryIsBoundAct - ? '查询结果:仍是当前绑定角色' - : bindPhaseText(qrBindPhase, qrAutoPolling); + ? (isXpdBindTask ? '绑定成功' : '已识别角色,待确认') + : (isXpdBindTask && qrTask?.status === 'failed') + ? '未检测到绑定' + : (isXpdBindTask && qrTask?.status === 'running') + ? '等待扫码绑定' + : qrQueryRunning + ? '查询角色中' + : qrQueryTask && qrQueryTask.id > (qrTask?.id || 0) && qrQueryTask.status === 'success' && qrQueryIsBoundAct + ? '查询结果:仍是当前绑定角色' + : bindPhaseText(qrBindPhase, qrAutoPolling); const qrAccountName = qrTask ? (qrTask.account_nickname || qrTask.account_username || qrTask.account_uid || `#${qrTask.account_id}`) : ''; @@ -2112,24 +2119,26 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) 最近查询: {qrQueryTask.message} ) : null} - - - - + {!isXpdBindTask && ( + + + + + )} )}