1
0

chatbot.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. import discord
  2. from discord.ext import commands
  3. import requests
  4. import json
  5. import os
  6. import random # Ajouté pour choisir un sticker aléatoire
  7. from dotenv import load_dotenv
  8. # Charger les variables d'environnement
  9. load_dotenv()
  10. # Version du bot
  11. VERSION = "4.0.0" # Modifiable selon la version actuelle
  12. # Récupérer les variables d'environnement
  13. MISTRAL_API_KEY = os.getenv('MISTRAL_API_KEY')
  14. DISCORD_TOKEN = os.getenv('DISCORD_TOKEN')
  15. CHANNEL_ID = int(os.getenv('CHANNEL_ID'))
  16. # Endpoint API Mistral
  17. MISTRAL_API_URL = "https://api.mistral.ai/v1/chat/completions"
  18. # Fichier pour stocker l'historique
  19. HISTORY_FILE = "conversation_history.json"
  20. MAX_HISTORY_LENGTH = 10 # Limité à 10 messages
  21. def load_history():
  22. """Charge l'historique depuis un fichier JSON."""
  23. if os.path.exists(HISTORY_FILE):
  24. with open(HISTORY_FILE, 'r', encoding='utf-8') as f:
  25. try:
  26. data = json.load(f)
  27. # Vérifier et limiter la taille de chaque historique
  28. for channel_id in data:
  29. if "messages" in data[channel_id]:
  30. if len(data[channel_id]["messages"]) > MAX_HISTORY_LENGTH:
  31. data[channel_id]["messages"] = data[channel_id]["messages"][-MAX_HISTORY_LENGTH:]
  32. return data
  33. except json.JSONDecodeError:
  34. print("Erreur de lecture du fichier d'historique. Création d'un nouveau fichier.")
  35. return {}
  36. return {}
  37. def save_history(history):
  38. """Sauvegarde l'historique dans un fichier JSON."""
  39. with open(HISTORY_FILE, 'w', encoding='utf-8') as f:
  40. json.dump(history, f, ensure_ascii=False, indent=4)
  41. def get_personality_prompt():
  42. try:
  43. with open('personality_prompt.txt', 'r', encoding='utf-8') as file:
  44. return file.read().strip()
  45. except FileNotFoundError:
  46. print("Le fichier personality_prompt.txt n'a pas été trouvé. Utilisation d'un prompt par défaut.")
  47. return """Tu es un assistant utile et poli qui peut analyser des images.
  48. Quand on te montre une image, décris-la et donne ton avis si on te le demande.
  49. Réponds toujours en français avec un ton naturel et amical.
  50. Lorsque tu analyses une image, décris d'abord ce que tu vois en détail,
  51. puis réponds à la question si elle est posée. Utilise un langage clair et accessible."""
  52. # Charger l'historique au démarrage
  53. conversation_history = load_history()
  54. intents = discord.Intents.default()
  55. intents.messages = True
  56. intents.message_content = True
  57. intents.presences = True
  58. bot = commands.Bot(command_prefix='!', intents=intents)
  59. @bot.event
  60. async def on_ready():
  61. print(f'Le bot est connecté en tant que {bot.user}')
  62. global conversation_history
  63. conversation_history = load_history()
  64. # Récupérer le canal spécifié
  65. channel = bot.get_channel(CHANNEL_ID)
  66. if channel is not None:
  67. # Trouver la guilde (serveur) à laquelle appartient le canal
  68. guild = channel.guild
  69. if guild is not None:
  70. # Récupérer le membre du bot sur cette guilde
  71. bot_member = guild.me
  72. # Récupérer le pseudo du bot sur cette guilde
  73. bot_nickname = bot_member.display_name
  74. else:
  75. bot_nickname = bot.user.name # Utiliser le nom global si la guilde n'est pas trouvée
  76. # Créer un embed avec le pseudo du bot
  77. embed = discord.Embed(
  78. title="Bot en ligne",
  79. description=f"{bot_nickname} est désormais en ligne. Version {VERSION}.",
  80. color=discord.Color.green()
  81. )
  82. # Envoyer l'embed dans le canal
  83. await channel.send(embed=embed)
  84. def call_mistral_api(prompt, history, image_url=None, user_id=None, username=None):
  85. headers = {
  86. "Content-Type": "application/json",
  87. "Authorization": f"Bearer {MISTRAL_API_KEY}"
  88. }
  89. personality_prompt = get_personality_prompt()
  90. # Vérifier si la structure messages existe
  91. if "messages" not in history:
  92. history["messages"] = []
  93. # Création du message utilisateur selon qu'il y a une image ou non
  94. if image_url:
  95. # Format multimodal pour les messages avec image
  96. user_content = [
  97. {"type": "text", "text": f"{username}: {prompt}" if username else prompt},
  98. {
  99. "type": "image_url",
  100. "image_url": {
  101. "url": image_url,
  102. "detail": "high" # Demander une analyse détaillée de l'image
  103. }
  104. }
  105. ]
  106. user_message = {
  107. "role": "user",
  108. "content": user_content
  109. }
  110. else:
  111. # Format standard pour les messages texte seulement
  112. user_content = f"{username}: {prompt}" if username else prompt
  113. user_message = {"role": "user", "content": user_content}
  114. # Ajouter le message utilisateur à l'historique
  115. history["messages"].append(user_message)
  116. # Limiter l'historique à MAX_HISTORY_LENGTH messages
  117. if len(history["messages"]) > MAX_HISTORY_LENGTH:
  118. history["messages"] = history["messages"][-MAX_HISTORY_LENGTH:]
  119. # Préparer les messages pour l'API
  120. messages = []
  121. # Ajouter le message système en premier
  122. messages.append({"role": "system", "content": personality_prompt})
  123. # Ajouter l'historique des messages (en gardant le format)
  124. for msg in history["messages"]:
  125. if isinstance(msg["content"], list): # C'est un message multimodal
  126. messages.append({
  127. "role": msg["role"],
  128. "content": msg["content"]
  129. })
  130. else: # C'est un message texte standard
  131. messages.append({
  132. "role": msg["role"],
  133. "content": msg["content"]
  134. })
  135. data = {
  136. "model": "pixtral-large-latest",
  137. "messages": messages,
  138. "max_tokens": 1000
  139. }
  140. try:
  141. response = requests.post(MISTRAL_API_URL, headers=headers, data=json.dumps(data))
  142. response.raise_for_status() # Lève une exception pour les erreurs HTTP
  143. if response.status_code == 200:
  144. response_data = response.json()
  145. # Vérifier si la réponse contient bien le champ attendu
  146. if 'choices' in response_data and len(response_data['choices']) > 0:
  147. assistant_response = response_data['choices'][0]['message']['content']
  148. # Ajouter la réponse de l'assistant à l'historique
  149. history["messages"].append({"role": "assistant", "content": assistant_response})
  150. # Limiter à nouveau après avoir ajouté la réponse
  151. if len(history["messages"]) > MAX_HISTORY_LENGTH:
  152. history["messages"] = history["messages"][-MAX_HISTORY_LENGTH:]
  153. # Sauvegarder l'historique après chaque modification
  154. save_history(conversation_history)
  155. return assistant_response
  156. else:
  157. print(f"Réponse API inattendue: {response_data}")
  158. return "Désolé, je n'ai pas reçu de réponse valide de l'API."
  159. else:
  160. return f"Erreur API: {response.status_code}"
  161. except requests.exceptions.RequestException as e:
  162. print(f"Erreur lors de l'appel API: {e}")
  163. return "Désolé, une erreur réseau est survenue lors de la communication avec l'API."
  164. @bot.command(name='reset')
  165. async def reset_history(ctx):
  166. channel_id = str(ctx.channel.id)
  167. if channel_id in conversation_history:
  168. # Conserver le même ID de conversation mais vider les messages
  169. if "messages" in conversation_history[channel_id]:
  170. conversation_history[channel_id]["messages"] = []
  171. else:
  172. conversation_history[channel_id] = {
  173. "conversation_id": conversation_history[channel_id].get("conversation_id", str(len(conversation_history) + 1)),
  174. "messages": []
  175. }
  176. save_history(conversation_history)
  177. await ctx.send("L'historique de conversation a été réinitialisé.")
  178. else:
  179. conversation_id = str(len(conversation_history) + 1)
  180. conversation_history[channel_id] = {
  181. "conversation_id": conversation_id,
  182. "messages": []
  183. }
  184. save_history(conversation_history)
  185. await ctx.send("Aucun historique de conversation trouvé pour ce channel. Créé un nouvel historique.")
  186. @bot.event
  187. async def on_message(message):
  188. # Ignorer les messages du bot lui-même
  189. if message.author == bot.user:
  190. return
  191. # Vérifier si le message contient des stickers
  192. if message.stickers:
  193. # Obtenir le serveur (guild) du message
  194. guild = message.guild
  195. if guild:
  196. # Obtenir la liste des stickers personnalisés du serveur
  197. stickers = guild.stickers
  198. if stickers:
  199. # Choisir un sticker aléatoire (ou spécifique)
  200. sticker = random.choice(stickers)
  201. # Envoyer le sticker en réponse
  202. await message.channel.send(stickers=[sticker])
  203. else:
  204. await message.channel.send("Aucun sticker personnalisé trouvé sur ce serveur.")
  205. else:
  206. await message.channel.send("Ce message ne provient pas d'un serveur.")
  207. return # Retourner pour éviter que le reste du code ne s'exécute
  208. # Vérifier si le message provient du channel spécifique
  209. if message.channel.id != CHANNEL_ID:
  210. return
  211. # Si le message commence par le préfixe du bot, traiter comme une commande
  212. if message.content.startswith('!'):
  213. await bot.process_commands(message)
  214. return
  215. # Résolution des mentions dans le message
  216. resolved_content = message.content
  217. for user in message.mentions:
  218. # Remplacer chaque mention par le nom d'utilisateur
  219. resolved_content = resolved_content.replace(f"<@{user.id}>", f"@{user.display_name}")
  220. # Vérifier le nombre d'images dans le message
  221. image_count = 0
  222. if message.attachments:
  223. for attachment in message.attachments:
  224. if attachment.content_type and attachment.content_type.startswith('image/'):
  225. image_count += 1
  226. # Vérifier si le nombre d'images dépasse la limite
  227. if image_count > 3:
  228. await message.channel.send("Erreur : Vous ne pouvez pas envoyer plus de trois images dans un seul message. Veuillez diviser votre envoi en plusieurs messages.")
  229. return
  230. # Vérifier les pièces jointes pour la taille des images
  231. if message.attachments:
  232. # D'abord, vérifier toutes les images pour leur taille
  233. too_large_images = []
  234. for attachment in message.attachments:
  235. if attachment.content_type and attachment.content_type.startswith('image/'):
  236. max_size = 2 * 1024 * 1024 # 2 Mo en octets
  237. if attachment.size > max_size:
  238. too_large_images.append(attachment.filename)
  239. # Si des images trop grandes sont trouvées, envoyer un message d'erreur et arrêter
  240. if too_large_images:
  241. image_list = ", ".join(too_large_images)
  242. await message.channel.send(f"Erreur : Les images suivantes dépassent la limite de 2 Mo : {image_list}. Veuillez envoyer des images plus petites.")
  243. return
  244. # Récupérer ou initialiser l'historique pour ce channel
  245. channel_id = str(message.channel.id)
  246. global conversation_history
  247. # Charger l'historique actuel
  248. conversation_history = load_history()
  249. if channel_id not in conversation_history:
  250. conversation_id = str(len(conversation_history) + 1)
  251. conversation_history[channel_id] = {
  252. "conversation_id": conversation_id,
  253. "messages": []
  254. }
  255. # Assurer que la clé messages existe
  256. if "messages" not in conversation_history[channel_id]:
  257. conversation_history[channel_id]["messages"] = []
  258. # Traitement des images dans le message
  259. image_url = None
  260. if message.attachments:
  261. for attachment in message.attachments:
  262. if attachment.content_type and attachment.content_type.startswith('image/'):
  263. image_url = attachment.url
  264. break
  265. # Utiliser le contenu résolu (avec les mentions remplacées)
  266. prompt = resolved_content
  267. # Indiquer que le bot est en train de taper
  268. async with message.channel.typing():
  269. try:
  270. # Appeler l'API Mistral avec l'historique de conversation, l'image si présente, et les infos de l'utilisateur
  271. response = call_mistral_api(
  272. prompt,
  273. conversation_history[channel_id],
  274. image_url,
  275. user_id=str(message.author.id),
  276. username=message.author.display_name
  277. )
  278. await message.channel.send(response)
  279. except Exception as e:
  280. print(f"Erreur lors de l'appel à l'API: {e}")
  281. await message.channel.send("Désolé, une erreur est survenue lors du traitement de votre demande.")
  282. # Assurer que les autres gestionnaires d'événements reçoivent également le message
  283. await bot.process_commands(message)
  284. bot.run(DISCORD_TOKEN)