Skip to content

Add Facebook comment and reaction webhook handling - #145

Open
Tharickv75 wants to merge 4 commits into
pageindex-lean-hitsfrom
feat/facebook-comments-reactions
Open

Add Facebook comment and reaction webhook handling#145
Tharickv75 wants to merge 4 commits into
pageindex-lean-hitsfrom
feat/facebook-comments-reactions

Conversation

@Tharickv75

Copy link
Copy Markdown
Contributor

Summary

Test plan

  • Webhook receives a page comment event and routes through the comment adapter
  • Webhook receives a reaction event and routes through the reaction adapter
  • Existing Messenger message webhooks still process unchanged
  • Comment filter skips unwanted comment types as configured
  • pytest tests/action/facebook_action/ (and any new coverage if added) passes

Made with Cursor

Introduce comment/reaction adapters and filters, extend the Graph API
helpers and webhook routing so page comments and reactions can be
ingested alongside existing Messenger message flows.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Tharickv75
Tharickv75 requested a review from eldonm as a code owner August 7, 2026 18:19
Tharickv75 and others added 3 commits August 7, 2026 14:35
Co-authored-by: Cursor <cursoragent@cursor.com>
Review follow-ups on the comments/reactions work. Two of these compound
into the same user-visible failure.

Meta delivers webhooks at-least-once and retries for days. The Messenger
path guards that on `mid` via remember_meta_wamid; the feed paths did not,
so a redelivery posted a SECOND public reply under the same comment. Both
now dedupe through the same helper. Comments key on the comment_id, which
is stable and cannot be an edit -- only verb == "add" reaches here.
Reactions carry no id, so the key is composed, and it includes the event
timestamp on purpose: a retry repeats the timestamp, while a user who
removes and re-adds a reaction produces a new one and is still heard.
Keying on (post, comment, sender, type) alone would have silently eaten
the second case.

The retries were partly self-inflicted. Both feed paths awaited a full
orchestrator turn inline in the webhook handler, holding the response open
while the model and tools ran, so Meta timed the delivery out and resent
it. They now dispatch via create_task with the same inline fallback the
Messenger path uses for runtimes that cannot background.

A comment longer than FEED_COMMENT_UTTERANCE_MAX was dropped with `continue`
and no log -- the commenter got no reply and nothing explained why, while
the reaction path 60 lines below truncated. It now truncates and logs.

The markdown-to-plain-text transformation existed twice, once in the filter
and once in the adapter, along with two independent declarations of
FACEBOOK_COMMENT_MAX_LENGTH. Both now call facebook_comment_text, so a fix
to one cannot leave the other emitting raw markdown into a public comment.
Links keep their destination there -- `[label](url)` becomes
`label (url)` -- because a comment reader cannot hover or recover a URL
that was thrown away.

Adds the first tests for this feature. TestEndpointWiring asserts on source
structure rather than behaviour, and says so: driving the webhook needs a
live graph, an agent and a model, so it guards that the dedup and the
backgrounding are present, while the dedup keys themselves are exercised
through the real helper. All three fixes were mutation-checked -- reverting
each one turns the suite red.
@eldonm

eldonm commented Aug 8, 2026

Copy link
Copy Markdown
Member

Reviewed this and pushed the fixes in 3b9109b rather than leaving a list — full reasoning in the commit message. Two of the findings compound into the same user-visible failure, so they are worth reading together.

Duplicate public comments

Meta delivers webhooks at-least-once and retries for days. The Messenger path guards that on mid via remember_meta_wamid (endpoints.py:746) — the feed paths did not, so a redelivery posts a second public reply under the same comment on a brand's page.

Both paths now dedupe through the same helper:

  • Comments key on comment_id — stable per comment, and only verb == "add" reaches there, so an edit cannot masquerade as a new comment.
  • Reactions carry no id, so the key is composed — and it includes the event timestamp on purpose. A retry repeats the timestamp; a user who removes and re-adds a reaction produces a new one and is still heard. Keying on (post, comment, sender, type) alone would have silently eaten that second case.

The retries were partly self-inflicted

Both feed paths awaited a full orchestrator turn inline in the webhook handler, holding the response open while the model and tools ran. Meta times the delivery out and resends — so the blocking call created the duplicates the missing dedup then failed to absorb. Both now dispatch via create_task with the same inline fallback the Messenger path uses for runtimes that cannot background.

Long comments were dropped silently

if len(comment_text) > FEED_COMMENT_UTTERANCE_MAX: continue, with no log — the commenter got no reply and nothing explained why, while the reaction path 60 lines below truncated instead. Facebook comments run far longer than 2000 chars. Now truncates and logs, matching its neighbour.

One copy of the text shaping

_strip_markdown in the adapter duplicated FacebookCommentFilter near line-for-line, and FACEBOOK_COMMENT_MAX_LENGTH = 9000 was declared independently in both files. Extracted to facebook_comment_text.py; both call it. A fix to one can no longer leave the other emitting raw markdown into a public comment.

While there: links keep their destination — [our pricing](https://x.test)our pricing (https://x.test). A comment reader cannot hover, and a dropped URL is unrecoverable. A label that already is the URL is not repeated.

Tests

14, where the feature had none. Mutation-checked — reverting each fix turns the suite red:

Mutation Result
remove the dedup guards fails
remove the backgrounding fails
restore the silent drop fails

One honest limit: TestEndpointWiring asserts on source structure, not behaviour, and its docstring says so. Driving that webhook end-to-end needs a live graph, an agent and a model; it guards that the dedup and backgrounding are present, which is what a refactor would silently undo. The dedup keys themselves are exercised through the real helper.

Worth calling out as genuinely good

iter_feed_comment_events is the strongest code in either of your PRs — type guards at every level, verb == "add" only, safe int coercion, and a self-reply loop filter with a comment explaining why Meta redelivers the Page's own reply. The access-control checks on both new paths, with the documented defense-in-depth re-check, are right too. And 6d1a3d13 on #141 improved on my API-compat fix: keeping start_index in the API and aliasing pages only in tool prompts is cleaner than the dual-key approach I had used.

Local gate green: pre-commit run --all-files, full pytest at 0 failures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants