diff --git a/assets/locales/en.json b/assets/locales/en.json index 4465658..61f4778 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -1,7 +1,5 @@ { - "guess_the_number": "Guess the number", - "your_guess": "Your guess (1-10)", - "memosry_file_valid": "The memory.json file is valid!", + "memory_file_valid": "The memory.json file is valid!", "file_aint_uft8": "File is not valid UTF-8 text. Might be binary or corrupted.", "psutil_not_installed": "Memory check skipped.", "not_cloned": "Goober is not cloned! Please clone it from GitHub.", diff --git a/bot.py b/bot.py index 3af7e5e..38a01b4 100644 --- a/bot.py +++ b/bot.py @@ -49,7 +49,7 @@ from modules.version import * from modules.sentenceprocessing import * from modules.unhandledexception import handle_exception from modules.image import gen_meme, gen_demotivator -from modules.minigames import guessthenumber, hangman + sys.excepthook = handle_exception check_for_update() # Check for updates (from modules/version.py) @@ -64,6 +64,12 @@ currenthash: str = "" launched: bool = False slash_commands_enabled: bool = False +# Set up Discord bot intents and create bot instance +intents: discord.Intents = discord.Intents.default() +intents.messages = True +intents.message_content = True +bot: commands.Bot = commands.Bot(command_prefix=PREFIX, intents=intents, allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False, replied_user=True)) + # Load memory and Markov model for text generation memory: List[str] = load_memory() markov_model: Optional[markovify.Text] = load_markov_model() @@ -413,14 +419,10 @@ async def on_message(message: discord.Message) -> None: await bot.process_commands(message) +# Event: Called on every interaction (slash command, etc.) @bot.event async def on_interaction(interaction: discord.Interaction) -> None: - name = None - if interaction.data.get('name') is None: - name = "Unknown" - else: - name = interaction.data['name'] - logger.info(f"{(_('command_ran_s')).format(interaction=interaction)}{name}") + logger.info(f"{(_('command_ran_s')).format(interaction=interaction)}{interaction.data['name']}") # Global check: Block blacklisted users from running commands @bot.check diff --git a/modules/commands.py b/modules/commands.py deleted file mode 100644 index 573e27f..0000000 --- a/modules/commands.py +++ /dev/null @@ -1,207 +0,0 @@ - -from modules.globalvars import * -# Command: Retrain the Markov model from memory -@bot.hybrid_command(description=f"{(_('command_desc_retrain'))}") -async def retrain(ctx: commands.Context) -> None: - if ctx.author.id != ownerid: - return - - message_ref: MessageReference = await send_message(ctx, f"{(_('command_markov_retrain'))}") - try: - with open(MEMORY_FILE, 'r') as f: - memory: List[str] = json.load(f) - except FileNotFoundError: - await send_message(ctx, f"{(_('command_markov_memory_not_found'))}") - return - except json.JSONDecodeError: - await send_message(ctx, f"{(_('command_markov_memory_is_corrupt'))}") - return - - data_size: int = len(memory) - processed_data: int = 0 - processing_message_ref: MessageReference = await send_message(ctx, f"{(_('command_markov_retraining')).format(processed_data=processed_data, data_size=data_size)}") - start_time: float = time.time() - - for i, data in enumerate(memory): - processed_data += 1 - - global markov_model - markov_model = train_markov_model(memory) - save_markov_model(markov_model) - - await send_message(ctx, f"{_('command_markov_retrain_successful').format(data_size=data_size)}", edit=True, message_reference=processing_message_ref) - -# Command: Generate a sentence using the Markov model -@bot.hybrid_command(description=f"{(_('command_desc_talk'))}") -async def talk(ctx: commands.Context, sentence_size: int = 5) -> None: - if not markov_model: - await send_message(ctx, f"{(_('command_talk_insufficent_text'))}") - return - - response: Optional[str] = None - for _ in range(20): - if sentence_size == 1: - response = markov_model.make_short_sentence(max_chars=100, tries=100) - if response: - response = response.split()[0] - else: - response = markov_model.make_sentence(tries=100, max_words=sentence_size) - - if response and response not in generated_sentences: - if sentence_size > 1: - response = improve_sentence_coherence(response) - generated_sentences.add(response) - break - - if response: - cleaned_response: str = re.sub(r'[^\w\s]', '', response).lower() - coherent_response: str = rephrase_for_coherence(cleaned_response) - if random.random() < 0.9 and is_positive(coherent_response): - gif_url: str = random.choice(positive_gifs) - combined_message: str = f"{coherent_response}\n[jif]({gif_url})" - else: - combined_message: str = coherent_response - logger.info(combined_message) - os.environ['gooberlatestgen'] = combined_message - await send_message(ctx, combined_message) - else: - await send_message(ctx, f"{(_('command_talk_generation_fail'))}") - -# Command: Generate an image -@bot.hybrid_command(description=f"{(_('command_desc_help'))}") -async def impact(ctx: commands.Context, text: Optional[str] = None) -> None: - assets_folder: str = "assets/images" - temp_input: Optional[str] = None - - def get_random_asset_image() -> Optional[str]: - files: List[str] = [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: discord.Attachment = ctx.message.attachments[0] - if attachment.content_type and attachment.content_type.startswith("image/"): - ext: str = os.path.splitext(attachment.filename)[1] - temp_input = f"tempy{ext}" - await attachment.save(temp_input) - input_path: str = temp_input - else: - fallback_image: Optional[str] = get_random_asset_image() - if fallback_image is None: - await ctx.reply(_('no_image_available')) - 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')) - return - temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) - shutil.copy(fallback_image, temp_input) - input_path = temp_input - - output_path: Optional[str] = await gen_meme(input_path, custom_text=text) - - - 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_generate_image')) - return - - await ctx.send(file=discord.File(output_path)) - - if temp_input and os.path.exists(temp_input): - os.remove(temp_input) - -# New demotivator command -@bot.hybrid_command(description="Generate a demotivator poster with two lines of text") -async def demotivator(ctx: commands.Context) -> None: - assets_folder: str = "assets/images" - temp_input: Optional[str] = None - - def get_random_asset_image() -> Optional[str]: - files: List[str] = [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: discord.Attachment = ctx.message.attachments[0] - if attachment.content_type and attachment.content_type.startswith("image/"): - ext: str = os.path.splitext(attachment.filename)[1] - temp_input = f"tempy{ext}" - await attachment.save(temp_input) - input_path: str = temp_input - else: - fallback_image: Optional[str] = get_random_asset_image() - if fallback_image is None: - await ctx.reply(_('no_image_available')) - 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')) - return - temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) - shutil.copy(fallback_image, temp_input) - input_path = temp_input - - output_path: Optional[str] = await gen_demotivator(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 demotivator.") - return - - await ctx.send(file=discord.File(output_path)) - - if temp_input and os.path.exists(temp_input): - os.remove(temp_input) - -bot.remove_command('help') -# Command: Show help information -@bot.hybrid_command(description=f"{(_('command_desc_help'))}") -async def help(ctx: commands.Context) -> None: - embed: discord.Embed = discord.Embed( - title=f"{(_('command_help_embed_title'))}", - description=f"{(_('command_help_embed_desc'))}", - color=Colour(0x000000) - ) - - command_categories: Dict[str, List[str]] = { - f"{(_('command_help_categories_general'))}": ["mem", "talk", "about", "ping", "impact", "demotivator", "help"], - f"{(_('command_help_categories_admin'))}": ["stats", "retrain", "setlanguage"] - } - - custom_commands: List[str] = [] - for cog_name, cog in bot.cogs.items(): - for command in cog.get_commands(): - if command.name not in command_categories[f"{(_('command_help_categories_general'))}"] and command.name not in command_categories[f"{(_('command_help_categories_admin'))}"]: - custom_commands.append(command.name) - - if custom_commands: - embed.add_field(name=f"{(_('command_help_categories_custom'))}", value="\n".join([f"{PREFIX}{command}" for command in custom_commands]), inline=False) - - for category, commands_list in command_categories.items(): - commands_in_category: str = "\n".join([f"{PREFIX}{command}" for command in commands_list]) - embed.add_field(name=category, value=commands_in_category, inline=False) - - await send_message(ctx, embed=embed) - -@bot.hybrid_command(description=f"{(_('command_desc_setlang'))}") -@app_commands.describe(locale="Choose your language") -async def setlanguage(ctx: commands.Context, locale: str) -> None: - if ctx.author.id != ownerid: - await ctx.send(":thumbsdown:") - return - await ctx.defer() - set_language(locale) - await ctx.send(":thumbsup:") \ No newline at end of file diff --git a/modules/globalvars.py b/modules/globalvars.py index 5c20f1d..37b22e9 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -2,12 +2,6 @@ import os import platform from dotenv import load_dotenv import pathlib -import discord -from discord.ext import commands -from discord import app_commands -from discord import Colour, Embed, File, Interaction, Message -from discord.abc import Messageable -from discord.ext import commands import subprocess def get_git_branch(): try: @@ -21,6 +15,7 @@ def get_git_branch(): env_path = pathlib.Path(__file__).parent.parent / '.env' load_dotenv(dotenv_path=env_path) + ANSI = "\033[" RED = f"{ANSI}31m" GREEN = f"{ANSI}32m" @@ -60,11 +55,4 @@ if get_git_branch() == "dev": beta = True # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks else: - beta = False - - -# Set up Discord bot intents and create bot instance -intents: discord.Intents = discord.Intents.default() -intents.messages = True -intents.message_content = True -bot: commands.Bot = commands.Bot(command_prefix=PREFIX, intents=intents, allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False, replied_user=True)) \ No newline at end of file + beta = False \ No newline at end of file diff --git a/modules/minigames.py b/modules/minigames.py deleted file mode 100644 index 9cf8b17..0000000 --- a/modules/minigames.py +++ /dev/null @@ -1,71 +0,0 @@ -import random -import discord -from discord import ui, Interaction, TextStyle -from discord.ext import commands -import aiohttp -import asyncio -from modules.globalvars import bot -from modules.volta.main import _ - -@bot.hybrid_command(description=_('guess_the_number')) -async def guessthenumber(ctx: commands.Context): - number = random.randint(1, 10) - class GuessModal(ui.Modal, title=_('guess_the_number')): - guess = ui.TextInput(label=_('your_guess'), style=TextStyle.short) - async def on_submit(self, interaction: Interaction): - try: - user_guess = int(self.guess.value) - except: - await interaction.response.send_message("Invalid number!", ephemeral=True) - return - if user_guess == number: - await interaction.response.send_message("Correct!", ephemeral=True) - else: - await interaction.response.send_message(f"Wrong! The number was {number}.", ephemeral=True) - async def button_callback(interaction: Interaction): - await interaction.response.send_modal(GuessModal()) - button = ui.Button(label="Guess", style=discord.ButtonStyle.primary) - button.callback = button_callback - view = ui.View() - view.add_item(button) - await ctx.send("Click to guess a number from 1 to 10", view=view) - -@bot.hybrid_command(description="Play Hangman with a random word") -async def hangman(ctx: commands.Context): - async with aiohttp.ClientSession() as session: - async with session.get("https://random-word-api.herokuapp.com/word?number=1") as resp: - if resp.status != 200: - await ctx.send("Failed to get a random word.") - return - data = await resp.json() - word = data[0].lower() - print(word) - guessed_letters = set() - wrong_guesses = 0 - max_wrong = 6 - def display_word(): - return " ".join([c if c in guessed_letters else "_" for c in word]) - class GuessModal(ui.Modal, title="Guess a Letter"): - letter = ui.TextInput(label="Your letter guess", style=TextStyle.short, max_length=1) - async def on_submit(self, interaction: Interaction): - nonlocal guessed_letters, wrong_guesses - guess = self.letter.value.lower() - if guess in guessed_letters: - await interaction.response.send_message(f"You already guessed '{guess}'!", ephemeral=True) - return - guessed_letters.add(guess) - if guess not in word: - wrong_guesses += 1 - if all(c in guessed_letters for c in word): - await interaction.response.edit_message(content=f"You won! The word was: **{word}**", view=None) - elif wrong_guesses >= max_wrong: - await interaction.response.edit_message(content=f"You lost! The word was: **{word}**", view=None) - else: - await interaction.response.edit_message(content=f"Word: {display_word()}\nWrong guesses: {wrong_guesses}/{max_wrong}", view=view) - async def button_callback(interaction: Interaction): - await interaction.response.send_modal(GuessModal()) - button = ui.Button(label="Guess a letter", style=discord.ButtonStyle.primary) - button.callback = button_callback - view = ui.View() - view.add_item(button) - await ctx.send(f"Word: {display_word()}\nWrong guesses: {wrong_guesses}/{max_wrong}\nClick the button to guess a letter.", view=view) \ No newline at end of file