优化斗鱼任务操作体验
This commit is contained in:
@@ -31,6 +31,8 @@ DOUYU_BIND_ROLE_POLL_SECONDS = 65
|
|||||||
DOUYU_BIND_ROLE_POLL_INTERVAL = 5
|
DOUYU_BIND_ROLE_POLL_INTERVAL = 5
|
||||||
DOUYU_PAYMENT_POLL_SECONDS = 600
|
DOUYU_PAYMENT_POLL_SECONDS = 600
|
||||||
DOUYU_PAYMENT_POLL_INTERVAL = 5
|
DOUYU_PAYMENT_POLL_INTERVAL = 5
|
||||||
|
DOUYU_GIFT_POINTS_REFRESH_TIMES = 3
|
||||||
|
DOUYU_GIFT_POINTS_REFRESH_INTERVAL = 2
|
||||||
|
|
||||||
|
|
||||||
class DouyuBatchRunner:
|
class DouyuBatchRunner:
|
||||||
@@ -476,6 +478,51 @@ class DouyuBatchRunner:
|
|||||||
result["gold_balance"] = last_gold
|
result["gold_balance"] = last_gold
|
||||||
return False
|
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:
|
def _task_payload(self, task: DouyuTask) -> dict:
|
||||||
result = task.result if isinstance(task.result, dict) else {}
|
result = task.result if isinstance(task.result, dict) else {}
|
||||||
payload = result.get("payload") if isinstance(result.get("payload"), dict) else {}
|
payload = result.get("payload") if isinstance(result.get("payload"), dict) else {}
|
||||||
@@ -1088,20 +1135,41 @@ class DouyuBatchRunner:
|
|||||||
payload = self._task_payload(task)
|
payload = self._task_payload(task)
|
||||||
gift_count = int(payload.get("gift_count") or payload.get("count") or 1)
|
gift_count = int(payload.get("gift_count") or payload.get("count") or 1)
|
||||||
client = self._client(cookie)
|
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(
|
result = client.donate_elite_gift(
|
||||||
gift_count=gift_count,
|
gift_count=gift_count,
|
||||||
room_id=str(payload.get("room_id") or config["room_id"]),
|
room_id=str(payload.get("room_id") or config["room_id"]),
|
||||||
gift_id=str(payload.get("gift_id") or config["gift_id"]),
|
gift_id=str(payload.get("gift_id") or config["gift_id"]),
|
||||||
skin_id=str(payload.get("skin_id") or config["skin_id"]),
|
skin_id=str(payload.get("skin_id") or config["skin_id"]),
|
||||||
)
|
)
|
||||||
|
result["gift_points_baseline"] = baseline_points
|
||||||
refresh_errors = []
|
refresh_errors = []
|
||||||
try:
|
try:
|
||||||
result.update(self._refresh_account_gold_balance(client, account))
|
result.update(self._refresh_account_gold_balance(client, account))
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
refresh_errors.append(f"鱼翅余额: {exc}")
|
refresh_errors.append(f"鱼翅余额: {exc}")
|
||||||
try:
|
try:
|
||||||
ctn = client.acf_ccn(refresh_subscribe=False)
|
result.update(
|
||||||
result.update(self._refresh_account_points(client, account, cookie, ctn=ctn))
|
self._refresh_points_after_elite_gift(
|
||||||
|
db,
|
||||||
|
task,
|
||||||
|
account,
|
||||||
|
client,
|
||||||
|
cookie,
|
||||||
|
ctn,
|
||||||
|
result,
|
||||||
|
baseline_points,
|
||||||
|
gift_count,
|
||||||
|
)
|
||||||
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
refresh_errors.append(f"积分: {exc}")
|
refresh_errors.append(f"积分: {exc}")
|
||||||
if refresh_errors:
|
if refresh_errors:
|
||||||
@@ -1113,6 +1181,8 @@ class DouyuBatchRunner:
|
|||||||
message += f",鱼翅余额: {account.gold_balance}"
|
message += f",鱼翅余额: {account.gold_balance}"
|
||||||
if account.points is not None:
|
if account.points is not None:
|
||||||
message += f",积分: {account.points}"
|
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)
|
self._mark_task(db, task, "success", message, result)
|
||||||
|
|
||||||
def _execute_query_points(self, db: Session, task: DouyuTask, account: Account, cookie: str, config: dict):
|
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 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 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> = {
|
const STATUS_COLORS: Record<string, string> = {
|
||||||
planned: 'default', pending: 'default', running: 'processing',
|
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'));
|
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 {
|
function bindPhaseText(phase: string, polling: boolean): string {
|
||||||
if (phase === 'role_ready') return '已识别角色,待确认';
|
if (phase === 'role_ready') return '已识别角色,待确认';
|
||||||
if (phase === 'change_waiting') return '换绑冷却中';
|
if (phase === 'change_waiting') return '换绑冷却中';
|
||||||
@@ -136,6 +144,11 @@ function resultNumber(result: Record<string, unknown> | null | undefined, key: s
|
|||||||
return null;
|
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() {
|
export default function DouyuTasksPage() {
|
||||||
const { can } = usePermissions();
|
const { can } = usePermissions();
|
||||||
const [accounts, setAccounts] = useState<DouyuTaskAccountItem[]>([]);
|
const [accounts, setAccounts] = useState<DouyuTaskAccountItem[]>([]);
|
||||||
@@ -148,8 +161,8 @@ export default function DouyuTasksPage() {
|
|||||||
const [runningBatchId, setRunningBatchId] = useState('');
|
const [runningBatchId, setRunningBatchId] = useState('');
|
||||||
const [configOpen, setConfigOpen] = useState(false);
|
const [configOpen, setConfigOpen] = useState(false);
|
||||||
const [concurrency, setConcurrency] = useState(3);
|
const [concurrency, setConcurrency] = useState(3);
|
||||||
const [goldAmount, setGoldAmount] = useState(1);
|
const [goldAmount, setGoldAmount] = useState(() => savedPositiveInteger(DOUYU_GOLD_AMOUNT_STORAGE_KEY));
|
||||||
const [giftCount, setGiftCount] = useState(1);
|
const [giftCount, setGiftCount] = useState(() => savedPositiveInteger(DOUYU_GIFT_COUNT_STORAGE_KEY));
|
||||||
const [selectedGoodsId, setSelectedGoodsId] = useState('');
|
const [selectedGoodsId, setSelectedGoodsId] = useState('');
|
||||||
const [accountSearch, setAccountSearch] = useState('');
|
const [accountSearch, setAccountSearch] = useState('');
|
||||||
|
|
||||||
@@ -242,6 +255,14 @@ export default function DouyuTasksPage() {
|
|||||||
|
|
||||||
useEffect(() => { loadData(); }, [loadData]);
|
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(
|
const hasActiveTasks = useMemo(
|
||||||
() => tasks.some((task) => ['pending', 'running', 'planned'].includes(task.status)),
|
() => tasks.some((task) => ['pending', 'running', 'planned'].includes(task.status)),
|
||||||
[tasks],
|
[tasks],
|
||||||
@@ -279,7 +300,7 @@ export default function DouyuTasksPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!autoOpenPayReady.current || payTask) return;
|
if (!autoOpenPayReady.current || payTask) return;
|
||||||
const nextPayTask = tasks
|
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];
|
.sort((a, b) => b.id - a.id)[0];
|
||||||
if (nextPayTask) openPayTask(nextPayTask);
|
if (nextPayTask) openPayTask(nextPayTask);
|
||||||
}, [openPayTask, payTask, tasks]);
|
}, [openPayTask, payTask, tasks]);
|
||||||
@@ -294,7 +315,12 @@ export default function DouyuTasksPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!payTask) return;
|
if (!payTask) return;
|
||||||
const latest = tasks.find((task) => task.id === payTask.id);
|
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]);
|
}, [payTask, tasks]);
|
||||||
|
|
||||||
const batchBusy = Boolean(runningBatchId || logs.connected || hasActiveTasks);
|
const batchBusy = Boolean(runningBatchId || logs.connected || hasActiveTasks);
|
||||||
@@ -749,13 +775,14 @@ export default function DouyuTasksPage() {
|
|||||||
gap: 12, overflow: 'hidden',
|
gap: 12, overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
{/* Left: accounts + tasks */}
|
{/* 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
|
<Card
|
||||||
size="small" title="账号"
|
size="small" title="账号"
|
||||||
extra={<Tag color="blue">已选 {selectedIds.length}/{accounts.length}</Tag>}
|
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>
|
<Space>
|
||||||
<Input
|
<Input
|
||||||
size="small" placeholder="搜索账号/昵称/游戏名" prefix={<SearchOutlined />}
|
size="small" placeholder="搜索账号/昵称/游戏名" prefix={<SearchOutlined />}
|
||||||
@@ -773,6 +800,7 @@ export default function DouyuTasksPage() {
|
|||||||
<Table
|
<Table
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
size="small"
|
size="small"
|
||||||
|
style={{ flex: 1, minHeight: 0 }}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
selectedRowKeys: selectedIds,
|
selectedRowKeys: selectedIds,
|
||||||
@@ -781,7 +809,7 @@ export default function DouyuTasksPage() {
|
|||||||
columns={accountColumns}
|
columns={accountColumns}
|
||||||
dataSource={filteredAccounts}
|
dataSource={filteredAccounts}
|
||||||
pagination={{ pageSize: 15, showSizeChanger: false }}
|
pagination={{ pageSize: 15, showSizeChanger: false }}
|
||||||
scroll={{ y: 'calc(100vh - 520px)' }}
|
scroll={{ y: 'max(180px, calc(100vh - 650px))' }}
|
||||||
onRow={(record) => ({
|
onRow={(record) => ({
|
||||||
onContextMenu: (e) => handleRowContextMenu(record, e),
|
onContextMenu: (e) => handleRowContextMenu(record, e),
|
||||||
style: { cursor: 'context-menu' },
|
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>
|
<Tag color="error">失败 {tasks.filter((t) => ['failed', 'error'].includes(t.status)).length}</Tag>
|
||||||
</Space>
|
</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
|
<Table
|
||||||
rowKey="id" size="small" columns={taskColumns} dataSource={tasks}
|
rowKey="id" size="small" columns={taskColumns} dataSource={tasks}
|
||||||
loading={loading} pagination={{ pageSize: 10, showSizeChanger: false }}
|
style={{ flex: 1, minHeight: 0 }}
|
||||||
scroll={{ x: 700 }}
|
loading={loading} pagination={{ pageSize: 4, showSizeChanger: false, size: 'small' }}
|
||||||
|
scroll={{ x: 700, y: 150 }}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user