chatbot.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import os
  2. import openai
  3. import discord
  4. import aiohttp
  5. import asyncio
  6. import base64
  7. import logging
  8. from dotenv import load_dotenv
  9. # Charger les variables d'environnement depuis le fichier .env
  10. load_dotenv()
  11. DISCORD_TOKEN = os.getenv('DISCORD_TOKEN')
  12. OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
  13. # Vérifier que les tokens sont récupérés
  14. if DISCORD_TOKEN is None or OPENAI_API_KEY is None:
  15. raise ValueError("Les tokens ne sont pas définis dans les variables d'environnement.")
  16. # Log configuration
  17. log_format='%(asctime)-13s : %(name)-20s : %(levelname)-8s : %(message)s'
  18. logging.basicConfig(handlers=[logging.FileHandler("./chatbot.log", 'a', 'utf-8')], format=log_format, level="DEBUG")
  19. console = logging.StreamHandler()
  20. console.setLevel(logging.INFO)
  21. console.setFormatter(logging.Formatter(log_format))
  22. logger = logging.getLogger("chatbot")
  23. logger.setLevel("DEBUG")
  24. logging.getLogger('').addHandler(console)
  25. # Initialiser les intents
  26. intents = discord.Intents.default()
  27. intents.message_content = True # Activer l'intent pour les contenus de message
  28. # Initialiser le client Discord avec les intents modifiés
  29. client_discord = discord.Client(intents=intents)
  30. # Initialiser l'API OpenAI avec un client
  31. client_openai = openai.OpenAI(api_key=OPENAI_API_KEY)
  32. # Dictionnaire pour stocker l'historique des conversations pour chaque utilisateur
  33. conversation_history = {}
  34. # L'ID du salon spécifique où le bot est autorisé à répondre
  35. chatgpt_channel_id = 1284699709188997150 # Remplace par l'ID réel de ton salon
  36. def calculate_cost(usage):
  37. input_tokens = usage.get('prompt_tokens', 0)
  38. output_tokens = usage.get('completion_tokens', 0)
  39. # Coûts estimés
  40. input_cost = input_tokens / 1_000_000 * 5.00 # 5$ pour 1M tokens d'entrée
  41. output_cost = output_tokens / 1_000_000 * 15.00 # 15$ pour 1M tokens de sortie
  42. total_cost = input_cost + output_cost
  43. return input_tokens, output_tokens, total_cost
  44. async def read_text_file(attachment):
  45. # Télécharger et lire le contenu du fichier texte
  46. async with aiohttp.ClientSession() as session:
  47. async with session.get(attachment.url) as resp:
  48. return await resp.text()
  49. async def encode_image_from_attachment(attachment):
  50. async with aiohttp.ClientSession() as session:
  51. async with session.get(attachment.url) as resp:
  52. image_data = await resp.read()
  53. return base64.b64encode(image_data).decode('utf-8')
  54. async def call_openai_api(user_id, user_text, image_data=None):
  55. # Récupérer l'historique de la conversation pour l'utilisateur
  56. user_history = conversation_history.get(user_id, [])
  57. # Préparer le contenu de l'utilisateur
  58. user_content = [{"type": "text", "text": user_text}]
  59. if image_data:
  60. user_content.append({
  61. "type": "image_url",
  62. "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}
  63. })
  64. # Ajouter le contenu à l'historique
  65. user_history.append({
  66. "role": "user",
  67. "content": user_content
  68. })
  69. payload = {
  70. "model": "gpt-4o",
  71. "messages": user_history,
  72. "max_tokens": 500,
  73. "stop": ["\n"] # Arrête la réponse à la fin d'une phrase
  74. }
  75. headers = {
  76. "Content-Type": "application/json",
  77. "Authorization": f"Bearer {OPENAI_API_KEY}"
  78. }
  79. try:
  80. async with aiohttp.ClientSession() as session:
  81. async with session.post("https://api.openai.com/v1/chat/completions", headers=headers, json=payload) as resp:
  82. result = await resp.json()
  83. if resp.status != 200:
  84. raise ValueError(f"API Error: {result.get('error', {}).get('message', 'Unknown error')}")
  85. # Calculer les coûts
  86. usage = result.get('usage', {})
  87. input_tokens, output_tokens, total_cost = calculate_cost(usage)
  88. # Afficher dans la console
  89. logger.info(f"Estimated Cost: ${total_cost:.4f} / Input Tokens: {input_tokens} / Output Tokens: {output_tokens} / Total Tokens: {input_tokens + output_tokens}")
  90. return result
  91. except Exception as e:
  92. logger.error(f"Error calling OpenAI API: {e}")
  93. return None
  94. @client_discord.event
  95. async def on_ready():
  96. logger.info(f'Bot connecté en tant que {client_discord.user}')
  97. @client_discord.event
  98. async def on_message(message):
  99. # Vérifier si le message provient du canal autorisé
  100. if message.channel.id != chatgpt_channel_id:
  101. return
  102. # Vérifier si l'auteur du message est le bot lui-même
  103. if message.author == client_discord.user:
  104. return
  105. user_text = message.content.strip()
  106. image_data = None
  107. file_content = None
  108. # Extensions de fichiers autorisées
  109. allowed_extensions = ['.txt', '.py', '.html', '.css', '.js']
  110. # Vérifier s'il y a une pièce jointe
  111. if message.attachments:
  112. for attachment in message.attachments:
  113. # Vérifier si c'est un fichier avec une extension autorisée
  114. if any(attachment.filename.endswith(ext) for ext in allowed_extensions):
  115. file_content = await read_text_file(attachment)
  116. break
  117. # Vérifier si c'est une image
  118. elif attachment.content_type.startswith('image/'):
  119. image_data = await encode_image_from_attachment(attachment)
  120. break
  121. # Ajouter le contenu du fichier à la requête si présent
  122. if file_content:
  123. user_text += f"\nContenu du fichier {attachment.filename}:\n{file_content}"
  124. # Appeler l'API OpenAI
  125. result = await call_openai_api(message.author.id, user_text, image_data)
  126. if result:
  127. reply = result['choices'][0]['message']['content']
  128. await message.channel.send(reply)
  129. # Ajouter la réponse du modèle à l'historique
  130. add_to_conversation_history(message.author.id, {
  131. "role": "assistant",
  132. "content": reply
  133. })
  134. MAX_HISTORY_LENGTH = 50 # Nombre maximum de messages à conserver
  135. def add_to_conversation_history(user_id, new_message):
  136. if user_id not in conversation_history:
  137. # Ajouter la personnalité de l'IA en tant que premier message
  138. conversation_history[user_id] = [
  139. {
  140. "role": "system",
  141. "content": "Tu es une utilisatrice Discord qui parle français."
  142. "Tu ne dis jamais que tu es un IA."
  143. "Tu ne peux pas poster de message de plusieurs lignes."
  144. "Tu peux analyser des images et des pièces jointes aux formats txt, py, html, css et js."
  145. "Tu es cynique et sarcastique."
  146. "Tu apprécies les compliments."
  147. "Réponds sans poser de questions finales comme 'Avez-vous besoin de quelque chose d'autre ?'"
  148. "Privilégie des réponses courtes et percutantes."
  149. "Tu évites de générer des réponses de plus de 500 tokens."
  150. }
  151. ]
  152. conversation_history[user_id].append(new_message)
  153. # Limiter la taille de l'historique
  154. if len(conversation_history[user_id]) > MAX_HISTORY_LENGTH:
  155. # Garder le premier message de personnalité et les messages les plus récents
  156. conversation_history[user_id] = conversation_history[user_id][:1] + conversation_history[user_id][-MAX_HISTORY_LENGTH:]
  157. # Démarrer le bot Discord
  158. client_discord.run(DISCORD_TOKEN)