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
3 changes: 3 additions & 0 deletions ui/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/public/
/resources/
/.hugo_build.lock
19 changes: 19 additions & 0 deletions ui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# shitpost docs UI

This folder contains a minimal Hugo documentation site for `shitpost`.

## Local development

```sh
hugo server --source ui
```

## Build

```sh
hugo --source ui --gc --minify
```

## Netlify

Set the Netlify base directory to `ui`. Netlify will read `ui/netlify.toml`, run Hugo, and publish `ui/public`.
4 changes: 4 additions & 0 deletions ui/content/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
title: "shitpost"
description: "Post once to Telegram, publish everywhere."
---
39 changes: 39 additions & 0 deletions ui/content/getting-started/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: "Getting started"
description: "Run a private Telegram bot that crossposts text and media to your social accounts."
weight: 10
---

`shitpost` is a small Go service that listens to your Telegram bot, downloads incoming media when needed, and calls the `crosspost` CLI with the platform flags you choose.

The happy path is:

1. Create a Telegram bot with BotFather.
2. Create a `.env` file from `.env.example`.
3. Add your Telegram token and the platform credentials you need.
4. Start in dry-run mode.
5. Send a message to your bot and inspect the command preview.
6. Disable dry-run when the output looks right.

```sh
curl -o .env https://raw.githubusercontent.com/bupd/shitpost/main/.env.example
docker run -d --name shitpost \
--env-file .env \
-v ./downloads:/app/downloads \
registry.goharbor.io/bupd/shitpost:latest
```

## What it does

- Accepts Telegram text messages and posts them through `crosspost`.
- Accepts Telegram photos, downloads the largest image, and passes it to `crosspost` with optional alt text.
- Accepts video and document uploads, stores them locally, and posts caption text when the installed `crosspost` media path cannot attach them.
- Replies back in Telegram with the `crosspost` logs or the dry-run command preview.
- Can restrict usage to specific Telegram usernames or numeric user IDs.

## What to read next

- [Installation](/getting-started/installation/) for Docker, Compose, and local development.
- [Authentication](/getting-started/authentication/) for Telegram and platform tokens.
- [Configuration](/getting-started/configuration/) for every environment variable.
- [Architecture](/getting-started/architecture/) for the full request flow.
61 changes: 61 additions & 0 deletions ui/content/getting-started/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
title: "Architecture"
description: "How Telegram messages move through shitpost, crosspost, and target platforms."
weight: 50
---

`shitpost` is intentionally thin. It owns the Telegram interface, local media download, environment normalization, and Telegram replies. Publishing is delegated to `crosspost`.

```text
Telegram user
-> Telegram Bot API
-> shitpost Go process
-> crosspost CLI
-> Bluesky / Mastodon / X / other targets
```

## Runtime pieces

| Piece | Responsibility |
| --- | --- |
| Telegram Bot API | Delivers messages, photos, videos, and documents to the bot. |
| `main.go` | Polls updates, authorizes users, parses messages, downloads files, and starts `crosspost`. |
| `downloads/` | Local working directory for Telegram media files. |
| `crosspost` CLI | Publishes to the selected social platforms. |
| `.env` | Holds Telegram and platform credentials. |
| Docker image | Bundles the Go bot, Bun runtime, and built `crosspost` CLI. |

## Message flow

1. The bot starts and reads `BOT_TOKEN`, `AUTHORIZED_TELEGRAM_USERS`, `CROSSPOST_FLAGS`, and `SHITPOST_DRY_RUN`.
2. The Telegram SDK opens a long-polling update channel.
3. Non-message updates are ignored.
4. If `AUTHORIZED_TELEGRAM_USERS` is set, the sender username or numeric ID must match.
5. Text messages call `crosspost` directly with the message body.
6. Photos are downloaded from Telegram, saved to `downloads/`, and passed to `crosspost` with `--image`.
7. Captions ending with `alt:` are split into clean caption and image alt text.
8. Videos and non-image documents are downloaded, but only caption text is posted by the current CLI path.
9. The bot sends the captured `crosspost` output back to the Telegram chat.

## Docker build flow

The Dockerfile has two stages:

1. A Go builder compiles the static `bot` binary.
2. A Bun runtime clones `crosspost`, installs dependencies, builds it, writes a `/usr/local/bin/crosspost` wrapper, and copies the Go binary.

The final container starts `./bot` from `/app` and stores downloaded media in `/app/downloads`.

## CI and releases

Pull requests and pushes run formatting, vet, build, and tests. Pushes to `main` also build multi-arch images for `linux/amd64` and `linux/arm64`, sign them, publish to GHCR, and copy `latest` to Harbor.

Tags beginning with `v` run GoReleaser, publish release archives, build semver image tags, copy them to Harbor, and sign the images.

