2025-01-02 17:22:32 -08:00
import os
import discord
from discord.ext import commands
2025-01-02 17:53:16 -08:00
from openai import OpenAI
2025-01-02 19:38:42 -08:00
import base64
import requests
from io import BytesIO
2025-01-02 17:22:32 -08:00
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' )
2025-01-02 17:53:16 -08:00
# 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"
)
2025-01-02 17:22:32 -08:00
# Configure OpenAI
2025-01-02 17:53:16 -08:00
# 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
2025-01-02 17:22:32 -08:00
# 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 = {}
2025-01-02 19:38:42 -08:00
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
2025-01-02 17:22:32 -08:00
async def get_chat_history ( channel , limit = 100 ):
messages = []
async for message in channel . history ( limit = limit ):
2025-01-02 19:38:42 -08:00
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 )
2025-01-02 17:22:32 -08:00
return " \n " . join ( reversed ( messages ))
2025-01-02 19:38:42 -08:00
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 } "
}
})
2025-01-02 17:53:16 -08:00
2025-01-02 17:22:32 -08:00
try :
2025-01-02 19:38:42 -08:00
response = client . chat . completions . create (
model = MODEL_NAME ,
messages = messages
)
2025-01-02 17:22:32 -08:00
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
2025-01-02 17:53:16 -08:00
2025-01-02 17:22:32 -08:00
# Check if bot was mentioned
if bot . user in message . mentions :
should_respond = True
2025-01-02 17:53:16 -08:00
2025-01-02 17:22:32 -08:00
# 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 )
2025-01-02 17:53:16 -08:00
2025-01-02 17:22:32 -08:00
# Remove bot mention from the message
user_message = message . content . replace ( f '<@ { bot . user . id } >' , '' ) . strip ()
2025-01-02 17:53:16 -08:00
2025-01-02 19:38:42 -08:00
# 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 )
2025-01-02 17:22:32 -08:00
# Get AI response
2025-01-02 19:38:42 -08:00
response = await get_ai_response ( history , user_message , image_urls )
2025-01-02 17:53:16 -08:00
2025-01-02 17:22:32 -08:00
# Send response
await message . reply ( response )
await bot . process_commands ( message )
2025-01-02 19:38:42 -08:00
@bot.event
async def on_ready ():
print ( f ' { bot . user } has connected to Discord!' )
2025-01-02 17:22:32 -08:00
def main ():
if not all ([ DISCORD_TOKEN , OPENAI_API_KEY , OPENWEBUI_API_BASE , MODEL_NAME ]):
print ( "Error: Missing required environment variables" )
return
2025-01-02 17:53:16 -08:00
2025-01-02 17:22:32 -08:00
bot . run ( DISCORD_TOKEN )
if __name__ == "__main__" :
main ()