Discord logo in a block Discord logo in a block

Integrating AI Into a Discord Bot: Ultimate Guide

Discord bots are a powerful tool for managing your server with commands. But what if your bot could do more than just follow commands? What if it could understand nuance, generate creative content, or even engage in meaningful conversations? We’re on the cusp of a revolution where artificial intelligence is transforming simple bots into powerful, intelligent companions.

For example:

  • Instead of typing a specific command, your members could simply ask your bot, “Hey, what’s new in the latest game update?” or “Tell me a joke!” and receive a relevant, natural response, making every interaction feel more personal and natural

In this ultimate guide, you will gain a full understanding of how to use AI with a Discord bot, and we will then guide you through creating your own for smarter, more natural interactions.

Contents of this blog:

In this blog, we are going to cover the following topics:

1. Creating and Running a Discord Bot

2. Integrating AI Into a Discord Bot

3. Allowing a Discord Bot to Reply Across All Channels 

4. Giving Your Discord Bot Personality & Rules

5. Giving Your Discord Bot the Ability to Remember

6. Integrating Web Search Ability Into Your Discord Bot

7. Adding The Ability For Your Discord Bot to Read Images

8. Adding The Ability For Your Discord Bot to Generate Images

9. Hosting Your Discord Bot

Code Structure:

/home/container/

|- index.js

|- .env

|- config.json

|- utils

  |-aiResponse.js

  |-memory.js

  |-search.js

  |-imageRead.js

  |-imageGenerate.js

Dependencies:

discord.js

dotenv

@google/generative-ai

axios

Hosting Your Bot

I’m currently hosting the bot in Cybrancee, which provides excellent server speed & quality, also they ensure your bot runs correctly with no downtime. If you are interested, you can choose your plan & register at Cybrancee.

1. Creating & Running A Discord Bot

Create your bot in the Discord Developer Portal, then copy your token.

Run the bot to ensure it’s working properly. Paste the following code in “index.js”

// Import packages
const { Client, GatewayIntentBits } = require('discord.js');
const dotenv = require('dotenv');

// Load environment variables
dotenv.config();

// Create Discord client
const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent,
    ],
});

// Bot ready event
client.once('ready', () => {
    console.log('Bot is online!');
});

// Login to Discord
client.login(process.env.DISCORD_BOT_TOKEN);

Also, your “.env” should look like this:

Now, start your bot. You will see that it prints “Bot is online!” in the console. And your bot will be online.

Screenshot of a bot active

2. Integrating AI into a Discord Bot

Now, it’s time to start integrating the AI into your bot. We will use Google Generative AI, specifically the Gemini 2.0 flash model, for now. But you can also choose an alternative.

How to get the Gemini API key?

To get the Gemini API key, you have to

1. Visit the Google AI Studio website.

2. Click the Create API Key button on the right

3. Select your Google Cloud Project, then click the “Create an API key in the existing project” button. And copy your API key.

Continuing:

At this point, you have to add “GEMINI_API_KEY” and “CHANNEL_ID” variables in your “.env”. Then, paste the Gemini API key that you copied, and select the channel where you want the bot to be located.

Your “.env” should look like this:

JavaScript
DISCORD_BOT_TOKEN="" # Paste in here your Discord bot token.
GEMINI_API_KEY="" # Paste in here your Gemini API key.
CHANNEL_ID="" # Paste in here the channel ID you want the bot to listen to.

Then, update your “index.js” file to include the new chatbot function.

JavaScript
// Simple Discord AI Chatbot
// Responds to messages in a specific channel with AI responses

// Import packages
const { Client, GatewayIntentBits } = require('discord.js');
const { GoogleGenerativeAI } = require('@google/generative-ai');
const dotenv = require('dotenv');

// Load environment variables
dotenv.config();

// Initialize Gemini AI
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });

// Create Discord client
const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent,
    ],
});

// Bot ready event
client.once('ready', () => {
    console.log('Bot is online!');
});

// Message event handler
client.on('messageCreate', async (message) => {
    // Ignore bot messages
    if (message.author.bot) return;

    // Only respond in the specific channel
    if (message.channel.id !== process.env.CHANNEL_ID) return;

    try {
        // Generate AI response
        const result = await model.generateContent(message.content);
        const aiResponse = result.response.text();

        // Send response
        await message.reply(aiResponse);

        console.log(`Responded to ${message.author.username}: "${message.content}"`);

    } catch (error) {
        console.error('Error:', error);
        await message.reply('Sorry, I had an error processing your message.');
    }
});

// Login to Discord
client.login(process.env.DISCORD_BOT_TOKEN);

Start your bot. And then start chatting with him.

An example:

Screenshot of messaging a Discord Bot

3. Allowing a Discord to Bot Reply Across All Channels

You have now finished the second step. What if we could let the bot send messages in all channels instead of just one? It will have the ability to chat in all channels. That is exactly what we are going to do.

In the “index.js” below, we added a new ability to the bot. The bot answers whenever someone mentions it in a message.

JavaScript
// Simple Discord AI Chatbot
// Responds to mentions in any channel with AI responses

// Import packages
const { Client, GatewayIntentBits } = require('discord.js');
const { GoogleGenerativeAI } = require('@google/generative-ai');
const dotenv = require('dotenv');

// Load environment variables
dotenv.config();

// Initialize Gemini AI
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });

// Create Discord client
const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent,
    ],
});

// Bot ready event
client.once('ready', () => {
    console.log('Bot is online!');
});

// Message event handler
client.on('messageCreate', async (message) => {
    // Ignore bot messages
    if (message.author.bot) return;

    // Only respond when mentioned
    if (!message.mentions.has(client.user)) return;

    try {
        // Generate AI response
        const result = await model.generateContent(message.content);
        const aiResponse = result.response.text();

        // Send response
        await message.reply(aiResponse);

        console.log(`Responded to ${message.author.username}: "${message.content}"`);

    } catch (error) {
        console.error('Error:', error);
        await message.reply('Sorry, I had an error processing your message.');
    }
});

// Login to Discord
client.login(process.env.DISCORD_BOT_TOKEN);

Get your bot started, then try mentioning it with your message on any channel, like in the example.

Screenshot of metion discord bot in a message

4. Giving Your Discord Bot Personality & Rules

It’s time to add some cool features to your blog, such as giving it a name, providing me with information about it, and setting rules etc. 

We will use the “config.json” file for data and the “utils/aiResponse.js” file to handle the AI response generation context.

Paste the following code in “config.json”

JavaScript
{
  "prompt": "You are a helpful AI assistant Discord bot. You should be conversational, engaging, and provide accurate information while maintaining a professional yet approachable tone.",
  "rules": [
    "Keep responses under 2000 characters to fit Discord's message limit",
    "Be respectful and polite to all users",
    "Stay on topic and provide relevant responses",
    "If you don't know something, be honest about it",
    "Follow Discord's community guidelines",
    "Don't share personal information about users",
    "Don't engage in harmful or inappropriate conversations",
    "Use emojis sparingly and appropriately",
    "Be helpful and try to solve user problems",
    "Maintain a friendly and professional tone",
    "Only mention your name (GG Bot) and creator (programmers) if specifically asked about your identity or who made you",
    "Don't introduce yourself unless asked"
  ]
}

Also, paste the following code in the “utils/aiResponse.js” file.

JavaScript
/**
 * Generate AI response using Gemini model
 * @param {string} userMessage - The user's message
 * @param {Object} model - The Gemini AI model instance
 * @param {string} systemPrompt - The system prompt with rules
 * @returns {Promise<string>} - The AI response
 */
async function generateAIResponse(userMessage, model, systemPrompt) {
  try {
    // Clean user message (remove mentions)
    const cleanedUserMessage = cleanUserMessage(userMessage);

    // Create the full prompt with context
    const fullPrompt = `${systemPrompt}\n\nUser message: ${cleanedUserMessage}`;

    // Generate response from AI
    const result = await model.generateContent(fullPrompt);
    const aiResponse = result.response.text();

    // Clean and validate response
    const cleanedResponse = cleanResponse(aiResponse);

    return cleanedResponse;

  } catch (error) {
    console.error('AI Response Error:', error);
    throw new Error('Failed to generate AI response');
  }
}

/**
 * Clean user message (remove mentions and extra whitespace)
 * @param {string} message - Raw user message
 * @returns {string} - Cleaned message
 */
