RetroDECK-Discord-Bot/README.md

163 lines
6.4 KiB
Markdown
Raw Permalink Normal View History

# RetroDECK Donation Bot
A Discord bot that automatically assigns a **Donator** role to users who have donated to the [RetroDECK OpenCollective](https://opencollective.com/retrodeck). The bot verifies donations by cross-referencing the user's email against the OpenCollective GraphQL API.
## How It Works
1. A user runs the `/claim-donation` slash command in any channel.
2. A private modal (form) opens, prompting them to enter the email they used on OpenCollective.
3. The bot queries the OpenCollective API to check if that email belongs to a backer of the collective.
4. If a match is found, the bot assigns the Donator role and confirms.
5. If no match is found, the bot suggests checking the email or contacting a moderator.
All interactions are **ephemeral** (only visible to the user), so the email address is never exposed to other members.
## Project Structure
```
retrodeck-donation-bot/
├── Dockerfile
├── docker-compose.yml
├── package.json
├── .env.example
├── data/
│ └── oc-token.json # OAuth token (created by setup script, git-ignored)
├── src/
│ ├── index.js # Bot entry point, client setup, event routing
│ ├── deploy-commands.js # Script to register slash commands with Discord
│ ├── setup-oauth.js # One-time OAuth setup script
│ ├── commands/
│ │ └── claim-donation.js # Slash command definition, modal, and verification logic
│ └── services/
│ ├── opencollective.js # OpenCollective GraphQL API query logic
│ └── token-store.js # OAuth token file read/write
```
## Prerequisites
- **Node.js** 20 or later
- **Docker** and **Docker Compose** (for containerized deployment)
- A **Discord bot application** with the required permissions
- An **OpenCollective OAuth application** with admin access to the collective
## Setup
### 1. Create a Discord Bot Application
1. Go to the [Discord Developer Portal](https://discord.com/developers/applications) and create a new application.
2. Navigate to **Bot** and generate a bot token. Save it for later.
3. Under **Privileged Gateway Intents**, enable **Server Members Intent** (required to assign roles).
4. Copy your **Application ID** from the **General Information** page.
5. Invite the bot to your server by opening this URL in your browser (replace `YOUR_APP_ID`):
```
https://discord.com/oauth2/authorize?client_id=YOUR_APP_ID&scope=bot+applications.commands&permissions=268435456
```
This requests the `bot` and `applications.commands` scopes with the `Manage Roles` permission (`268435456`).
### 2. Create an OpenCollective OAuth Application
1. Log in to [OpenCollective](https://opencollective.com/) as an admin of the collective.
2. Go to `https://opencollective.com/{your-org}/admin/for-developers`.
3. Create a new OAuth application.
4. Set the callback URL to `http://localhost:3000/callback`.
5. Note the **Client ID** and **Client Secret**.
### 3. Identify the Donator Role
1. In your Discord server, create a role called "Donator" (or use an existing one).
2. Make sure the bot's role is **above** the Donator role in the role hierarchy (Server Settings > Roles), otherwise it won't be able to assign it.
3. Enable **Developer Mode** in Discord (Settings > Advanced) and right-click the role to copy its ID.
### 4. Configure Environment Variables
Copy the example environment file and fill in your values:
```bash
cp .env.example .env
```
| Variable | Description |
|---|---|
| `DISCORD_BOT_TOKEN` | The bot token from the Discord Developer Portal |
| `DISCORD_GUILD_ID` | Your Discord server ID (right-click server name > Copy Server ID) |
| `DISCORD_DONATOR_ROLE_ID` | The role ID for the Donator role |
| `OC_CLIENT_ID` | OpenCollective OAuth app client ID |
| `OC_CLIENT_SECRET` | OpenCollective OAuth app client secret |
| `OC_REDIRECT_URI` | OAuth redirect URI (default: `http://localhost:3000/callback`) |
| `OC_COLLECTIVE_SLUG` | The OpenCollective collective slug (e.g. `retrodeck`) |
### 5. Authenticate with OpenCollective
Run the OAuth setup script to obtain an access token:
```bash
npm install
npm run setup-oauth
```
A browser window will open asking you to authorize the app on OpenCollective with `email` and `account` scopes. After approval, the token is saved to `data/oc-token.json`. This only needs to be done once. If the token expires, re-run the command.
### 6. Register the Slash Command
Register the `/claim-donation` slash command with Discord:
```bash
npm run deploy-commands
```
This registers the command as a **guild-specific** command for fast updates. You only need to re-run this if you change the command definition.
### 7. Start the Bot
#### With Docker (recommended)
```bash
docker compose up -d
```
To view logs:
```bash
docker compose logs -f donation-bot
```
To rebuild after code changes:
```bash
docker compose up -d --build
```
#### Without Docker
```bash
npm start
```
## OpenCollective API Details
The bot uses the [OpenCollective GraphQL API v2](https://docs.opencollective.com/help/contributing/development/api) to verify donations, authenticating with an OAuth Bearer token.
**Primary strategy:** Query the collective's members (backers) and match the provided email against each member's `emails` field. The query paginates through all members automatically.
**Fallback strategy:** If the members query fails (e.g. due to permissions), the bot falls back to querying credit transactions and matching against the `fromAccount.emails` field.
Both strategies perform **case-insensitive** email matching.
The OAuth app requires the `email` and `account` scopes.
## Edge Cases
| Scenario | Behavior |
|---|---|
| User already has the Donator role | Skips the API call and tells the user they already have it |
| Email not found | Suggests double-checking the email or contacting a moderator |
| Guest/anonymous donation | Cannot be verified automatically; the bot mentions this possibility |
| OpenCollective API error | Responds with a generic error and logs the details to the console |
| OAuth token expired/invalid | Bot logs a clear error; re-run `npm run setup-oauth` to re-authenticate |
| No token file present | Bot refuses to start with instructions to run `npm run setup-oauth` |
| Discord rate limits | Handled automatically by Discord.js |
## License
This project is licensed under the [MIT License](LICENSE).