GoToSocial to Bluesky cross poster application.
- Python 99.1%
- Dockerfile 0.9%
Document three discovered gotchas: user stream scope, local_only flag behaviour, and the empty post guard. Assisted-By: Claude <noreply@anthropic.com> |
||
|---|---|---|
| .env.example | ||
| .gitignore | ||
| bsky.py | ||
| CLAUDE.md | ||
| compose.yml | ||
| db.py | ||
| Dockerfile | ||
| gts.py | ||
| LICENSE | ||
| main.py | ||
| pyproject.toml | ||
| README.md | ||
| split.py | ||
| uv.lock | ||
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.