Add image/GIF sending support in bot responses

- Extract image URLs from AI responses
- Send images as Discord embeds
- Support standalone URLs and markdown image syntax
- Clean image URLs from text to avoid duplication
- Handle multiple images in a single response
This commit is contained in:
2026-01-11 21:00:36 +01:00
parent 1f6e792fa3
commit bd73fe68ce

View File

@@ -105,18 +105,99 @@ class AIChatCog(commands.Cog):
try: try:
response_text = await self._generate_response(message, content) response_text = await self._generate_response(message, content)
# Split and send response # Extract image URLs and clean response text
chunks = split_message(response_text) text_content, image_urls = self._extract_image_urls(response_text)
await message.reply(chunks[0])
# Split and send response
chunks = split_message(text_content) if text_content.strip() else []
# Send first chunk as reply (or just images if no text)
if chunks:
first_embed = self._create_image_embed(image_urls[0]) if image_urls else None
await message.reply(chunks[0], embed=first_embed)
remaining_images = image_urls[1:] if image_urls else []
elif image_urls:
# Only images, no text
await message.reply(embed=self._create_image_embed(image_urls[0]))
remaining_images = image_urls[1:]
else:
await message.reply("I don't have a response for that.")
return
# Send remaining text chunks
for chunk in chunks[1:]: for chunk in chunks[1:]:
await message.channel.send(chunk) await message.channel.send(chunk)
# Send remaining images as separate embeds
for img_url in remaining_images:
await message.channel.send(embed=self._create_image_embed(img_url))
except Exception as e: except Exception as e:
logger.error(f"Mention response error: {e}", exc_info=True) logger.error(f"Mention response error: {e}", exc_info=True)
error_message = self._get_error_message(e) error_message = self._get_error_message(e)
await message.reply(error_message) await message.reply(error_message)
def _extract_image_urls(self, text: str) -> tuple[str, list[str]]:
"""Extract image URLs from text and return cleaned text with URLs.
Args:
text: The response text that may contain image URLs
Returns:
Tuple of (cleaned text, list of image URLs)
"""
# Pattern to match image URLs (common formats)
image_extensions = r"\.(png|jpg|jpeg|gif|webp|bmp)"
url_pattern = rf"(https?://[^\s<>\"\')]+{image_extensions}(?:\?[^\s<>\"\')]*)?)"
# Find all image URLs
image_urls = re.findall(url_pattern, text, re.IGNORECASE)
# The findall returns tuples when there are groups, extract full URLs
image_urls = re.findall(
rf"https?://[^\s<>\"\')]+{image_extensions}(?:\?[^\s<>\"\')]*)?",
text,
re.IGNORECASE,
)
# Also check for markdown image syntax ![alt](url)
markdown_images = re.findall(r"!\[[^\]]*\]\(([^)]+)\)", text)
for url in markdown_images:
if url not in image_urls:
# Check if it looks like an image URL
if re.search(image_extensions, url, re.IGNORECASE) or "image" in url.lower():
image_urls.append(url)
# Clean the text by removing standalone image URLs (but keep them if part of markdown links)
cleaned_text = text
for url in image_urls:
# Remove standalone URLs (not part of markdown)
cleaned_text = re.sub(
rf"(?<!\()(?<!\[){re.escape(url)}(?!\))",
"",
cleaned_text,
)
# Remove markdown image syntax
cleaned_text = re.sub(rf"!\[[^\]]*\]\({re.escape(url)}\)", "", cleaned_text)
# Clean up extra whitespace
cleaned_text = re.sub(r"\n{3,}", "\n\n", cleaned_text)
cleaned_text = cleaned_text.strip()
return cleaned_text, image_urls
def _create_image_embed(self, image_url: str) -> discord.Embed:
"""Create a Discord embed with an image.
Args:
image_url: The URL of the image
Returns:
Discord Embed object with the image
"""
embed = discord.Embed()
embed.set_image(url=image_url)
return embed
def _get_error_message(self, error: Exception) -> str: def _get_error_message(self, error: Exception) -> str:
"""Get a user-friendly error message based on the exception type. """Get a user-friendly error message based on the exception type.