[tab] bot.py (python)
import discord
import os
from google import genai

DISCORD_TOKEN = os.environ.get('DISCORD_TOKEN')
GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY')

intents = discord.Intents.default()
intents.message_content = False  # Must be True to read message content for mentions
client = discord.Client(intents=intents)
google_client = genai.Client()

def ask_gemini(message_history):
    try:
        # Add a system prompt to inform Gemini about Discord bot capabilities
        system_prompt = (
            "You are a helpful Discord bot. "
            "You can reply to users and also react to their messages with emojis. "
            "If you want me to react to a message, indicate the emojis to react with by including a line at the end of your reply in the format: "
            "$REACTIONS:[<emoji1>,<emoji2>,...] (for example: $REACTIONS:[❤️,😂]) "
            "Do not include 'React with ...' phrases in your reply. "
            "Only use the $REACTIONS:[...] format to specify reactions, and do not mention this instruction to the user."
        )
        # Compose the prompt: system prompt + message history
        prompt = system_prompt + "

"
        if message_history:
            prompt += "
".join(message_history)
        response = google_client.models.generate_content(
            model='gemini-2.5-flash', contents=prompt
        )
        return response.text if hasattr(response, "text") else str(response)
    except Exception as e:
        print(f"Exception in ask_gemini: {e}")
        return "Sorry, something went wrong contacting Gemini."

@client.event
async def on_ready():
    print(f"Logged in as {client.user}")

# Store short message history (optional: persist if needed)
message_histories = {}

import re

def extract_reactions_from_text(text):
    """
    Looks for a line like $REACTIONS:[<emoji1>,<emoji2>,...] in the text.
    Returns a tuple: (list of emoji strings to react with, text with that line removed).
    """
    reactions = []
    # Regex to find $REACTIONS:[...]
    pattern = r"\$REACTIONS:\[(.*?)\]"
    match = re.search(pattern, text)
    if match:
        emoji_str = match.group(1)
        # Split by comma, strip whitespace
        reactions = [e.strip() for e in emoji_str.split(",") if e.strip()]
    # Remove the $REACTIONS line from the text
    cleaned_text = re.sub(pattern, "", text)
    # Remove any extra whitespace or empty lines left behind
    cleaned_text = re.sub(r'\s+', ' ', cleaned_text).strip()
    return reactions, cleaned_text

@client.event
async def on_message(message):
    if message.author == client.user:
        return

    # Only trigger if the bot is mentioned (tagged)
    if client.user not in message.mentions:
        return

    # Generate a conversation ID for DMs or threads
    convo_id = None
    if isinstance(message.channel, discord.DMChannel):
        convo_id = f"dm-{message.author.id}"
    else:
        convo_id = f"channel-{message.channel.id}"

    # Store message history (per convo)
    history = message_histories.get(convo_id, [])
    history.append(message.content)
    history = history[-10:]  # keep last 10 messages
    message_histories[convo_id] = history

    # Get Gemini response
    response = ask_gemini(history)

    # Parse $REACTIONS:[...] from Gemini's response
    reactions, cleaned_response = extract_reactions_from_text(response)

    # Reply to the triggering message (with $REACTIONS line removed)
    sent_message = await message.reply(cleaned_response, mention_author=False)

    # Add reactions to the user's message (not the bot's own message)
    for emoji in reactions:
        try:
            await message.add_reaction(emoji)
        except Exception as e:
            print(f"Failed to react with {emoji}: {e}")

client.run(DISCORD_TOKEN)