Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.env
.git
bot
downloads
*.test
*.out
29 changes: 28 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,17 +1,44 @@
# Telegram bot token
BOT_TOKEN=

# Comma-separated Telegram usernames or numeric user IDs allowed to use the bot.
# Empty means anyone can message the bot, which is not recommended.
AUTHORIZED_TELEGRAM_USERS=bupddotxyz

# crosspost CLI targets. -bmt means Bluesky, Mastodon, Twitter/X.
CROSSPOST_FLAGS=-bmt

# Set to 1 to exercise Telegram handling without posting to any platform.
SHITPOST_DRY_RUN=0

# Optional crosspost source override for Docker builds.
CROSSPOST_REPO=https://github.com/bupd/crosspost.git
CROSSPOST_REF=main

# Bluesky
BLUESKY_HOST=
BLUESKY_IDENTIFIER=
BLUESKY_PASSWORD=

# TWitter / X
# Twitter / X
AUTH_TOKEN=
# TWITTER_AUTH_TOKEN=
# TWITTER_AUTH_CLIENT=web
# TWITTER_GRAPHQL_ENDPOINT=web
# TWITTER_PROXY=

# Legacy official Twitter API fallback
TWITTER_API_CONSUMER_KEY=
TWITTER_API_CONSUMER_SECRET=
TWITTER_ACCESS_TOKEN_KEY=
TWITTER_ACCESS_TOKEN_SECRET=

# Optional aliases also understood by shitpost before calling crosspost:
# consumer_key=
# consumer_key_secret=
# access_token=
# access_token_secret=

# Mastodon
MASTODON_HOST=
MASTODON_ACCESS_TOKEN=
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ jobs:
with:
go-version: "1.25"

- name: Verify formatting
run: test -z "$(gofmt -l .)"

- name: Lint
run: go vet ./...

- name: Build
run: go build -v ./...

Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ go.work.sum

# env file
.env

# local build output
bot
downloads/
20 changes: 18 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,27 @@ RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o bot main.go
# ----------------------------------------
# STAGE 2 — RUNTIME WITH BUN & CROSSPOST
# ----------------------------------------
FROM oven/bun:alpine
FROM oven/bun:1.3.10

WORKDIR /app

RUN bun install -g @humanwhocodes/crosspost
ARG CROSSPOST_REPO=https://github.com/bupd/crosspost.git
ARG CROSSPOST_REF=main