function cleanUserMessage(message) {
  if (!message || typeof message !== 'string') {
    return '';
  }

  // Remove mentions (like <@123456789>)
  let cleaned = message.replace(/<@!?\d+>/g, '').trim();

  // Remove extra whitespace
  cleaned = cleaned.replace(/\s+/g, ' ').trim();

  return cleaned || 'hi';
}

/**
 * Clean and validate the AI response
 * @param {string} response - Raw AI response
 * @returns {string} - Cleaned response
 */
function cleanResponse(response) {
  if (!response || typeof response !== 'string') {
    return 'Sorry, I couldn\'t generate a proper response.';
  }

  // Trim whitespace
  let cleaned = response.trim();

  // Ensure response is not too long for Discord (2000 char limit)
  if (cleaned.length > 1900) {
    cleaned = cleaned.substring(0, 1900) + '...';
  }

  // Remove any potential harmful content markers
  cleaned = cleaned.replace(/\[HARMFUL\]|\[NSFW\]|\[INAPPROPRIATE\]/gi, '');

  return cleaned || 'Sorry, I couldn\'t generate a response.';
}

/**
 * Build system prompt from config
 * @param {Object} config - Configuration object with prompt and rules
 * @returns {string} - Complete system prompt
 */
function buildSystemPrompt(config) {
  let systemPrompt = config.prompt || 'You are a helpful AI assistant.';

  if (config.rules && Array.isArray(config.rules)) {
    systemPrompt += '\n\nRules you must follow:\n';
    config.rules.forEach((rule, index) => {
      systemPrompt += `${index + 1}. ${rule}\n`;
    });
  }

  return systemPrompt;
}

module.exports = {
  generateAIResponse,
  cleanResponse,
  cleanUserMessage,
  buildSystemPrompt
};

Also, you have to update “index.js” to better handle all functions and run the bot correctly.

JavaScript
// Import packages
const { Client, GatewayIntentBits } = require('discord.js');
const { GoogleGenerativeAI } = require('@google/generative-ai');
const dotenv = require('dotenv');
const fs = require('fs');

// Import utilities
const aiResponse = require('./utils/aiResponse.js');

// Load environment variables
dotenv.config();

// Load configuration
const config = JSON.parse(fs.readFileSync('./config.json', 'utf8'));

// Initialize Gemini AI
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });

// Build system prompt from config
const systemPrompt = aiResponse.buildSystemPrompt(config);

// Create Discord client
const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent,
    ],
});

// Bot ready event
client.once('ready', () => {
    console.log('CybranceBot is online!');
    console.log(`System prompt loaded: ${systemPrompt.substring(0, 100)}...`);
});

// Track processed messages to prevent duplicates
const processedMessages = new Set();

// Message event handler
client.on('messageCreate', async (message) => {
    // Ignore bot messages
    if (message.author.bot) return;

    // Only respond when mentioned
    if (!message.mentions.has(client.user)) return;

    // Prevent duplicate processing
    if (processedMessages.has(message.id)) return;
    processedMessages.add(message.id);

    // Clean up old message IDs (keep only last 100)
    if (processedMessages.size > 100) {
        const oldIds = Array.from(processedMessages).slice(0, processedMessages.size - 100);
        oldIds.forEach(id => processedMessages.delete(id));
    }

    try {
        console.log(`Processing message from ${message.author.username}: "${message.content}"`);

        // Generate AI response using utility
        const response = await aiResponse.generateAIResponse(
            message.content,
            model,
            systemPrompt
        );

        // Send response
        await message.reply(response);

        console.log(`✅ Responded to ${message.author.username}`);

    } catch (error) {
        console.error('❌ Error:', error);
        await message.reply('Sorry, I had an error processing your message.');
    }
});

// Login to Discord
client.login(process.env.DISCORD_BOT_TOKEN);

You have made it now! Test your bot and see all the changes. The bot will have better context & response handling and the ability to have perfect knowledge.

An Example:

Screenshot of A Discord Chatbot that haves a personality & rules

5. Giving Your Discord Bot the Ability to Remember

Adding memory to your bot is important, as it will allow your bot to respond more like a human. 

That is exactly what we will do in this part; we will be adding a function that will store at least 10 past user conversations with the bot. So your bot will have more context.

This time, we will use “utils/memory.js” to handle the memory functions. We will update “utils/aiResponse.js” to include the new memory in the AI response context. And we will update “index.js” to pass the userId to the AI response function.

Here is “utils/memory.js”

JavaScript
// In-memory storage for user conversations
// Structure: { userId: [{ user: "message", bot: "response", timestamp: Date }] }
const userMemories = new Map();

// Maximum number of conversations to store per user
const MAX_CONVERSATIONS = 10;

/**
 * Add a conversation to the user's memory
 * @param {string} userId - Discord user ID
 * @param {string} userMessage - User's message
 * @param {string} botResponse - Bot's response
 */
function addToMemory(userId, userMessage, botResponse) {
  try {
    // Get existing memory or create a new array
    let userConversations = userMemories.get(userId) || [];

    // Create conversation entry
    const conversation = {
      user: userMessage,
      bot: botResponse,
      timestamp: new Date()
    };

    // Add to memory
    userConversations.push(conversation);

    // Keep only the last MAX_CONVERSATIONS
    if (userConversations.length > MAX_CONVERSATIONS) {
      userConversations = userConversations.slice(-MAX_CONVERSATIONS);
    }

    // Update memory
    userMemories.set(userId, userConversations);

    console.log(`💾 Stored conversation for user ${userId} (${userConversations.length}/${MAX_CONVERSATIONS})`);

  } catch (error) {
    console.error('Memory storage error:', error);
  }
}

/**
 * Get the user's conversation history
 * @param {string} userId - Discord user ID
 * @returns {Array} - Array of conversation objects
 */
function getMemory(userId) {
  return userMemories.get(userId) || [];
}

/**
 * Format memory for AI context
 * @param {string} userId - Discord user ID
 * @returns {string} - Formatted conversation history
 */
function formatMemoryForAI(userId) {
  const conversations = getMemory(userId);

  if (conversations.length === 0) {
    return '';
  }

  let memoryContext = '\n\nPrevious conversation history:\n';

  conversations.forEach((conv, index) => {
    memoryContext += `${index + 1}. User: ${conv.user}\n`;
    memoryContext += `   Bot: ${conv.bot}\n`;
  });

  memoryContext += '\nUse this context to provide more relevant and personalized responses.\n';

  return memoryContext;
}

/**
 * Clear memory for a specific user
 * @param {string} userId - Discord user ID
 */
function clearMemory(userId) {
  userMemories.delete(userId);
  console.log(`🗑️ Cleared memory for user ${userId}`);
}

/**
 * Get memory statistics
 * @returns {Object} - Memory usage statistics
 */
function getMemoryStats() {
  const totalUsers = userMemories.size;
  let totalConversations = 0;

  userMemories.forEach(conversations => {
    totalConversations += conversations.length;
  });

  return {
    totalUsers,
    totalConversations,
    averageConversationsPerUser: totalUsers > 0 ? (totalConversations / totalUsers).toFixed(2) : 0
  };
}

/**
 * Clean old memories (optional - for memory management)
 * Remove conversations older than specified days
 * @param {number} daysOld - Days to keep conversations
 */
function cleanOldMemories(daysOld = 7) {
  const cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - daysOld);

  let cleanedCount = 0;

  userMemories.forEach((conversations, userId) => {
    const filteredConversations = conversations.filter(conv => conv.timestamp > cutoffDate);

    if (filteredConversations.length !== conversations.length) {
      if (filteredConversations.length === 0) {
        userMemories.delete(userId);
      } else {
        userMemories.set(userId, filteredConversations);
      }
      cleanedCount++;
    }
  });

  console.log(`🧹 Cleaned old memories for ${cleanedCount} users`);
  return cleanedCount;
}

module.exports = {
  addToMemory,
  getMemory,
  formatMemoryForAI,
  clearMemory,
  getMemoryStats,
  cleanOldMemories
};

Here are the updated “utils/aiResponse.js”

JavaScript
const memory = require('./memory.js');

/**
 * Generate AI response using Gemini model with memory context
 * @param {string} userMessage - The user's message
 * @param {Object} model - The Gemini AI model instance
 * @param {string} systemPrompt - The system prompt with rules
 * @param {string} userId - Discord user ID for memory context
 * @returns {Promise<string>} - The AI response
 */
