Skip to content

Discord Bot Integration for Feature Requests

Last Updated: 2026-01-12
Status: 📝 Documented (Not Yet Implemented)

Overview

This guide explains how to allow users to submit feature requests directly from Discord using a bot with slash commands.

Why Discord Integration?

  • Convenience: Users can submit feedback without leaving Discord
  • Community Engagement: Encourages participation from the Discord community
  • Accessibility: Non-technical users may prefer Discord over GitHub
  • Notifications: Team already monitors Discord channels

Architecture

Discord User

/feature-request command

Discord Bot (Node.js)

Firebase Cloud Function (createFeatureRequest)

GitHub Issue + Discord Notification

Implementation Options

Use Discord's built-in modal forms for structured input.

Pros:

  • Native Discord UI
  • Input validation
  • Character limits enforced
  • Best UX for Discord users

Cons:

  • Requires custom Discord bot
  • Additional hosting for bot
  • More complex setup

Bot provides a link to the web form.

Pros:

  • Simplest implementation
  • Reuses existing web form
  • No duplicate code

Cons:

  • Takes user out of Discord
  • Less seamless experience

Option 1: Discord Bot with Slash Commands

Prerequisites

  • Discord Application with bot token
  • Node.js hosting (Cloudflare Workers, Railway, Heroku, etc.)
  • Firebase Functions already deployed

1. Create Discord Application

  1. Go to Discord Developer Portal
  2. Click "New Application"
  3. Name it "Lantern Feedback Bot"
  4. Go to "Bot" tab
  5. Click "Add Bot"
  6. Copy the bot token (save it securely)
  7. Enable "Message Content Intent" if needed

2. Set Up Bot Permissions

In the "OAuth2" → "URL Generator":

Scopes:

  • bot
  • applications.commands

Bot Permissions:

  • Send Messages
  • Use Slash Commands
  • Embed Links

Copy the generated URL and invite the bot to your Discord server.

3. Register Slash Command

Create /feature-request command:

javascript
// register-commands.js
import { REST, Routes, SlashCommandBuilder } from 'discord.js'

const commands = [
  new SlashCommandBuilder()
    .setName('feature-request')
    .setDescription('Submit a feature request or bug report')
    .toJSON()
]

const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN)

async function registerCommands() {
  try {
    await rest.put(
      Routes.applicationGuildCommands(
        process.env.DISCORD_CLIENT_ID,
        process.env.DISCORD_GUILD_ID
      ),
      { body: commands }
    )
    console.log('✅ Slash command registered!')
  } catch (error) {
    console.error('Error registering command:', error)
  }
}

registerCommands()

Run once to register:

bash
node register-commands.js

4. Create Bot Server

javascript
// bot.js
import { Client, GatewayIntentBits, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder } from 'discord.js'
import { initializeApp } from 'firebase-admin/app'
import { getFunctions, httpsCallable } from 'firebase-admin/functions'

// Initialize Firebase Admin
initializeApp()

const client = new Client({
  intents: [GatewayIntentBits.Guilds]
})

client.on('interactionCreate', async interaction => {
  if (!interaction.isChatInputCommand()) return

  if (interaction.commandName === 'feature-request') {
    // Show modal form
    const modal = new ModalBuilder()
      .setCustomId('featureRequestModal')
      .setTitle('Submit Feature Request')

    // Request Type (doesn't support select in modals, use description field)
    const typeInput = new TextInputBuilder()
      .setCustomId('type')
      .setLabel('Type (feature/bug/improvement/question)')
      .setStyle(TextInputStyle.Short)
      .setPlaceholder('feature')
      .setRequired(true)
      .setMaxLength(20)

    // Title
    const titleInput = new TextInputBuilder()
      .setCustomId('title')
      .setLabel('Title')
      .setStyle(TextInputStyle.Short)
      .setPlaceholder('Brief summary of your request')
      .setRequired(true)
      .setMaxLength(100)

    // Description
    const descriptionInput = new TextInputBuilder()
      .setCustomId('description')
      .setLabel('Description')
      .setStyle(TextInputStyle.Paragraph)
      .setPlaceholder('Detailed explanation...')
      .setRequired(true)
      .setMaxLength(1000)

    // Use Case
    const useCaseInput = new TextInputBuilder()
      .setCustomId('useCase')
      .setLabel('Use Case (Optional)')
      .setStyle(TextInputStyle.Paragraph)
      .setPlaceholder('Example: As a user, when I...')
      .setRequired(false)
      .setMaxLength(500)

    // Priority
    const priorityInput = new TextInputBuilder()
      .setCustomId('priority')
      .setLabel('Priority (low/medium/high/critical)')
      .setStyle(TextInputStyle.Short)
      .setPlaceholder('medium')
      .setRequired(true)
      .setMaxLength(20)

    // Add inputs to action rows (max 5 per modal)
    const firstRow = new ActionRowBuilder().addComponents(typeInput)
    const secondRow = new ActionRowBuilder().addComponents(titleInput)
    const thirdRow = new ActionRowBuilder().addComponents(descriptionInput)
    const fourthRow = new ActionRowBuilder().addComponents(useCaseInput)
    const fifthRow = new ActionRowBuilder().addComponents(priorityInput)

    modal.addComponents(firstRow, secondRow, thirdRow, fourthRow, fifthRow)

    await interaction.showModal(modal)
  }
})