RUN apt-get update && \
apt-get install -y --no-install-recommends git ca-certificates && \
git clone --depth=1 --branch "$CROSSPOST_REF" "$CROSSPOST_REPO" /opt/crosspost && \
cd /opt/crosspost && \
bun install --frozen-lockfile && \
bun run build && \
printf '#!/usr/bin/env sh\nexec bun /opt/crosspost/dist/bin.js "$@"\n' > /usr/local/bin/crosspost && \
chmod +x /usr/local/bin/crosspost && \
apt-get purge -y --auto-remove git && \
rm -rf /var/lib/apt/lists/*

# Mastodon rejects Bun FormData Blob uploads without a filename as blank files.
RUN sed -i 's/data.append("file", new Blob(\[image.data\], { type }));/data.append("file", new Blob([image.data], { type }), "image.jpg");/' \
/opt/crosspost/dist/strategies/mastodon.js

COPY --from=builder /app/bot /app/bot

Expand Down
49 changes: 37 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ A lightweight, self-hosted alternative to Postiz, Buffer, and Hootsuite for deve

| Platform | Text | Images | Videos |
|----------|------|--------|--------|
| Twitter/X | Yes | Yes | Yes |
| Bluesky | Yes | Yes | Yes |
| Mastodon | Yes | Yes | Yes |
| Twitter/X | Yes | Yes | Caption only |
| Bluesky | Yes | Yes | Caption only |
| Mastodon | Yes | Yes | Caption only |
| LinkedIn | Yes | Yes | No |
| Discord | Yes | Yes | Yes |
| Telegram | Yes | Yes | Yes |
Expand Down Expand Up @@ -129,6 +129,8 @@ For production, use a versioned tag (e.g., `v1.0.0`) to avoid unexpected updates
| Variable | Required | Description |
|----------|----------|-------------|
| `BOT_TOKEN` | Yes | Telegram bot token from BotFather |
| `AUTH_TOKEN` | No | X `auth_token` cookie value used by the emusks-backed crosspost Twitter strategy |
| `TWITTER_AUTH_TOKEN` | No | Explicit X `auth_token` cookie value; overrides the `AUTH_TOKEN` alias when set |
| `TWITTER_API_CONSUMER_KEY` | No | Twitter API consumer key |
| `TWITTER_API_CONSUMER_SECRET` | No | Twitter API consumer secret |
| `TWITTER_ACCESS_TOKEN_KEY` | No | Twitter access token |
Expand All @@ -147,7 +149,7 @@ crosspost itself may require additional environment variables (API keys, tokens
Send any text message to your bot. It will be posted to all configured platforms.

### Media Posts
Send images, videos, or documents with an optional caption.
Send images with an optional caption. Videos and non-image documents are downloaded, but the current `crosspost` CLI path posts their caption text only.

### Alt Text
Add alt text for accessibility by ending your caption with `alt:`:
Expand All @@ -159,16 +161,39 @@ alt: Orange and purple sunset over mountains
## Running Locally (without Docker)

1. Install Go 1.25+ and crosspost CLI:
```sh
npm install -g @humanwhocodes/crosspost
```
```sh
git clone https://github.com/bupd/crosspost.git
cd crosspost
bun install
bun run build
bun link
```

2. Build and run:
```sh
go build -o bot main.go
export BOT_TOKEN=your_token_here
./bot
```
```sh
task setup
task up
```

### Taskfile workflow

```sh
task setup # create .env and download deps
task up # run in a container
task up:dry-run # run in a container without posting
task up:dry-run:detached # run dry-run mode in the background
task up:detached # run in a container in the background
task logs # follow container logs
task down # stop the container
task doctor # check .env shape without printing secret values
task validate # gofmt, go vet, go test, go build
```

Set `CROSSPOST_FLAGS=-bmt` to post to Bluesky, Mastodon, and X. Set `AUTH_TOKEN` to your X `auth_token` cookie value to use the emusks-backed X poster in `crosspost`. Set `AUTHORIZED_TELEGRAM_USERS` to your Telegram username or numeric user ID so only you can use the bot.

Use `task up:dry-run` first. Send a Telegram message to the bot and it will reply with the `crosspost` command it would run without posting anything.

Run `task doctor` if a platform fails. It prints which keys are present and their lengths without exposing token values. For X, `shitpost` accepts `AUTH_TOKEN` as an alias for `TWITTER_AUTH_TOKEN`, and accepts the legacy official API aliases `consumer_key`, `consumer_key_secret`, `access_token`, and `access_token_secret` before invoking `crosspost`.

## Architecture

Expand Down
116 changes: 116 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
version: "3"

vars:
ENV_FILE: '{{default ".env" .ENV_FILE}}'
IMAGE: '{{default "registry.goharbor.io/bupd/shitpost" .IMAGE}}'
TAG: '{{default "dev" .TAG}}'
CROSSPOST_REPO: '{{default "https://github.com/bupd/crosspost.git" .CROSSPOST_REPO}}'
CROSSPOST_REF: '{{default "main" .CROSSPOST_REF}}'

tasks:
default:
desc: List available tasks
cmds:
- task --list

setup:
desc: Create .env from .env.example if missing and download Go deps
cmds:
- cmd: cp .env.example {{.ENV_FILE}}
status:
- test -f {{.ENV_FILE}}
- go mod download

up:
desc: Run the Telegram crosspost bot in Docker Compose
cmds:
- mkdir -p downloads
- docker compose up --build

up:dry-run:
desc: Run the Telegram crosspost bot in Docker Compose without posting
cmds:
- mkdir -p downloads
- SHITPOST_DRY_RUN=1 docker compose up --build

up:dry-run:detached:
desc: Run the Telegram crosspost bot in Docker Compose detached dry-run mode
cmds:
- mkdir -p downloads
- SHITPOST_DRY_RUN=1 docker compose up --build -d

up:detached:
desc: Run the Telegram crosspost bot in Docker Compose detached mode
cmds:
- mkdir -p downloads
- docker compose up --build -d

down:
desc: Stop the Docker Compose bot
cmds:
- docker compose down

logs:
desc: Follow Docker Compose bot logs
cmds:
- docker compose logs -f shitpost-bot

doctor:
desc: Check .env configuration without printing secret values
cmds:
- sh scripts/doctor.sh {{.ENV_FILE}}

dev:
desc: Run the Telegram crosspost bot locally without Docker
dotenv: ["{{.ENV_FILE}}"]
cmds:
- go run ./main.go

dev:dry-run:
desc: Run the Telegram crosspost bot locally without posting
dotenv: ["{{.ENV_FILE}}"]
cmds:
- SHITPOST_DRY_RUN=1 go run ./main.go

build:
desc: Build the bot binary
cmds:
- go build -o bot ./main.go

test:
desc: Run Go tests
cmds:
- go test ./...

lint:
desc: Run Go vet
cmds:
- go vet ./...

fmt:
desc: Format Go code
cmds:
- gofmt -w main.go

validate:
desc: Format, lint, test, and build
cmds:
- task: fmt
- task: lint
- task: test
- task: build

image:build:
desc: Build the container image
cmds:
- docker build --build-arg CROSSPOST_REPO={{.CROSSPOST_REPO}} --build-arg CROSSPOST_REF={{.CROSSPOST_REF}} -t {{.IMAGE}}:{{.TAG}} .

compose:up:
desc: Run the bot in Docker Compose
cmds:
- task: up

compose:down:
desc: Stop the Docker Compose bot
cmds:
- task: down
8 changes: 7 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
services:
shitpost-bot:
image: registry.goharbor.io/bupd/shitpost:latest
build: .
build:
context: .
args:
CROSSPOST_REPO: ${CROSSPOST_REPO:-https://github.com/bupd/crosspost.git}
CROSSPOST_REF: ${CROSSPOST_REF:-main}
container_name: shitpost-engine
env_file: .env
environment:
SHITPOST_DRY_RUN: ${SHITPOST_DRY_RUN:-}
restart: unless-stopped
volumes:
- ./downloads:/app/downloads
Loading
Loading