async function generateAIResponse(userMessage, model, systemPrompt, userId) {
  try {
    // Clean user message (remove mentions)
    const cleanedUserMessage = cleanUserMessage(userMessage);

    // Get memory context for this user
    const memoryContext = memory.formatMemoryForAI(userId);

    // Create the full prompt with system prompt, memory context, and current message
    const fullPrompt = `${systemPrompt}${memoryContext}\n\nCurrent user message: ${cleanedUserMessage}`;

    // Generate response from AI
    const result = await model.generateContent(fullPrompt);
    const aiResponse = result.response.text();

    // Clean and validate response
    const cleanedResponse = cleanResponse(aiResponse);

    // Store this conversation in memory
    memory.addToMemory(userId, cleanedUserMessage, cleanedResponse);

    return cleanedResponse;

  } catch (error) {
    console.error('AI Response Error:', error);
    throw new Error('Failed to generate AI response');
  }
}

/**
 * Clean user message (remove mentions and extra whitespace)
 * @param {string} message - Raw user message
 * @returns {string} - Cleaned message
 */
function cleanUserMessage(message) {
  if (!message || typeof message !== 'string') {
    return '';
  }

  // Remove mentions (like <@123456789>)
  let cleaned = message.replace(/<@!?\d+>/g, '').trim();

  // Remove extra whitespace
  cleaned = cleaned.replace(/\s+/g, ' ').trim();

  return cleaned || 'hi';
}

/**
 * Clean and validate the AI response
 * @param {string} response - Raw AI response
 * @returns {string} - Cleaned response
 */
function cleanResponse(response) {
  if (!response || typeof response !== 'string') {
    return 'Sorry, I couldn\'t generate a proper response.';
  }

  // Trim whitespace
  let cleaned = response.trim();

  // Ensure response is not too long for Discord (2000 char limit)
  if (cleaned.length > 1900) {
    cleaned = cleaned.substring(0, 1900) + '...';
  }

  // Remove any potential harmful content markers
  cleaned = cleaned.replace(/\[HARMFUL\]|\[NSFW\]|\[INAPPROPRIATE\]/gi, '');

  return cleaned || 'Sorry, I couldn\'t generate a response.';
}

/**
 * Build system prompt from config
 * @param {Object} config - Configuration object with prompt and rules
 * @returns {string} - Complete system prompt
 */
function buildSystemPrompt(config) {
  let systemPrompt = config.prompt || 'You are a helpful AI assistant.';

  if (config.rules && Array.isArray(config.rules)) {
    systemPrompt += '\n\nRules you must follow:\n';
    config.rules.forEach((rule, index) => {
      systemPrompt += `${index + 1}. ${rule}\n`;
    });
  }

  return systemPrompt;
}

module.exports = {
  generateAIResponse,
  cleanResponse,
  cleanUserMessage,
  buildSystemPrompt
};

Here is also the updated “index.js”

JavaScript
// Import packages
const { Client, GatewayIntentBits } = require('discord.js');
const { GoogleGenerativeAI } = require('@google/generative-ai');
const dotenv = require('dotenv');
const fs = require('fs');

// Import utilities
const aiResponse = require('./utils/aiResponse.js');

// Load environment variables
dotenv.config();

// Load configuration
const config = JSON.parse(fs.readFileSync('./config.json', 'utf8'));

// Initialize Gemini AI
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });

// Build system prompt from config
const systemPrompt = aiResponse.buildSystemPrompt(config);

// Create Discord client
const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent,
    ],
});

// Bot ready event
client.once('ready', () => {
    console.log('CybranceBot is online!');
    console.log(`System prompt loaded: ${systemPrompt.substring(0, 100)}...`);
});

// Track processed messages to prevent duplicates
const processedMessages = new Set();

// Message event handler
client.on('messageCreate', async (message) => {
    // Ignore bot messages
    if (message.author.bot) return;

    // Only respond when mentioned
    if (!message.mentions.has(client.user)) return;

    // Prevent duplicate processing
    if (processedMessages.has(message.id)) return;
    processedMessages.add(message.id);

    // Clean up old message IDs (keep only last 100)
    if (processedMessages.size > 100) {
        const oldIds = Array.from(processedMessages).slice(0, processedMessages.size - 100);
        oldIds.forEach(id => processedMessages.delete(id));
    }

    try {
        console.log(`Processing message from ${message.author.username}: "${message.content}"`);

        // Generate AI response using utility with memory
        const response = await aiResponse.generateAIResponse(
            message.content,
            model,
            systemPrompt,
            message.author.id // Pass the user ID for memory handling
        );

        // Send response
        await message.reply(response);

        console.log(`✅ Responded to ${message.author.username}`);

    } catch (error) {
        console.error('❌ Error:', error);
        await message.reply('Sorry, I had an error processing your message.');
    }
});

// Login to Discord
client.login(process.env.DISCORD_BOT_TOKEN);

Boom! You have now implemented a useful feature in your bot. Start and test it now; you will see that the bot remembers 10 past conversations with you.

Here is an Example Of Using Memory:

Screenshot of A Discord Chatbot that haves ability to remember

6. Integrating Web Search Ability Into Your Discord Bot

Enhancing your bot with web search ability could make it even better. Your bot can search the web for anything that it couldn’t find, it can help people with searching for information they need, and much more.

We will be using the Serper API for web search.

How to get the Serper API key?

1/- Visit Serper’s Website

2/- Click the Sign in or Sign up (if you don’t have an account yet) button

3/- Once accessed the Dashboard, click the API key tab in your sidebar, then copy your API key

Continuing:

You will be using “utils/search.js” to get the search function, and you will need to update “utils/aiResponse.js” to include the search function. And also your new “.env” should look like this:

JavaScript
DISCORD_BOT_TOKEN="" # Paste in here your Discord bot token.
GEMINI_API_KEY="" # Paste in here your Gemini API key.
SERPER_API_KEY="" # Paste in here your Serper API key.

Here is “utils/search.js”

JavaScript
/**
 * Perform a web search using the Serper API
 * @param {string} query - Search query
 * @returns {Promise<Object>} - Search results object
 */
async function performSearch(query) {
  try {
    const response = await fetch('https://google.serper.dev/search', {
      method: 'POST',
      headers: {
        'X-API-KEY': process.env.SERPER_API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        q: query,
        num: 5 // Get top 5 results
      })
    });

    if (!response.ok) {
      throw new Error(`Serper API error: ${response.status} ${response.statusText}`);
    }

    const data = await response.json();
    return {
      success: true,
      query: query,
      results: data
    };

  } catch (error) {
    console.error('Search error:', error);
    return {
      success: false,
      query: query,
      error: error.message
    };
  }
}

/**
 * Detect if the user message needs a web search
 * @param {string} message - User's message
 * @returns {boolean} - Whether search is needed
 */
function detectSearchIntent(message) {
  if (!message || typeof message !== 'string') {
    return false;
  }

  const searchKeywords = [
    // Direct search requests
    'search for', 'look up', 'find information', 'google', 'search',

    // Current information requests
    'latest', 'recent', 'current', 'today', 'now', 'this year', 'new',

    // News and events
    'news', 'breaking', 'happened', 'event', 'update',

    // Weather and time-sensitive
    'weather', 'temperature', 'forecast', 'stock price', 'exchange rate',

    // Questions about the current state
    'what is happening', 'what happened', 'tell me about', 'information about',

    // Specific domains that need current info
    'price of', 'cost of', 'how much', 'when did', 'who is', 'where is'
  ];

  const lowerMessage = message.toLowerCase();

  // Check for search keywords
  const hasSearchKeywords = searchKeywords.some(keyword =>
    lowerMessage.includes(keyword)
  );

  // Check for question patterns that might need current info
  const questionPatterns = [
    /what.*(?:latest|recent|current|new|today)/i,
    /when.*(?:did|will|does)/i,
    /who.*(?:is|was|are|were)/i,
    /where.*(?:is|are|can)/i,
    /how.*(?:much|many|to)/i,
    /why.*(?:did|is|are)/i
  ];

  const hasQuestionPattern = questionPatterns.some(pattern =>
    pattern.test(message)
  );

  return hasSearchKeywords || hasQuestionPattern;
}

