Skip to content

feat(sources): add facebook_group connector (Camofox-based) with schema parity - #178

Open
matthewdonsemail-lab wants to merge 3 commits into
obris-dev:mainfrom
matthewdonsemail-lab:feat/facebook-group-connector
Open

feat(sources): add facebook_group connector (Camofox-based) with schema parity#178
matthewdonsemail-lab wants to merge 3 commits into
obris-dev:mainfrom
matthewdonsemail-lab:feat/facebook-group-connector

Conversation

@matthewdonsemail-lab

Copy link
Copy Markdown

Adds the Facebook group search connector as a built-in source kind, using the Camofox anti-detect browser subprocess for cookie-based auth and group post extraction — mirroring the existing twitter_search connector pattern.

What's included

Schema (packages/openmagpie-schema):

  • FacebookGroupSourceSpec with group_ids (required), terms (optional keywords), count (per-cycle cap, default 20)
  • NewFacebookPostPayload in the FeedItemData union (kind: new_fb_post)
  • schema.json regenerated (84 models, up from 82), --check passes

Connector (apps/core/sources/connectors/facebook/):

  • errors.py: FacebookError dataclass + map_worker_error() — maps worker JSON error responses to typed errors with retry semantics
  • payloads.py: NewFacebookPostPayload with from_record() classmethod and sample() for hydrated test data
  • client.py: FacebookClient class that spawns facebook-worker.py as a subprocess with JSON stdin/stdout contract. Cookie resolution follows the Twitter pattern: FACEBOOK_COOKIES_JSON, FACEBOOK_COOKIES_FILE, FACEBOOK_CREDENTIALS_DIR. Supports FACEBOOK_PROXY. 300s timeout for browser launch
  • connector.py: FacebookGroupConnector(BaseConnector[FacebookGroupSourceSpec]) — lazy client init (avoids worker-path resolution at import time when the sibling checkout isn't present), poll() calls the client, filters by watermark, yields payloads. Self-registers via register()

Registration:

  • facebook_group added to connectors/__init__.py, sources/registry.py, and tools/schema_sync/models.py (CONTRACT_MODELS + INPUT_MODELS)
  • Built-in source-kind invariant test extended to the known six
  • NewFacebookPostPayload added to the FeedItemData union

Tests (20 tests, all passing):

  • _FakeGroupPost — normalized FB post record matching the worker shape
  • Spec validation: empty/whitespace group IDs rejected, count bounds, terms stripped
  • Connector: watermark filter, no-since = all payloads, terms passed to client, error → ConnectorParseError, empty results
  • Payload: from_record with full record, author-as-string, missing optional fields, query_terms fallback, URL generation, sample distinct, source_slug (group_id)

matthewdonsemail-lab and others added 3 commits August 8, 2026 17:50
…a parity

Ports the listeningkit Twitter-search listener into openmagpie-core as a built-in source kind, with twikit added as an openmagpie-core library dependency (git pin, unclecode fork) per operator direction - not vendored from listeningkit.

- schema: TwitterSearchSourceSpec (query, mode Top/Latest, count) added to the _BuiltinSourceSpec union; NewTweetPayload added to FeedItemData; schema.json regenerated (82 models); --check passes.
- connector: TwikitClient with the live-cookie credential chain (TWITTER_COOKIES_JSON -> TWITTER_COOKIE_AUTH_TOKEN/CT0 -> cookies file -> login fallback) so the working listeningkit cookie JSONs keep functioning; error taxonomy translated to ConnectorParseError; tests use fakes only (no live I/O).
- registration: twitter_search registered in sources/registry.py + connectors/__init__.py; payload self-registers; source-kind invariant test extended to the known five.
- infra: Dockerfile builder gains git for the git-pinned dep; docker-compose.override.yml (local-only, gitignored) publishes Postgres on host 5433 to coexist with the buzz stack.
- validation: 9 new connector tests + invariant tests green; CLI suite 145/145; ruff + ty clean.

Co-authored-by: Matthew Don <matthewdonsemail@gmail.com>
Signed-off-by: Matthew Don <matthewdonsemail@gmail.com>
…pping

Live X polling through the connector exposed two issues (verified against
live cookies from listeningkit/backend/credentials/personal.json, no
proxy, no vendor API):

1. Event loop is closed on 2nd/3rd source in a multi-source feed poll.
   TwikitClient built one twikit Client at import time; twikit's Client
   creates an httpx.AsyncClient bound to the *currently running* event
   loop, and TwikitClient.search() drives it with a fresh asyncio.run
   loop per call. The shared client died with the first loop, so later
   sources in the same poll failed. Fix: construct the twikit Client
   inside _search_async (per call, inside the running loop); cookies are
   still resolved once in __init__.

2. X SearchTimeline intermittently 404s with an EMPTY body (reproduced:
   same query succeeds on immediate retry, session-independent). That is
   a transient upstream flake, not a deleted tweet/user; the old
   not_found/non-retryable mapping made ops treat a healthy source as
   dead. Fix: empty-body NotFound maps to search_timeline_unavailable
   (retryable=True, backoff); message-bearing NotFound stays not_found.

- client.py: per-call twikit Client construction + docstring.
- errors.py: empty-404 special case in map_twikit_error.
- tests_twitter.py: regression test for the retryable empty-404 mapping.

Validation: ruff check + format (416 files), ty, makemigrations --check,
schema.json --check, full Django suite 587 tests OK, live poll of the
x-buying-signals feed (4 twitter_search sources) green end-to-end.

Signed-off-by: matthewdonsemail-lab <matthewdonsemail@gmail.com>
Co-authored-by: Matthew Don <matthewdonsemail@gmail.com>
…ma parity

Adds the Facebook group search connector as a built-in source kind, using
the Camofox anti-detect browser subprocess (facebook-camofox-client) for
cookie-based auth and group post extraction -- mirroring the existing
twitter_search connector pattern.

- schema: FacebookGroupSourceSpec (group_ids, terms, count) added to the
  _BuiltinSourceSpec union; NewFacebookPostPayload added to FeedItemData;
  schema.json regenerated (84 models); --check passes.
- connector: FacebookClient with the env/file/credentials-dir cookie chain
  (FACEBOOK_COOKIES_JSON -> FACEBOOK_COOKIES_FILE -> credentials dir with
  c_user/xs guard); FacebookGroupConnector with lazy client init (avoids
  worker-path resolution at import time); error taxonomy translated to
  ConnectorParseError.
- registration: facebook_group registered in sources/registry.py +
  connectors/__init__.py; payload self-registers; source-kind invariant
  test extended to the known six.
- validation: 20 new connector tests + invariant tests green; ruff clean.

Co-authored-by: Matthew Don <matthewdonsemail@gmail.com>
Signed-off-by: Matthew Don <matthewdonsemail@gmail.com>
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.

1 participant