NuroPicks Discord Onboarding (Lyra Phase 3)
Last updated: 2026-05-06
This doc covers (a) the new code David's bot ships in Phase 3 and (b) the GUI/admin work in Discord that only David can do. Read top to bottom on first setup; use the appendix as a checklist when adjusting.
What changed in code
| File | Purpose |
|---|---|
src/bot/constants/channels.js |
Single source of truth for channel IDs. Resolves env-var first, name-needle fallback. |
src/bot/constants/roles.js |
Single source of truth for role IDs. Backward-compatible with existing DISCORD_ROLE_ID_PRO/ELITE/ON_FIRE/... env vars. Adds entries for Lifetime + Founder. |
src/bot/events/guildMemberAdd.js |
Welcome embed now uses the channels registry + Phase 3 button row: Verify 21+, Pick Sports, Read Today's Pick, Upgrade. |
src/bot/commands/start.js |
New /start master command. Premium walkthrough for new and existing members. Shows step state (e.g. ✅ already 21+) so returners don't re-do steps. |
src/bot/commands/setup-roles-panel.js |
New admin-only command. Posts the sport-role button matrix in #get-your-role. Restricted to ManageGuild permission. |
src/bot/index.js |
Two new button handlers: onboarding:verify-21 (instructions, no auto-grant) and alerts-role:<SPORT> (idempotent role toggle). |
src/bot/utils/brandedEmbed.js |
Added premiumEmbed({ tier }) helper. Tints picks gold for Lifetime, cyan for Elite, lime for Pro. Used when picks-publisher posts to tier-restricted channels. |
Existing /alerts, /help, /quickstart, /getting-started,
/onboarding-help, /howto, /learn, /onboarding-status,
/age-verify, /sync-role all keep working.
David's GUI checklist
Do these in order. Each is a Discord-side or Whop-side action that code can't do.
1. Server Settings → Onboarding (pre-join 21+ gate)
- Server Settings → Onboarding → enable "Membership Screening"
- Add a custom rule: "I am 21 or older and bet for entertainment only."
- Save. New joins now hit this BEFORE they see any channel.
- The bot's
/age-verifyslash command remains the manual fallback for members who skipped the pre-join screen.
2. Channel structure
Create channels and pin the bot ID to them. Order matters for sidebar hierarchy.
| Channel | Visibility | Notes |
|---|---|---|
#welcome |
All members | Welcome embed posts here. System channel. |
#age-verify |
All members | Pin a single message: "Type /age-verify confirm:CONFIRM to unlock the rest of the server." |
#rules |
All members, read-only | House rules + 21+ + responsible-betting. Pin and lock. |
#announcements |
All members, read-only | Bot posts changelog + outage notes. |
#get-your-role |
Member 21+ only | Run /setup-roles-panel here once to drop the sport-role buttons. Pin the resulting message. |
#free-picks |
Member 21+ only | Public free picks. The 9 AM ET morning pulse fires here. |
#premium-picks |
Pro role required | Full slate. Pro members only. |
#elite-lounge |
Elite role required | Elite-only. Locked until the public record proves itself. |
#lifetime-lounge |
Lifetime role required | Founder community. |
#founder-chat |
Founder role required | Highest-trust channel. David + Lifetime members. |
After creating each channel, copy the channel ID (right-click → Copy Channel ID — requires Developer Mode enabled in User Settings → Advanced) and paste into Coolify env vars per the table below.
DISCORD_CHANNEL_WELCOME=...
DISCORD_CHANNEL_AGE_VERIFY=...
DISCORD_CHANNEL_GET_YOUR_ROLE=...
DISCORD_CHANNEL_RULES=...
DISCORD_CHANNEL_ANNOUNCEMENTS=...
DISCORD_CHANNEL_FREE_PICKS=...
DISCORD_CHANNEL_PREMIUM_PICKS=...
DISCORD_CHANNEL_ELITE_LOUNGE=...
DISCORD_CHANNEL_LIFETIME_LOUNGE=...
DISCORD_CHANNEL_FOUNDER_CHAT=...
DISCORD_CHANNEL_GM_LOUNGE=...
DISCORD_CHANNEL_GENERAL_CHAT=...
If env vars aren't set, the bot falls back to scanning channel names that include the needle (e.g. "welcome", "elite", "lifetime"). That keeps the bot working in staging guilds.
3. Roles + colors
Create or rename these roles. Bot role MUST sit above any role it needs to grant — drag it in the role list.
| Role | Hex color | Purpose |
|---|---|---|
| ✅ Member | #8FA3B8 (silver) |
Granted by /age-verify |
| Free | #8FA3B8 |
Default tier (optional explicit role) |
| Pro | #8CE004 (lime) |
Granted by Whop webhook on Pro purchase |
| Elite | #00E5FF (cyan) |
Locked until public record proves itself |
| Lifetime | #FFD166 (gold) |
Granted by Whop webhook on $999 Lifetime |
| Founder | #FFD166 + custom icon |
Granted to Lifetime + early supporters |
| 🏀 NBA | any | Sport-alert ping role |
| 🏈 NFL | any | Sport-alert ping role |
| ⚾ MLB | any | Sport-alert ping role |
| 🏒 NHL | any | Sport-alert ping role |
Capture each role ID and set the corresponding env var:
DISCORD_ROLE_ID_MEMBER_21=...
DISCORD_ROLE_ID_FREE=...
DISCORD_ROLE_ID_PRO=...
DISCORD_ROLE_ID_ELITE=...
DISCORD_ROLE_ID_LIFETIME=...
DISCORD_ROLE_ID_FOUNDER=...
NBA_ALERTS_ROLE_ID=...
NFL_ALERTS_ROLE_ID=...
MLB_ALERTS_ROLE_ID=...
NHL_ALERTS_ROLE_ID=...
The Pro / Elite / streak-badge env vars (DISCORD_ROLE_ID_PRO,
DISCORD_ROLE_ID_ELITE, DISCORD_ROLE_ID_ON_FIRE,
DISCORD_ROLE_ID_STREAK_MASTER, DISCORD_ROLE_ID_LEGEND_STREAK) are
already wired to existing services. Don't rename — roles.js reads
the same env names.
4. Custom emoji (optional polish)
- Server Settings → Emoji → Upload Emoji
- Upload
np_lime,np_cyan,np_goldfrom the brand mark variants inpackages/brand/ - These are nice-to-have for chrome polish in pick embeds. Phase 3 ships without them; the bot uses Unicode emoji that work cross-server (🏀 🏈 ⚾ 🏒 🛡️ 🎫 🔥 ⚡).
5. Server banner + icon + tagline
- Server Settings → Overview → Server Banner: upload the wordmark
banner from
nuropicks.com/brand/discord/banner-wordmark.png - Server Icon: upload the chrome-X mark
- Server description (300 chars): "AI sports picks with a public, append-only record. 21+ entertainment only. Free + Pro + Lifetime tiers. Cancel from your dashboard in two clicks."
6. Run /setup-roles-panel
Once #get-your-role exists and the bot has Send Messages + Embed
Links permission there:
- Type
/setup-roles-panelin the channel (or passchannel:to target another channel) - Bot posts the sport-role button embed
- Right-click the bot's message → Pin Message
- Done. Members can now click NBA / NFL / MLB / NHL to toggle.
7. Whop → Discord role grant (verify, don't touch)
The Whop webhook receiver is in the web backend, not the bot. To verify it works:
- Test purchase a Pro subscription via Whop → confirm the user gets the Pro role in Discord within ~60 seconds
- Cancel the test subscription → confirm the role drops on the next sync cycle
- If it doesn't fire, manually run
/sync-rolein Discord to force a re-apply from the database
There is no bot-side webhook code to change. Phase 3 doesn't touch billing.
8. Test Phase 3 from a fresh account
- Create a test Discord account, join the server
- Confirm pre-join Onboarding rule fires
- Confirm welcome embed posts in
#welcomewith 4 buttons - Click "Verify 21+" → ephemeral instructions appear (no role yet)
- Run
/age-verify confirm:CONFIRM→ ✅ Member role granted - Click "Pick Sports" button on welcome → routed to
#get-your-role - Click NBA button → ephemeral confirmation, NBA role granted
- Click NBA again → ephemeral confirmation, NBA role removed
- Run
/startfrom anywhere → walkthrough embed appears with Step 2 marked ✅ since you're already 21+
If any step fails, check Coolify logs for the bot. The new constants files print a warning at startup when env vars are missing for any configured channel/role.
Appendix: Phase 3 button + command reference
New customIds in src/bot/index.js
onboarding:verify-21— ephemeral /age-verify instructionsalerts-role:<SPORT>— toggle NBA/NFL/MLB/NHL role (idempotent)
New slash commands
/start— premium master walkthrough (DM + guild)/setup-roles-panel— admin-only, posts sport-role buttons
Preserved existing commands (don't remove)
/alerts, /help, /quickstart, /getting-started,
/onboarding-help, /howto, /learn, /onboarding-status,
/age-verify, /sync-role
Brand colors used in Phase 3
- Lime (
#8CE004) — primary brand - Cyan (
#00E5FF) — Elite + secondary chrome - Gold (
#FFD166) — Lifetime / Founder - Silver (
#8FA3B8) — Member / Free