diff --git a/.gitignore b/.gitignore index b5f4223..ccdc725 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ memory.json *.pkl memory.json venv/ +output.png diff --git a/assets/fonts/impact.ttf b/assets/fonts/impact.ttf new file mode 100644 index 0000000..7b7956f Binary files /dev/null and b/assets/fonts/impact.ttf differ diff --git a/assets/images/attention.webp b/assets/images/attention.webp new file mode 100644 index 0000000..f680665 Binary files /dev/null and b/assets/images/attention.webp differ diff --git a/assets/images/bibinos.png b/assets/images/bibinos.png new file mode 100644 index 0000000..5a7b846 Binary files /dev/null and b/assets/images/bibinos.png differ diff --git a/assets/images/crash.webp b/assets/images/crash.webp new file mode 100644 index 0000000..60d918b Binary files /dev/null and b/assets/images/crash.webp differ diff --git a/assets/images/genuineidiot.png b/assets/images/genuineidiot.png new file mode 100644 index 0000000..226ca47 Binary files /dev/null and b/assets/images/genuineidiot.png differ diff --git a/bot.py b/bot.py index f4ff3c1..ad51b56 100644 --- a/bot.py +++ b/bot.py @@ -12,6 +12,7 @@ import psutil import discord from discord.ext import commands, tasks from discord import app_commands +from discord import Colour import nltk from nltk.data import find @@ -26,6 +27,7 @@ from modules.version import * from modules.sentenceprocessing import * from modules.prestartchecks import start_checks from modules.unhandledexception import handle_exception +from modules.image import gen_image from discord.ext import commands, tasks from discord import app_commands sys.excepthook = handle_exception @@ -244,13 +246,23 @@ async def talk(ctx, sentence_size: int = 5): # Remove default help command to use custom help bot.help_command = None +# Command: Show help information +@bot.hybrid_command(description=f"{get_translation(LOCALE, 'command_desc_help')}") +async def image(ctx): + await gen_image() + await send_message(ctx, file=discord.File("output.png")) + + +# Remove default help command to use custom help +bot.help_command = None + # Command: Show help information @bot.hybrid_command(description=f"{get_translation(LOCALE, 'command_desc_help')}") async def help(ctx): embed = discord.Embed( title=f"{get_translation(LOCALE, 'command_help_embed_title')}", description=f"{get_translation(LOCALE, 'command_help_embed_desc')}", - color=discord.Color.blue() + color=Colour(0x000000) ) command_categories = { @@ -343,7 +355,7 @@ async def ping(ctx): f"{PING_LINE}\n" f"`{get_translation(LOCALE, 'command_ping_embed_desc')}: {latency}ms`\n" ), - color=discord.Color.blue() + color=Colour(0x000000) ) LOLembed.set_footer(text=f"{get_translation(LOCALE, 'command_ping_footer')} {ctx.author.name}", icon_url=ctx.author.avatar.url) @@ -355,7 +367,7 @@ async def about(ctx): print("-----------------------------------\n\n") latest_version = check_for_update() # check_for_update from modules/version.py print("-----------------------------------") - embed = discord.Embed(title=f"{get_translation(LOCALE, 'command_about_embed_title')}", description="", color=discord.Color.blue()) + embed = discord.Embed(title=f"{get_translation(LOCALE, 'command_about_embed_title')}", description="", color=Colour(0x000000)) embed.add_field(name=f"{get_translation(LOCALE, 'command_about_embed_field1')}", value=f"{NAME}", inline=False) embed.add_field(name=f"{get_translation(LOCALE, 'command_about_embed_field2name')}", value=f"{get_translation(LOCALE, 'command_about_embed_field2value').format(local_version=local_version, latest_version=latest_version)}", inline=False) @@ -374,7 +386,7 @@ async def stats(ctx): with open(memory_file, 'r') as file: line_count = sum(1 for _ in file) - embed = discord.Embed(title=f"{get_translation(LOCALE, 'command_stats_embed_title')}", description=f"{get_translation(LOCALE, 'command_stats_embed_desc')}", color=discord.Color.blue()) + embed = discord.Embed(title=f"{get_translation(LOCALE, 'command_stats_embed_title')}", description=f"{get_translation(LOCALE, 'command_stats_embed_desc')}", color=Colour(0x000000)) embed.add_field(name=f"{get_translation(LOCALE, 'command_stats_embed_field1name')}", value=f"{get_translation(LOCALE, 'command_stats_embed_field1value').format(file_size=file_size, line_count=line_count)}", inline=False) embed.add_field(name=f"{get_translation(LOCALE, 'command_stats_embed_field2name')}", value=f"{get_translation(LOCALE, 'command_stats_embed_field2value').format(local_version=local_version, latest_version=latest_version)}", inline=False) embed.add_field(name=f"{get_translation(LOCALE, 'command_stats_embed_field3name')}", value=f"{get_translation(LOCALE, 'command_stats_embed_field3value').format(NAME=NAME, PREFIX=PREFIX, ownerid=ownerid, cooldown_time=cooldown_time, PING_LINE=PING_LINE, showmemenabled=showmemenabled, USERTRAIN_ENABLED=USERTRAIN_ENABLED, last_random_talk_time=last_random_talk_time, song=song, splashtext=splashtext)}", inline=False) diff --git a/modules/globalvars.py b/modules/globalvars.py index 36cf7b6..a65da79 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -40,5 +40,5 @@ song = os.getenv("song") arch = platform.machine() slash_commands_enabled = False latest_version = "0.0.0" -local_version = "0.15.8" +local_version = "1.0.0" os.environ['gooberlocal_version'] = local_version diff --git a/modules/image.py b/modules/image.py new file mode 100644 index 0000000..bbce31c --- /dev/null +++ b/modules/image.py @@ -0,0 +1,112 @@ +import os +import random +import re +from PIL import Image, ImageDraw, ImageFont +from modules.markovmemory import load_markov_model +from modules.sentenceprocessing import improve_sentence_coherence, rephrase_for_coherence + +generated_sentences = set() + +async def gen_image(sentence_size=5, max_attempts=10): + images_folder = "assets/images" + image_files = [f for f in os.listdir(images_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp'))] + if not image_files: + return + + markov_model = load_markov_model() + if not markov_model: + return + + def load_font(size): + return ImageFont.truetype("assets/fonts/Impact.ttf", size=size) + + def draw_text_with_outline(draw, text, x, y, font): + outline_offsets = [(-2, -2), (-2, 2), (2, -2), (2, 2), (0, -2), (0, 2), (-2, 0), (2, 0)] + for ox, oy in outline_offsets: + draw.text((x + ox, y + oy), text, font=font, fill="black") + draw.text((x, y), text, font=font, fill="white") + + def text_height(font, text): + bbox = draw.textbbox((0, 0), text, font=font) + return bbox[3] - bbox[1] + + def fits_in_width(text, font, max_width): + bbox = draw.textbbox((0,0), text, font=font) + text_width = bbox[2] - bbox[0] + return text_width <= max_width + + def split_text_to_fit(text, font, max_width): + words = text.split() + for i in range(len(words), 0, -1): + top_text = " ".join(words[:i]) + bottom_text = " ".join(words[i:]) + if fits_in_width(top_text, font, max_width) and fits_in_width(bottom_text, font, max_width): + return top_text, bottom_text + midpoint = len(words)//2 + return " ".join(words[:midpoint]), " ".join(words[midpoint:]) + + attempt = 0 + while attempt < max_attempts: + chosen_image_path = os.path.join(images_folder, random.choice(image_files)) + img = Image.open(chosen_image_path).convert("RGBA") + draw = ImageDraw.Draw(img) + width, height = img.size + + font_size = int(height / 10) + font = load_font(font_size) + + # Generate text + response = None + for _ in range(20): + if sentence_size == 1: + candidate = markov_model.make_short_sentence(max_chars=100, tries=100) + if candidate: + candidate = candidate.split()[0] + else: + candidate = markov_model.make_sentence(tries=100, max_words=sentence_size) + + if candidate and candidate not in generated_sentences: + if sentence_size > 1: + candidate = improve_sentence_coherence(candidate) + generated_sentences.add(candidate) + response = candidate + break + + if not response: + response = "no text generated" + + cleaned_response = re.sub(r'[^\w\s]', '', response).lower() + coherent_response = rephrase_for_coherence(cleaned_response).upper() + bbox = draw.textbbox((0, 0), coherent_response, font=font) + text_width = bbox[2] - bbox[0] + text_height_px = bbox[3] - bbox[1] + max_text_height = height // 4 + + if text_width <= width and text_height_px <= max_text_height: + # Fits on top, draw at y=0 (top) + draw_text_with_outline(draw, coherent_response, (width - text_width) / 2, 0, font) + img.save("output.png") + return + top_text, bottom_text = split_text_to_fit(coherent_response, font, width) + + top_bbox = draw.textbbox((0,0), top_text, font=font) + bottom_bbox = draw.textbbox((0,0), bottom_text, font=font) + + top_height = top_bbox[3] - top_bbox[1] + bottom_height = bottom_bbox[3] - bottom_bbox[1] + if top_height <= max_text_height and bottom_height <= max_text_height: + # top text + draw_text_with_outline(draw, top_text, (width - (top_bbox[2]-top_bbox[0])) / 2, 0, font) + # bottom text + y_bottom = height - bottom_height + draw_text_with_outline(draw, bottom_text, (width - (bottom_bbox[2]-bottom_bbox[0])) / 2, y_bottom, font) + img.save("output.png") + return + attempt += 1 + + # If all attempts fail, cry. + truncated = coherent_response[:100] + bbox = draw.textbbox((0, 0), truncated, font=font) + text_width = bbox[2] - bbox[0] + draw_text_with_outline(draw, truncated, (width - text_width) / 2, 0, font) + img.save("output.png") diff --git a/requirements.txt b/requirements.txt index 0da8506..e7d8898 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ requests psutil better_profanity python-dotenv +pillow \ No newline at end of file