Skip to content

feat(chat-adapter): the inbox as a send/receive interface for chatbots - #24

Merged
alihesari merged 3 commits into
mainfrom
feature/chat-adapter
Sep 20, 2026
Merged

alihesari merged 3 commits into
mainfrom
feature/chat-adapter

Conversation

@alihesari

Copy link
Copy Markdown
Member

The Python twin of @fopost/sdk/chat-adapter. fopost.chat_adapter wraps the inbox conversation and reply endpoints so a chatbot framework can treat FoPost as one channel across every network that carries DMs.

from fopost.chat_adapter import ChatAdapter

chat = ChatAdapter(client, workspace_id=workspace_id, webhook_secret=secret)
event = chat.parse_webhook(raw_body, request.headers)
message = chat.receive_one(event)
if message:
    chat.send(reply_to=message.id, text="On it.")

receive, receive_one, send, typing, mark_read, plus parse_webhook / verify_webhook. Outbound takes one of three shapes: reply to a message, reply into a thread, or open one by handle. The surface mirrors the TypeScript adapter method for method, so the two move together.

No version bump and no release here. pyproject.toml stays at 0.3.0, to go out with everything else that is queued.

No API change. ChatAdapter is a consumer of the client: it holds a Fopost instance and calls client.inbox.*, never touching HttpClient. It is deliberately not re-exported from fopost/__init__.py — from fopost.chat_adapter import ChatAdapter is the import, matching the TypeScript subpath.

inbox.message_received carries ids only — no text, no author — and there is no GET /v1/inbox/:id and no id filter on the list, so receive_one scans lookback_pages of that account's items and answers None when it does not find one. Verification prefers X-FoPost-Signature-256 (HMAC over {timestamp}.{body}, refused past a 300s tolerance) and falls back to the body-only X-FoPost-Signature; both compare with hmac.compare_digest.

Tests use respx to round-trip a signed delivery through parse_webhook → receive_one → send → mark_read, and cover a forged body, a stale delivery, the compatibility signature, a wrong event and a missing secret.

ruff check, ruff format --check, mypy (strict) and pytest (138) all pass on the rebased branch.

@alihesari
alihesari merged commit 6e3a811 into main Sep 20, 2026
4 checks passed
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