← Docs

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-verify slash 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_gold from the brand mark variants in packages/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:

  1. Type /setup-roles-panel in the channel (or pass channel: to target another channel)
  2. Bot posts the sport-role button embed
  3. Right-click the bot's message → Pin Message
  4. 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:

  1. Test purchase a Pro subscription via Whop → confirm the user gets the Pro role in Discord within ~60 seconds
  2. Cancel the test subscription → confirm the role drops on the next sync cycle
  3. If it doesn't fire, manually run /sync-role in 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

  1. Create a test Discord account, join the server
  2. Confirm pre-join Onboarding rule fires
  3. Confirm welcome embed posts in #welcome with 4 buttons
  4. Click "Verify 21+" → ephemeral instructions appear (no role yet)
  5. Run /age-verify confirm:CONFIRM → ✅ Member role granted
  6. Click "Pick Sports" button on welcome → routed to #get-your-role
  7. Click NBA button → ephemeral confirmation, NBA role granted
  8. Click NBA again → ephemeral confirmation, NBA role removed
  9. Run /start from 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 instructions
  • alerts-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

21+ only. Not financial advice. 1-800-GAMBLER.