// Handle modal submission
client.on('interactionCreate', async interaction => {
  if (!interaction.isModalSubmit()) return
  
  if (interaction.customId === 'featureRequestModal') {
    await interaction.deferReply({ ephemeral: true })

    try {
      // Get form values
      const type = interaction.fields.getTextInputValue('type').toLowerCase()
      const title = interaction.fields.getTextInputValue('title')
      const description = interaction.fields.getTextInputValue('description')
      const useCase = interaction.fields.getTextInputValue('useCase') || null
      const priority = interaction.fields.getTextInputValue('priority').toLowerCase()

      // Validate type
      const validTypes = ['feature', 'bug', 'improvement', 'question']
      if (!validTypes.includes(type)) {
        await interaction.editReply({
          content: '❌ Invalid type. Must be: feature, bug, improvement, or question'
        })
        return
      }

      // Validate priority
      const validPriorities = ['low', 'medium', 'high', 'critical']
      if (!validPriorities.includes(priority)) {
        await interaction.editReply({
          content: '❌ Invalid priority. Must be: low, medium, high, or critical'
        })
        return
      }

      // Call Firebase Function
      const createFeatureRequest = httpsCallable(getFunctions(), 'createFeatureRequest')
      const result = await createFeatureRequest({
        type,
        title,
        description,
        useCase,
        priority,
        impact: 'individual',  // Default for Discord submissions
        isAnonymous: false,    // Discord username will be in GitHub issue
        submittedBy: interaction.user.tag  // Discord username
      })

      // Success!
      await interaction.editReply({
        content: `✅ **Request Submitted!**\n\nGitHub Issue: ${result.data.issueUrl}\nIssue #${result.data.issueNumber}\n\nYou can track progress using the link above.`
      })
    } catch (error) {
      console.error('Error submitting request:', error)
      await interaction.editReply({
        content: '❌ Failed to submit request. Please try again or use the web form.'
      })
    }
  }
})

client.login(process.env.DISCORD_BOT_TOKEN)

5. Deploy Bot

Option A: Cloudflare Workers

bash
# Cloudflare Workers doesn't support Discord.js well
# Use Railway or Heroku instead

Option B: Railway (RECOMMENDED)

bash
# 1. Create account at railway.app
# 2. New Project → Deploy from GitHub
# 3. Add environment variables:
#    DISCORD_BOT_TOKEN=your_bot_token
#    DISCORD_CLIENT_ID=your_client_id
#    DISCORD_GUILD_ID=your_server_id
#    FIREBASE_PROJECT_ID=lantern-app-dev (or prod)
# 4. Deploy!

Option C: Heroku

bash
heroku create lantern-discord-bot
heroku config:set DISCORD_BOT_TOKEN=your_token
heroku config:set FIREBASE_PROJECT_ID=lantern-app-dev
git push heroku main

6. Testing

In Discord:

/feature-request

Modal appears → Fill out form → Submit → Bot replies with GitHub issue link

Much simpler - just provides a link to the web form:

javascript
// simple-bot.js
import { Client, GatewayIntentBits } from 'discord.js'

const client = new Client({
  intents: [GatewayIntentBits.Guilds]
})

client.on('interactionCreate', async interaction => {
  if (!interaction.isChatInputCommand()) return

  if (interaction.commandName === 'feedback') {
    await interaction.reply({
      content: '📝 **Submit Feedback**\n\n' +
               '🌐 Web Form: https://ourlantern.app/#/feedback\n' +
               '📖 GitHub Issues: https://github.com/cattreedev/lantern_app/issues\n\n' +
               'Please use the web form to submit feature requests, bug reports, or questions.',
      ephemeral: true
    })
  }
})

client.login(process.env.DISCORD_BOT_TOKEN)

Register command:

javascript
new SlashCommandBuilder()
  .setName('feedback')
  .setDescription('Get link to submit feedback')

