Skip to content

Commit b79732c

Browse files
committed
Match Facebook's before-snapshot scroll depth to the verification loop
Codex-reported: the before-submit article snapshot only covered the initially-loaded viewport, but the post-submit verification loop scrolls (VERIFY_SCROLL_ATTEMPTS x VERIFY_SCROLL_PIXELS) to reveal more posts. An older post sitting below the fold at snapshot time -- absent from that snapshot -- would get revealed by the verification loop's own scrolling and wrongly look "new", the same class of false positive the snapshot was added to prevent. Extracted _snapshot_existing_articles(), which scrolls through the exact same depth as the verification loop and accumulates every article fingerprint seen at each step (not just the final scroll position, since Facebook's feed can remove off-screen articles from the DOM as new ones load in). Both the before-snapshot and the verification loop now share VERIFY_SCROLL_ATTEMPTS/VERIFY_SCROLL_PIXELS constants so they can't drift apart again the way this bug happened in the first place. No live TikTok/Facebook API calls, no account access. 149 tests pass, compileall clean, git diff --check clean.
1 parent 34c13e0 commit b79732c

2 files changed

Lines changed: 47 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ that will firm up once it leaves beta. Dates are when a release was tagged, not
3535
safe-by-default test suite; added a dedicated TikTok OAuth check group to `doctor.py`
3636
(`python doctor.py --tiktok`). Still safe by default either way -- `--confirm-publish` is
3737
required for a real publish on both, exactly as for every other platform.
38-
- **Nine further issues found by code review before any live retest, none of them guessed:**
38+
- **Ten further issues found by code review before any live retest, none of them guessed:**
3939
(1) `auth/publish_facebook.py` never forced its browser context to English like every other
4040
publisher does -- fixed with the same `FORCE_ENGLISH_LOCALE` constant. (2) The documented
4141
`--client-secrets path/to/tiktok_client.json` setup form silently broke every token refresh
@@ -77,7 +77,14 @@ that will firm up once it leaves beta. Dates are when a release was tagged, not
7777
round) narrowed false positives from the profile bio/nav text, but could still match an
7878
*older* post with similar leading text (a repeated caption, or a common generic opening).
7979
Fixed by snapshotting the profile timeline's existing posts before submitting anything, then
80-
only counting a match in a post that's genuinely new since that snapshot.
80+
only counting a match in a post that's genuinely new since that snapshot. (10) That
81+
before-snapshot only covered the initially-loaded viewport, but the post-submit verification
82+
loop scrolls to reveal more posts -- so an OLDER post sitting below the fold at snapshot time
83+
would get revealed by the verification loop's own scrolling and wrongly look "new" too. Fixed
84+
by having the before-snapshot scroll through the exact same depth
85+
(`VERIFY_SCROLL_ATTEMPTS` x `VERIFY_SCROLL_PIXELS`) the verification loop does, accumulating
86+
every article seen at each step (Facebook's feed can remove off-screen articles from the DOM
87+
as new ones load in, so a single final-position snapshot isn't enough either).
8188
- **YouTube's upload category is no longer hardcoded.** `auth/publish_youtube.py` gained
8289
`--category-id` (default unchanged: `22`, People & Blogs) -- closes a previously-documented
8390
defect in the upload path.

auth/publish_facebook.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@
5252
VIDEO_PROCESSING_WAIT_ITERATIONS = 150
5353
VIDEO_PROCESSING_WAIT_INTERVAL_MS = 2000
5454

55+
# How far the post-submit verification step scrolls, and in how many steps, looking for the new
56+
# post. The pre-submit snapshot (_snapshot_existing_articles) scrolls through exactly the same
57+
# depth -- Codex-reported: an earlier version only snapshotted the initially-loaded viewport, so
58+
# an OLDER post sitting below the fold at snapshot time (absent from that snapshot) but revealed
59+
# by the verification loop's own scrolling would wrongly look "new". Both loops must cover the
60+
# same ground for the before/after comparison to mean anything.
61+
VERIFY_SCROLL_ATTEMPTS = 3
62+
VERIFY_SCROLL_PIXELS = 4000
63+
5564

5665
def _save_debug_screenshot(page) -> Path | None:
5766
"""Best-effort screenshot saved BEFORE the calling code raises -- the browser context is
@@ -111,6 +120,21 @@ def _article_fingerprints(page) -> list[str]:
111120
)
112121

113122

123+
def _snapshot_existing_articles(page) -> set[str]:
124+
"""Fingerprint every article currently reachable by scrolling through
125+
VERIFY_SCROLL_ATTEMPTS steps of VERIFY_SCROLL_PIXELS each -- the exact same depth the
126+
post-submit verification loop scrolls through. Accumulates the union seen at every step
127+
(not just the final one) since Facebook's feed can virtualize/remove off-screen articles from
128+
the DOM as new ones load in, so an article visible at step 1 might not still be present once
129+
the page has scrolled to step 3."""
130+
fingerprints: set[str] = set(_article_fingerprints(page))
131+
for _ in range(VERIFY_SCROLL_ATTEMPTS):
132+
page.mouse.wheel(0, VERIFY_SCROLL_PIXELS)
133+
page.wait_for_timeout(1500)
134+
fingerprints.update(_article_fingerprints(page))
135+
return fingerprints
136+
137+
114138
def _open_composer(page) -> bool:
115139
"""Click the "What's on your mind?" opener on the home feed. The visible text includes the
116140
logged-in user's own first name (e.g. "What's on your mind, Alex?"), so this matches on the
@@ -236,7 +260,7 @@ def publish_facebook(
236260
if profile_url:
237261
page.goto(profile_url, timeout=STEP_TIMEOUT_MS)
238262
page.wait_for_timeout(3000)
239-
existing_article_fingerprints = set(_article_fingerprints(page))
263+
existing_article_fingerprints = _snapshot_existing_articles(page)
240264
page.goto("https://www.facebook.com/", timeout=STEP_TIMEOUT_MS)
241265
page.wait_for_timeout(2000)
242266

@@ -338,27 +362,32 @@ def publish_facebook(
338362
snippet = text[:40]
339363
page.goto(profile_url, timeout=STEP_TIMEOUT_MS)
340364
page.wait_for_timeout(4000)
341-
for attempt in range(3):
342-
# Codex-reported, in two parts. First: a bare
365+
for attempt in range(VERIFY_SCROLL_ATTEMPTS):
366+
# Codex-reported, in three parts so far. First: a bare
343367
# document.body.innerText.includes() check would also match the snippet in
344368
# the profile bio or nav text -- fixed by scoping to `[role="article"]`
345369
# (Facebook's own ARIA role for a feed post). Second: scoping to articles
346370
# alone still doesn't prove the match is the post just made, not an older one
347371
# starting with similar text (a repeated caption, or a common generic
348372
# opening) -- fixed by comparing against the before-snapshot taken earlier
349-
# and only counting a match in an article that's NEW since then. Still
350-
# unverified against a live account like everything else in this file -- this
351-
# narrows the false-positive surface further, it doesn't eliminate every way
352-
# this could be wrong (e.g. two genuinely new posts with identical leading
353-
# text in the same run).
373+
# and only counting a match in an article that's NEW since then. Third: the
374+
# before-snapshot only covered the initially-loaded viewport, so an older post
375+
# sitting below the fold at snapshot time -- but revealed by THIS loop's own
376+
# scrolling -- would wrongly look "new" too. Fixed by having
377+
# _snapshot_existing_articles scroll through the exact same
378+
# VERIFY_SCROLL_ATTEMPTS x VERIFY_SCROLL_PIXELS depth up front, so both passes
379+
# cover identical ground. Still unverified against a live account like
380+
# everything else in this file -- this narrows the false-positive surface
381+
# further, it doesn't eliminate every way this could be wrong (e.g. two
382+
# genuinely new posts with identical leading text in the same run).
354383
current_fingerprints = _article_fingerprints(page)
355384
new_fingerprints = [
356385
fp for fp in current_fingerprints if fp not in existing_article_fingerprints
357386
]
358387
if any(snippet in fp for fp in new_fingerprints):
359388
verified = True
360389
break
361-
page.mouse.wheel(0, 4000)
390+
page.mouse.wheel(0, VERIFY_SCROLL_PIXELS)
362391
page.wait_for_timeout(3000)
363392

364393
result = {

0 commit comments

Comments
 (0)