diff --git a/.gitignore b/.gitignore index 078e8b0..f0e4fa4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ memory.json memory.json venv/ output.png -.vscode/ \ No newline at end of file +.vscode/ +received_memory.json diff --git a/bot.py b/bot.py index 2edbed5..f528cbd 100644 --- a/bot.py +++ b/bot.py @@ -5,6 +5,8 @@ import time import random import traceback import subprocess +import tempfile +import shutil from modules.globalvars import * from modules.prestartchecks import start_checks @@ -35,7 +37,6 @@ from modules.image import gen_image sys.excepthook = handle_exception check_for_update() # Check for updates (from modules/version.py) -launched = False # Ensure required NLTK resources are available def check_resources(): @@ -240,10 +241,60 @@ async def talk(ctx, sentence_size: int = 5): bot.help_command = None # Command: Show help information + +import uuid +import random + + @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")) + assets_folder = "assets/images" + temp_input = None + + def get_random_asset_image(): + files = [f for f in os.listdir(assets_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp'))] + if not files: + return None + return os.path.join(assets_folder, random.choice(files)) + + if ctx.message.attachments: + attachment = ctx.message.attachments[0] + if attachment.content_type and attachment.content_type.startswith("image/"): + ext = os.path.splitext(attachment.filename)[1] + temp_input = f"tempy{ext}" + await attachment.save(temp_input) + input_path = temp_input + else: + fallback_image = get_random_asset_image() + if fallback_image is None: + await ctx.reply("No image available to process.") + return + temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) + shutil.copy(fallback_image, temp_input) + input_path = temp_input + else: + fallback_image = get_random_asset_image() + if fallback_image is None: + await ctx.reply("No image available to process.") + return + # got lazy here + temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) + shutil.copy(fallback_image, temp_input) + input_path = temp_input + + output_path = await gen_image(input_path) + + if output_path is None or not os.path.isfile(output_path): + if temp_input and os.path.exists(temp_input): + os.remove(temp_input) + await ctx.reply("Failed to generate text on the image.") + return + + await ctx.send(file=discord.File(output_path)) + + if temp_input and os.path.exists(temp_input): + os.remove(temp_input) + # Remove default help command to use custom help @@ -259,7 +310,7 @@ async def help(ctx): ) command_categories = { - f"{get_translation(LOCALE, 'command_help_categories_general')}": ["mem", "talk", "about", "ping"], + f"{get_translation(LOCALE, 'command_help_categories_general')}": ["mem", "talk", "about", "ping", "image"], f"{get_translation(LOCALE, 'command_help_categories_admin')}": ["stats", "retrain"] } diff --git a/cogs/filesharing.py b/cogs/filesharing.py new file mode 100644 index 0000000..ad6ac98 --- /dev/null +++ b/cogs/filesharing.py @@ -0,0 +1,48 @@ +import discord +from discord.ext import commands +from modules.globalvars import ownerid +class FileSync(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.mode = None + self.peer_id = None + self.awaiting_file = False + + @commands.command() + async def syncfile(self, ctx, mode: str, peer: discord.User): + self.mode = mode.lower() + self.peer_id = peer.id + if ctx.author.id != ownerid: + await ctx.send("You don't have permission to execute this command.") + return + if self.mode == "s": + await ctx.send(f"<@{self.peer_id}> FILE_TRANSFER_REQUEST") + await ctx.send(file=discord.File("memory.json")) + await ctx.send("File sent in this channel.") + elif self.mode == "r": + await ctx.send("Waiting for incoming file...") + self.awaiting_file = True + else: + await ctx.send("Invalid mode, use 's' or 'r'.") + + + @commands.Cog.listener() + async def on_message(self, message): + if message.author == self.bot.user or not self.awaiting_file: + return + if message.author.id != self.peer_id: + return + + if message.content == "FILE_TRANSFER_REQUEST": + print("Ping received. Awaiting file...") + if message.attachments: + for attachment in message.attachments: + if attachment.filename.endswith(".json"): + filename = "received_memory.json" + await attachment.save(filename) + print(f"File saved as {filename}") + await message.channel.send("File received and saved.") + self.awaiting_file = False + +async def setup(bot): + await bot.add_cog(FileSync(bot)) diff --git a/modules/globalvars.py b/modules/globalvars.py index f51a41a..e401037 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -37,6 +37,7 @@ IGNOREWARNING = False song = os.getenv("song") arch = platform.machine() slash_commands_enabled = False +launched = False latest_version = "0.0.0" -local_version = "1.0.2" +local_version = "1.0.3" os.environ['gooberlocal_version'] = local_version diff --git a/modules/image.py b/modules/image.py index ed71ae0..87a5dbd 100644 --- a/modules/image.py +++ b/modules/image.py @@ -1,5 +1,4 @@ import os -import random import re from PIL import Image, ImageDraw, ImageFont from modules.markovmemory import load_markov_model @@ -7,106 +6,101 @@ from modules.sentenceprocessing import improve_sentence_coherence, rephrase_for_ 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 - +async def gen_image(input_image_path, sentence_size=5, max_attempts=10): markov_model = load_markov_model() if not markov_model: - return - + return None + if not os.path.isfile(input_image_path): + return None 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): + def fits_in_width(text, font, max_width, draw): 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): + def split_text_to_fit(text, font, max_width, draw): 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): + if fits_in_width(top_text, font, max_width, draw) and fits_in_width(bottom_text, font, max_width, draw): return top_text, bottom_text - midpoint = len(words)//2 + midpoint = len(words) // 2 return " ".join(words[:midpoint]), " ".join(words[midpoint:]) - coherent_response = "no text generated" 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") + with Image.open(input_image_path).convert("RGBA") as img: + draw = ImageDraw.Draw(img) + width, height = img.size + + font_size = int(height / 10) + font = load_font(font_size) + + 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: + draw_text_with_outline(draw, coherent_response, (width - text_width) / 2, 0, font) + img.save(input_image_path) + return input_image_path + else: + top_text, bottom_text = split_text_to_fit(coherent_response, font, width, draw) + + 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: + draw_text_with_outline(draw, top_text, (width - (top_bbox[2] - top_bbox[0])) / 2, 0, font) + y_bottom = height - bottom_height - int(height * 0.04) + draw_text_with_outline(draw, bottom_text, (width - (bottom_bbox[2] - bottom_bbox[0])) / 2, y_bottom, font) + img.save(input_image_path) + return input_image_path + + attempt += 1 + + + with Image.open(input_image_path).convert("RGBA") as img: 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) + truncated = coherent_response[:100] + bbox = draw.textbbox((0, 0), truncated, 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) - y_bottom = height - bottom_height - int(height * 0.04) - 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") + draw_text_with_outline(draw, truncated, (width - text_width) / 2, 0, font) + img.save(input_image_path) + return input_image_path diff --git a/modules/version.py b/modules/version.py index 2dd5cf7..d832f4a 100644 --- a/modules/version.py +++ b/modules/version.py @@ -17,6 +17,9 @@ def is_remote_ahead(branch='main', remote='origin'): # Automatically update the local repository if the remote is ahead def auto_update(branch='main', remote='origin'): + if launched == True: + print("I've already started! Im not updating...") + return if AUTOUPDATE != "True": pass # Auto-update is disabled if is_remote_ahead(branch, remote):