Copilot Integration for Refinement

AI-Powered Request Improvement

When a user submits via Discord, the bot can use AI to refine the request before creating the GitHub issue.

javascript
import { Configuration, OpenAIApi } from 'openai'  // Or Claude, etc.

async function refineWithAI(userInput) {
  const prompt = `
You are helping refine a feature request for the Lantern app.
Lantern is an anonymous, location-based social app for meeting people at real places.

User's request:
Title: ${userInput.title}
Description: ${userInput.description}

Please suggest:
1. A clearer, more specific title (max 100 chars)
2. An improved description that explains:
   - What problem this solves
   - How it would work
   - Why it matters
3. A concrete use case or user story

Keep the user's intent but make it more actionable for developers.
`

  const response = await openai.createCompletion({
    model: 'gpt-4',
    prompt,
    max_tokens: 500
  })

  return response.data.choices[0].text
}

// In the modal submission handler:
const refinedRequest = await refineWithAI({ title, description })

// Show user the suggestions
await interaction.followUp({
  content: `🤖 **AI Suggestions:**\n\n${refinedRequest}\n\n` +
           'Would you like to:\n' +
           '1️⃣ Use AI suggestions\n' +
           '2️⃣ Keep original\n' +
           '3️⃣ Edit manually',
  ephemeral: true
})

Duplicate Detection in Discord

Before submitting, check for duplicates and warn the user:

javascript
const checkDuplicates = httpsCallable(getFunctions(), 'checkDuplicates')
const duplicates = await checkDuplicates({ title, description })

if (duplicates.data.similar.length > 0) {
  const similarList = duplicates.data.similar
    .slice(0, 3)
    .map(req => `• #${req.githubIssueNumber}: ${req.title} (${Math.round(req.score * 100)}% similar)`)
    .join('\n')

  await interaction.followUp({
    content: `⚠️ **Similar Requests Found:**\n\n${similarList}\n\n` +
             'Your request may be a duplicate. Continue anyway?',
    components: [
      new ActionRowBuilder().addComponents(
        new ButtonBuilder()
          .setCustomId('submit_anyway')
          .setLabel('Submit Anyway')
          .setStyle(ButtonStyle.Primary),
        new ButtonBuilder()
          .setCustomId('cancel')
          .setLabel('Cancel')
          .setStyle(ButtonStyle.Secondary)
      )
    ],
    ephemeral: true
  })
}

Environment Configuration

The Discord bot should respect the same dev/prod split:

Dev Bot:

  • Uses lantern-app-dev Firebase project
  • Submits to #dev-feedback Discord channel
  • Creates issues with env-development label

Prod Bot:

  • Uses lantern-app-prod Firebase project
  • Submits to #user-feedback Discord channel
  • Creates issues with env-production label

Or use a single bot with environment detection based on the Discord server.

Security Considerations

  • Rate Limiting: Enforce same 5 submissions per 24 hours per Discord user
  • Validation: Always validate input (type, priority, character limits)
  • Bot Token: Never commit the bot token - use environment variables
  • Permissions: Bot should have minimal Discord permissions
  • Authentication: Identify users by Discord ID, not username (usernames can change)

Monitoring

Track Discord submissions:

  • Count per user
  • Success/failure rate
  • Most common request types
  • Duplicate detection effectiveness

Cost Considerations

Discord Bot Hosting:

  • Railway: Free tier available, then $5/month
  • Heroku: Free tier deprecated, starts at $7/month
  • Self-hosted: Free if you have a server

Firebase Functions:

  • Included in free tier for low volume
  • ~$0.40 per million invocations after free tier

AI Refinement (Optional):

  • OpenAI GPT-4: ~$0.03 per request
  • Claude: ~$0.008 per request
  • Can skip AI to reduce costs

Implementation Status

FeatureStatusPriority
Basic slash command📝 DocumentedP1
Modal form📝 DocumentedP1
Firebase integration✅ ReadyP1
Duplicate detection✅ ReadyP2
AI refinement📝 DocumentedP3
Simple link bot📝 DocumentedP0 (Quick win)

The fastest implementation - just add a /feedback command that links to the web form:

  1. Create Discord bot application
  2. Deploy 50-line Node.js script to Railway
  3. Invite bot to server
  4. Users type /feedback and get web form link

Estimated setup time: 30 minutes

Next Steps

  1. ⏳ Deploy simple link bot first (lowest effort, immediate value)
  2. ⏳ Implement full modal form bot (better UX)
  3. ⏳ Add AI refinement (optional enhancement)
  4. ⏳ Monitor usage and iterate

Built with VitePress