Compare commits
2 commits
9a4d486e25
...
52d9b058cf
Author | SHA1 | Date | |
---|---|---|---|
![]() |
52d9b058cf | ||
![]() |
8666c83565 |
5 changed files with 302 additions and 12 deletions
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
"memory_file_valid": "The memory.json file is valid!",
|
"guess_the_number": "Guess the number",
|
||||||
|
"your_guess": "Your guess (1-10)",
|
||||||
|
"memosry_file_valid": "The memory.json file is valid!",
|
||||||
"file_aint_uft8": "File is not valid UTF-8 text. Might be binary or corrupted.",
|
"file_aint_uft8": "File is not valid UTF-8 text. Might be binary or corrupted.",
|
||||||
"psutil_not_installed": "Memory check skipped.",
|
"psutil_not_installed": "Memory check skipped.",
|
||||||
"not_cloned": "Goober is not cloned! Please clone it from GitHub.",
|
"not_cloned": "Goober is not cloned! Please clone it from GitHub.",
|
||||||
|
|
16
bot.py
16
bot.py
|
@ -49,7 +49,7 @@ from modules.version import *
|
||||||
from modules.sentenceprocessing import *
|
from modules.sentenceprocessing import *
|
||||||
from modules.unhandledexception import handle_exception
|
from modules.unhandledexception import handle_exception
|
||||||
from modules.image import gen_meme, gen_demotivator
|
from modules.image import gen_meme, gen_demotivator
|
||||||
|
from modules.minigames import guessthenumber, hangman
|
||||||
sys.excepthook = handle_exception
|
sys.excepthook = handle_exception
|
||||||
check_for_update() # Check for updates (from modules/version.py)
|
check_for_update() # Check for updates (from modules/version.py)
|
||||||
|
|
||||||
|
@ -64,12 +64,6 @@ currenthash: str = ""
|
||||||
launched: bool = False
|
launched: bool = False
|
||||||
slash_commands_enabled: 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
|
# Load memory and Markov model for text generation
|
||||||
memory: List[str] = load_memory()
|
memory: List[str] = load_memory()
|
||||||
markov_model: Optional[markovify.Text] = load_markov_model()
|
markov_model: Optional[markovify.Text] = load_markov_model()
|
||||||
|
@ -419,10 +413,14 @@ async def on_message(message: discord.Message) -> None:
|
||||||
|
|
||||||
await bot.process_commands(message)
|
await bot.process_commands(message)
|
||||||
|
|
||||||
# Event: Called on every interaction (slash command, etc.)
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_interaction(interaction: discord.Interaction) -> None:
|
async def on_interaction(interaction: discord.Interaction) -> None:
|
||||||
logger.info(f"{(_('command_ran_s')).format(interaction=interaction)}{interaction.data['name']}")
|
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}")
|
||||||
|
|
||||||
# Global check: Block blacklisted users from running commands
|
# Global check: Block blacklisted users from running commands
|
||||||
@bot.check
|
@bot.check
|
||||||
|
|
207
modules/commands.py
Normal file
207
modules/commands.py
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
|
||||||
|
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:")
|
|
@ -2,6 +2,12 @@ import os
|
||||||
import platform
|
import platform
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import pathlib
|
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
|
import subprocess
|
||||||
def get_git_branch():
|
def get_git_branch():
|
||||||
try:
|
try:
|
||||||
|
@ -15,7 +21,6 @@ def get_git_branch():
|
||||||
|
|
||||||
env_path = pathlib.Path(__file__).parent.parent / '.env'
|
env_path = pathlib.Path(__file__).parent.parent / '.env'
|
||||||
load_dotenv(dotenv_path=env_path)
|
load_dotenv(dotenv_path=env_path)
|
||||||
|
|
||||||
ANSI = "\033["
|
ANSI = "\033["
|
||||||
RED = f"{ANSI}31m"
|
RED = f"{ANSI}31m"
|
||||||
GREEN = f"{ANSI}32m"
|
GREEN = f"{ANSI}32m"
|
||||||
|
@ -55,4 +60,11 @@ if get_git_branch() == "dev":
|
||||||
beta = True
|
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
|
# this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks
|
||||||
else:
|
else:
|
||||||
beta = False
|
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))
|
71
modules/minigames.py
Normal file
71
modules/minigames.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
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)
|
Loading…
Add table
Add a link
Reference in a new issue