/**
 * Format search results for AI context
 * @param {Object} searchData - Search results from Serper API
 * @returns {string} - Formatted search context
 */
function formatSearchResults(searchData) {
  if (!searchData.success || !searchData.results) {
    return '';
  }

  const { results } = searchData;
  let formattedResults = `\n\nCurrent web search results for "${searchData.query}":\n`;

  // Add organic search results
  if (results.organic && results.organic.length > 0) {
    formattedResults += '\nTop search results:\n';
    results.organic.slice(0, 3).forEach((result, index) => {
      formattedResults += `${index + 1}. ${result.title}\n`;
      formattedResults += `   ${result.snippet}\n`;
      formattedResults += `   Source: ${result.link}\n\n`;
    });
  }

  // Add knowledge graph if available
  if (results.knowledgeGraph) {
    const kg = results.knowledgeGraph;
    formattedResults += `Knowledge Graph:\n`;
    formattedResults += `Title: ${kg.title}\n`;
    if (kg.description) {
      formattedResults += `Description: ${kg.description}\n`;
    }
    if (kg.attributes) {
      Object.entries(kg.attributes).forEach(([key, value]) => {
        formattedResults += `${key}: ${value}\n`;
      });
    }
    formattedResults += '\n';
  }

  // Add people also ask if available
  if (results.peopleAlsoAsk && results.peopleAlsoAsk.length > 0) {
    formattedResults += 'Related questions:\n';
    results.peopleAlsoAsk.slice(0, 2).forEach((item, index) => {
      formattedResults += `${index + 1}. ${item.question}\n`;
    });
    formattedResults += '\n';
  }

  formattedResults += 'Use this current information to provide an accurate and up-to-date response.\n';

  return formattedResults;
}

/**
 * Perform search and format results (main function)
 * @param {string} query - Search query
 * @returns {Promise<Object>} - Formatted search results
 */
async function searchAndFormat(query) {
  const searchResults = await performSearch(query);

  if (searchResults.success) {
    const formattedContext = formatSearchResults(searchResults);
    return {
      success: true,
      query: query,
      context: formattedContext,
      rawResults: searchResults.results
    };
  } else {
    return {
      success: false,
      query: query,
      error: searchResults.error
    };
  }
}

module.exports = {
  performSearch,
  detectSearchIntent,
  formatSearchResults,
  searchAndFormat
};

Here is the updated “utils/aiResponse.js”

JavaScript
const memory = require('./memory.js');
const search = require('./search.js');

/**
 * Generate AI response using Gemini model with memory context
 * @param {string} userMessage - The user's message
 * @param {Object} model - The Gemini AI model instance
 * @param {string} systemPrompt - The system prompt with rules
 * @param {string} userId - Discord user ID for memory context
 * @returns {Promise<string>} - The AI response
 */
async function generateAIResponse(userMessage, model, systemPrompt, userId) {
  try {
    // Clean user message (remove mentions)
    const cleanedUserMessage = cleanUserMessage(userMessage);

    // Get memory context for this user
    const memoryContext = memory.formatMemoryForAI(userId);

    // Check if search is needed and perform search
    let searchContext = '';
    const needsSearch = search.detectSearchIntent(cleanedUserMessage);

    if (needsSearch) {
      console.log(`🔍 Search detected for: "${cleanedUserMessage}"`);
      const searchResults = await search.searchAndFormat(cleanedUserMessage);

      if (searchResults.success) {
        searchContext = searchResults.context;
        console.log(`✅ Search completed successfully`);
      } else {
        console.log(`❌ Search failed: ${searchResults.error}`);
      }
    }

    // Create the full prompt with system prompt, memory context, search context, and current message
    const fullPrompt = `${systemPrompt}${memoryContext}${searchContext}\n\nCurrent user message: ${cleanedUserMessage}`;

    // Generate response from AI
    const result = await model.generateContent(fullPrompt);
    const aiResponse = result.response.text();

    // Clean and validate response
    const cleanedResponse = cleanResponse(aiResponse);

    // Store this conversation in memory
    memory.addToMemory(userId, cleanedUserMessage, cleanedResponse);

    return cleanedResponse;

  } catch (error) {
    console.error('AI Response Error:', error);
    throw new Error('Failed to generate AI response');
  }
}

/**
 * Clean user message (remove mentions and extra whitespace)
 * @param {string} message - Raw user message
 * @returns {string} - Cleaned message
 */
function cleanUserMessage(message) {
  if (!message || typeof message !== 'string') {
    return '';
  }

  // Remove mentions (like <@123456789>)
  let cleaned = message.replace(/<@!?\d+>/g, '').trim();

  // Remove extra whitespace
  cleaned = cleaned.replace(/\s+/g, ' ').trim();

  return cleaned || 'hi';
}

/**
 * Clean and validate the AI response
 * @param {string} response - Raw AI response
 * @returns {string} - Cleaned response
 */
function cleanResponse(response) {
  if (!response || typeof response !== 'string') {
    return 'Sorry, I couldn\'t generate a proper response.';
  }

  // Trim whitespace
  let cleaned = response.trim();

  // Ensure response is not too long for Discord (2000 char limit)
  if (cleaned.length > 1900) {
    cleaned = cleaned.substring(0, 1900) + '...';
  }

  // Remove any potential harmful content markers
  cleaned = cleaned.replace(/\[HARMFUL\]|\[NSFW\]|\[INAPPROPRIATE\]/gi, '');

  return cleaned || 'Sorry, I couldn\'t generate a response.';
}

/**
 * Build system prompt from config
 * @param {Object} config - Configuration object with prompt and rules
 * @returns {string} - Complete system prompt
 */
function buildSystemPrompt(config) {
  let systemPrompt = config.prompt || 'You are a helpful AI assistant.';

  if (config.rules && Array.isArray(config.rules)) {
    systemPrompt += '\n\nRules you must follow:\n';
    config.rules.forEach((rule, index) => {
      systemPrompt += `${index + 1}. ${rule}\n`;
    });
  }

  return systemPrompt;
}

module.exports = {
  generateAIResponse,
  cleanResponse,
  cleanUserMessage,
  buildSystemPrompt
};

You have successfully added a new Web Search ability! Start and test your bot.

An Example of Using the Web Search Function:

Screenshot of A Discord Chat Bot Uses Web Search

7. Adding The Ability For Your Discord Bot to Read Images

Adding the ability to read images is an important feature for your bot to have. It can help people with their homework documents or image descriptions.

Let’s set it up.

You will be using “utils/imageRead.js” to handle reading image functions, and you need to update your “utils/aiResponse.js” to work perfectly with the image, and you will also need to update “index.js” to handle attachments.

Here is “utils/imageRead.js”

JavaScript
/**
 * Supported image types and their MIME types
 */
const SUPPORTED_IMAGE_TYPES = {
  // Common formats
  'image/jpeg': ['.jpg', '.jpeg'],
  'image/png': ['.png'],
  'image/gif': ['.gif'],
  'image/webp': ['.webp'],
  'image/bmp': ['.bmp'],
  'image/tiff': ['.tiff', '.tif'],
  'image/svg+xml': ['.svg'],

  // Additional formats
  'image/x-icon': ['.ico'],
  'image/vnd.microsoft.icon': ['.ico'],
  'image/avif': ['.avif'],
  'image/heic': ['.heic'],
  'image/heif': ['.heif']
};

/**
 * Check if the attachment is a supported image
 * @param {Object} attachment - Discord attachment object
 * @returns {boolean} - Whether the attachment is a supported image
 */
function isSupportedImage(attachment) {
  if (!attachment) {
    return false;
  }

  // Check by content type first
  if (attachment.contentType) {
    const contentType = attachment.contentType.toLowerCase();
    const isSupported = Object.keys(SUPPORTED_IMAGE_TYPES).includes(contentType);
    if (isSupported) {
      return true;
    }
  }

  // Fallback to filename extension check
  if (attachment.name) {
    const extension = getFileExtension(attachment.name);
    const isSupported = Object.values(SUPPORTED_IMAGE_TYPES).some(extensions =>
      extensions.includes(extension)
    );
    return isSupported;
  }

  // Last resort: check URL extension
  if (attachment.url) {
    const extension = getFileExtension(attachment.url);
    const isSupported = Object.values(SUPPORTED_IMAGE_TYPES).some(extensions =>
      extensions.includes(extension)
    );
    return isSupported;
  }

  return false;
}

