import os import discord from discord.ext import commands from openai import OpenAI import base64 import requests from io import BytesIO from collections import deque from dotenv import load_dotenv # Load environment variables load_dotenv() # Get environment variables DISCORD_TOKEN = os.getenv('DISCORD_TOKEN') OPENAI_API_KEY = os.getenv('OPENAI_API_KEY') OPENWEBUI_API_BASE = os.getenv('OPENWEBUI_API_BASE') MODEL_NAME = os.getenv('MODEL_NAME') # Configure OpenAI client to point to OpenWebUI client = OpenAI( api_key=os.getenv('OPENAI_API_KEY'), base_url=os.getenv('OPENWEBUI_API_BASE') # e.g., "http://localhost:8080/v1" ) # Configure OpenAI # TODO: The 'openai.api_base' option isn't read in the client API. You will need to pass it when you instantiate the client, e.g. 'OpenAI(base_url=OPENWEBUI_API_BASE)' # openai.api_base = OPENWEBUI_API_BASE # Initialize Discord bot intents = discord.Intents.default() intents.message_content = True intents.messages = True bot = commands.Bot(command_prefix='!', intents=intents) # Message history cache channel_history = {} async def download_image(url): response = requests.get(url) if response.status_code == 200: image_data = BytesIO(response.content) base64_image = base64.b64encode(image_data.read()).decode('utf-8') return base64_image return None async def get_chat_history(channel, limit=100): messages = [] async for message in channel.history(limit=limit): content = f"{message.author.name}: {message.content}" # Handle attachments (images) for attachment in message.attachments: if any(attachment.filename.lower().endswith(ext) for ext in ['.png', '.jpg', '.jpeg', '.gif', '.webp']): content += f" [Image: {attachment.url}]" messages.append(content) return "\n".join(reversed(messages)) async def get_ai_response(context, user_message, image_urls=None): messages = [{"role": "user", "content": []}] # Add text content text_content = f"##CONTEXT##\n{context}\n##ENDCONTEXT##\n\n{user_message}" messages[0]["content"].append({"type": "text", "text": text_content}) # Add image content if present if image_urls: for url in image_urls: base64_image = await download_image(url) if base64_image: messages[0]["content"].append({ "type": "image_url", "image_url": { "url": f"data:image/jpeg;base64,{base64_image}" } }) try: response = client.chat.completions.create( model=MODEL_NAME, messages=messages ) return response.choices[0].message.content except Exception as e: return f"Error: {str(e)}" @bot.event async def on_message(message): # Ignore messages from the bot itself if message.author == bot.user: return should_respond = False # Check if bot was mentioned if bot.user in message.mentions: should_respond = True # Check if message is a DM if isinstance(message.channel, discord.DMChannel): should_respond = True if should_respond: async with message.channel.typing(): # Get chat history history = await get_chat_history(message.channel) # Remove bot mention from the message user_message = message.content.replace(f'<@{bot.user.id}>', '').strip() # Collect image URLs from the message image_urls = [] for attachment in message.attachments: if any(attachment.filename.lower().endswith(ext) for ext in ['.png', '.jpg', '.jpeg', '.gif', '.webp']): image_urls.append(attachment.url) # Get AI response response = await get_ai_response(history, user_message, image_urls) # Send response await message.reply(response) await bot.process_commands(message) @bot.event async def on_ready(): print(f'{bot.user} has connected to Discord!') def main(): if not all([DISCORD_TOKEN, OPENAI_API_KEY, OPENWEBUI_API_BASE, MODEL_NAME]): print("Error: Missing required environment variables") return bot.run(DISCORD_TOKEN) if __name__ == "__main__": main()