Skip to content

Add Bluesky#714

Open
xlcrr wants to merge 5 commits into
masterfrom
feature/2026/06/add-bluesky
Open

Add Bluesky#714
xlcrr wants to merge 5 commits into
masterfrom
feature/2026/06/add-bluesky

Conversation

@xlcrr

@xlcrr xlcrr commented Jun 28, 2026

Copy link
Copy Markdown
Member

OLMbot: pause X/Twitter posting, add Bluesky

Summary

OLMbot — the bot that automatically shares OpenLitterMap milestones (new cities, states and countries, daily and impact-report stats, and new badges) on social media — has been posting into the void. X (Twitter) recently moved its API behind paid billing, and our account has no credits, so every automated post has been silently failing.

This PR does two things:

  1. Cleanly switches off X posting behind a simple on/off setting, instead of leaving a broken integration that fails and piles up errors.
  2. Adds Bluesky — a free, open social network — as a new posting channel, so OLMbot keeps sharing updates with the community.

It's built so that turning X back on, or posting to both X and Bluesky at once, is a one-line setting change if we ever add API credits — no code changes needed.

What changed

  • A new "social dispatcher" now sits between OLMbot's content and the social networks. Each automated post is handed to the dispatcher, which sends it to whichever networks are switched on. The logic that generates the stats and milestone text is completely untouched.
  • X/Twitter posting is now behind an on/off switch, defaulting to off — stopping the silent failures and making the state explicit.
  • Bluesky support covers everything OLMbot already did on X: single posts, threads (like the daily report), and posts with images (impact reports and badges).
  • On Bluesky, map links in the "new city" posts are clickable, and images are automatically resized to fit Bluesky's size limit (with a text-only fallback if an image can't be shrunk enough).

Why

X's API now requires a paid plan we don't have, so the bot's posts fail with no visible result. Rather than leave that in place, we're pausing it cleanly and moving to Bluesky — which is free and has no usage tiers — so the community keeps getting milestone updates while we retain the option to return to X later.

How it works (technical — for reviewers)

  • App\Helpers\Social — a thin dispatcher (text / thread / withImage) that fans out to enabled networks. thread aggregates sent/total across enabled networks only, preserving the commands' existing partial-failure detection. No registry or plugin layer — adding another network later is a new helper plus two lines here.
  • App\Helpers\Bluesky — AT Protocol (XRPC) client over Laravel's HTTP client: session auth → post; reply-chained threads (root/parent refs); image upload with <1MB recompression via intervention/image; and link facets so URLs render clickable.
  • App\Helpers\Twitter — behavior unchanged, now gated by Twitter::isEnabled() (flag + production + credentials present, so a missing key can't misfire).
  • Both networks self-gate; a disabled one is a no-op. The 9 call sites (5 scheduled commands + 4 event listeners) changed from Twitter::… to Social::….
  • Design spec: docs/superpowers/specs/2026-06-28-bluesky-posting-design.md.

Testing

New Http::fake() tests cover the Bluesky client (auth→post, thread reply-chaining, image upload + embed, clickable-link byte ranges, disabled no-op, error swallowing) and the dispatcher. 83 OLMbot tests pass, including the existing command tests — confirming the swap is transparent.

Enabling in production

Set in the production environment, then rebuild config:

BLUESKY_ENABLED=true
BLUESKY_IDENTIFIER=olmbot.bsky.social
BLUESKY_APP_PASSWORD=

X stays off. To post to both networks later, set TWITTER_ENABLED=true once credits are available.

Known limitations / follow-ups

  • Hashtags post as plain text on Bluesky for now (URLs are already clickable); clickable hashtags are a small fast-follow.
  • No external link-card previews yet.

Version: 5.12.10 (patch)

xlcrr and others added 5 commits June 28, 2026 20:11
- Twitter/Bluesky isEnabled() treat blank/partial credentials as disabled
  (filled()/blank() instead of !== null) — a blank env var resolves to "" and
  would otherwise pass the guard and attempt a doomed live call (Codex P2).
- Bluesky createdAt now emits the canonical RFC 3339 form (UTC, Z, milliseconds)
  to match the AT Protocol datetime lexicon.
- Regression tests for blank-cred gating and createdAt format; 85 OLMbot tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OLMbot social-switch credential hardening (Codex P2) + canonical Bluesky createdAt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.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