/**
 * Get file extension from filename
 * @param {string} filename - The filename
 * @returns {string} - File extension with dot (e.g., '.jpg')
 */
function getFileExtension(filename) {
  if (!filename || typeof filename !== 'string') {
    return '';
  }

  const lastDot = filename.lastIndexOf('.');
  if (lastDot === -1) {
    return '';
  }

  return filename.substring(lastDot).toLowerCase();
}

/**
 * Download image and convert to base64
 * @param {string} imageUrl - URL of the image
 * @returns {Promise<string>} - Base64 encoded image data
 */
async function downloadImageAsBase64(imageUrl) {
  try {
    console.log(`📥 Downloading image: ${imageUrl}`);

    const response = await fetch(imageUrl);

    if (!response.ok) {
      throw new Error(`Failed to download image: ${response.status} ${response.statusText}`);
    }

    // Get the image data as an array buffer
    const arrayBuffer = await response.arrayBuffer();

    // Convert to base64
    const base64 = Buffer.from(arrayBuffer).toString('base64');

    // Get content type from response or guess from URL
    let mimeType = response.headers.get('content-type');
    if (!mimeType) {
      mimeType = guessMimeTypeFromUrl(imageUrl);
    }

    console.log(`✅ Image downloaded successfully (${arrayBuffer.byteLength} bytes, ${mimeType})`);

    return {
      base64: base64,
      mimeType: mimeType,
      size: arrayBuffer.byteLength
    };

  } catch (error) {
    console.error('❌ Error downloading image:', error);
    throw new Error(`Failed to download image: ${error.message}`);
  }
}

/**
 * Guess MIME type from URL extension
 * @param {string} url - Image URL
 * @returns {string} - Guessed MIME type
 */
function guessMimeTypeFromUrl(url) {
  const extension = getFileExtension(url.split('?')[0]); // Remove query params

  for (const [mimeType, extensions] of Object.entries(SUPPORTED_IMAGE_TYPES)) {
    if (extensions.includes(extension)) {
      return mimeType;
    }
  }

  return 'image/jpeg'; // Default fallback
}

/**
 * Validate image size and type
 * @param {Object} attachment - Discord attachment object
 * @returns {Object} - Validation result
 */
function validateImage(attachment) {
  const MAX_SIZE = 20 * 1024 * 1024; // 20MB limit

  const result = {
    valid: true,
    errors: []
  };

  // Check if it's a supported image type
  const isSupported = isSupportedImage(attachment);
  if (!isSupported) {
    result.valid = false;
    result.errors.push(`Unsupported image format (${attachment.contentType || 'unknown type'})`);
  }

  // Check file size
  if (attachment.size && attachment.size > MAX_SIZE) {
    result.valid = false;
    result.errors.push(`Image too large (${(attachment.size / 1024 / 1024).toFixed(2)}MB). Maximum size is 20MB`);
  }

  return result;
}

/**
 * Get image information
 * @param {Object} attachment - Discord attachment object
 * @returns {Object} - Image information
 */
function getImageInfo(attachment) {
  return {
    name: attachment.name || 'unknown',
    size: attachment.size || 0,
    contentType: attachment.contentType || guessMimeTypeFromUrl(attachment.url),
    url: attachment.url,
    width: attachment.width || null,
    height: attachment.height || null,
    extension: getFileExtension(attachment.name || attachment.url)
  };
}

module.exports = {
  isSupportedImage,
  downloadImageAsBase64,
  validateImage,
  getImageInfo,
  getFileExtension,
  guessMimeTypeFromUrl,
  SUPPORTED_IMAGE_TYPES
};

Here is “utils/aiResponse.js”

JavaScript
const memory = require('./memory.js');
const search = require('./search.js');
const imageRead = require('./imageRead.js');

/**
 * Generate an AI response with image analysis
 * @param {string} userMessage - The user's message
 * @param {Object} model - The Gemini AI model instance
 * @param {string} systemPrompt - The system prompt with rules
 * @param {string} userId - Discord user ID for memory context
 * @param {Array} imageAttachments - Array of Discord image attachments
 * @returns {Promise<string>} - The AI response
 */
async function generateAIResponseWithImages(userMessage, model, systemPrompt, userId, imageAttachments) {
  try {
    // Clean user message (remove mentions)
    const cleanedUserMessage = cleanUserMessage(userMessage);

    // Get memory context for this user
    const memoryContext = memory.formatMemoryForAI(userId);

    // Process images if any
    let imageContext = '';
    const imageParts = [];

    if (imageAttachments && imageAttachments.length > 0) {
      console.log(`🖼️ Processing ${imageAttachments.length} image(s)`);

      for (const attachment of imageAttachments) {
        // Validate image
        const validation = imageRead.validateImage(attachment);
        if (!validation.valid) {
          console.log(`❌ Invalid image: ${validation.errors.join(', ')}`);
          continue;
        }

        try {
          // Download and convert the image
          const imageData = await imageRead.downloadImageAsBase64(attachment.url);
          const imageInfo = imageRead.getImageInfo(attachment);

          // Add image to Gemini parts
          imageParts.push({
            inlineData: {
              data: imageData.base64,
              mimeType: imageData.mimeType
            }
          });

          // Add image context for AI
          imageContext += `\nImage attached: ${imageInfo.name} (${imageInfo.extension}, ${(imageInfo.size / 1024).toFixed(1)}KB)`;

        } catch (error) {
          console.error(`❌ Error processing image ${attachment.name}:`, error);
          imageContext += `\nNote: Could not process image ${attachment.name || 'unknown'}`;
        }
      }
    }

    // Check if search is needed and perform search
    let searchContext = '';
    const needsSearch = search.detectSearchIntent(cleanedUserMessage);

    if (needsSearch) {
      console.log(`🔍 Search detected for: "${cleanedUserMessage}"`);
      const searchResults = await search.searchAndFormat(cleanedUserMessage);

      if (searchResults.success) {
        searchContext = searchResults.context;
        console.log(`✅ Search completed successfully`);
      } else {
        console.log(`❌ Search failed: ${searchResults.error}`);
      }
    }

    // Create the full prompt with all contexts
    const textPrompt = `${systemPrompt}${memoryContext}${searchContext}${imageContext}\n\nCurrent user message: ${cleanedUserMessage}`;

    // Prepare content for Gemini
    const content = [{ text: textPrompt }, ...imageParts];

    // Generate response from AI
    const result = await model.generateContent(content);
    const aiResponse = result.response.text();

    // Clean and validate response
    const cleanedResponse = cleanResponse(aiResponse);

    // Store this conversation in memory
    memory.addToMemory(userId, cleanedUserMessage, cleanedResponse);

    return cleanedResponse;

  } catch (error) {
    console.error('AI Response Error:', error);
    throw new Error('Failed to generate AI response');
  }
}

/**
 * Generate AI response using Gemini model with memory context (text only)
 * @param {string} userMessage - The user's message
 * @param {Object} model - The Gemini AI model instance
 * @param {string} systemPrompt - The system prompt with rules
 * @param {string} userId - Discord user ID for memory context
 * @returns {Promise<string>} - The AI response
 */
async function generateAIResponse(userMessage, model, systemPrompt, userId) {
  try {
    // Clean user message (remove mentions)
    const cleanedUserMessage = cleanUserMessage(userMessage);

    // Get memory context for this user
    const memoryContext = memory.formatMemoryForAI(userId);

    // Check if search is needed and perform search
    let searchContext = '';
    const needsSearch = search.detectSearchIntent(cleanedUserMessage);

    if (needsSearch) {
      console.log(`🔍 Search detected for: "${cleanedUserMessage}"`);
      const searchResults = await search.searchAndFormat(cleanedUserMessage);

      if (searchResults.success) {
        searchContext = searchResults.context;
        console.log(`✅ Search completed successfully`);
      } else {
        console.log(`❌ Search failed: ${searchResults.error}`);
      }
    }

    // Create the full prompt with system prompt, memory context, search context, and current message
    const fullPrompt = `${systemPrompt}${memoryContext}${searchContext}\n\nCurrent user message: ${cleanedUserMessage}`;

    // Generate response from AI
    const result = await model.generateContent(fullPrompt);
    const aiResponse = result.response.text();

    // Clean and validate response
    const cleanedResponse = cleanResponse(aiResponse);

    // Store this conversation in memory
    memory.addToMemory(userId, cleanedUserMessage, cleanedResponse);

    return cleanedResponse;

  } catch (error) {
    console.error('AI Response Error:', error);
    throw new Error('Failed to generate AI response');
  }
}

