Skip to content
Open
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
88 changes: 88 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copy this file to `.env` and fill in real values.
# `.env` is gitignored; this template mirrors it so a fresh checkout knows what to set.

# --- Showcase moderator (showcase.js) ---
SHOWCASE_BOT_TOKEN=
SHOWCASE_CLIENT_ID=
SHOWCASE_GUILD_ID=
SHOWCASE_CHANNEL_ID=

# --- Mac Mini host access (ops only; not used by the bots) ---
MAC_MINI_ADMIN_PASSWORD=
MAC_MINI_BOT_USER_PASSWORD=

# --- RocketRide support bot / Rocket Ralph (support.ts) — LOCAL engine, webhook + multi-modal ---
# Runs rocket-ralph.pipe + summariser.pipe on the LOCAL engine; support.ts auto-detects the
# engine's dynamic port, so no URI is needed normally. Qdrant is local (127.0.0.1:6333) — no key.
# ROCKETRIDE_URI=http://localhost:5565 # optional override of the auto-detected engine URI
ROCKETRIDE_APIKEY= # local engine API key (if the engine requires one)
SUPPORT_BOT_TOKEN= # Rocket Ralph Discord bot token
SUPPORT_CHANNEL_ID= # the support channel to listen in
# SUPPORT_MENTION_CHANNEL_ID= # optional "guest" channel: answers only when @-mentioned
# Used by rocket-ralph.pipe: OpenAI (agent LLM) + GitHub PAT (read-only GitHub tools).
ROCKETRIDE_OPENAI_KEY=
ROCKETRIDE_GITHUB_TOKEN=

# --- RocketRide support TEST bot (support-test.ts) — test channel → CLOUD pipeline via webhook ---
# Posts to pipelines DEPLOYED on RocketRide Cloud via their webhook endpoints (HTTP POST, pk_ auth).
SUPPORT_TEST_CHANNEL_ID=
SUPPORT_TEST_WEBHOOK_URL=https://api.rocketride.ai/webhook
SUPPORT_TEST_WEBHOOK_KEY= # pk_… — ralph pipeline (per-modality processing)
SUPPORT_TEST_SUMMARISER_KEY= # pk_… — summariser pipeline (merges multi-part results)
# SUPPORT_TEST_BOT_TOKEN= # blank → reuses SUPPORT_BOT_TOKEN (same Rocket Ralph bot)

# --- Discord Post Scheduler (scheduler.ts; needs Redis) ---
SCHEDULER_BOT_TOKEN=
SCHEDULER_GUILD_ID=
# REDIS_URL=redis://127.0.0.1:6379 # optional, this is the default

# --- Social feed announcer (social.ts) ---
# How many latest items to consider per source (default 5)
SOCIAL_FETCH_LIMIT=5
# Schedule (defaults shown) — hours 0-23 + IANA timezone
# SOCIAL_CRON_HOURS=9,10,11,16,17,18
# SOCIAL_TZ=America/Los_Angeles

# Newsletter (Ghost) — read-only Content API
GHOST_API_URL=https://your-site.ghost.io
GHOST_CONTENT_API_KEY=
# GHOST_ADMIN_KEY= # not needed for read-only fetching

# X / Twitter (official API v2) — bearer token + numeric user id are what the fetcher uses
X_BEARER_TOKEN=
X_USER_ID=
X_USERNAME=
# X_EXCLUDE_REPLIES=true # optional (default true) — excludes comments
# X_EXCLUDE_RETWEETS=true # optional (default true) — excludes reposts
# OAuth 1.0a creds — only for future write access, unused by read fetch
# X_API_KEY=
# X_API_SECRET=
# X_ACCESS_TOKEN=
# X_ACCESS_SECRET=

# YouTube — Data API v3 key + channel id
YOUTUBE_API_KEY=
YOUTUBE_CHANNEL_ID=
# YOUTUBE_HANDLE=@yourhandle # alternative to the channel id

# Instagram — Meta Graph API (Business/Creator account)
INSTAGRAM_ACCOUNT_ID=
INSTAGRAM_ACCESS_TOKEN=
# INSTAGRAM_API_VERSION=v22.0 # optional (default v22.0)

# LinkedIn — Community Management API. A refresh token is exchanged for an access token each run
# via the client id+secret (access tokens expire ~60d). Only original posts are announced (reshares filtered).
LINKEDIN_CLIENT_ID=
LINKEDIN_CLIENT_SECRET=
LINKEDIN_ACCESS_TOKEN= # the refresh token; exchanged via client id+secret each run
LINKEDIN_ORG_ID= # numeric company-page id → urn:li:organization:{id}
# LINKEDIN_REFRESH_TOKEN= # alt to putting the refresh token in LINKEDIN_ACCESS_TOKEN
# LINKEDIN_AUTHOR_URN= # alt to LINKEDIN_ORG_ID (full urn:li:person/organization)
# LINKEDIN_API_VERSION=202508 # LinkedIn-Version header (YYYYMM); bump if it 426s

