fix(douyu): 优化和平小店绑定逻辑
This commit is contained in:
@@ -1086,7 +1086,10 @@ class DouyuBatchRunner:
|
||||
cookie: str,
|
||||
config: dict,
|
||||
):
|
||||
"""生成和平小店绑定二维码并轮询等待微信扫码绑定/换绑完成。"""
|
||||
"""生成和平小店绑定二维码并轮询等待微信扫码绑定/换绑完成。
|
||||
|
||||
识别到新角色后仅标记"待确认",不自动回写账号,由用户手动确认绑定。
|
||||
"""
|
||||
client = self._client(cookie)
|
||||
act_alias = str(config.get("xpd_act_alias") or "").strip()
|
||||
if not act_alias:
|
||||
@@ -1114,19 +1117,15 @@ class DouyuBatchRunner:
|
||||
"二维码已生成,请微信扫码在小程序中绑定角色",
|
||||
result,
|
||||
)
|
||||
bound = self._wait_xpd_bind(db, task, account, client, act_alias, result)
|
||||
if self._stop.is_set():
|
||||
result["bind_polling"] = False
|
||||
state = self._wait_xpd_bind(db, task, client, act_alias, result)
|
||||
result["bind_polling"] = False
|
||||
if state == "stopped":
|
||||
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)
|
||||
if state == "pending":
|
||||
role_text = str(result.get("role_name") or "-")
|
||||
self._mark_task(db, task, "success", f"已识别角色: {role_text},待确认绑定", result)
|
||||
return
|
||||
result["bind_polling"] = False
|
||||
self._mark_task(
|
||||
db,
|
||||
task,
|
||||
@@ -1139,15 +1138,15 @@ class DouyuBatchRunner:
|
||||
self,
|
||||
db: Session,
|
||||
task: DouyuTask,
|
||||
account: Account,
|
||||
client: DouyuActivityClient,
|
||||
act_alias: str,
|
||||
result: dict,
|
||||
) -> bool:
|
||||
"""轮询 bindInfo 等待绑定/换绑完成。
|
||||
) -> str:
|
||||
"""轮询 bindInfo 检测绑定/换绑角色,识别到后停在"待确认",不自动回写账号。
|
||||
|
||||
- 绑定前未绑定:检测到 bind_role=1 即绑定成功
|
||||
- 绑定前已绑定(换绑):检测到角色名变化才算换绑成功,角色不变继续等
|
||||
- 绑定前未绑定:检测到 bind_role=1 即识别到待确认角色
|
||||
- 绑定前已绑定(换绑):检测到角色名变化才算换绑完成,角色不变继续等
|
||||
返回 "pending"=已识别待确认角色, "stopped"=任务停止, "timeout"=超时未识别。
|
||||
"""
|
||||
before_bound = bool(result.get("before_bound"))
|
||||
before_role_name = str(result.get("before_role_name") or "")
|
||||
@@ -1165,11 +1164,8 @@ class DouyuBatchRunner:
|
||||
if (not before_bound and bound_now and role_name) or changed:
|
||||
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
|
||||
result["xpd_pending_confirm"] = True
|
||||
return "pending"
|
||||
self._update_task_progress(
|
||||
db,
|
||||
task,
|
||||
@@ -1191,7 +1187,48 @@ class DouyuBatchRunner:
|
||||
if self._stop.wait(DOUYU_XPD_BIND_POLL_INTERVAL):
|
||||
break
|
||||
result["bind_polling"] = False
|
||||
return False
|
||||
return "stopped" if self._stop.is_set() else "timeout"
|
||||
|
||||
def _execute_confirm_xpd_bind(
|
||||
self,
|
||||
db: Session,
|
||||
task: DouyuTask,
|
||||
account: Account,
|
||||
cookie: str,
|
||||
config: dict,
|
||||
):
|
||||
"""确认和平小店绑定:回查 bindInfo,确认绑定角色后将账号回写为已绑定。"""
|
||||
client = self._client(cookie)
|
||||
act_alias = str(config.get("xpd_act_alias") or "").strip()
|
||||
if not act_alias:
|
||||
self._mark_task(db, task, "failed", "请先配置小店活动代号 actAlias")
|
||||
return
|
||||
result = client.xpd_bind_info(act_alias=act_alias)
|
||||
role_name = str(result.get("role_name") or "")
|
||||
if not result.get("bind_role") or not role_name:
|
||||
account.xpd_bind_status = "xpd_not_bound"
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
db.commit()
|
||||
self._mark_task(db, task, "failed", "尚未检测到小店绑定角色,请先扫码绑定", result)
|
||||
return
|
||||
# 优先用完整角色信息回写(与查询角色一致),失败时回退 bindInfo 角色名
|
||||
try:
|
||||
ctx = self._xpd_role_context(client, config)
|
||||
role = ctx["role"]
|
||||
if role.get("role_id"):
|
||||
self._apply_xpd_role_to_account(account, role, self._xpd_area_id(role, account))
|
||||
else:
|
||||
account.xpd_game_name = role_name
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
except Exception:
|
||||
account.xpd_game_name = role_name
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
account.xpd_bind_status = "xpd_bound"
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
db.commit()
|
||||
result["xpd_pending_confirm"] = False
|
||||
result["xpd_bound"] = True
|
||||
self._mark_task(db, task, "success", f"小店绑定成功: {role_name}", result)
|
||||
|
||||
def _execute_query_xpd_bind_info(
|
||||
self,
|
||||
@@ -1201,19 +1238,16 @@ class DouyuBatchRunner:
|
||||
cookie: str,
|
||||
config: dict,
|
||||
):
|
||||
"""查询和平小店绑定信息(bindInfo)。"""
|
||||
"""查询和平小店绑定信息(bindInfo)。
|
||||
|
||||
仅查询展示,不回写账号;确认绑定由 confirm_xpd_bind 任务完成。
|
||||
"""
|
||||
client = self._client(cookie)
|
||||
act_alias = str(config.get("xpd_act_alias") or "").strip()
|
||||
if not act_alias:
|
||||
self._mark_task(db, task, "failed", "请先配置小店活动代号 actAlias")
|
||||
return
|
||||
result = client.xpd_bind_info(act_alias=act_alias)
|
||||
role_name = str(result.get("role_name") or "")
|
||||
if role_name:
|
||||
account.xpd_game_name = role_name
|
||||
account.xpd_bind_status = "xpd_bound" if result.get("bind_role") else "xpd_not_bound"
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
db.commit()
|
||||
status = "已绑定" if result.get("bind_role") else "未绑定"
|
||||
text = str(result.get("role_name") or result.get("nick") or "-")
|
||||
self._mark_task(
|
||||
@@ -2593,6 +2627,7 @@ class DouyuBatchRunner:
|
||||
"prefetch_csrf_token": self._execute_prefetch_csrf_token,
|
||||
"get_xpd_bind_qr": self._execute_get_xpd_bind_qr,
|
||||
"query_xpd_bind_info": self._execute_query_xpd_bind_info,
|
||||
"confirm_xpd_bind": self._execute_confirm_xpd_bind,
|
||||
"query_xpd_role": self._execute_query_xpd_role,
|
||||
"refresh_xpd_goods": self._execute_refresh_xpd_goods,
|
||||
"query_xpd_balance": self._execute_query_xpd_balance,
|
||||
|
||||
@@ -40,6 +40,7 @@ SUPPORTED_DOUYU_TASK_TYPES = {
|
||||
"prefetch_csrf_token": "一键获取兑换 CSRF Token",
|
||||
"get_xpd_bind_qr": "生成小店绑定二维码",
|
||||
"query_xpd_bind_info": "查询小店绑定信息",
|
||||
"confirm_xpd_bind": "确认小店绑定",
|
||||
"query_xpd_role": "查询小店绑定角色",
|
||||
"refresh_xpd_goods": "刷新小店商品列表",
|
||||
"query_xpd_balance": "查询小店点券余额",
|
||||
|
||||
@@ -86,6 +86,7 @@ const ESPORTS_QUICK_ACTIONS = [
|
||||
const PEACE_QUICK_ACTIONS = [
|
||||
{ key: 'get_xpd_bind_qr', icon: <QrcodeOutlined /> },
|
||||
{ key: 'query_xpd_bind_info', icon: <SearchOutlined /> },
|
||||
{ key: 'confirm_xpd_bind', icon: <CheckCircleOutlined /> },
|
||||
{ key: 'query_xpd_role', icon: <SearchOutlined /> },
|
||||
{ key: 'refresh_xpd_goods', icon: <ReloadOutlined /> },
|
||||
{ key: 'query_xpd_balance', icon: <SearchOutlined /> },
|
||||
@@ -126,6 +127,7 @@ const ESPORTS_TASK_TYPES = new Set([
|
||||
const PEACE_TASK_TYPES = new Set([
|
||||
'get_xpd_bind_qr',
|
||||
'query_xpd_bind_info',
|
||||
'confirm_xpd_bind',
|
||||
'query_xpd_role',
|
||||
'refresh_xpd_goods',
|
||||
'query_xpd_balance',
|
||||
@@ -164,7 +166,13 @@ function taskQrUrl(task: DouyuTaskItem | null | undefined): string {
|
||||
}
|
||||
|
||||
function bindReadyForConfirm(task: DouyuTaskItem | null | undefined): boolean {
|
||||
if (!task || task.task_type !== 'get_bind_qr') return false;
|
||||
if (!task) return false;
|
||||
if (task.task_type === 'get_xpd_bind_qr') {
|
||||
if (resultFlag(task.result, 'xpd_bound') || resultFlag(task.result, 'bind_confirmed')) return false;
|
||||
// 轮询识别到角色后停在待确认(xpd_pending_confirm),有角色名即可确认绑定
|
||||
return Boolean(resultText(task.result, 'role_name'));
|
||||
}
|
||||
if (task.task_type !== 'get_bind_qr') return false;
|
||||
if (task.result?.bind_ready_for_confirm === true) return true;
|
||||
const roleName = resultText(task.result, 'role_name');
|
||||
if (!roleName) return false;
|
||||
@@ -389,6 +397,26 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
return map;
|
||||
}, [tasks]);
|
||||
|
||||
const latestXpdQueryTaskByAccount = useMemo(() => {
|
||||
const map = new Map<number, DouyuTaskItem>();
|
||||
for (const task of tasks) {
|
||||
if (!['query_xpd_bind_info', 'query_xpd_role'].includes(task.task_type)) continue;
|
||||
const previous = map.get(task.account_id);
|
||||
if (!previous || task.id > previous.id) map.set(task.account_id, task);
|
||||
}
|
||||
return map;
|
||||
}, [tasks]);
|
||||
|
||||
const latestConfirmXpdBindTaskByAccount = useMemo(() => {
|
||||
const map = new Map<number, DouyuTaskItem>();
|
||||
for (const task of tasks) {
|
||||
if (task.task_type !== 'confirm_xpd_bind') continue;
|
||||
const previous = map.get(task.account_id);
|
||||
if (!previous || task.id > previous.id) map.set(task.account_id, task);
|
||||
}
|
||||
return map;
|
||||
}, [tasks]);
|
||||
|
||||
const latestConfirmBindFailureByAccount = useMemo(() => {
|
||||
const map = new Map<number, DouyuTaskItem>();
|
||||
for (const task of tasks) {
|
||||
@@ -1033,15 +1061,23 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
const qrUrl = resultText(qrResult, 'url');
|
||||
const qrBindPhase = resultText(qrResult, 'bind_phase');
|
||||
const qrAutoPolling = qrTask?.status === 'running' && qrResult?.bind_polling === true;
|
||||
const isXpdBindTask = qrTask?.task_type === 'get_xpd_bind_qr';
|
||||
const qrQueryTask = qrTask
|
||||
? latestQueryGameTaskByAccount.get(qrTask.account_id) || null
|
||||
? (isXpdBindTask
|
||||
? (latestXpdQueryTaskByAccount.get(qrTask.account_id) || null)
|
||||
: (latestQueryGameTaskByAccount.get(qrTask.account_id) || null))
|
||||
: null;
|
||||
const qrQueryResult = qrTask && qrQueryTask && qrQueryTask.id > qrTask.id ? qrQueryTask.result : null;
|
||||
const qrQueryRunning = Boolean(qrQueryTask && qrTask && qrQueryTask.id > qrTask.id && qrQueryTask.status === 'running');
|
||||
const qrXpdConfirmTask = isXpdBindTask && qrTask
|
||||
? (latestConfirmXpdBindTaskByAccount.get(qrTask.account_id) || null)
|
||||
: null;
|
||||
const qrXpdBound = Boolean(qrXpdConfirmTask && qrXpdConfirmTask.status === 'success');
|
||||
const qrXpdConfirmRunning = Boolean(qrXpdConfirmTask && ['planned', 'pending', 'running'].includes(qrXpdConfirmTask.status));
|
||||
const qrQueryRoleName = resultText(qrQueryResult, 'role_name');
|
||||
const qrQueryPendingRoleName = resultText(qrQueryResult, 'pending_role_name');
|
||||
const qrQueryIsBoundAct = resultFlag(qrQueryResult, 'is_bound_act') || resultFlag(qrQueryResult, 'bind_confirmed');
|
||||
const qrQueryPending = Boolean(
|
||||
const qrQueryPending = !isXpdBindTask && Boolean(
|
||||
qrQueryResult
|
||||
&& (
|
||||
qrQueryPendingRoleName
|
||||
@@ -1054,12 +1090,10 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
)
|
||||
),
|
||||
);
|
||||
const isXpdBindTask = qrTask?.task_type === 'get_xpd_bind_qr';
|
||||
const qrXpdBound = Boolean(isXpdBindTask && resultFlag(qrResult, 'xpd_bound'));
|
||||
const qrBindReady = bindReadyForConfirm(qrTask) || qrQueryPending || qrXpdBound;
|
||||
const qrBindReady = (bindReadyForConfirm(qrTask) || qrQueryPending) && !qrXpdBound;
|
||||
const qrRoleSourceResult = qrQueryPending
|
||||
? qrQueryResult
|
||||
: (bindReadyForConfirm(qrTask) || qrXpdBound ? 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');
|
||||
@@ -1098,17 +1132,23 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
? [qrPlatName, qrAreaName, qrRoleName].filter(Boolean).join(' - ')
|
||||
: '';
|
||||
const qrBindSummary = resultText(qrResult, 'bind_summary') || resultText(qrQueryResult, 'bind_summary');
|
||||
const qrStatusText = qrBindReady
|
||||
? (isXpdBindTask ? '绑定成功' : '已识别角色,待确认')
|
||||
: (isXpdBindTask && qrTask?.status === 'failed')
|
||||
? '未检测到绑定'
|
||||
: (isXpdBindTask && qrTask?.status === 'running')
|
||||
? '等待扫码绑定'
|
||||
: qrQueryRunning
|
||||
? '查询角色中'
|
||||
: qrQueryTask && qrQueryTask.id > (qrTask?.id || 0) && qrQueryTask.status === 'success' && qrQueryIsBoundAct
|
||||
? '查询结果:仍是当前绑定角色'
|
||||
: bindPhaseText(qrBindPhase, qrAutoPolling);
|
||||
const qrStatusText = qrXpdBound
|
||||
? '绑定成功'
|
||||
: (isXpdBindTask && qrBindReady)
|
||||
? '已识别角色,待确认'
|
||||
: (isXpdBindTask && qrTask?.status === 'failed')
|
||||
? '未检测到绑定'
|
||||
: (isXpdBindTask && qrTask?.status === 'running')
|
||||
? '等待扫码绑定'
|
||||
: qrXpdConfirmRunning
|
||||
? '正在确认绑定'
|
||||
: qrBindReady
|
||||
? '已识别角色,待确认'
|
||||
: 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}`)
|
||||
: '';
|
||||
@@ -1121,7 +1161,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
const accountId = qrTask.account_id;
|
||||
const taskId = qrTask.id;
|
||||
closeQrTask(taskId);
|
||||
void startTask('confirm_bind', [accountId]);
|
||||
void startTask(isXpdBindTask ? 'confirm_xpd_bind' : 'confirm_bind', [accountId]);
|
||||
};
|
||||
|
||||
const queryQrRole = () => {
|
||||
@@ -1130,7 +1170,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
message.info('正在自动识别角色,请稍候');
|
||||
return;
|
||||
}
|
||||
void startTask('query_game_name', [qrTask.account_id]);
|
||||
void startTask(isXpdBindTask ? 'query_xpd_bind_info' : 'query_game_name', [qrTask.account_id]);
|
||||
};
|
||||
|
||||
// Account table
|
||||
@@ -1463,6 +1503,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={6}>
|
||||
{renderActionButton('get_xpd_bind_qr', 'primary')}
|
||||
{renderActionButton('query_xpd_bind_info', 'primary')}
|
||||
{renderActionButton('confirm_xpd_bind', 'primary')}
|
||||
{renderActionButton('query_xpd_role', 'primary')}
|
||||
{renderActionButton('query_xpd_balance', 'primary')}
|
||||
{renderActionButton('refresh_xpd_goods')}
|
||||
@@ -2081,7 +2122,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
<Text type="secondary" style={{ marginBottom: 12, display: 'block' }}>{qrAccountName}</Text>
|
||||
)}
|
||||
<Space direction="vertical" align="center" size={12}>
|
||||
<Tag color={qrBindReady ? 'gold' : qrBindPhase === 'role_timeout' ? 'orange' : 'processing'}>
|
||||
<Tag color={qrXpdBound ? 'success' : qrBindReady ? 'gold' : qrBindPhase === 'role_timeout' ? 'orange' : 'processing'}>
|
||||
{qrStatusText}
|
||||
</Tag>
|
||||
{qrUrl ? (
|
||||
@@ -2096,7 +2137,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
<Text type="secondary">本次未生成二维码</Text>
|
||||
)}
|
||||
<QrActions wrapRef={qrCanvasWrapRef} url={qrUrl} disabled={!qrUrl} />
|
||||
{qrBindReady ? (
|
||||
{qrBindReady || qrXpdBound ? (
|
||||
<>
|
||||
<Tag color="gold" style={{ fontSize: 16, padding: '6px 16px' }}>
|
||||
{qrRoleName}
|
||||
@@ -2125,26 +2166,24 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
最近查询: {qrQueryTask.message}
|
||||
</Text>
|
||||
) : null}
|
||||
{!isXpdBindTask && (
|
||||
<Space>
|
||||
<Button
|
||||
icon={<SearchOutlined />}
|
||||
onClick={queryQrRole}
|
||||
loading={qrQueryRunning}
|
||||
disabled={qrAutoPolling}
|
||||
>
|
||||
{qrAutoPolling ? '自动识别中' : '查询角色'}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<CheckCircleOutlined />}
|
||||
onClick={confirmQrBind}
|
||||
disabled={!qrBindReady}
|
||||
>
|
||||
确认绑定
|
||||
</Button>
|
||||
</Space>
|
||||
)}
|
||||
<Space>
|
||||
<Button
|
||||
icon={<SearchOutlined />}
|
||||
onClick={queryQrRole}
|
||||
loading={qrQueryRunning}
|
||||
disabled={qrAutoPolling}
|
||||
>
|
||||
{qrAutoPolling ? '自动识别中' : '查询角色'}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<CheckCircleOutlined />}
|
||||
onClick={confirmQrBind}
|
||||
disabled={!qrBindReady}
|
||||
>
|
||||
确认绑定
|
||||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user