/**
 * Clean user message (remove mentions and extra whitespace)
 * @param {string} message - Raw user message
 * @returns {string} - Cleaned message
 */
function cleanUserMessage(message) {
  if (!message || typeof message !== 'string') {
    return '';
  }

  // Remove mentions (like <@123456789>)
  let cleaned = message.replace(/<@!?\d+>/g, '').trim();

  // Remove extra whitespace
  cleaned = cleaned.replace(/\s+/g, ' ').trim();

  return cleaned || 'hi';
}

/**
 * Clean and validate AI response
 * @param {string} response - Raw AI response
 * @returns {string} - Cleaned response
 */
function cleanResponse(response) {
  if (!response || typeof response !== 'string') {
    return 'Sorry, I couldn\'t generate a proper response.';
  }

  // Trim whitespace
  let cleaned = response.trim();

  // Ensure response is not too long for Discord (2000 char limit)
  if (cleaned.length > 1900) {
    cleaned = cleaned.substring(0, 1900) + '...';
  }

  // Remove any potential harmful content markers
  cleaned = cleaned.replace(/\[HARMFUL\]|\[NSFW\]|\[INAPPROPRIATE\]/gi, '');

  return cleaned || 'Sorry, I couldn\'t generate a response.';
}

/**
 * Build system prompt from config
 * @param {Object} config - Configuration object with prompt and rules
 * @returns {string} - Complete system prompt
 */
function buildSystemPrompt(config) {
  let systemPrompt = config.prompt || 'You are a helpful AI assistant.';

  if (config.rules && Array.isArray(config.rules)) {
    systemPrompt += '\n\nRules you must follow:\n';
    config.rules.forEach((rule, index) => {
      systemPrompt += `${index + 1}. ${rule}\n`;
    });
  }

  return systemPrompt;
}

module.exports = {
  generateAIResponse,
  generateAIResponseWithImages,
  cleanResponse,
  cleanUserMessage,
  buildSystemPrompt
};

Finally, here is the “index.js”

JavaScript
// Import packages
const { Client, GatewayIntentBits } = require('discord.js');
const { GoogleGenerativeAI } = require('@google/generative-ai');
const dotenv = require('dotenv');
const fs = require('fs');

// Import utilities
const aiResponse = require('./utils/aiResponse.js');
const imageRead = require('./utils/imageRead.js');

// Load environment variables
dotenv.config();

// Load configuration
const config = JSON.parse(fs.readFileSync('./config.json', 'utf8'));

// Initialize Gemini AI
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });

// Build system prompt from config
const systemPrompt = aiResponse.buildSystemPrompt(config);

// Create Discord client
const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent,
    ],
});

// Bot ready event
client.once('ready', () => {
    console.log('CybranceBot is online!');
    console.log(`System prompt loaded: ${systemPrompt.substring(0, 100)}...`);
});

// Track processed messages to prevent duplicates
const processedMessages = new Set();

// Message event handler
client.on('messageCreate', async (message) => {
    // Ignore bot messages
    if (message.author.bot) return;

    // Only respond when mentioned
    if (!message.mentions.has(client.user)) return;

    // Prevent duplicate processing
    if (processedMessages.has(message.id)) return;
    processedMessages.add(message.id);

    // Clean up old message IDs (keep only last 100)
    if (processedMessages.size > 100) {
        const oldIds = Array.from(processedMessages).slice(0, processedMessages.size - 100);
        oldIds.forEach(id => processedMessages.delete(id));
    }

    try {
        console.log(`Processing message from ${message.author.username}: "${message.content}"`);

        // Check for image attachments
        console.log(`📎 Total attachments: ${message.attachments.size}`);

        // Convert Discord Collection to Array first
        const allAttachments = Array.from(message.attachments.values());
        console.log(`📋 Converted to array: ${allAttachments.length} attachments`);

        const imageAttachments = allAttachments.filter(attachment => {
            console.log(`🔍 Checking attachment: ${attachment.name} (${attachment.contentType})`);
            const isSupported = imageRead.isSupportedImage(attachment);
            console.log(`📋 Is supported: ${isSupported}`);
            return isSupported;
        });

        console.log(`🖼️ Image attachments found: ${imageAttachments.length}`);

        let response;

        if (imageAttachments.length > 0) {
            // Handle message with images
            console.log(`📋 Processing ${imageAttachments.length} image attachments`);
            console.log(`📋 First attachment:`, {
                name: imageAttachments[0].name,
                contentType: imageAttachments[0].contentType,
                size: imageAttachments[0].size
            });

            response = await aiResponse.generateAIResponseWithImages(
                message.content,
                model,
                systemPrompt,
                message.author.id,
                imageAttachments
            );
        } else {
            // Handle text-only message
            response = await aiResponse.generateAIResponse(
                message.content,
                model,
                systemPrompt,
                message.author.id
            );
        }

        // Send response
        await message.reply(response);

        console.log(`✅ Responded to ${message.author.username}`);

    } catch (error) {
        console.error('❌ Error:', error);
        await message.reply('Sorry, I had an error processing your message.');
    }
});

// Login to Discord
client.login(process.env.DISCORD_BOT_TOKEN);

You can now start and test your bot by uploading an image and telling it to describe that image.

An Example:

Screenshot of A Discord Chatbot Can Read Images

8. Adding The Ability For Your Discord Bot to Generate Images

In this part, you will be adding the ability for your AI bot to generate an image. You might be saying, “That’s impossible”, but with Pollinations AI, you will make it possible. 

Pollinations AI provides complete, free text-to-image generation, with no limits. Also, you don’t need any API key for it, so don’t worry.

You will use “utils/imageGenerate.js” for the image generation function, and you are also going to update “utils/aiResponse.js” to handle Image generation requests and add it to the context. You will also update “index.js” to handle the user requests correctly.

The “imageGenerate.js” should look like this:

JavaScript
// Image Generation Utility
// Handles AI image generation from text prompts

/**
 * Image generation using Pollinations API (free, no API key required)
 */
const IMAGE_SERVICE = 'Pollinations AI';

/**
 * Detect if the user message is requesting image generation
 * @param {string} message - User's message
 * @returns {boolean} - Whether image generation is requested
 */
function detectImageGenerationIntent(message) {
  if (!message || typeof message !== 'string') {
    return false;
  }

  const generationKeywords = [
    // Direct generation requests
    'generate image', 'create image', 'make image', 'draw image',
    'generate picture', 'create picture', 'make picture', 'draw picture',

    // Art requests
    'generate art', 'create art', 'make art', 'draw art',
    'paint', 'sketch', 'illustrate',

    // Specific commands
    'image of', 'picture of', 'drawing of', 'artwork of',
    'show me', 'visualize', 'render',

    // Creative requests
    'design', 'concept art', 'digital art'
  ];

  const lowerMessage = message.toLowerCase();

  // Check for generation keywords
  const hasGenerationKeywords = generationKeywords.some(keyword =>
    lowerMessage.includes(keyword)
  );

  // Check for command patterns
  const commandPatterns = [
    /(?:generate|create|make|draw|paint|sketch)\s+(?:an?|some)?\s*(?:image|picture|art|drawing)/i,
    /(?:image|picture|art|drawing)\s+of\s+/i,
    /show\s+me\s+(?:an?|some)?\s*(?:image|picture)/i
  ];

  const hasCommandPattern = commandPatterns.some(pattern =>
    pattern.test(message)
  );

  return hasGenerationKeywords || hasCommandPattern;
}

/**
 * Extract prompt from user message
 * @param {string} message - User's message
 * @returns {string} - Cleaned prompt for image generation
 */
