优化斗鱼任务操作体验
This commit is contained in:
@@ -31,6 +31,8 @@ DOUYU_BIND_ROLE_POLL_SECONDS = 65
|
||||
DOUYU_BIND_ROLE_POLL_INTERVAL = 5
|
||||
DOUYU_PAYMENT_POLL_SECONDS = 600
|
||||
DOUYU_PAYMENT_POLL_INTERVAL = 5
|
||||
DOUYU_GIFT_POINTS_REFRESH_TIMES = 3
|
||||
DOUYU_GIFT_POINTS_REFRESH_INTERVAL = 2
|
||||
|
||||
|
||||
class DouyuBatchRunner:
|
||||
@@ -476,6 +478,51 @@ class DouyuBatchRunner:
|
||||
result["gold_balance"] = last_gold
|
||||
return False
|
||||
|
||||
def _refresh_points_after_elite_gift(
|
||||
self,
|
||||
db: Session,
|
||||
task: DouyuTask,
|
||||
account: Account,
|
||||
client: DouyuActivityClient,
|
||||
cookie: str,
|
||||
ctn: str | None,
|
||||
result: dict,
|
||||
baseline_points: int | None,
|
||||
gift_count: int,
|
||||
) -> dict:
|
||||
"""赠送精英令后短轮询积分;1 个精英令约等于 10 积分。"""
|
||||
expected_gain = max(0, gift_count) * 10
|
||||
target_points = baseline_points + expected_gain if baseline_points is not None else None
|
||||
result["gift_points_baseline"] = baseline_points
|
||||
result["gift_points_expected_gain"] = expected_gain
|
||||
result["gift_points_target"] = target_points
|
||||
|
||||
last_points = None
|
||||
refresh_result: dict = {}
|
||||
for index in range(1, DOUYU_GIFT_POINTS_REFRESH_TIMES + 1):
|
||||
refresh_result = self._refresh_account_points(client, account, cookie, ctn=ctn)
|
||||
db.commit()
|
||||
last_points = refresh_result["points"]
|
||||
result.update(refresh_result)
|
||||
result["gift_points_refresh_count"] = index
|
||||
if target_points is None or (last_points is not None and last_points >= target_points):
|
||||
result["gift_points_confirmed"] = target_points is None or last_points is not None
|
||||
return refresh_result
|
||||
if index < DOUYU_GIFT_POINTS_REFRESH_TIMES:
|
||||
self._update_task_progress(
|
||||
db,
|
||||
task,
|
||||
"running",
|
||||
f"赠送精英令成功,等待积分同步(当前 {last_points if last_points is not None else '-'},预期 {target_points})",
|
||||
result,
|
||||
)
|
||||
if self._stop.wait(DOUYU_GIFT_POINTS_REFRESH_INTERVAL):
|
||||
break
|
||||
|
||||
result["gift_points_confirmed"] = False
|
||||
result["points"] = last_points
|
||||
return refresh_result
|
||||
|
||||
def _task_payload(self, task: DouyuTask) -> dict:
|
||||
result = task.result if isinstance(task.result, dict) else {}
|
||||
payload = result.get("payload") if isinstance(result.get("payload"), dict) else {}
|
||||
@@ -1088,20 +1135,41 @@ class DouyuBatchRunner:
|
||||
payload = self._task_payload(task)
|
||||
gift_count = int(payload.get("gift_count") or payload.get("count") or 1)
|
||||
client = self._client(cookie)
|
||||
ctn = None
|
||||
baseline_points = account.points
|
||||
try:
|
||||
ctn = client.acf_ccn(refresh_subscribe=False)
|
||||
baseline_result = self._refresh_account_points(client, account, cookie, ctn=ctn)
|
||||
db.commit()
|
||||
baseline_points = baseline_result["points"]
|
||||
except Exception as exc:
|
||||
self._push_log("warning", f"赠送精英令前刷新积分失败: {exc}")
|
||||
result = client.donate_elite_gift(
|
||||
gift_count=gift_count,
|
||||
room_id=str(payload.get("room_id") or config["room_id"]),
|
||||
gift_id=str(payload.get("gift_id") or config["gift_id"]),
|
||||
skin_id=str(payload.get("skin_id") or config["skin_id"]),
|
||||
)
|
||||
result["gift_points_baseline"] = baseline_points
|
||||
refresh_errors = []
|
||||
try:
|
||||
result.update(self._refresh_account_gold_balance(client, account))
|
||||
except Exception as exc:
|
||||
refresh_errors.append(f"鱼翅余额: {exc}")
|
||||
try:
|
||||
ctn = client.acf_ccn(refresh_subscribe=False)
|
||||
result.update(self._refresh_account_points(client, account, cookie, ctn=ctn))
|
||||
result.update(
|
||||
self._refresh_points_after_elite_gift(
|
||||
db,
|
||||
task,
|
||||
account,
|
||||
client,
|
||||
cookie,
|
||||
ctn,
|
||||
result,
|
||||
baseline_points,
|
||||
gift_count,
|
||||
)
|
||||
)
|
||||
except Exception as exc:
|
||||
refresh_errors.append(f"积分: {exc}")
|
||||
if refresh_errors:
|
||||
@@ -1113,6 +1181,8 @@ class DouyuBatchRunner:
|
||||
message += f",鱼翅余额: {account.gold_balance}"
|
||||
if account.points is not None:
|
||||
message += f",积分: {account.points}"
|
||||
if result.get("gift_points_target") is not None and not result.get("gift_points_confirmed"):
|
||||
message += f"(未确认涨到 {result['gift_points_target']})"
|
||||
self._mark_task(db, task, "success", message, result)
|
||||
|
||||
def _execute_query_points(self, db: Session, task: DouyuTask, account: Account, cookie: str, config: dict):
|
||||
|
||||
@@ -40,6 +40,8 @@ const QUICK_ACTIONS = [
|
||||
|
||||
const BIND_ACTION_KEYS = ['get_bind_qr', 'confirm_bind', 'query_game_name', 'query_change_bind_time'];
|
||||
const QUERY_ACTION_KEYS = ['query_points', 'query_limited_goods', 'query_gold_balance'];
|
||||
const DOUYU_GOLD_AMOUNT_STORAGE_KEY = 'douyu_task_gold_amount';
|
||||
const DOUYU_GIFT_COUNT_STORAGE_KEY = 'douyu_task_gift_count';
|
||||
|
||||
const STATUS_COLORS: Record<string, string> = {
|
||||
planned: 'default', pending: 'default', running: 'processing',
|
||||
@@ -98,6 +100,12 @@ function hasPaymentQrcode(task: DouyuTaskItem | null | undefined): boolean {
|
||||
return Boolean(task && ['create_elite_qr', 'create_gold_qr'].includes(task.task_type) && resultText(task.result, 'pay_url'));
|
||||
}
|
||||
|
||||
function paymentArrived(task: DouyuTaskItem | null | undefined): boolean {
|
||||
if (!task || !['create_elite_qr', 'create_gold_qr'].includes(task.task_type)) return false;
|
||||
if (task.status !== 'success') return false;
|
||||
return task.result?.gold_recharged === true || task.result?.elite_opened === true;
|
||||
}
|
||||
|
||||
function bindPhaseText(phase: string, polling: boolean): string {
|
||||
if (phase === 'role_ready') return '已识别角色,待确认';
|
||||
if (phase === 'change_waiting') return '换绑冷却中';
|
||||
@@ -136,6 +144,11 @@ function resultNumber(result: Record<string, unknown> | null | undefined, key: s
|
||||
return null;
|
||||
}
|
||||
|
||||
function savedPositiveInteger(key: string, fallback = 1): number {
|
||||
const value = Number(localStorage.getItem(key));
|
||||
return Number.isInteger(value) && value >= 1 ? value : fallback;
|
||||
}
|
||||
|
||||
export default function DouyuTasksPage() {
|
||||
const { can } = usePermissions();
|
||||
const [accounts, setAccounts] = useState<DouyuTaskAccountItem[]>([]);
|
||||
@@ -148,8 +161,8 @@ export default function DouyuTasksPage() {
|
||||
const [runningBatchId, setRunningBatchId] = useState('');
|
||||
const [configOpen, setConfigOpen] = useState(false);
|
||||
const [concurrency, setConcurrency] = useState(3);
|
||||
const [goldAmount, setGoldAmount] = useState(1);
|
||||
const [giftCount, setGiftCount] = useState(1);
|
||||
const [goldAmount, setGoldAmount] = useState(() => savedPositiveInteger(DOUYU_GOLD_AMOUNT_STORAGE_KEY));
|
||||
const [giftCount, setGiftCount] = useState(() => savedPositiveInteger(DOUYU_GIFT_COUNT_STORAGE_KEY));
|
||||
const [selectedGoodsId, setSelectedGoodsId] = useState('');
|
||||
const [accountSearch, setAccountSearch] = useState('');
|
||||
|
||||
@@ -242,6 +255,14 @@ export default function DouyuTasksPage() {
|
||||
|
||||
useEffect(() => { loadData(); }, [loadData]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(DOUYU_GOLD_AMOUNT_STORAGE_KEY, String(goldAmount));
|
||||
}, [goldAmount]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(DOUYU_GIFT_COUNT_STORAGE_KEY, String(giftCount));
|
||||
}, [giftCount]);
|
||||
|
||||
const hasActiveTasks = useMemo(
|
||||
() => tasks.some((task) => ['pending', 'running', 'planned'].includes(task.status)),
|
||||
[tasks],
|
||||
@@ -279,7 +300,7 @@ export default function DouyuTasksPage() {
|
||||
useEffect(() => {
|
||||
if (!autoOpenPayReady.current || payTask) return;
|
||||
const nextPayTask = tasks
|
||||
.filter((task) => hasPaymentQrcode(task) && !autoOpenedPayTaskIds.current.has(task.id))
|
||||
.filter((task) => hasPaymentQrcode(task) && !paymentArrived(task) && !autoOpenedPayTaskIds.current.has(task.id))
|
||||
.sort((a, b) => b.id - a.id)[0];
|
||||
if (nextPayTask) openPayTask(nextPayTask);
|
||||
}, [openPayTask, payTask, tasks]);
|
||||
@@ -294,7 +315,12 @@ export default function DouyuTasksPage() {
|
||||
useEffect(() => {
|
||||
if (!payTask) return;
|
||||
const latest = tasks.find((task) => task.id === payTask.id);
|
||||
if (latest && latest !== payTask) setPayTask(latest);
|
||||
if (!latest) return;
|
||||
if (paymentArrived(latest)) {
|
||||
setPayTask(null);
|
||||
return;
|
||||
}
|
||||
if (latest !== payTask) setPayTask(latest);
|
||||
}, [payTask, tasks]);
|
||||
|
||||
const batchBusy = Boolean(runningBatchId || logs.connected || hasActiveTasks);
|
||||
@@ -749,13 +775,14 @@ export default function DouyuTasksPage() {
|
||||
gap: 12, overflow: 'hidden',
|
||||
}}>
|
||||
{/* Left: accounts + tasks */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, overflow: 'hidden' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, minHeight: 0, overflow: 'hidden' }}>
|
||||
<Card
|
||||
size="small" title="账号"
|
||||
extra={<Tag color="blue">已选 {selectedIds.length}/{accounts.length}</Tag>}
|
||||
styles={{ body: { flex: 1, overflow: 'auto', padding: 8 } }}
|
||||
style={{ flex: '1 1 0', minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}
|
||||
styles={{ body: { flex: 1, minHeight: 0, overflow: 'hidden', padding: 8, display: 'flex', flexDirection: 'column' } }}
|
||||
>
|
||||
<Space style={{ marginBottom: 8, width: '100%', justifyContent: 'space-between' }} wrap>
|
||||
<Space style={{ marginBottom: 8, width: '100%', justifyContent: 'space-between', flexShrink: 0 }} wrap>
|
||||
<Space>
|
||||
<Input
|
||||
size="small" placeholder="搜索账号/昵称/游戏名" prefix={<SearchOutlined />}
|
||||
@@ -773,6 +800,7 @@ export default function DouyuTasksPage() {
|
||||
<Table
|
||||
rowKey="id"
|
||||
size="small"
|
||||
style={{ flex: 1, minHeight: 0 }}
|
||||
loading={loading}
|
||||
rowSelection={{
|
||||
selectedRowKeys: selectedIds,
|
||||
@@ -781,7 +809,7 @@ export default function DouyuTasksPage() {
|
||||
columns={accountColumns}
|
||||
dataSource={filteredAccounts}
|
||||
pagination={{ pageSize: 15, showSizeChanger: false }}
|
||||
scroll={{ y: 'calc(100vh - 520px)' }}
|
||||
scroll={{ y: 'max(180px, calc(100vh - 650px))' }}
|
||||
onRow={(record) => ({
|
||||
onContextMenu: (e) => handleRowContextMenu(record, e),
|
||||
style: { cursor: 'context-menu' },
|
||||
@@ -798,12 +826,14 @@ export default function DouyuTasksPage() {
|
||||
<Tag color="error">失败 {tasks.filter((t) => ['failed', 'error'].includes(t.status)).length}</Tag>
|
||||
</Space>
|
||||
}
|
||||
styles={{ body: { padding: 8 } }}
|
||||
style={{ flex: '0 0 300px', minHeight: 260, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}
|
||||
styles={{ body: { flex: 1, minHeight: 0, padding: 8, overflow: 'hidden', display: 'flex', flexDirection: 'column' } }}
|
||||
>
|
||||
<Table
|
||||
rowKey="id" size="small" columns={taskColumns} dataSource={tasks}
|
||||
loading={loading} pagination={{ pageSize: 10, showSizeChanger: false }}
|
||||
scroll={{ x: 700 }}
|
||||
style={{ flex: 1, minHeight: 0 }}
|
||||
loading={loading} pagination={{ pageSize: 4, showSizeChanger: false, size: 'small' }}
|
||||
scroll={{ x: 700, y: 150 }}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user