A Discord bot that automatically assigns a Donator role to users who have donated to the RetroDECK OpenCollective. The bot verifies donations by cross-referencing the user's email against the OpenCollective GraphQL API.
- A user runs the
/claim-donationslash command in any channel. - A private modal (form) opens, prompting them to enter the email they used on OpenCollective.
- The bot queries the OpenCollective API to check if that email belongs to a backer of the collective.
- If a match is found, the bot assigns the Donator role and confirms.
- 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.
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
- 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
- Go to the Discord Developer Portal and create a new application.
- Navigate to Bot and generate a bot token. Save it for later.
- Under Privileged Gateway Intents, enable Server Members Intent (required to assign roles).
- Copy your Application ID from the General Information page.
- Invite the bot to your server by opening this URL in your browser (replace
YOUR_APP_ID):This requests thehttps://discord.com/oauth2/authorize?client_id=YOUR_APP_ID&scope=bot+applications.commands&permissions=268435456botandapplications.commandsscopes with theManage Rolespermission (268435456).
- Log in to OpenCollective as an admin of the collective.
- Go to
https://opencollective.com/{your-org}/admin/for-developers. - Create a new OAuth application.
- Set the callback URL to
http://localhost:3000/callback. - Note the Client ID and Client Secret.
- In your Discord server, create a role called "Donator" (or use an existing one).
- 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.
- Enable Developer Mode in Discord (Settings > Advanced) and right-click the role to copy its ID.
Copy the example environment file and fill in your values:
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) |
Run the OAuth setup script to obtain an access token:
npm install
npm run setup-oauthA 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.
Register the /claim-donation slash command with Discord:
npm run deploy-commandsThis registers the command as a guild-specific command for fast updates. You only need to re-run this if you change the command definition.
docker compose up -dTo view logs:
docker compose logs -f donation-botTo rebuild after code changes:
docker compose up -d --buildnpm startThe bot uses the OpenCollective GraphQL API v2 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.
| 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 |
This project is licensed under the MIT License.