function extractPrompt(message) {
  if (!message || typeof message !== 'string') {
    return 'a beautiful landscape';
  }

  let prompt = message.toLowerCase();

  // Remove common prefixes
  const prefixesToRemove = [
    'generate an image of',
    'generate image of',
    'create an image of',
    'create image of',
    'make an image of',
    'make image of',
    'draw an image of',
    'draw image of',
    'generate a picture of',
    'generate picture of',
    'create a picture of',
    'create picture of',
    'make a picture of',
    'make picture of',
    'draw a picture of',
    'draw a picture of',
    'generate art of',
    'create art of',
    'make art of',
    'draw art of',
    'an image of',
    'a picture of',
    'image of',
    'picture of',
    'drawing of',
    'artwork of',
    'show me',
    'visualize',
    'render',
    'paint',
    'sketch',
    'illustrate',
    'design'
  ];

  // Remove prefixes
  for (const prefix of prefixesToRemove) {
    if (prompt.startsWith(prefix)) {
      prompt = prompt.substring(prefix.length).trim();
      break;
    }
  }

  // Remove articles at the beginning
  prompt = prompt.replace(/^(a|an|the)\s+/i, '');

  // Clean up
  prompt = prompt.trim();

  // Fallback if prompt is empty
  if (!prompt || prompt.length < 3) {
    return 'a beautiful landscape';
  }

  return prompt;
}


/**
 * Generate image using Pollinations API (free, no API key required)
 * @param {string} prompt - Text prompt for image generation
 * @returns {Promise<Object>} - Generated image data
 */
async function generateImage(prompt) {
  try {
    console.log(`🖼️ Generating image with prompt: "${prompt}"`);
    console.log(`🌸 Using Pollinations API (free, no API key required)`);

    // Pollinations API - completely free, no API key needed
    const encodedPrompt = encodeURIComponent(prompt);
    const imageUrl = `https://image.pollinations.ai/prompt/${encodedPrompt}?width=512&height=512&nologo=true`;

    console.log(`🔗 Fetching from: ${imageUrl}`);

    const response = await fetch(imageUrl);

    if (!response.ok) {
      throw new Error(`Pollinations API error: ${response.status} ${response.statusText}`);
    }

    const imageBuffer = await response.arrayBuffer();

    if (!imageBuffer || imageBuffer.byteLength === 0) {
      throw new Error('Received empty image data from Pollinations');
    }

    console.log(`✅ Image generated successfully (${imageBuffer.byteLength} bytes)`);

    return {
      success: true,
      imageBuffer: Buffer.from(imageBuffer),
      prompt: prompt,
      model: 'Pollinations AI',
      size: imageBuffer.byteLength
    };

  } catch (error) {
    console.error('❌ Image generation error:', error);
    return {
      success: false,
      error: error.message,
      prompt: prompt
    };
  }
}


/**
 * Generate image and format for Discord
 * @param {string} userMessage - User's message
 * @returns {Promise<Object>} - Formatted image result
 */
async function generateAndFormat(userMessage) {
  try {
    // Extract prompt from user message
    const prompt = extractPrompt(userMessage);
    console.log(`🔍 Extracted prompt: "${prompt}"`);

    // Generate image
    const result = await generateImage(prompt);

    if (result.success) {
      return {
        success: true,
        prompt: prompt,
        imageBuffer: result.imageBuffer,
        filename: `generated_${Date.now()}.png`,
        size: result.size
      };
    } else {
      return {
        success: false,
        prompt: prompt,
        error: result.error
      };
    }

  } catch (error) {
    console.error('❌ Image generation and formatting error:', error);
    return {
      success: false,
      error: error.message
    };
  }
}

module.exports = {
  detectImageGenerationIntent,
  extractPrompt,
  generateImage,
  generateAndFormat,
};

“aiResponse” updated:

JavaScript
const memory = require('./memory.js');
const search = require('./search.js');
const imageRead = require('./imageRead.js');
const imageGenerate = require('./imageGenerate.js');

/**
 * Generate an AI response with image analysis
 * @param {string} userMessage - The user's message
 * @param {Object} model - The Gemini AI model instance
 * @param {string} systemPrompt - The system prompt with rules
 * @param {string} userId - Discord user ID for memory context
 * @param {Array} imageAttachments - Array of Discord image attachments
 * @returns {Promise<string>} - The AI response
 */
async function generateAIResponseWithImages(userMessage, model, systemPrompt, userId, imageAttachments) {
  try {
    // Clean user message (remove mentions)
    const cleanedUserMessage = cleanUserMessage(userMessage);

    // Get memory context for this user
    const memoryContext = memory.formatMemoryForAI(userId);

    // Process images if any
    let imageContext = '';
    const imageParts = [];

    if (imageAttachments && imageAttachments.length > 0) {
      console.log(`🖼️ Processing ${imageAttachments.length} image(s)`);

      for (const attachment of imageAttachments) {
        // Validate image
        const validation = imageRead.validateImage(attachment);
        if (!validation.valid) {
          console.log(`❌ Invalid image: ${validation.errors.join(', ')}`);
          continue;
        }

        try {
          // Download and convert the image
          const imageData = await imageRead.downloadImageAsBase64(attachment.url);
          const imageInfo = imageRead.getImageInfo(attachment);

          // Add image to Gemini parts
          imageParts.push({
            inlineData: {
              data: imageData.base64,
              mimeType: imageData.mimeType
            }
          });

          // Add image context for AI
          imageContext += `\nImage attached: ${imageInfo.name} (${imageInfo.extension}, ${(imageInfo.size / 1024).toFixed(1)}KB)`;

        } catch (error) {
          console.error(`❌ Error processing image ${attachment.name}:`, error);
          imageContext += `\nNote: Could not process image ${attachment.name || 'unknown'}`;
        }
      }
    }

    // Check if search is needed and perform search
    let searchContext = '';
    const needsSearch = search.detectSearchIntent(cleanedUserMessage);

    if (needsSearch) {
      console.log(`🔍 Search detected for: "${cleanedUserMessage}"`);
      const searchResults = await search.searchAndFormat(cleanedUserMessage);

      if (searchResults.success) {
        searchContext = searchResults.context;
        console.log(`✅ Search completed successfully`);
      } else {
        console.log(`❌ Search failed: ${searchResults.error}`);
      }
    }

    // Create the full prompt with all contexts
    const textPrompt = `${systemPrompt}${memoryContext}${searchContext}${imageContext}\n\nCurrent user message: ${cleanedUserMessage}`;

    // Prepare content for Gemini
    const content = [{ text: textPrompt }, ...imageParts];

    // Generate response from AI
    const result = await model.generateContent(content);
    const aiResponse = result.response.text();

    // Clean and validate response
    const cleanedResponse = cleanResponse(aiResponse);

    // Store this conversation in memory
    memory.addToMemory(userId, cleanedUserMessage, cleanedResponse);

    return cleanedResponse;

  } catch (error) {
    console.error('AI Response Error:', error);
    throw new Error('Failed to generate AI response');
  }
}

/**
 * Generate AI response using Gemini model with memory context (text only)
 * @param {string} userMessage - The user's message
 * @param {Object} model - The Gemini AI model instance
 * @param {string} systemPrompt - The system prompt with rules
 * @param {string} userId - Discord user ID for memory context
 * @returns {Promise<string>} - The AI response
 */
async function generateAIResponse(userMessage, model, systemPrompt, userId) {
  try {
    // Clean user message (remove mentions)
    const cleanedUserMessage = cleanUserMessage(userMessage);

    // Get memory context for this user
    const memoryContext = memory.formatMemoryForAI(userId);

    // Check if search is needed and perform search
    let searchContext = '';
    const needsSearch = search.detectSearchIntent(cleanedUserMessage);

    if (needsSearch) {
      console.log(`🔍 Search detected for: "${cleanedUserMessage}"`);
      const searchResults = await search.searchAndFormat(cleanedUserMessage);

      if (searchResults.success) {
        searchContext = searchResults.context;
        console.log(`✅ Search completed successfully`);
      } else {
        console.log(`❌ Search failed: ${searchResults.error}`);
      }
    }

    // Create the full prompt with system prompt, memory context, search context, and current message
    const fullPrompt = `${systemPrompt}${memoryContext}${searchContext}\n\nCurrent user message: ${cleanedUserMessage}`;

    // Generate response from AI
    const result = await model.generateContent(fullPrompt);
    const aiResponse = result.response.text();

    // Clean and validate response
    const cleanedResponse = cleanResponse(aiResponse);

    // Store this conversation in memory
    memory.addToMemory(userId, cleanedUserMessage, cleanedResponse);

    return cleanedResponse;

  } catch (error) {
    console.error('AI Response Error:', error);
    throw new Error('Failed to generate AI response');
  }
}

