# Social & Community Channels

Social media and community platform integrations.

---

## Reddit

### Prerequisites

- A Reddit account and a registered [Reddit API application](https://www.reddit.com/prefs/apps) (script type)

Reddit runs as an out-of-process Python sidecar adapter (`librefang.sidecar.adapters.reddit`, ships in `librefang-sdk`).

### Setup

1. Go to [reddit.com/prefs/apps](https://www.reddit.com/prefs/apps) and create a new **script** application. Note the client ID (shown under the app name) and the client secret.
2. Open the dashboard Channels page, pick Reddit, and fill the form — the dashboard writes `REDDIT_CLIENT_SECRET` and `REDDIT_PASSWORD` to `~/.librefang/secrets.env` and the rest of the fields to the `[sidecar_channels.env]` table in `~/.librefang/config.toml`. Equivalent manual config:

```toml
[[sidecar_channels]]
name = "reddit"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.reddit"]
channel_type = "reddit"
default_agent = "assistant"
[sidecar_channels.env]
REDDIT_CLIENT_ID = "abc123"
REDDIT_USERNAME = "librefang-bot"
REDDIT_SUBREDDITS = "mysubreddit,rust"
REDDIT_USER_AGENT = "myorg/1.0 (by /u/myactual-reddit-name)"   # REQUIRED, see below
# REDDIT_ACCOUNT_ID = "prod"               # optional, multi-bot routing key
# REDDIT_POLL_INTERVAL_SECS = "30"         # optional, default 30, floor 5
```

### Ban-avoidance — read before going live

Reddit is strict about bot behaviour. The adapter enforces what it can:

- **`REDDIT_USER_AGENT` is required.** Reddit's API rules require the UA to identify a real maintainer (`by /u/<your-reddit-handle>`). The literal default placeholder is rejected at startup to prevent IP+account bans. Use your own Reddit username.
- **Default 30 s polling.** Lower than 5 s burns the 60 req/min/account budget fast. The floor is 5 s; raise `REDDIT_POLL_INTERVAL_SECS` for less-active subs.
- **Automatic throttling.** When Reddit reports `X-Ratelimit-Remaining < 10`, the poller pre-emptively sleeps until the reset window. A 429 on polling backs off; a 429 on `/api/comment` honours `Retry-After` and retries once.

What the adapter cannot enforce — your responsibility:

- Bot account should be at least 30 days old with >100 comment karma; new accounts get shadowbanned quickly.
- Get mod approval before joining any subreddit. Reply-bots in unfamiliar subs are a common report-and-ban trigger.
- Gate triggers via `group_trigger_patterns` in your agent's config so the bot only replies to `/cmd` or named-mention — replying to every new comment is the fastest way to a sitewide ban.

3. Put the two secrets in `~/.librefang/secrets.env`:

```bash
REDDIT_CLIENT_SECRET=your-client-secret
REDDIT_PASSWORD=your-reddit-password
```

4. Restart the daemon.

### How It Works

The Reddit sidecar adapter authenticates via OAuth2 (password grant for script-type apps), polls each configured subreddit's new-comments stream every five seconds, and posts replies as comments through `POST /api/comment`. Reddit allows one comment per parent, so a chunked reply joins with `\n\n---\n\n` rather than splitting into multiple comments.

---

## Mastodon

### Prerequisites

- A Mastodon account on any instance
- An application access token (from the instance's **Preferences > Development** page, see [Mastodon API docs](https://docs.joinmastodon.org/api/))

Mastodon runs as an out-of-process Python sidecar adapter (`librefang.sidecar.adapters.mastodon`, ships in `librefang-sdk`).

### Setup

1. On your Mastodon instance, go to **Preferences > Development > New Application**.
2. Grant the scopes `read` and `write`.
3. Copy the access token (this becomes `MASTODON_ACCESS_TOKEN`).
4. Open the dashboard Channels page, pick Mastodon, and fill the instance URL and access token — the dashboard writes `MASTODON_ACCESS_TOKEN` to `~/.librefang/secrets.env` and `MASTODON_INSTANCE_URL` to the `[sidecar_channels.env]` table in `~/.librefang/config.toml`. Equivalent manual config:

```toml
[[sidecar_channels]]
name = "mastodon"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.mastodon"]
channel_type = "mastodon"
[sidecar_channels.env]
MASTODON_INSTANCE_URL = "https://mastodon.social"
# MASTODON_VISIBILITY = "unlisted"          # public | unlisted | private | direct
# MASTODON_MAX_MESSAGE_LEN = "500"          # raise for higher-limit instances
# MASTODON_ACCOUNT_ID = "prod"              # optional, multi-bot routing
```

5. The daemon supervises the sidecar — no manual restart is needed; the kernel's hot-reload spawns or replaces the subprocess on config change.

### How It Works

The Mastodon sidecar connects to the instance's user streaming API via Server-Sent Events (SSE) to receive mention notifications in real time. On SSE failure it falls back to REST polling of `/api/v1/notifications`. Outbound replies are posted as toots via `POST /api/v1/statuses` with `in_reply_to_id` chaining so a chunked reply stays as one thread.

---

## Bluesky

### Prerequisites

- A Bluesky account (see [Bluesky API docs](https://docs.bsky.app))
- An app password (generated in Bluesky **Settings > App Passwords**)

Bluesky runs as an out-of-process Python sidecar adapter (`librefang.sidecar.adapters.bluesky`, ships in `librefang-sdk`).

### Setup

1. In Bluesky, go to **Settings > App Passwords** and create a new app password.
2. Add a `[[sidecar_channels]]` entry:

```toml
[[sidecar_channels]]
name = "bluesky"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.bluesky"]
channel_type = "bluesky"
[sidecar_channels.env]
BLUESKY_IDENTIFIER = "your-handle.bsky.social"
# BLUESKY_SERVICE_URL = "https://bsky.social"   # custom PDS
# BLUESKY_ACCOUNT_ID = "prod"                   # optional, multi-bot routing key
```

3. Set the app password via the dashboard's Channels page (writes `BLUESKY_APP_PASSWORD` to `~/.librefang/secrets.env`), or place it directly in your vault / `.env`.
4. Restart the daemon.

### How It Works

The sidecar authenticates with `com.atproto.server.createSession` and refreshes the session JWT before expiry. It polls `app.bsky.notification.listNotifications` every 5 s (filtering `mention` + `reply` reasons) and posts replies via `com.atproto.repo.createRecord` using the `app.bsky.feed.post` lexicon. An in-memory LRU cache (capacity 200) maps inbound notification URIs to their `{root, parent}` reply refs so outbound responses thread back to the originating post; on a chunked reply every chunk reuses the same reply ref to keep the thread tight.

---

## Twitch

### Prerequisites

- A Twitch account and an OAuth token with chat permissions (from the [Twitch Developer Portal](https://dev.twitch.tv/docs/irc/))

Twitch runs as an out-of-process Python sidecar adapter (`librefang.sidecar.adapters.twitch`, ships in `librefang-sdk`).

### Setup

1. Register an application on the [Twitch Developer Console](https://dev.twitch.tv/console/apps).
2. Generate an OAuth token with the `chat:read` and `chat:edit` scopes (use a tool like [twitchtokengenerator.com](https://twitchtokengenerator.com/)).
3. Open the dashboard Channels page, pick Twitch, and fill the form — the dashboard writes `TWITCH_OAUTH_TOKEN` to `~/.librefang/secrets.env` and the rest of the fields to the `[sidecar_channels.env]` table in `~/.librefang/config.toml`. Equivalent manual config:

```toml
[[sidecar_channels]]
name = "twitch"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.twitch"]
channel_type = "twitch"
default_agent = "assistant"
[sidecar_channels.env]
TWITCH_NICK = "librefang-bot"
TWITCH_CHANNELS = "channel1,channel2"      # comma-separated, no '#'
# TWITCH_ACCOUNT_ID = "prod"                # optional, multi-bot routing
# TWITCH_RATE_LIMIT_MSGS = "20"             # 20/30s for unmodded; 100 if bot is mod
# TWITCH_RATE_LIMIT_SECS = "30"
```

4. Put the OAuth token in `~/.librefang/secrets.env`:

```bash
TWITCH_OAUTH_TOKEN=oauth:your-token
```

The `oauth:` prefix is auto-added if you leave it off. Restart the daemon.

### Security — read before going live

- **TLS is the default.** The sidecar connects to `irc.chat.twitch.tv:6697` and wraps the socket with `ssl.create_default_context()`. The previous in-process Rust adapter used plaintext port 6667, which leaked the OAuth token on every connect / reconnect — operators upgrading from a pre-migration release immediately get this fix.
- **Per-message reply threading is enabled.** The sidecar requests the `twitch.tv/tags` capability so every inbound PRIVMSG carries an `@id` tag; outbound replies attach `@reply-parent-msg-id=<id>` so Twitch renders the bot's response threaded under the source message.
- **Send rate-limiter.** Twitch's anti-spam logic drops a non-mod account from chat above 20 msgs / 30 s (100 / 30 s if mod). The sidecar throttles every PRIVMSG through an in-process token bucket. Operators with a mod-status bot can raise via `TWITCH_RATE_LIMIT_MSGS=100`; multi-process operators should still keep one process per account because the throttle is local-only.

### How It Works

The Twitch sidecar adapter holds a persistent TLS IRC connection to `irc.chat.twitch.tv:6697`, authenticates via `PASS oauth:<token>` / `NICK`, requests the `twitch.tv/tags twitch.tv/commands` capabilities, and joins every configured channel. It parses inbound PRIVMSG frames (including the IRCv3 `@`-tag block), surfaces the `@id` tag as `thread_id` for round-trip threading, dedupes by tag id, and routes `/cmd` / `!cmd` lines as Commands. Outbound `on_send` calls pass through the rate-limit bucket and emit PRIVMSG frames with the reply-parent tag when the daemon supplied a `thread_id`.
