GoToSocial to Bluesky cross poster application.
  • Python 99.1%
  • Dockerfile 0.9%
Find a file
Erick Ruiz de Chavez 1f447b2c32 update CLAUDE.md with operational findings
Document three discovered gotchas: user stream scope, local_only flag
behaviour, and the empty post guard.

Assisted-By: Claude <noreply@anthropic.com>
2026-05-14 08:21:12 -04:00
.env.example initial implementation 2026-05-14 07:37:20 -04:00
.gitignore initial implementation 2026-05-14 07:37:20 -04:00
bsky.py initial implementation 2026-05-14 07:37:20 -04:00
CLAUDE.md update CLAUDE.md with operational findings 2026-05-14 08:21:12 -04:00
compose.yml initial implementation 2026-05-14 07:37:20 -04:00
db.py initial implementation 2026-05-14 07:37:20 -04:00
Dockerfile initial implementation 2026-05-14 07:37:20 -04:00
gts.py filter out posts not authored by the authenticated account 2026-05-14 08:19:24 -04:00
LICENSE add MIT license, fix empty posts, update compose 2026-05-14 07:55:15 -04:00
main.py add MIT license, fix empty posts, update compose 2026-05-14 07:55:15 -04:00
pyproject.toml initial implementation 2026-05-14 07:37:20 -04:00
README.md initial implementation 2026-05-14 07:37:20 -04:00
split.py initial implementation 2026-05-14 07:37:20 -04:00
uv.lock initial implementation 2026-05-14 07:37:20 -04:00

crossposter

Cross-posts from a GoToSocial instance to Bluesky. Connects to the GoToSocial streaming API for real-time delivery and handles post splitting, threads, images, edits, and deletes.

Features

  • Real-time streaming via GoToSocial WebSocket API (no polling)
  • Splits posts longer than 300 graphemes into a Bluesky thread
  • Preserves GoToSocial self-reply threads on the Bluesky side
  • Attaches images with alt text (up to 4 per post)
  • Propagates deletes from GoToSocial to Bluesky, including descendant posts
  • Propagates edits by deleting and recreating the affected Bluesky posts, cascading through any descendants
  • Skips replies to other accounts, boosts, non-public posts, local-only posts, and posts that mention other accounts
  • Dry-run mode for safe testing

Setup

cp .env.example .env
# fill in .env with your credentials
docker compose up --build -d

Configuration

All configuration is via environment variables (.env):

Variable Required Default Description
GTS_BASE_URL yes Your GoToSocial instance URL
GTS_ACCESS_TOKEN yes OAuth bearer token with read scope
BSKY_HANDLE yes Your Bluesky handle
BSKY_APP_PASSWORD yes Bluesky app password
SYNC_SINCE no now First-run behaviour (see below)
DRY_RUN no false Log what would be posted without posting
LOG_LEVEL no INFO DEBUG, INFO, WARNING, ERROR

GTS access token

Create an application on your GoToSocial instance and complete the OAuth flow to obtain a bearer token. The application needs read scope (for fetching statuses and streaming) and no write scope is required.

SYNC_SINCE (first run only)

Controls what happens the very first time the program runs against an empty database. Ignored on subsequent starts.

Value Behaviour
now (default) Sets a watermark at the current latest post; nothing historical is synced
all Syncs everything GoToSocial can return (paginated)
2024-05-01 Syncs posts from that date onward (ISO date or datetime)

Development

# Run locally (requires .env)
uv run python main.py

# Dry run
DRY_RUN=true LOG_LEVEL=DEBUG uv run python main.py

There are no automated tests in this project.