# Where the announcer posts
SOCIAL_DISCORD_CHANNEL_ID=
# SOCIAL_DISCORD_TOKEN= # optional; defaults to SCHEDULER_BOT_TOKEN
# Dedicated announcer bot (not used yet — the scheduler bot posts for now)
# DISCORD_BOT_TOKEN=
# DISCORD_GUILD_ID=
17 changes: 16 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,17 @@
node_modules
# dependencies
node_modules/

# secrets & local env
.env
.env.local

# runtime state & logs (pids, bot logs, paused-threads.json)
logs/

# RocketRide SDK docs fetched locally (see refresh-docs.sh)
.rocketride/

# OS / editor junk
.DS_Store
**/.DS_Store
Thumbs.db
87 changes: 87 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# rocketride-discord

Discord bots for the RocketRide community server. Each bot is a **single, standalone
file** run as its own process — there's no shared framework or import graph between
them, which keeps each one easy to reason about and deploy independently.

| Bot | File | What it does |
| --- | --- | --- |
| **Showcase** | `showcase.js` | `/submit` flow that turns project submissions into formatted cards + feedback threads. |
| **Support** (Rocket Ralph) | `support.ts` | Answers support questions in threads, powered by a RocketRide pipeline; escalates to the team. |
| **Scheduler** | `scheduler.ts` | Schedule Discord posts for later; durable via Redis. |
| **Social announcer** | `social.ts` | Posts RocketRide's latest YouTube / X / newsletter items to a channel on a schedule. |

Helper scripts `deploy.js` / `migrate.js` are one-shot showcase utilities (see
`.claude/CLAUDE.md`).

## Setup

```bash
cp .env.example .env # then fill in the values
npm install
```

- Runtime: Node (via nvm) + `tsx` for the TypeScript bots. `.env` is loaded with `dotenv`.
- `.env` is gitignored — secrets never get committed. `.env.example` documents every var.

## Running

The bots are managed as a fleet by the shell scripts (they run detached via `nohup`,
log to `logs/*.log`, and track pids in `logs/*.pid`):

```bash
./start-bots.sh # start any that aren't already running
./status-bots.sh # show RUNNING / STOPPED per bot
./stop-bots.sh # stop them all
```

`start-bots.sh` is idempotent (it skips a bot that's already up), auto-detects the
RocketRide engine's dynamic port for the support bot, and ensures the scheduler's
Redis container is up. To pick up new code, `./stop-bots.sh` first.

To run a single bot directly (node isn't on `PATH` in a bare shell — point at nvm):

```bash
export PATH="$HOME/.nvm/versions/node/v26.3.0/bin:$PATH"
node showcase.js
./node_modules/.bin/tsx support.ts
./node_modules/.bin/tsx scheduler.ts
./node_modules/.bin/tsx social.ts
```

## Social announcer (`social.ts`)

Fetches the latest posts from each configured source, and announces only **new** ones
to `SOCIAL_DISCORD_CHANNEL_ID` as embeds (posting via REST using the scheduler bot's
token — it has no gateway login of its own).

- **YouTube** — Data API v3 (`YOUTUBE_API_KEY` + `YOUTUBE_CHANNEL_ID`).
- **X / Twitter** — official API v2 (`X_BEARER_TOKEN` + `X_USER_ID`); replies (comments)
and retweets (reposts) are excluded — only real posts.
- **Newsletter** — Ghost Content API (`GHOST_API_URL` + `GHOST_CONTENT_API_KEY`).

**Only the latest, never old:** a per-platform *watermark* (`logs/social-seen.json`)
records the newest item seen the first time each platform is checked, then only posts
items newer than that — so history is never backfilled.

**Schedule:** in-process, via Luxon in an explicit timezone (DST-correct regardless of
the host clock). Default `9, 10, 11 AM and 4, 5, 6 PM America/Los_Angeles`
(`SOCIAL_CRON_HOURS` / `SOCIAL_TZ` to override).

**Manual run / preview:**

```bash
./node_modules/.bin/tsx social.ts --social-once # one pass now, then exit
./node_modules/.bin/tsx social.ts --social-once --dry # show what it would post, send nothing
./node_modules/.bin/tsx social.ts --social-once --backfill # post current items (skip first-run seeding)
```

**Add a platform** (LinkedIn, Instagram, TikTok, Medium, …): write a `fetch…` function
returning `FeedItem[]` and add an entry to the `SOCIAL_SOURCES` array in `social.ts`.

## Conventions

- **One bot = one file** at the repo root. Add a new bot by dropping a new file and
adding a line to the three `*-bots.sh` scripts. No folders needed until a single bot
genuinely outgrows one file.
- Config lives in `.env` (mirror new vars into `.env.example`).
200 changes: 0 additions & 200 deletions bot.ts

This file was deleted.

Loading