diff --git a/assets/cogs/fuckup.py b/assets/cogs/fuckup.py
deleted file mode 100644
index 1dbc4d0..0000000
--- a/assets/cogs/fuckup.py
+++ /dev/null
@@ -1,98 +0,0 @@
-import discord
-from discord.ext import commands
-from modules.image import *
-from modules.volta.main import _
-from PIL import Image, ImageEnhance, ImageFilter, ImageOps, ImageChops, ImageColor
-import os, random, shutil, tempfile
-
-async def deepfryimage(path):
- with Image.open(path).convert("RGB") as im:
- # make it burn
- for _ in range(3):
- im = im.resize((int(im.width * 0.7), int(im.height * 0.7)))
- im = im.resize((int(im.width * 1.5), int(im.height * 1.5)))
- im = ImageEnhance.Contrast(im).enhance(random.uniform(5, 10))
- im = ImageEnhance.Sharpness(im).enhance(random.uniform(10, 50))
- im = ImageEnhance.Brightness(im).enhance(random.uniform(1.5, 3))
- r, g, b = im.split()
- r = r.point(lambda i: min(255, i * random.uniform(1.2, 2.0)))
- g = g.point(lambda i: min(255, i * random.uniform(0.5, 1.5)))
- b = b.point(lambda i: min(255, i * random.uniform(0.5, 2.0)))
- channels = [r, g, b]
- random.shuffle(channels)
- im = Image.merge("RGB", tuple(channels))
- overlay_color = tuple(random.randint(0, 255) for _ in range(3))
- overlay = Image.new("RGB", im.size, overlay_color)
- im = ImageChops.add(im, overlay, scale=2.0, offset=random.randint(-64, 64))
-
- im = im.filter(ImageFilter.EDGE_ENHANCE_MORE)
- im = im.filter(ImageFilter.GaussianBlur(radius=random.uniform(0.5, 2)))
- for _ in range(3):
- tmp_path = tempfile.mktemp(suffix=".jpg")
- im.save(tmp_path, format="JPEG", quality=random.randint(5, 15))
- im = Image.open(tmp_path)
- if random.random() < 0.3:
- im = ImageOps.posterize(im, bits=random.choice([2, 3, 4]))
- if random.random() < 0.2:
- im = ImageOps.invert(im)
- out_path = tempfile.mktemp(suffix=".jpg")
- im.save(out_path, format="JPEG", quality=5)
- return out_path
-
-
-class whami(commands.Cog):
- def __init__(self, bot):
- self.bot = bot
-
-
- @commands.command()
- async def fuckup(self, ctx):
- 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'))
- 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 = await gen_meme(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_generate_image'))
- return
-
- deepfried_path = await deepfryimage(output_path)
- await ctx.send(file=discord.File(deepfried_path))
-
- if temp_input and os.path.exists(temp_input):
- os.remove(temp_input)
-
-async def setup(bot):
- await bot.add_cog(whami(bot))
diff --git a/assets/cogs/lastfm.py.disabled b/assets/cogs/lastfm.py.disabled
deleted file mode 100644
index 822305a..0000000
--- a/assets/cogs/lastfm.py.disabled
+++ /dev/null
@@ -1,84 +0,0 @@
-import os
-import discord
-from discord.ext import commands, tasks
-import aiohttp
-from dotenv import load_dotenv
-
-load_dotenv()
-
-#stole most of this code from my old expect bot so dont be suprised if its poorly made
-
-LASTFM_API_KEY = os.getenv("LASTFM_API_KEY")
-LASTFM_USERNAME = os.getenv("LASTFM_USERNAME")
-
-class LastFmCog(commands.Cog):
- def __init__(self, bot):
- self.bot = bot
- self.current_track = None
- self.update_presence_task = None
- self.ready = False
- bot.loop.create_task(self.wait_until_ready())
-
- async def wait_until_ready(self):
- await self.bot.wait_until_ready()
- self.ready = True
- self.update_presence.start()
-
- @tasks.loop(seconds=60)
- async def update_presence(self):
- print("Looped!")
- if not self.ready:
- return
- track = await self.fetch_current_track()
- if track and track != self.current_track:
- self.current_track = track
- artist, song = track
- activity_name = f"{artist} - {song}"
- await self.bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=activity_name))
- print(f"Updated song to {artist} - {song}")
- else:
- print("LastFM gave me the same track! not updating...")
-
- @update_presence.before_loop
- async def before_update_presence(self):
- await self.bot.wait_until_ready()
-
- @commands.command(name="lastfm")
- async def lastfm_command(self, ctx):
- track = await self.fetch_current_track()
- if not track:
- await ctx.send("No track currently playing or could not fetch data")
- return
- self.current_track = track
- artist, song = track
- activity_name = f"{artist} - {song}"
- await self.bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=activity_name))
- await ctx.send(f"Updated presence to: Listening to {activity_name}")
-
- async def fetch_current_track(self):
- url = (
- f"http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks"
- f"&user={LASTFM_USERNAME}&api_key={LASTFM_API_KEY}&format=json&limit=1"
- )
- async with aiohttp.ClientSession() as session:
- async with session.get(url) as resp:
- if resp.status != 200:
- return None
- data = await resp.json()
-
- recenttracks = data.get("recenttracks", {}).get("track", [])
- if not recenttracks:
- return None
-
- track = recenttracks[0]
- if '@attr' in track and track['@attr'].get('nowplaying') == 'true':
- artist = track.get('artist', {}).get('#text', 'Unknown Artist')
- song = track.get('name', 'Unknown Song')
- return artist, song
- return None
-
-async def setup(bot):
- if not LASTFM_API_KEY or not LASTFM_USERNAME:
- return
- else:
- await bot.add_cog(LastFmCog(bot))
diff --git a/assets/cogs/songchanger.py b/assets/cogs/songchanger.py
deleted file mode 100644
index 36fde47..0000000
--- a/assets/cogs/songchanger.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import discord
-from discord.ext import commands
-from modules.globalvars import RED, GREEN, RESET, LOCAL_VERSION_FILE
-import os
-
-class songchange(commands.Cog):
- def __init__(self, bot):
- self.bot = bot
-
- def get_local_version():
- if os.path.exists(LOCAL_VERSION_FILE):
- with open(LOCAL_VERSION_FILE, "r") as f:
- return f.read().strip()
- return "0.0.0"
-
- global local_version
- local_version = get_local_version()
-
- @commands.command()
- async def changesong(self, ctx):
- if LOCAL_VERSION_FILE > "0.11.8":
- await ctx.send(f"Goober is too old! you must have version 0.11.8 you have {local_version}")
- return
- await ctx.send("Check the terminal! (this does not persist across restarts)")
- song = input("\nEnter a song:\n")
- try:
- await self.bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=f"{song}"))
- print(f"{GREEN}Changed song to {song}{RESET}")
- except Exception as e:
- print(f"{RED}An error occurred while changing songs..: {str(e)}{RESET}")
-
-async def setup(bot):
- await bot.add_cog(songchange(bot))
diff --git a/assets/cogs/webscraper.py.disabled b/assets/cogs/webscraper.py.disabled
deleted file mode 100644
index 351e2e8..0000000
--- a/assets/cogs/webscraper.py.disabled
+++ /dev/null
@@ -1,113 +0,0 @@
-import discord
-from discord.ext import commands
-import aiohttp
-from bs4 import BeautifulSoup
-import json
-import asyncio
-from urllib.parse import urljoin
-from modules.globalvars import ownerid
-class WebScraper(commands.Cog):
- def __init__(self, bot):
- self.bot = bot
- self.visited_urls = set()
-
- async def fetch(self, session, url):
- """Fetch the HTML content of a URL."""
- try:
- async with session.get(url, timeout=10) as response:
- return await response.text()
- except Exception as e:
- print(f"Failed to fetch {url}: {e}")
- return None
-
- def extract_sentences(self, text):
- """Extract sentences from text."""
- sentences = text.split('.')
- return [sentence.strip() for sentence in sentences if sentence.strip()]
-
- def save_to_json(self, sentences):
- """Save sentences to memory.json."""
- try:
- try:
- with open("memory.json", "r") as file:
- data = json.load(file)
- except (FileNotFoundError, json.JSONDecodeError):
- data = []
- data.extend(sentences)
- with open("memory.json", "w") as file:
- json.dump(data, file, indent=4)
- except Exception as e:
- print(f"Failed to save to JSON: {e}")
-
- def undo_last_scrape(self):
- """Undo the last scrape by removing the most recent sentences."""
- try:
- with open("memory.json", "r") as file:
- data = json.load(file)
-
- if not data:
- print("No data to undo.")
- return False
-
-
- data = data[:-1]
-
- with open("memory.json", "w") as file:
- json.dump(data, file, indent=4)
-
- return True
- except (FileNotFoundError, json.JSONDecodeError):
- print("No data to undo or failed to load JSON.")
- return False
- except Exception as e:
- print(f"Failed to undo last scrape: {e}")
- return False
-
- async def scrape_links(self, session, url, depth=2):
- print(f"Scraping: {url}")
- self.visited_urls.add(url)
-
- html = await self.fetch(session, url)
- if not html:
- return
-
- soup = BeautifulSoup(html, "html.parser")
-
- for paragraph in soup.find_all('p'):
- sentences = self.extract_sentences(paragraph.get_text())
- self.save_to_json(sentences)
-
-
- @commands.command()
- async def start_scrape(self, ctx, start_url: str):
- """Command to start the scraping process."""
- if ctx.author.id != ownerid:
- await ctx.send("You do not have permission to use this command.")
- return
-
- if not start_url.startswith("http"):
- await ctx.send("Please provide a valid URL.")
- return
-
- await ctx.send(f"Starting scrape from {start_url}... This may take a while!")
-
- async with aiohttp.ClientSession() as session:
- await self.scrape_links(session, start_url)
-
- await ctx.send("Scraping complete! Sentences saved to memory.json.")
-
- @commands.command()
- async def undo_scrape(self, ctx):
- """Command to undo the last scrape."""
- if ctx.author.id != ownerid:
- await ctx.send("You do not have permission to use this command.")
- return
-
- success = self.undo_last_scrape()
- if success:
- await ctx.send("Last scrape undone successfully.")
- else:
- await ctx.send("No data to undo or an error occurred.")
-
-async def setup(bot):
- await bot.add_cog(WebScraper(bot))
diff --git a/assets/cogs/webserver.py b/assets/cogs/webserver.py
deleted file mode 100644
index 110c20d..0000000
--- a/assets/cogs/webserver.py
+++ /dev/null
@@ -1,880 +0,0 @@
-import discord
-from discord.ext import commands, tasks
-import asyncio
-from aiohttp import web
-import psutil
-import os
-import json
-from datetime import datetime
-import time
-import aiohttp
-import re
-from aiohttp import WSMsgType
-from modules.globalvars import VERSION_URL
-import sys
-import subprocess
-
-class GooberWeb(commands.Cog):
- def __init__(self, bot):
- self.bot = bot
- self.app = web.Application()
- self.runner = None
- self.site = None
- self.last_command = "No commands executed yet"
- self.last_command_time = "Never"
- self.start_time = time.time()
- self.websockets = set()
-
- self.app.add_routes([
- web.get('/', self.handle_index),
- web.get('/changesong', self.handle_changesong),
- web.get('/stats', self.handle_stats),
- web.get('/data', self.handle_json_data),
- web.get('/ws', self.handle_websocket),
- web.get('/styles.css', self.handle_css),
- web.get('/settings', self.handle_settings),
- web.post('/update_settings', self.handle_update_settings),
- web.post('/restart_bot', self.handle_restart_bot),
- ])
-
- self.bot.loop.create_task(self.start_web_server())
- self.update_clients.start()
-
- async def restart_bot(self):
- await asyncio.sleep(1)
- python = sys.executable
- os.execl(python, python, *sys.argv)
-
- async def handle_restart_bot(self, request):
- asyncio.create_task(self.restart_bot())
- return web.Response(text="Bot is restarting...")
-
- async def get_blacklisted_users(self):
- blacklisted_ids = os.getenv("BLACKLISTED_USERS", "").split(",")
- blacklisted_users = []
-
- for user_id in blacklisted_ids:
- if not user_id.strip():
- continue
-
- try:
- user = await self.bot.fetch_user(int(user_id))
- blacklisted_users.append({
- "name": f"{user.name}",
- "avatar_url": str(user.avatar.url) if user.avatar else str(user.default_avatar.url),
- "id": user.id
- })
- except discord.NotFound:
- blacklisted_users.append({
- "name": f"Unknown User ({user_id})",
- "avatar_url": "",
- "id": user_id
- })
- except discord.HTTPException as e:
- print(f"Error fetching user {user_id}: {e}")
- continue
-
- return blacklisted_users
-
- async def get_enhanced_guild_info(self):
- guilds = sorted(self.bot.guilds, key=lambda g: g.member_count, reverse=True)
- guild_info = []
-
- for guild in guilds:
- icon_url = str(guild.icon.url) if guild.icon else ""
- guild_info.append({
- "name": guild.name,
- "member_count": guild.member_count,
- "icon_url": icon_url,
- "id": guild.id
- })
-
- return guild_info
-
- async def start_web_server(self):
- self.runner = web.AppRunner(self.app)
- await self.runner.setup()
- self.site = web.TCPSite(self.runner, '0.0.0.0', 8080)
- await self.site.start()
- print("Goober web server started on port 8080")
-
- async def stop_web_server(self):
- await self.site.stop()
- await self.runner.cleanup()
- print("Web server stopped")
-
- def cog_unload(self):
- self.update_clients.cancel()
- self.bot.loop.create_task(self.stop_web_server())
-
- @tasks.loop(seconds=5)
- async def update_clients(self):
- if not self.websockets:
- return
-
- stats = await self.get_bot_stats()
- message = json.dumps(stats)
-
- for ws in set(self.websockets):
- try:
- await ws.send_str(message)
- except ConnectionResetError:
- self.websockets.remove(ws)
- except Exception as e:
- print(f"Error sending to websocket: {e}")
- self.websockets.remove(ws)
-
- async def handle_websocket(self, request):
- ws = web.WebSocketResponse()
- await ws.prepare(request)
- self.websockets.add(ws)
-
- try:
- async for msg in ws:
- if msg.type == WSMsgType.ERROR:
- print(f"WebSocket error: {ws.exception()}")
- finally:
- self.websockets.remove(ws)
-
- return ws
-
- async def handle_css(self, request):
- css_path = os.path.join(os.path.dirname(__file__), 'styles.css')
- if os.path.exists(css_path):
- return web.FileResponse(css_path)
- return web.Response(text="CSS file not found", status=404)
-
- @commands.Cog.listener()
- async def on_message(self, message):
- if message.author.bot:
- return
-
- ctx = await self.bot.get_context(message)
- if ctx.valid and ctx.command:
- self._update_command_stats(ctx.command.name, ctx.author)
-
- @commands.Cog.listener()
- async def on_app_command_completion(self, interaction, command):
- self._update_command_stats(command.name, interaction.user)
-
- def _update_command_stats(self, command_name, user):
- self.last_command = f"{command_name} (by {user.name})"
- self.last_command_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- if self.websockets:
- asyncio.create_task(self.update_clients())
-
- async def get_bot_stats(self):
- process = psutil.Process(os.getpid())
- mem_info = process.memory_full_info()
- cpu_percent = psutil.cpu_percent()
- process_cpu = process.cpu_percent()
-
- memory_json_size = "N/A"
- if os.path.exists("memory.json"):
- memory_json_size = f"{os.path.getsize('memory.json') / 1024:.2f} KB"
-
- guild_info = await self.get_enhanced_guild_info()
- blacklisted_users = await self.get_blacklisted_users()
-
- uptime_seconds = int(time.time() - self.start_time)
- uptime_str = f"{uptime_seconds // 86400}d {(uptime_seconds % 86400) // 3600}h {(uptime_seconds % 3600) // 60}m {uptime_seconds % 60}s"
-
- return {
- "ram_usage": f"{mem_info.rss / 1024 / 1024:.2f} MB",
- "cpu_usage": f"{process_cpu}%",
- "system_cpu": f"{cpu_percent}%",
- "memory_json_size": memory_json_size,
- "guild_count": len(guild_info),
- "bl_count": len(blacklisted_users),
- "guilds": guild_info,
- "blacklisted_users": blacklisted_users,
- "last_command": self.last_command,
- "last_command_time": self.last_command_time,
- "bot_uptime": uptime_str,
- "latency": f"{self.bot.latency * 1000:.2f} ms",
- "bot_name": self.bot.user.name,
- "bot_avatar_url": str(self.bot.user.avatar.url) if self.bot.user.avatar else "",
- "authenticated": os.getenv("gooberauthenticated"),
- "lastmsg": os.getenv("gooberlatestgen"),
- "localversion": os.getenv("gooberlocal_version"),
- "latestversion": os.getenv("gooberlatest_version"),
- "owner": os.getenv("ownerid")
- }
-
- async def handle_update(self, request):
- if os.path.exists("goob/update.py"):
- return web.FileResponse("goob/update.py")
- return web.Response(text="Update file not found", status=404)
-
- async def handle_changesong(self, request):
- song = request.query.get('song', '')
- if song:
- await self.bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=song))
- return web.Response(text=f"Changed song to: {song}")
- return web.Response(text="Please provide a song parameter", status=400)
-
- async def handle_changes(self, request):
- if os.path.exists("goob/changes.txt"):
- return web.FileResponse("goob/changes.txt")
- return web.Response(text="Changelog not found", status=404)
-
- async def read_env_file(self):
- env_vars = {}
- try:
- with open('.env', 'r') as f:
- for line in f:
- line = line.strip()
- if not line or line.startswith('#') or '=' not in line:
- continue
-
- key, value = line.split('=', 1)
- key = key.strip()
- if key in ['splashtext', 'DISCORD_BOT_TOKEN']:
- continue
-
- env_vars[key] = value.strip('"\'')
- except FileNotFoundError:
- print(".env file not found")
- return env_vars
-
-
- async def handle_settings(self, request):
- env_vars = await self.read_env_file()
-
- # Get config.py variables
- config_vars = {}
- try:
- with open('config.py', 'r') as f:
- for line in f:
- if line.startswith('VERSION_URL'):
- config_vars['VERSION_URL'] = line.split('=', 1)[1].strip().strip('"')
- except FileNotFoundError:
- pass
-
- settings_html = """
-
-
-
- Goober Settings
-
-
-
-
-
-
- """
-
- return web.Response(text=settings_html, content_type='text/html')
-
- async def handle_update_settings(self, request):
- data = await request.post()
- env_text = ""
-
- try:
- with open('.env', 'r') as f:
- env_text = f.read()
- except FileNotFoundError:
- pass
-
- def replace_match(match):
- key = match.group(1)
- value = match.group(2)
- if key in ['splashtext', 'DISCORD_BOT_TOKEN']:
- return match.group(0)
- if key in data:
- new_value = data[key]
- if not (new_value.startswith('"') and new_value.endswith('"')):
- new_value = f'"{new_value}"'
- return f'{key}={new_value}'
- return match.group(0)
-
- env_text = re.sub(r'^(\w+)=([\s\S]+?)(?=\n\w+=|\Z)', replace_match, env_text, flags=re.MULTILINE)
-
- with open('.env', 'w') as f:
- f.write(env_text.strip() + '\n')
-
- if 'VERSION_URL' in data:
- config_text = ""
- try:
- with open('config.py', 'r') as f:
- config_text = f.read()
- except FileNotFoundError:
- pass
-
- config_text = re.sub(r'^(VERSION_URL\s*=\s*").+?"', f'\\1{data["VERSION_URL"]}"', config_text, flags=re.MULTILINE)
-
- with open('config.py', 'w') as f:
- f.write(config_text.strip() + '\n')
-
- return aiohttp.web.Response(text="Settings updated successfully!")
-
- async def handle_index(self, request):
- stats = await self.get_bot_stats()
-
- guild_list_html = ""
- for guild in stats['guilds']:
- icon_html = f'
' if guild["icon_url"] else ''
- guild_list_html += f"""
-
- {icon_html}
-
-
{guild["name"]}
-
{guild["member_count"]} members
-
-
- """
- blacklisted_users_html = ""
- for user in stats['blacklisted_users']:
- avatar_html = f'
' if user["avatar_url"] else ''
- blacklisted_users_html += f"""
-
- {avatar_html}
-
-
{user["name"]}
-
ID: {user["id"]}
-
-
- """
-
- owner_id = stats.get('owner')
- owner = None
- owner_username = "Owner"
- owner_pfp = ""
-
- if owner_id:
- try:
- owner = await self.bot.fetch_user(int(owner_id))
- owner_username = f"{owner.name}"
- owner_pfp = str(owner.avatar.url) if owner and owner.avatar else ""
- except:
- pass
-
-
- html_content = f"""
-
-
-
- goobs central
-
-
-
-
-

-
Welcome, {owner_username}
-
-
-
- RAM:
- {stats['ram_usage']}
-
-
- CPU:
- {stats['system_cpu']}
-
-
- Latency:
- {stats['latency']}
-
-
- JSON Size:
- {stats['memory_json_size']}
-
-
- Uptime:
- {stats['bot_uptime']}
-
-
-
-
-
-

-
{stats['bot_name']}
-
-
-
your stupid little goober that learns off other people's messages
-
-
-
-
-
Last Command
-
{stats['last_command']}
-
at {stats['last_command_time']}
-
-
Logged into goober central
-
{stats['authenticated']}
-
-
Last generated message
-
{stats['lastmsg']}
-
-
Version
-
Installed Version: {stats['localversion']}
-
Latest Version: {stats['latestversion']}
-
-
goober-central URL
-
{VERSION_URL}
-
-
Change song
-
-
-
-
-
Servers ({stats['guild_count']})
-
- {guild_list_html}
-
-
-
Blacklisted Users ({stats['bl_count']})
-
- {blacklisted_users_html if stats['blacklisted_users'] else "
No blacklisted users
"}
-
-
-
-
-
-
- """
-
- return web.Response(text=html_content, content_type='text/html')
-
- async def handle_stats(self, request):
- return await self.handle_index(request)
-
- async def handle_json_data(self, request):
- stats = await self.get_bot_stats()
- return web.json_response(stats)
-
-async def setup(bot):
- await bot.add_cog(GooberWeb(bot))
diff --git a/assets/fonts/TNR.ttf b/assets/fonts/TNR.ttf
deleted file mode 100644
index 51261a0..0000000
Binary files a/assets/fonts/TNR.ttf and /dev/null differ
diff --git a/bot.py b/main.py
similarity index 78%
rename from bot.py
rename to main.py
index bbee94d..6ad7ca1 100644
--- a/bot.py
+++ b/main.py
@@ -49,11 +49,9 @@ from modules.markovmemory import *
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
+from modules.image import gen_meme
sys.excepthook = handle_exception
-check_for_update() # Check for updates (from modules/version.py)
-# Type aliases
+check_for_update()
T = TypeVar('T')
MessageContext = Union[commands.Context, discord.Interaction]
MessageReference = Union[Message, discord.WebhookMessage]
@@ -75,6 +73,17 @@ if not markov_model:
generated_sentences: Set[str] = set()
used_words: Set[str] = set()
+def get_git_remote_url():
+ try:
+ url = subprocess.check_output(
+ ["git", "config", "--get", "remote.origin.url"],
+ text=True,
+ stderr=subprocess.DEVNULL,
+ ).strip()
+ return url
+ except subprocess.CalledProcessError:
+ return "Unknown"
+
async def load_cogs_from_folder(bot, folder_name="assets/cogs"):
for filename in os.listdir(folder_name):
if filename.endswith(".py") and not filename.startswith("_"):
@@ -87,14 +96,6 @@ async def load_cogs_from_folder(bot, folder_name="assets/cogs"):
logger.error(f"{(_('cog_fail'))} {cog_name} {e}")
traceback.print_exc()
-async def send_alive_ping_periodically() -> None:
- while True:
- try:
- requests.post(f"{VERSION_URL}/aliveping", json={"name": NAME})
- except Exception as e:
- logger.error(f"{(_('error_sending_alive_ping'))}{RESET} {e}")
- await asyncio.sleep(60)
-
# Event: Called when the bot is ready
@bot.event
async def on_ready() -> None:
@@ -113,7 +114,6 @@ async def on_ready() -> None:
logger.info(f"{_('synced_commands')} {len(synced)} {(_('synced_commands2'))}")
slash_commands_enabled = True
logger.info(f"{(_('started')).format(name=NAME)}")
- bot.loop.create_task(send_alive_ping_periodically())
except discord.errors.Forbidden as perm_error:
logger.error(f"Permission error while syncing commands: {perm_error}")
logger.error("Make sure the bot has the 'applications.commands' scope and is invited with the correct permissions.")
@@ -150,11 +150,11 @@ async def on_command_error(ctx: commands.Context, error: commands.CommandError)
context=f"Command: {ctx.command} | User: {ctx.author}"
)
-# 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
+ global markov_model
message_ref: MessageReference = await send_message(ctx, f"{(_('command_markov_retrain'))}")
try:
@@ -166,22 +166,13 @@ async def retrain(ctx: commands.Context) -> None:
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:
@@ -221,9 +212,8 @@ async def talk(ctx: commands.Context, sentence_size: int = 5) -> None:
async def ramusage(ctx):
process = psutil.Process(os.getpid())
mem = process.memory_info().rss
- await send_message(ctx, f"Total memory used: {mem / 1024 / 1024:.2f} MB")
+ await send_message(ctx, f"{mem / 1024 / 1024:.2f} MB")
-# 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"
@@ -273,55 +263,6 @@ async def impact(ctx: commands.Context, text: Optional[str] = None) -> None:
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'))}")
@@ -362,11 +303,9 @@ async def setlanguage(ctx: commands.Context, locale: str) -> None:
set_language(locale)
await ctx.send(":thumbsup:")
-# Event: Called on every message
@bot.event
async def on_message(message: discord.Message) -> None:
global memory, markov_model
- EMOJIS = ["\U0001F604", "\U0001F44D", "\U0001F525", "\U0001F4AF", "\U0001F389", "\U0001F60E"] # originally was emojis but it would probably shit itself on systems without unicode so....
if message.author.bot:
return
@@ -383,38 +322,7 @@ async def on_message(message: discord.Message) -> None:
return
formatted_message: str = message.content
cleaned_message: str = formatted_message
- if cleaned_message:
- memory.append(cleaned_message)
- message_metadata = {
- "user_id": str(message.author.id),
- "user_name": str(message.author),
- "guild_id": str(message.guild.id) if message.guild else "DM",
- "guild_name": str(message.guild.name) if message.guild else "DM",
- "channel_id": str(message.channel.id),
- "channel_name": str(message.channel),
- "message": message.content,
- "timestamp": time.time()
- }
- try:
- if isinstance(memory, list):
- memory.append({"_meta": message_metadata})
- else:
- logger.warning("Memory is not a list; can't append metadata")
- except Exception as e:
- logger.warning(f"Failed to append metadata to memory: {e}")
-
- save_memory(memory)
-
- sentiment_score = is_positive(message.content) # doesnt work but im scared to change the logic now please ignore
- if sentiment_score > 0.8:
- if REACT != "True":
- return
- emoji = random.choice(EMOJIS)
- try:
- await message.add_reaction(emoji)
- except Exception as e:
- logger.info(f"Failed to react with emoji: {e}")
-
+ save_memory(memory)
await bot.process_commands(message)
@bot.event
@@ -425,8 +333,6 @@ async def on_interaction(interaction: discord.Interaction) -> None:
else:
name = interaction.data['name']
logger.info(f"{(_('command_ran_s')).format(interaction=interaction)}{name}")
-
-# Global check: Block blacklisted users from running commands
@bot.check
async def block_blacklisted(ctx: commands.Context) -> bool:
if str(ctx.author.id) in BLACKLISTED_USERS:
@@ -442,8 +348,6 @@ async def block_blacklisted(ctx: commands.Context) -> bool:
pass
return False
return True
-
-# Command: Show bot latency
@bot.hybrid_command(description=f"{(_('command_desc_ping'))}")
async def ping(ctx: commands.Context) -> None:
await ctx.defer()
@@ -460,18 +364,6 @@ async def ping(ctx: commands.Context) -> None:
LOLembed.set_footer(text=f"{(_('command_ping_footer'))} {ctx.author.name}", icon_url=ctx.author.avatar.url)
await ctx.send(embed=LOLembed)
-
-def get_git_remote_url():
- try:
- url = subprocess.check_output(
- ["git", "config", "--get", "remote.origin.url"],
- text=True,
- stderr=subprocess.DEVNULL,
- ).strip()
- return url
- except subprocess.CalledProcessError:
- return "Unknown"
-
# Command: Show about information
@bot.hybrid_command(description=f"{(_('command_about_desc'))}")
async def about(ctx: commands.Context) -> None:
diff --git a/modules/globalvars.py b/modules/globalvars.py
index ac37ed4..f7dcda4 100644
--- a/modules/globalvars.py
+++ b/modules/globalvars.py
@@ -56,7 +56,7 @@ latest_version = "0.0.0"
local_version = "2.3.5"
os.environ['gooberlocal_version'] = local_version
REACT = os.getenv("REACT")
-if get_git_branch() == "dev":
+if get_git_branch() != "main":
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:
diff --git a/modules/image.py b/modules/image.py
index d3807df..9e9bc3c 100644
--- a/modules/image.py
+++ b/modules/image.py
@@ -12,10 +12,6 @@ generated_sentences = set()
def load_font(size):
return ImageFont.truetype("assets/fonts/Impact.ttf", size=size)
-
-def load_tnr(size):
- return ImageFont.truetype("assets/fonts/TNR.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:
@@ -115,63 +111,3 @@ async def gen_meme(input_image_path, sentence_size=5, max_attempts=10, custom_te
draw_text_with_outline(draw, truncated, (width - text_width) / 2, 0, font)
img.save(input_image_path)
return input_image_path
-
-async def gen_demotivator(input_image_path, max_attempts=5):
- markov_model = load_markov_model()
- if not markov_model or not os.path.isfile(input_image_path):
- return None
-
- attempt = 0
- while attempt < max_attempts:
- with Image.open(input_image_path).convert("RGB") as img:
- size = max(img.width, img.height)
- frame_thick = int(size * 0.0054)
- inner_size = size - 2 * frame_thick
- resized_img = img.resize((inner_size, inner_size), Image.LANCZOS)
- framed = Image.new("RGB", (size, size), "white")
- framed.paste(resized_img, (frame_thick, frame_thick))
- landscape_w = int(size * 1.5)
- caption_h = int(size * 0.3)
- canvas_h = framed.height + caption_h
- canvas = Image.new("RGB", (landscape_w, canvas_h), "black")
- # the above logic didnt even work, fml
- fx = (landscape_w - framed.width) // 2
- canvas.paste(framed, (fx, 0))
-
- draw = ImageDraw.Draw(canvas)
-
- title = subtitle = None
- for _ in range(20):
- t = markov_model.make_sentence(tries=100, max_words=4)
- s = markov_model.make_sentence(tries=100, max_words=5)
- if t and s and t != s:
- title = t.upper()
- subtitle = s.capitalize()
- break
- if not title: title = "DEMOTIVATOR"
- if not subtitle: subtitle = "no text generated"
-
- title_sz = int(caption_h * 0.4)
- sub_sz = int(caption_h * 0.25)
- title_font = load_tnr(title_sz)
- sub_font = load_tnr(sub_sz)
-
- bbox = draw.textbbox((0, 0), title, font=title_font)
- txw, txh = bbox[2] - bbox[0], bbox[3] - bbox[1]
- tx = (landscape_w - txw) // 2
- ty = framed.height + int(caption_h * 0.1)
- draw_text_with_outline(draw, title, tx, ty, title_font)
-
- bbox = draw.textbbox((0, 0), subtitle, font=sub_font)
- sxw, sxh = bbox[2] - bbox[0], bbox[3] - bbox[1]
- sx = (landscape_w - sxw) // 2
- sy = ty + txh + int(caption_h * 0.05)
- for ox, oy in [(-1, -1), (1, -1), (-1, 1), (1, 1)]:
- draw.text((sx + ox, sy + oy), subtitle, font=sub_font, fill="black")
- draw.text((sx, sy), subtitle, font=sub_font, fill="#AAAAAA")
-
- canvas.save(input_image_path)
- return input_image_path
-
- attempt += 1
- return None
diff --git a/modules/markovmemory.py b/modules/markovmemory.py
index 3235035..5014c48 100644
--- a/modules/markovmemory.py
+++ b/modules/markovmemory.py
@@ -6,7 +6,7 @@ from modules.globalvars import *
from modules.volta.main import _
import logging
logger = logging.getLogger("goober")
-# Get file size and line count for a given file path
+
def get_file_info(file_path):
try:
file_size = os.path.getsize(file_path)
@@ -15,12 +15,8 @@ def get_file_info(file_path):
return {"file_size_bytes": file_size, "line_count": len(lines)}
except Exception as e:
return {"error": str(e)}
-
-# Load memory data from file, or use default dataset if not loaded yet
def load_memory():
data = []
-
- # Try to load data from MEMORY_FILE
try:
with open(MEMORY_FILE, "r") as f:
data = json.load(f)
@@ -28,12 +24,9 @@ def load_memory():
pass
return data
-
-# Save memory data to MEMORY_FILE
def save_memory(memory):
with open(MEMORY_FILE, "w") as f:
json.dump(memory, f, indent=4)
-
def train_markov_model(memory, additional_data=None):
if not memory:
return None
@@ -45,14 +38,9 @@ def train_markov_model(memory, additional_data=None):
text = "\n".join(filtered_memory)
model = markovify.NewlineText(text, state_size=2)
return model
-
-# Save the Markov model to a pickle file
def save_markov_model(model, filename='markov_model.pkl'):
with open(filename, 'wb') as f:
pickle.dump(model, f)
- logger.info(f"Markov model saved to {filename}.")
-
-# Load the Markov model from a pickle file
def load_markov_model(filename='markov_model.pkl'):
try:
with open(filename, 'rb') as f:
diff --git a/modules/minigames.py b/modules/minigames.py
deleted file mode 100644
index 0a74a82..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=_('minigames_guess_the_number'))
-async def guessthenumber(ctx: commands.Context):
- number = random.randint(1, 10)
- class GuessModal(ui.Modal, title=_('minigames_guess_the_number')):
- guess = ui.TextInput(label=_('minigames_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(_('minigames_invalid_number'), ephemeral=True)
- return
- if user_guess == number:
- await interaction.response.send_message(_('minigames_correct'), ephemeral=True)
- else:
- await interaction.response.send_message(f"{_('minigames_wrong_number')} {number}.", ephemeral=True)
- async def button_callback(interaction: Interaction):
- await interaction.response.send_modal(GuessModal())
- button = ui.Button(label=_('minigames_guess_button'), style=discord.ButtonStyle.primary)
- button.callback = button_callback
- view = ui.View()
- view.add_item(button)
- await ctx.send(_('minigames_click_to_guess'), view=view)
-
-# @bot.hybrid_command(description=_('minigames_hangman')) nope nope nope fuck no nope no thanks no nuh uh not today nope
-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=_('minigames_hangman_guess')):
- letter = ui.TextInput(label=_('minigames_hangman_user_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"{_('minigames_hangman_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"{_('minigames_hangman_won')} **{word}**", view=None)
- elif wrong_guesses >= max_wrong:
- await interaction.response.edit_message(content=f"{_('minigames_hangman_lost')} **{word}**", view=None)
- else:
- await interaction.response.edit_message(content=_('minigames_hangman_game').format(display_word=display_word(),wrong_guesses=wrong_guesses,max_wrong=max_wrong), view=view)
- async def button_callback(interaction: Interaction):
- await interaction.response.send_modal(GuessModal())
- button = ui.Button(label=_('minigames_click_to_guess'), style=discord.ButtonStyle.primary)
- button.callback = button_callback
- view = ui.View()
- view.add_item(button)
- await ctx.send(_('minigames_hangman_game').format(display_word=display_word,wrong_guesses=wrong_guesses,max_wrong=max_wrong), view=view)
\ No newline at end of file
diff --git a/modules/prestartchecks.py b/modules/prestartchecks.py
index bb952f4..29e155f 100644
--- a/modules/prestartchecks.py
+++ b/modules/prestartchecks.py
@@ -28,8 +28,7 @@ def check_for_model():
logger.info("Model is installed.")
else:
logger.info("Model is not installed.")
-
-
+
def iscloned():
if os.path.exists(".git"):
return True
diff --git a/modules/unhandledexception.py b/modules/unhandledexception.py
index 9521e72..eac98e8 100644
--- a/modules/unhandledexception.py
+++ b/modules/unhandledexception.py
@@ -6,18 +6,14 @@ from modules.volta.main import _
def handle_exception(exc_type, exc_value, exc_traceback, *, context=None):
os.system('cls' if os.name == 'nt' else 'clear')
-
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
-
print(splashtext)
print(f"{RED}=====BEGINNING OF TRACEBACK====={RESET}")
traceback.print_exception(exc_type, exc_value, exc_traceback)
print(f"{RED}========END OF TRACEBACK========{RESET}")
print(f"{RED}{_('unhandled_exception')}{RESET}")
-
-
if context:
print(f"{RED}Context: {context}{RESET}")
diff --git a/modules/version.py b/modules/version.py
index a5c1132..37e2264 100644
--- a/modules/version.py
+++ b/modules/version.py
@@ -21,7 +21,6 @@ def is_remote_ahead(branch='main', remote='origin'):
count = run_cmd(f'git rev-list --count HEAD..{remote}/{branch}')
return int(count) > 0
-# Automatically update the local repository if the remote is ahead
def auto_update(branch='main', remote='origin'):
if launched == True:
print(_("already_started"))