Skip to content

Commit d29ed5f

Browse files
LeyckerSclaude
andauthored
fix(datanodes): follow the step-2 trigger chain instead of clicking once (LeyckerS#111)
datanodes now serves step 2 as two buttons: 'Free Download / Standard Speed' reveals 'Start Download / Your file is ready', and only the second starts the transfer. The trigger handler latched after the first click, so every free account has been hanging for the full 420s budget and failing silently. The page was never unreadable -- DN_STEP2_JS already matches the second button, 'start download' is the first alternative in its regex. Every poll after the first click reported the new trigger and the latch discarded it. Track which labels were clicked rather than whether anything was, so each new trigger in the chain gets exactly one click. DN_STEP2_MAX_CLICKS caps it so a page cycling labels stops instead of being clicked until the deadline. Closes LeyckerS#109 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 87e9cfe commit d29ed5f

1 file changed

Lines changed: 20 additions & 4 deletions

File tree

moon_extract.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ async def extract_fuckingfast(url: str) -> str | None:
223223

224224
DN_STEP1_GATE_TIMEOUT = 22.0
225225
DN_STEP2_TIMEOUT = 420.0
226+
# Step 2 is a chain of buttons, not one button: each click can reveal the next.
227+
# The cap stops a page that cycles labels from being clicked until the deadline.
228+
DN_STEP2_MAX_CLICKS = 4
226229
# Auto-click budget. After this the widget is left alone so a human sitting at the
227230
# headful window can tick it; DN_MANUAL_CAPTCHA_TIMEOUT is that grace period.
228231
DN_CAPTCHA_AUTO_TIMEOUT = 45.0
@@ -724,9 +727,16 @@ def _on_popup(pop):
724727
if await _dn_eval(page, DN_DEAD_JS, default=False):
725728
return None, None
726729

727-
# ── step 2: Turnstile + countdown, then one click on the trigger ───────
730+
# ── step 2: Turnstile + countdown, then the trigger chain ─────────────
728731
for hard_fail_retry in range(2): # one reload if Turnstile hard-fails
729-
clicked = False
732+
# Not a boolean. datanodes serves the trigger as a *chain*: the first
733+
# button ("Free Download / Standard Speed") only reveals the second
734+
# ("Start Download / Your file is ready"), and only the second starts
735+
# the transfer. A one-shot latch clicked the first, then watched the
736+
# second sit there for the whole 420s budget -- the page was never
737+
# unreadable, we simply refused to press it. Remember which labels
738+
# were used instead, so each new one gets a click and a repeat does not.
739+
clicked_labels : set[str] = set()
730740
solved_captcha = False
731741
last_sweep = 0.0
732742
hard_failed = False
@@ -769,11 +779,17 @@ def _on_popup(pop):
769779
solved_captcha = True
770780
continue
771781

772-
if st["trigger"] and not clicked:
782+
if st["trigger"] and st["trigger"] not in clicked_labels:
783+
if len(clicked_labels) >= DN_STEP2_MAX_CLICKS:
784+
# A page cycling labels would otherwise be clicked until the
785+
# deadline. Stop and let the caller retry from a clean page.
786+
_d(f"step-2 trigger chain exceeded {DN_STEP2_MAX_CLICKS} "
787+
f"distinct labels, last: {st['trigger']!r} — giving up")
788+
break
773789
await _dn_eval(page, DN_OVERLAY_JS)
774790
if await _dn_eval(page, DN_CLICK_JS, st["trigger"], default=False):
775791
_d("clicked step-2 trigger:", st["trigger"])
776-
clicked = True
792+
clicked_labels.add(st["trigger"])
777793
await asyncio.sleep(0.4)
778794

779795
if not hard_failed:

0 commit comments

Comments
 (0)