新增电竞手册独立绑定流程
This commit is contained in:
@@ -277,6 +277,53 @@ class DouyuBatchRunner:
|
||||
account.bind_status = status
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
|
||||
def _apply_esports_bind_info_to_account(self, account: Account, bind_info: dict, status: str) -> None:
|
||||
"""将电竞手册角色状态写入专属字段,避免覆盖精英宝典数据。"""
|
||||
role_name = str(bind_info.get("role_name") or "")
|
||||
account.esports_game_name = role_name or account.esports_game_name
|
||||
account.esports_game_channel = self._role_channel(bind_info) or account.esports_game_channel
|
||||
account.esports_change_role_wait_time = self._to_int(bind_info.get("change_role_wait_time"))
|
||||
account.esports_bind_status = status
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
|
||||
def _esports_bind_state(
|
||||
self,
|
||||
client: DouyuActivityClient,
|
||||
act_alias: str,
|
||||
) -> dict:
|
||||
"""查询电竞手册活动状态,接口同时返回当前角色和换绑冷却信息。"""
|
||||
activity_info = client.esports_bind_info(act_alias)
|
||||
activity_snapshot = self._bind_snapshot(activity_info)
|
||||
esports_bound = activity_snapshot["is_bound_act"]
|
||||
has_selected_role = bool(activity_snapshot["role_name"])
|
||||
tx_act = activity_info.get("tx_act") or {}
|
||||
return {
|
||||
**activity_snapshot,
|
||||
"is_bound_role": has_selected_role,
|
||||
"act_alias": act_alias,
|
||||
"game_id": tx_act.get("gameId") or "",
|
||||
"esports_bound": esports_bound,
|
||||
"has_selected_role": has_selected_role,
|
||||
"bind_ready_for_confirm": has_selected_role and not esports_bound,
|
||||
"bind_confirmed": esports_bound,
|
||||
"bind_phase": "confirmed" if esports_bound else ("role_ready" if has_selected_role else "waiting_role"),
|
||||
"activity_bind_info": activity_info,
|
||||
"activity_bind_snapshot": activity_snapshot,
|
||||
"role_source": "activity",
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _esports_role_text(state: dict) -> str:
|
||||
role_name = str(state.get("role_name") or "")
|
||||
channel = " / ".join(
|
||||
str(part)
|
||||
for part in [state.get("plat_name"), state.get("area_name")]
|
||||
if part
|
||||
)
|
||||
if not role_name:
|
||||
return ""
|
||||
return f"{role_name}({channel})" if channel else role_name
|
||||
|
||||
def _mark_task(
|
||||
self,
|
||||
db: Session,
|
||||
@@ -415,6 +462,100 @@ class DouyuBatchRunner:
|
||||
result["points"] = last_points
|
||||
return False
|
||||
|
||||
def _refresh_esports_handbook(
|
||||
self,
|
||||
client: DouyuActivityClient,
|
||||
account: Account,
|
||||
*,
|
||||
manual_id: str,
|
||||
) -> dict:
|
||||
"""刷新电竞手册开通状态并将积分写回账号。"""
|
||||
result = client.esports_user_info(manual_id=manual_id)
|
||||
manual_type = self._to_int(result.get("manual_type"))
|
||||
manual_score = self._to_int(result.get("manual_score"))
|
||||
account.esports_points = manual_score
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
return {
|
||||
"esports_manual_type": manual_type,
|
||||
"esports_manual_score": manual_score,
|
||||
"esports_expire_time": result.get("expire_time"),
|
||||
"esports_user_info": result,
|
||||
"esports_points": manual_score,
|
||||
"points": manual_score,
|
||||
}
|
||||
|
||||
def _wait_esports_open_after_payment(
|
||||
self,
|
||||
db: Session,
|
||||
task: DouyuTask,
|
||||
account: Account,
|
||||
client: DouyuActivityClient,
|
||||
*,
|
||||
manual_id: str,
|
||||
result: dict,
|
||||
baseline_manual_type: int | None,
|
||||
baseline_manual_score: int | None,
|
||||
) -> bool:
|
||||
"""等待电竞手册支付到账,以 manualType=1 或积分变化作为成功条件。"""
|
||||
deadline = time.monotonic() + DOUYU_PAYMENT_POLL_SECONDS
|
||||
result["payment_polling"] = True
|
||||
result["esports_manual_type_baseline"] = baseline_manual_type
|
||||
result["esports_manual_score_baseline"] = baseline_manual_score
|
||||
poll_count = 0
|
||||
last_manual_type = baseline_manual_type
|
||||
last_manual_score = baseline_manual_score
|
||||
|
||||
while not self._stop.is_set() and time.monotonic() <= deadline:
|
||||
try:
|
||||
handbook_result = self._refresh_esports_handbook(
|
||||
client,
|
||||
account,
|
||||
manual_id=manual_id,
|
||||
)
|
||||
db.commit()
|
||||
poll_count += 1
|
||||
last_manual_type = handbook_result["esports_manual_type"]
|
||||
last_manual_score = handbook_result["esports_manual_score"]
|
||||
result.update(handbook_result)
|
||||
result["payment_poll_count"] = poll_count
|
||||
result["payment_polling"] = True
|
||||
opened = (
|
||||
last_manual_type is not None
|
||||
and last_manual_type >= 1
|
||||
) or (
|
||||
baseline_manual_score is not None
|
||||
and last_manual_score is not None
|
||||
and last_manual_score > baseline_manual_score
|
||||
)
|
||||
if opened:
|
||||
result["payment_polling"] = False
|
||||
result["esports_opened"] = True
|
||||
return True
|
||||
self._update_task_progress(
|
||||
db,
|
||||
task,
|
||||
"running",
|
||||
"电竞手册支付码已生成,等待开通到账"
|
||||
f"(类型 {last_manual_type if last_manual_type is not None else '-'},"
|
||||
f"积分 {last_manual_score if last_manual_score is not None else '-'})",
|
||||
result,
|
||||
)
|
||||
except Exception as exc:
|
||||
poll_count += 1
|
||||
result["payment_poll_count"] = poll_count
|
||||
result["payment_poll_error"] = str(exc)
|
||||
self._update_task_progress(db, task, "running", f"等待电竞手册到账: {exc}", result)
|
||||
if self._stop.wait(DOUYU_PAYMENT_POLL_INTERVAL):
|
||||
break
|
||||
|
||||
result["payment_polling"] = False
|
||||
result["esports_opened"] = False
|
||||
result["esports_manual_type"] = last_manual_type
|
||||
result["esports_manual_score"] = last_manual_score
|
||||
result["esports_points"] = last_manual_score
|
||||
result["points"] = last_manual_score
|
||||
return False
|
||||
|
||||
def _wait_gold_balance_after_payment(
|
||||
self,
|
||||
db: Session,
|
||||
@@ -1093,6 +1234,257 @@ class DouyuBatchRunner:
|
||||
result,
|
||||
)
|
||||
|
||||
def _execute_prepare_esports_bind(
|
||||
self,
|
||||
db: Session,
|
||||
task: DouyuTask,
|
||||
account: Account,
|
||||
cookie: str,
|
||||
config: dict,
|
||||
):
|
||||
"""打开电竞手册绑定面板:查活动状态、当前角色和换绑冷却。"""
|
||||
client = self._client(cookie)
|
||||
act_alias = str(config.get("esports_act_alias") or "").strip()
|
||||
if not act_alias:
|
||||
self._mark_task(db, task, "failed", "请先配置电竞手册活动 actAlias")
|
||||
return
|
||||
|
||||
state = self._esports_bind_state(client, act_alias)
|
||||
esports_bound = state["esports_bound"]
|
||||
role_text = self._esports_role_text(state)
|
||||
self._apply_esports_bind_info_to_account(
|
||||
account,
|
||||
state,
|
||||
"esports_bound" if esports_bound else ("esports_bind_ready" if role_text else "game_not_bound"),
|
||||
)
|
||||
# 二维码用于重新选择角色,不应因为已有角色或换绑冷却而隐藏。
|
||||
# 冷却是否允许最终由 actBind 返回结果决定。
|
||||
qr_result = client.get_esports_bind_qr(act_alias)
|
||||
result = {
|
||||
**state,
|
||||
**qr_result,
|
||||
"esports_bind_dialog": True,
|
||||
"can_open_role_selector": True,
|
||||
}
|
||||
if esports_bound:
|
||||
message = f"电竞手册已绑定: {role_text or '-'},可扫码换绑"
|
||||
elif role_text:
|
||||
message = f"当前角色: {role_text},可扫码切换角色或直接完成绑定"
|
||||
else:
|
||||
message = "请扫码选择游戏角色,完成后查询最新角色"
|
||||
self._mark_task(db, task, "success", message, result)
|
||||
|
||||
def _execute_get_esports_bind_qr(
|
||||
self,
|
||||
db: Session,
|
||||
task: DouyuTask,
|
||||
account: Account,
|
||||
cookie: str,
|
||||
config: dict,
|
||||
):
|
||||
"""生成电竞手册切换角色用的腾讯入口。"""
|
||||
client = self._client(cookie)
|
||||
act_alias = str(config.get("esports_act_alias") or "").strip()
|
||||
if not act_alias:
|
||||
self._mark_task(db, task, "failed", "请先配置电竞手册活动 actAlias")
|
||||
return
|
||||
|
||||
state = self._esports_bind_state(client, act_alias)
|
||||
qr_result = client.get_esports_bind_qr(act_alias)
|
||||
result = {
|
||||
**state,
|
||||
**qr_result,
|
||||
"esports_bind_dialog": True,
|
||||
"can_open_role_selector": True,
|
||||
"bind_phase": "switching_role",
|
||||
}
|
||||
account.esports_bind_status = "esports_role_switching"
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
self._mark_task(
|
||||
db,
|
||||
task,
|
||||
"success",
|
||||
"请在腾讯页面选择角色,返回后查询最新角色",
|
||||
result,
|
||||
)
|
||||
|
||||
def _execute_confirm_esports_bind(
|
||||
self,
|
||||
db: Session,
|
||||
task: DouyuTask,
|
||||
account: Account,
|
||||
cookie: str,
|
||||
config: dict,
|
||||
):
|
||||
"""通过 actBind 确认电竞手册绑定,并回读唯一状态接口确认结果。"""
|
||||
client = self._client(cookie)
|
||||
act_alias = str(config.get("esports_act_alias") or "").strip()
|
||||
if not act_alias:
|
||||
self._mark_task(db, task, "failed", "请先配置电竞手册活动 actAlias")
|
||||
return
|
||||
|
||||
account.esports_bind_status = "esports_bind_confirming"
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
self._update_task_progress(
|
||||
db,
|
||||
task,
|
||||
"running",
|
||||
"正在确认电竞手册绑定",
|
||||
{
|
||||
"bind_phase": "confirming",
|
||||
"bind_confirmed": False,
|
||||
},
|
||||
)
|
||||
confirm_result = client.confirm_esports_bind(
|
||||
act_alias,
|
||||
room_id=str(config.get("room_id") or "9263298"),
|
||||
)
|
||||
after_state = self._esports_bind_state(client, act_alias)
|
||||
result = {
|
||||
**after_state,
|
||||
"confirm": confirm_result,
|
||||
"bind_phase": "confirmed" if after_state["esports_bound"] else "confirm_failed",
|
||||
}
|
||||
if not after_state["esports_bound"]:
|
||||
self._apply_esports_bind_info_to_account(account, after_state, "esports_bind_ready")
|
||||
self._mark_task(
|
||||
db,
|
||||
task,
|
||||
"failed",
|
||||
f"电竞手册绑定未生效: {self._esports_role_text(after_state) or '-'}",
|
||||
result,
|
||||
)
|
||||
return
|
||||
|
||||
self._apply_esports_bind_info_to_account(account, after_state, "esports_bound")
|
||||
self._mark_task(
|
||||
db,
|
||||
task,
|
||||
"success",
|
||||
f"电竞手册绑定成功: {self._esports_role_text(after_state) or '-'}",
|
||||
result,
|
||||
)
|
||||
|
||||
def _execute_query_esports_game_name(
|
||||
self,
|
||||
db: Session,
|
||||
task: DouyuTask,
|
||||
account: Account,
|
||||
cookie: str,
|
||||
config: dict,
|
||||
):
|
||||
"""查询电竞手册活动返回的最新角色和换绑冷却状态。"""
|
||||
client = self._client(cookie)
|
||||
act_alias = str(config.get("esports_act_alias") or "").strip()
|
||||
if not act_alias:
|
||||
self._mark_task(db, task, "failed", "请先配置电竞手册活动 actAlias")
|
||||
return
|
||||
|
||||
state = self._esports_bind_state(client, act_alias)
|
||||
role_name = state["role_name"]
|
||||
is_bound = state["esports_bound"]
|
||||
self._apply_esports_bind_info_to_account(
|
||||
account,
|
||||
state,
|
||||
"esports_bound" if is_bound else ("esports_bind_ready" if role_name else "game_not_bound"),
|
||||
)
|
||||
result = {
|
||||
**state,
|
||||
"esports_bind_dialog": True,
|
||||
"can_open_role_selector": True,
|
||||
}
|
||||
if role_name:
|
||||
message = f"最新角色: {self._esports_role_text(state)}"
|
||||
else:
|
||||
message = "未查询到游戏角色,请先切换角色"
|
||||
self._mark_task(db, task, "success", message, result)
|
||||
|
||||
def _execute_create_esports_qr(
|
||||
self,
|
||||
db: Session,
|
||||
task: DouyuTask,
|
||||
account: Account,
|
||||
cookie: str,
|
||||
config: dict,
|
||||
):
|
||||
"""生成电竞手册支付二维码,并通过活动状态确认开通到账。"""
|
||||
client = self._client(cookie)
|
||||
ctn = str(self._task_payload(task).get("ctn") or "")
|
||||
if not ctn:
|
||||
ctn = client.acf_ccn(refresh_subscribe=True)
|
||||
act_alias = str(config.get("esports_act_alias") or "").strip()
|
||||
manual_id = str(config.get("esports_manual_id") or "").strip()
|
||||
if not act_alias or not manual_id:
|
||||
self._mark_task(db, task, "failed", "请先配置电竞手册活动 actAlias 和 manualID")
|
||||
return
|
||||
|
||||
baseline_manual_type = None
|
||||
baseline_manual_score = None
|
||||
baseline_result: dict = {}
|
||||
try:
|
||||
baseline_result = self._refresh_esports_handbook(client, account, manual_id=manual_id)
|
||||
baseline_manual_type = baseline_result["esports_manual_type"]
|
||||
baseline_manual_score = baseline_result["esports_manual_score"]
|
||||
db.commit()
|
||||
except Exception as exc:
|
||||
self._push_log("warning", f"生成电竞手册支付码前查询活动状态失败: {exc}")
|
||||
|
||||
if baseline_manual_type is not None and baseline_manual_type >= 1:
|
||||
account.esports_bind_status = "esports_opened"
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
self._mark_task(
|
||||
db,
|
||||
task,
|
||||
"success",
|
||||
f"电竞手册已开通,积分: {baseline_manual_score if baseline_manual_score is not None else '-'}",
|
||||
{**baseline_result, "esports_opened": True, "payment_polling": False},
|
||||
)
|
||||
return
|
||||
|
||||
result = client.create_esports_qr(
|
||||
ctn=ctn,
|
||||
act_alias=act_alias,
|
||||
amount=int(config["esports_amount"]),
|
||||
room_id=str(config["room_id"]),
|
||||
)
|
||||
result.update(baseline_result)
|
||||
account.esports_bind_status = "esports_qr_created"
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
self._update_task_progress(db, task, "running", "电竞手册支付码已生成,等待开通到账", result)
|
||||
opened = self._wait_esports_open_after_payment(
|
||||
db,
|
||||
task,
|
||||
account,
|
||||
client,
|
||||
manual_id=manual_id,
|
||||
result=result,
|
||||
baseline_manual_type=baseline_manual_type,
|
||||
baseline_manual_score=baseline_manual_score,
|
||||
)
|
||||
if self._stop.is_set():
|
||||
self._mark_task(db, task, "stopped", "任务已停止", result)
|
||||
return
|
||||
if opened:
|
||||
account.esports_bind_status = "esports_opened"
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
self._mark_task(
|
||||
db,
|
||||
task,
|
||||
"success",
|
||||
f"电竞手册已开通,积分: {account.esports_points if account.esports_points is not None else '-'}",
|
||||
result,
|
||||
)
|
||||
return
|
||||
self._mark_task(
|
||||
db,
|
||||
task,
|
||||
"failed",
|
||||
"未检测到电竞手册开通到账"
|
||||
f",类型: {result.get('esports_manual_type', '-')},"
|
||||
f"积分: {result.get('esports_manual_score', '-')}",
|
||||
result,
|
||||
)
|
||||
|
||||
def _execute_create_gold_qr(self, db: Session, task: DouyuTask, account: Account, cookie: str, config: dict):
|
||||
payload = self._task_payload(task)
|
||||
amount = int(payload.get("amount") or payload.get("gold_amount") or 1)
|
||||
@@ -1474,6 +1866,11 @@ class DouyuBatchRunner:
|
||||
"get_bind_qr": self._execute_get_bind_qr,
|
||||
"confirm_bind": self._execute_confirm_bind,
|
||||
"create_elite_qr": self._execute_create_elite_qr,
|
||||
"prepare_esports_bind": self._execute_prepare_esports_bind,
|
||||
"get_esports_bind_qr": self._execute_get_esports_bind_qr,
|
||||
"query_esports_game_name": self._execute_query_esports_game_name,
|
||||
"confirm_esports_bind": self._execute_confirm_esports_bind,
|
||||
"create_esports_qr": self._execute_create_esports_qr,
|
||||
"create_gold_qr": self._execute_create_gold_qr,
|
||||
"donate_elite_gift": self._execute_donate_elite_gift,
|
||||
"query_points": self._execute_query_points,
|
||||
|
||||
Reference in New Issue
Block a user