## Security model

- The service is self-hosted; messages and downloaded media stay on your machine or server.
- No web UI is exposed by this project.
- The Telegram bot should be treated as a private command surface.
- `AUTHORIZED_TELEGRAM_USERS` should be set for any real account.
- `.env` should never be committed or shared.
93 changes: 93 additions & 0 deletions ui/content/getting-started/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
title: "Authentication"
description: "Create the Telegram bot and collect the platform credentials that crosspost needs."
weight: 30
---

## Telegram bot token

1. Open [@BotFather](https://t.me/BotFather) in Telegram.
2. Send `/newbot`.
3. Pick a display name and bot username.
4. Copy the token BotFather returns.
5. Put it in `.env` as `BOT_TOKEN`.

```dotenv
BOT_TOKEN=123456789:telegram-secret
```

Treat the bot token like a password. Anyone with it can control the bot API for that bot.

## Private bot access

Set `AUTHORIZED_TELEGRAM_USERS` so only trusted Telegram accounts can trigger posts.

```dotenv
AUTHORIZED_TELEGRAM_USERS=alice,123456789
```

Accepted values are Telegram usernames without `@`, usernames with `@`, or numeric Telegram user IDs. Separate multiple users with commas.

Leaving this variable empty means anyone who can message the bot can ask it to post. That is only safe for a throwaway test bot.

## Bluesky credentials

Use an app password instead of your account password.

1. Open Bluesky settings.
2. Go to app passwords.
3. Create a new app password for `shitpost`.
4. Set your handle or email as `BLUESKY_IDENTIFIER`.
5. Set the app password as `BLUESKY_PASSWORD`.

```dotenv
BLUESKY_HOST=bsky.social
BLUESKY_IDENTIFIER=you.bsky.social
BLUESKY_PASSWORD=xxxx-xxxx-xxxx-xxxx
```

## Mastodon credentials

Create an application from your Mastodon instance preferences.

1. Open your Mastodon instance in a browser.
2. Go to Preferences, then Development.
3. Create a new application with write scopes.
4. Copy the access token.
5. Set the instance URL and token in `.env`.

```dotenv
MASTODON_HOST=https://mastodon.social
MASTODON_ACCESS_TOKEN=secret-token
```

Some `crosspost` flows may also use `MASTODON_CLIENT_KEY` and `MASTODON_CLIENT_SECRET`; keep them available if your target command needs them.

## Twitter / X credentials

`shitpost` supports the `crosspost` Twitter strategies and normalizes several legacy aliases before invoking `crosspost`.

For the emusks-backed strategy, provide the X `auth_token` cookie value:

```dotenv
AUTH_TOKEN=your-x-auth-token-cookie
```

You can also set the explicit variable:

```dotenv
TWITTER_AUTH_TOKEN=your-x-auth-token-cookie
```

If both are set, `TWITTER_AUTH_TOKEN` wins.

For official API fallback paths, use OAuth 1.0a credentials:

```dotenv
TWITTER_API_CONSUMER_KEY=
TWITTER_API_CONSUMER_SECRET=
TWITTER_ACCESS_TOKEN_KEY=
TWITTER_ACCESS_TOKEN_SECRET=
```

To find the X `auth_token` cookie, sign in to X in a browser you control, open developer tools, inspect cookies for `x.com`, and copy the value named `auth_token`. Do not paste the full cookie header; only the token value belongs in `.env`.
87 changes: 87 additions & 0 deletions ui/content/getting-started/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
title: "Configuration"
description: "Environment variables, target flags, aliases, and safe validation."
weight: 40
---

## Minimal `.env`

Start with the example file:

```sh
cp .env.example .env
```

The smallest useful dry-run config is:

```dotenv
BOT_TOKEN=123456789:telegram-secret
AUTHORIZED_TELEGRAM_USERS=your_username
CROSSPOST_FLAGS=-bmt
SHITPOST_DRY_RUN=1
```

`CROSSPOST_FLAGS=-bmt` tells `crosspost` to post to Bluesky, Mastodon, and Twitter/X. Change it to match the destinations you actually configured.

## Core variables

| Variable | Required | Purpose |
| --- | --- | --- |
| `BOT_TOKEN` | Yes | Telegram bot token from BotFather. |
| `AUTHORIZED_TELEGRAM_USERS` | Recommended | Comma-separated usernames or numeric IDs allowed to use the bot. Empty means public. |
| `CROSSPOST_FLAGS` | No | Flags passed directly to `crosspost`; defaults to `-bmt`. |
| `SHITPOST_DRY_RUN` | No | Set to `1`, `true`, or `yes` to preview commands without posting. |
| `CROSSPOST_REPO` | No | Source repo used when building the Docker image. |
| `CROSSPOST_REF` | No | Branch, tag, or ref used when building the Docker image. |

## Platform variables

| Variable | Platform | Purpose |
| --- | --- | --- |
| `BLUESKY_HOST` | Bluesky | Usually `bsky.social`. |
| `BLUESKY_IDENTIFIER` | Bluesky | Handle or email. |
| `BLUESKY_PASSWORD` | Bluesky | App password. |
| `MASTODON_HOST` | Mastodon | Instance URL, such as `https://mastodon.social`. |
| `MASTODON_ACCESS_TOKEN` | Mastodon | Access token with posting permission. |
| `MASTODON_CLIENT_KEY` | Mastodon | Optional client key for crosspost flows that need it. |
| `MASTODON_CLIENT_SECRET` | Mastodon | Optional client secret for crosspost flows that need it. |
| `AUTH_TOKEN` | X | Alias for the X `auth_token` cookie. |
| `TWITTER_AUTH_TOKEN` | X | Explicit X `auth_token`; overrides `AUTH_TOKEN`. |
| `TWITTER_AUTH_CLIENT` | X | Optional client identity understood by the emusks-backed strategy. |
| `TWITTER_GRAPHQL_ENDPOINT` | X | Optional endpoint profile understood by the emusks-backed strategy. |
| `TWITTER_PROXY` | X | Optional proxy for X requests. |
| `TWITTER_API_CONSUMER_KEY` | X | Official API consumer key fallback. |
| `TWITTER_API_CONSUMER_SECRET` | X | Official API consumer secret fallback. |
| `TWITTER_ACCESS_TOKEN_KEY` | X | Official API access token fallback. |
| `TWITTER_ACCESS_TOKEN_SECRET` | X | Official API access token secret fallback. |

## Alias normalization

Before `shitpost` starts `crosspost`, it copies legacy aliases into the variable names `crosspost` expects when the target variable is empty.

| Target | Accepted aliases |
| --- | --- |
| `TWITTER_AUTH_TOKEN` | `AUTH_TOKEN` |
| `TWITTER_API_CONSUMER_KEY` | `consumer_key`, `TWITTER_CONSUMER_KEY` |
| `TWITTER_API_CONSUMER_SECRET` | `consumer_key_secret`, `TWITTER_CONSUMER_SECRET` |
| `TWITTER_ACCESS_TOKEN_KEY` | `access_token`, `access_token_key`, `TWITTER_ACCESS_TOKEN` |
| `TWITTER_ACCESS_TOKEN_SECRET` | `access_token_secret`, `TWITTER_ACCESS_SECRET` |

## Validate without leaking secrets

Run the doctor task when posts fail or before deploying a new `.env`:

```sh
task doctor
```

The doctor script prints whether each key is present and the value length. It does not print secret values.

## Recommended first run

```dotenv
SHITPOST_DRY_RUN=1
CROSSPOST_FLAGS=-bmt
```

Send one text message and one image with a caption. If the Telegram replies show the expected command, set `SHITPOST_DRY_RUN=0` and restart the service.
77 changes: 77 additions & 0 deletions ui/content/getting-started/deployment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
title: "Deployment"
description: "Run the bot on a server and deploy these docs on Netlify."
weight: 70
---

## Bot deployment

For a server, prefer the published container image and a persistent `downloads` directory.

```sh
mkdir -p /opt/shitpost/downloads
cd /opt/shitpost
curl -o .env https://raw.githubusercontent.com/bupd/shitpost/main/.env.example
```

Edit `/opt/shitpost/.env`, then run:

```sh
docker run -d --name shitpost \
--restart unless-stopped \
--env-file /opt/shitpost/.env \
-v /opt/shitpost/downloads:/app/downloads \
registry.goharbor.io/bupd/shitpost:latest
```

Check logs:

```sh
docker logs -f shitpost
```

## Docker Compose deployment

The repo includes `docker-compose.yml`:

```sh
cp .env.example .env
docker compose up --build -d
docker compose logs -f shitpost-bot
```

The Compose service is named `shitpost-bot` and the container is named `shitpost-engine`.

## Image registries

Published images are available from:

```sh
docker pull registry.goharbor.io/bupd/shitpost:latest
docker pull ghcr.io/bupd/shitpost:latest
```

For production, use a versioned tag when one is available.

## Docs deployment on Netlify

This `ui` folder is a Hugo site. In Netlify:

1. Connect the Git repository.
2. Set the base directory to `ui`.
3. Keep the build command as `hugo --gc --minify`.
4. Keep the publish directory as `public`.

`ui/netlify.toml` already contains those settings.

## Local docs preview

```sh
hugo server --source ui
```

Build the static site:

```sh
hugo --source ui --gc --minify
```
Loading
Loading