/**
 * Clean user message (remove mentions and extra whitespace)
 * @param {string} message - Raw user message
 * @returns {string} - Cleaned message
 */
function cleanUserMessage(message) {
  if (!message || typeof message !== 'string') {
    return '';
  }

  // Remove mentions (like <@123456789>)
  let cleaned = message.replace(/<@!?\d+>/g, '').trim();

  // Remove extra whitespace
  cleaned = cleaned.replace(/\s+/g, ' ').trim();

  return cleaned || 'hi';
}

/**
 * Clean and validate AI response
 * @param {string} response - Raw AI response
 * @returns {string} - Cleaned response
 */
function cleanResponse(response) {
  if (!response || typeof response !== 'string') {
    return 'Sorry, I couldn\'t generate a proper response.';
  }

  // Trim whitespace
  let cleaned = response.trim();

  // Ensure response is not too long for Discord (2000 char limit)
  if (cleaned.length > 1900) {
    cleaned = cleaned.substring(0, 1900) + '...';
  }

  // Remove any potential harmful content markers
  cleaned = cleaned.replace(/\[HARMFUL\]|\[NSFW\]|\[INAPPROPRIATE\]/gi, '');

  return cleaned || 'Sorry, I couldn\'t generate a response.';
}

/**
 * Build system prompt from config
 * @param {Object} config - Configuration object with prompt and rules
 * @returns {string} - Complete system prompt
 */
function buildSystemPrompt(config) {
  let systemPrompt = config.prompt || 'You are a helpful AI assistant.';

  if (config.rules && Array.isArray(config.rules)) {
    systemPrompt += '\n\nRules you must follow:\n';
    config.rules.forEach((rule, index) => {
      systemPrompt += `${index + 1}. ${rule}\n`;
    });
  }

  return systemPrompt;
}

/**
 * Check if the user wants image generation and handle accordingly
 * @param {string} userMessage - The user's message
 * @param {Object} model - The Gemini AI model instance
 * @param {string} systemPrompt - The system prompt with rules
 * @param {string} userId - Discord user ID for memory context
 * @returns {Promise<Object>} - Response object with type and content
 */
async function handleUserRequest(userMessage, model, systemPrompt, userId) {
  try {
    // Clean user message (remove mentions)
    const cleanedUserMessage = cleanUserMessage(userMessage);

    // Check if user wants image generation
    const wantsImageGeneration = imageGenerate.detectImageGenerationIntent(cleanedUserMessage);

    if (wantsImageGeneration) {
      console.log(`🎨 Image generation request detected: "${cleanedUserMessage}"`);

      // Generate image
      const imageResult = await imageGenerate.generateAndFormat(cleanedUserMessage);

      if (imageResult.success) {
        // Store in memory
        memory.addToMemory(userId, cleanedUserMessage, `Generated image: ${imageResult.prompt}`);

        return {
          type: 'image',
          success: true,
          imageBuffer: imageResult.imageBuffer,
          filename: imageResult.filename,
          prompt: imageResult.prompt,
          textResponse: `🎨 Here's your generated image: **${imageResult.prompt}**`
        };
      } else {
        // Fallback to text response if image generation fails
        const textResponse = `Sorry, I couldn't generate an image for "${imageResult.prompt}". Error: ${imageResult.error}`;
        memory.addToMemory(userId, cleanedUserMessage, textResponse);

        return {
          type: 'text',
          success: false,
          textResponse: textResponse
        };
      }
    } else {
      // Handle as regular text conversation
      const response = await generateAIResponse(userMessage, model, systemPrompt, userId);

      return {
        type: 'text',
        success: true,
        textResponse: response
      };
    }

  } catch (error) {
    console.error('❌ Error handling user request:', error);
    return {
      type: 'text',
      success: false,
      textResponse: 'Sorry, I encountered an error processing your request.'
    };
  }
}

module.exports = {
  generateAIResponse,
  generateAIResponseWithImages,
  handleUserRequest,
  cleanResponse,
  cleanUserMessage,
  buildSystemPrompt
};

Finally, updated “index.js”

JavaScript
// Import packages
const { Client, GatewayIntentBits, AttachmentBuilder } = require('discord.js'); // Added AttachmentBuilder here
const { GoogleGenerativeAI } = require('@google/generative-ai');
const dotenv = require('dotenv');
const fs = require('fs');

// Import utilities
const aiResponse = require('./utils/aiResponse.js');
const imageRead = require('./utils/imageRead.js');
const imageGenerate = require('./utils/imageGenerate.js');

// Load environment variables
dotenv.config();

// Load configuration
const config = JSON.parse(fs.readFileSync('./config.json', 'utf8'));

// Initialize Gemini AI
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });

// Build system prompt from config
const systemPrompt = aiResponse.buildSystemPrompt(config);

// Create Discord client
const client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildMessages,
    GatewayIntentBits.MessageContent,
  ],
});

// Bot ready event
client.once('ready', async () => {
  console.log('CybranceBot is online!');
  console.log(`System prompt loaded: ${systemPrompt.substring(0, 100)}...`);
});

// Track processed messages to prevent duplicates
const processedMessages = new Set();

// Message event handler
client.on('messageCreate', async (message) => {
  // Ignore bot messages
  if (message.author.bot) return;

  // Only respond when mentioned
  if (!message.mentions.has(client.user)) return;

  // Prevent duplicate processing
  if (processedMessages.has(message.id)) return;
  processedMessages.add(message.id);

  // Clean up old message IDs (keep only last 100)
  if (processedMessages.size > 100) {
    const oldIds = Array.from(processedMessages).slice(0, processedMessages.size - 100);
    oldIds.forEach(id => processedMessages.delete(id));
  }

  try {
    console.log(`Processing message from ${message.author.username}: "${message.content}"`);

    // Check for image attachments
    console.log(`📎 Total attachments: ${message.attachments.size}`);

    // Convert Discord Collection to Array first
    const allAttachments = Array.from(message.attachments.values());
    console.log(`📋 Converted to array: ${allAttachments.length} attachments`);

    const imageAttachments = allAttachments.filter(attachment => {
      console.log(`🔍 Checking attachment: ${attachment.name} (${attachment.contentType})`);
      const isSupported = imageRead.isSupportedImage(attachment);
      console.log(`📋 Is supported: ${isSupported}`);
      return isSupported;
    });

    console.log(`🖼️ Image attachments found: ${imageAttachments.length}`);

    let result;

    if (imageAttachments.length > 0) {
      // Handle message with images
      console.log(`📋 Processing ${imageAttachments.length} image attachments`);
      console.log(`📋 First attachment:`, {
        name: imageAttachments[0].name,
        contentType: imageAttachments[0].contentType,
        size: imageAttachments[0].size
      });

      const response = await aiResponse.generateAIResponseWithImages(
        message.content,
        model,
        systemPrompt,
        message.author.id,
        imageAttachments
      );

      // Send text response
      await message.reply(response);
      console.log(`✅ Responded to ${message.author.username} with image analysis`);

    } else {
      // Handle text-only message (could be image generation or regular chat)
      result = await aiResponse.handleUserRequest(
        message.content,
        model,
        systemPrompt,
        message.author.id
      );

      if (result.type === 'image' && result.success) {
        // Send generated image
        const attachment = new AttachmentBuilder(result.imageBuffer, {
          name: result.filename
        });

        await message.reply({
          content: result.textResponse,
          files: [attachment]
        });

        console.log(`✅ Responded to ${message.author.username} with generated image: "${result.prompt}"`);

      } else {
        // Send text response
        await message.reply(result.textResponse);
        console.log(`✅ Responded to ${message.author.username} with text`);
      }
    }

  } catch (error) {
    console.error('❌ Error:', error);
    await message.reply('Sorry, I had an error processing your message.');
  }
});

// Login to Discord
client.login(process.env.DISCORD_BOT_TOKEN);

Example of Generating Image:

Screenshot of A Discord Chatbot Can Generate Images

—————————————————————————————————-

Wow!! You have made it! 

You have succeeded in making an Advanced AI Discord Bot that uses multiple features, like better context, remembering, reading images, and generating images.

Have fun using your new AI Assistant Discord Bot and enhancing it. This creation opens a world of amazing features that can be added in the future.