lobotmized goober and cleared out junk
This commit is contained in:
parent
b860d0e271
commit
8021d17d27
14 changed files with 19 additions and 1488 deletions
|
@ -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))
|
|
|
@ -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))
|
|
|
@ -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))
|
|
|
@ -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))
|
|
|
@ -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 = """
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Goober Settings</title>
|
|
||||||
<style>
|
|
||||||
body { background-color: #121212; color: #ffffff; font-family: 'Segoe UI', sans-serif; }
|
|
||||||
h1 { color: #ff5555; text-align: center; }
|
|
||||||
.settings-container { max-width: 800px; margin: auto; background-color: #1e1e1e; padding: 20px; border-radius: 8px; }
|
|
||||||
.form-group { margin-bottom: 15px; }
|
|
||||||
label { display: block; margin-bottom: 5px; color: #ff9999; }
|
|
||||||
input { width: 100%; padding: 8px; background-color: #252525; color: white; border: 1px solid #444; border-radius: 4px; }
|
|
||||||
button { background-color: #5f1b1b; color: white; border: none; padding: 10px; border-radius: 4px; cursor: pointer; }
|
|
||||||
button:hover { background-color: #7a2323; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class='settings-container'>
|
|
||||||
<h1>Goober Settings</h1>
|
|
||||||
<form id='settingsForm' action='/update_settings' method='post'>
|
|
||||||
"""
|
|
||||||
|
|
||||||
for key, value in env_vars.items():
|
|
||||||
settings_html += f"""
|
|
||||||
<div class='form-group'>
|
|
||||||
<label for='{key}'>{key}</label>
|
|
||||||
<input type='text' id='{key}' name='{key}' value='{value}'>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
|
|
||||||
for key, value in config_vars.items():
|
|
||||||
settings_html += f"""
|
|
||||||
<div class='form-group'>
|
|
||||||
<label for='{key}'>{key}</label>
|
|
||||||
<input type='text' id='{key}' name='{key}' value='{value}'>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
|
|
||||||
settings_html += """
|
|
||||||
<button type='submit'>Save Settings</button>
|
|
||||||
</form>
|
|
||||||
<form action="/restart_bot" method="POST">
|
|
||||||
<button type="submit">Restart</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
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'<img src="{guild["icon_url"]}" alt="guild icon" class="guild-icon">' if guild["icon_url"] else '<div class="guild-icon-placeholder"></div>'
|
|
||||||
guild_list_html += f"""
|
|
||||||
<div class="guild-item">
|
|
||||||
{icon_html}
|
|
||||||
<div class="guild-info">
|
|
||||||
<div class="guild-name">{guild["name"]}</div>
|
|
||||||
<div class="guild-members">{guild["member_count"]} members</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
blacklisted_users_html = ""
|
|
||||||
for user in stats['blacklisted_users']:
|
|
||||||
avatar_html = f'<img src="{user["avatar_url"]}" alt="user avatar" class="user-avatar">' if user["avatar_url"] else '<div class="user-avatar-placeholder"></div>'
|
|
||||||
blacklisted_users_html += f"""
|
|
||||||
<div class="blacklisted-user">
|
|
||||||
{avatar_html}
|
|
||||||
<div class="user-info">
|
|
||||||
<div class="user-name">{user["name"]}</div>
|
|
||||||
<div class="user-id">ID: {user["id"]}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
|
|
||||||
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"""
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>goobs central</title>
|
|
||||||
<style>
|
|
||||||
#loading-screen {{
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #000;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 9999;
|
|
||||||
transition: opacity 1.5s ease-out;
|
|
||||||
}}
|
|
||||||
|
|
||||||
#loading-screen.fade-out {{
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}}
|
|
||||||
|
|
||||||
#welcome-message {{
|
|
||||||
color: #fff;
|
|
||||||
font-size: 2em;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 0 10px #ff5555;
|
|
||||||
}}
|
|
||||||
|
|
||||||
#owner-avatar {{
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
border: 3px solid #5f1b1b;
|
|
||||||
box-shadow: 0 0 20px #ff5555;
|
|
||||||
}}
|
|
||||||
body {{
|
|
||||||
background-color: #121212;
|
|
||||||
color: #ffffff;
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 1.6;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.topnav {{
|
|
||||||
background-color: #2a0a0a;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 10px;
|
|
||||||
gap: 15px;
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
|
|
||||||
}}
|
|
||||||
|
|
||||||
.stat-item {{
|
|
||||||
gap: 5px;
|
|
||||||
color: white;
|
|
||||||
font-size: 14px;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
padding: 8px 15px;
|
|
||||||
border-radius: 6px;
|
|
||||||
align-items: center;
|
|
||||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
|
|
||||||
position: relative;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.stat-item:hover {{
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
|
|
||||||
}}
|
|
||||||
|
|
||||||
.stat-item::after {{
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: -5px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 60%;
|
|
||||||
height: 3px;
|
|
||||||
background: linear-gradient(90deg, transparent, #ff5555, transparent);
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.stat-item:hover::after {{
|
|
||||||
opacity: 1;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.stat-title {{
|
|
||||||
font-weight: bold;
|
|
||||||
color: #ff9999;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.stat-item span:not(.stat-title) {{
|
|
||||||
font-weight: bold;
|
|
||||||
color: #ffffff;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.center {{
|
|
||||||
text-align: center;
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 20px auto;
|
|
||||||
padding: 0 20px;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.bot-info {{
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 15px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.bot-avatar {{
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 3px solid #5f1b1b;
|
|
||||||
object-fit: cover;
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
|
|
||||||
}}
|
|
||||||
|
|
||||||
hr {{
|
|
||||||
border: 0;
|
|
||||||
height: 1px;
|
|
||||||
background-image: linear-gradient(to right, transparent, #5f1b1b, transparent);
|
|
||||||
margin: 20px 0;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.stat-container-row {{
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 30px;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 20px;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.stat-container {{
|
|
||||||
flex: 1;
|
|
||||||
background-color: #1e1e1e;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
||||||
min-width: 0;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.stat-title {{
|
|
||||||
color: #ff5555;
|
|
||||||
font-size: 1.1em;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border-bottom: 1px solid #333;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.guild-item {{
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 15px;
|
|
||||||
padding: 10px;
|
|
||||||
margin: 5px 0;
|
|
||||||
background-color: #252525;
|
|
||||||
border-radius: 5px;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.guild-item:hover {{
|
|
||||||
background-color: #333;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.guild-icon {{
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.guild-icon-placeholder {{
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #7289da;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.guild-info {{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-grow: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.guild-name {{
|
|
||||||
font-weight: bold;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.guild-members {{
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: #99aab5;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.blacklisted-user {{
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 15px;
|
|
||||||
padding: 10px;
|
|
||||||
margin: 5px 0;
|
|
||||||
background-color: #2a1a1a;
|
|
||||||
border-radius: 5px;
|
|
||||||
border-left: 3px solid #ff5555;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.user-avatar {{
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.user-avatar-placeholder {{
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #7289da;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.user-info {{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.user-name {{
|
|
||||||
font-weight: bold;
|
|
||||||
color: #ff5555;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.user-id {{
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: #99aab5;
|
|
||||||
}}
|
|
||||||
|
|
||||||
input[type="text"] {{
|
|
||||||
background-color: #252525;
|
|
||||||
color: white;
|
|
||||||
border: 1px solid #444;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
width: 200px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}}
|
|
||||||
|
|
||||||
button {{
|
|
||||||
background-color: #5f1b1b;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 8px 15px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}}
|
|
||||||
|
|
||||||
button:hover {{
|
|
||||||
background-color: #7a2323;
|
|
||||||
}}
|
|
||||||
|
|
||||||
#guild-list, #blacklisted-users {{
|
|
||||||
max-height: 400px;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding-right: 5px;
|
|
||||||
}}
|
|
||||||
|
|
||||||
#guild-list::-webkit-scrollbar, #blacklisted-users::-webkit-scrollbar {{
|
|
||||||
width: 6px;
|
|
||||||
}}
|
|
||||||
|
|
||||||
#guild-list::-webkit-scrollbar-track, #blacklisted-users::-webkit-scrollbar-track {{
|
|
||||||
background: #1a1a1a;
|
|
||||||
}}
|
|
||||||
|
|
||||||
#guild-list::-webkit-scrollbar-thumb, #blacklisted-users::-webkit-scrollbar-thumb {{
|
|
||||||
background-color: #5f1b1b;
|
|
||||||
border-radius: 3px;
|
|
||||||
}}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {{
|
|
||||||
.stat-container-row {{
|
|
||||||
flex-direction: column;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.topnav {{
|
|
||||||
gap: 10px;
|
|
||||||
padding: 10px 5px;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.stat-item {{
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="loading-screen">
|
|
||||||
<img id="owner-avatar" src="{owner_pfp}" onerror="this.style.display='none'">
|
|
||||||
<div id="welcome-message"><b>Welcome, {owner_username}</b></div>
|
|
||||||
</div>
|
|
||||||
<div class="topnav">
|
|
||||||
<div class="stat-item" id="ram-usage">
|
|
||||||
<span class="stat-title">RAM:</span>
|
|
||||||
<span>{stats['ram_usage']}</span>
|
|
||||||
</div>
|
|
||||||
<div class="stat-item" id="system-cpu">
|
|
||||||
<span class="stat-title">CPU:</span>
|
|
||||||
<span>{stats['system_cpu']}</span>
|
|
||||||
</div>
|
|
||||||
<div class="stat-item" id="latency">
|
|
||||||
<span class="stat-title">Latency:</span>
|
|
||||||
<span>{stats['latency']}</span>
|
|
||||||
</div>
|
|
||||||
<div class="stat-item" id="json-size">
|
|
||||||
<span class="stat-title">JSON Size:</span>
|
|
||||||
<span>{stats['memory_json_size']}</span>
|
|
||||||
</div>
|
|
||||||
<div class="stat-item" id="uptime">
|
|
||||||
<span class="stat-title">Uptime:</span>
|
|
||||||
<span>{stats['bot_uptime']}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="center">
|
|
||||||
<div class="bot-info">
|
|
||||||
<img src="{stats['bot_avatar_url']}" alt="botvatar" class="bot-avatar" id="bot-avatar">
|
|
||||||
<h1 id="bot-name">{stats['bot_name']}</h1>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<p>your stupid little goober that learns off other people's messages</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stat-container-row">
|
|
||||||
<div class="stat-container">
|
|
||||||
<div class="stat-title">Last Command</div>
|
|
||||||
<div id="last-command">{stats['last_command']}</div>
|
|
||||||
<div style="font-size: 0.9em; color: #999;" id="last-command-time">at {stats['last_command_time']}</div>
|
|
||||||
<br>
|
|
||||||
<div class="stat-title">Logged into goober central</div>
|
|
||||||
<div id="last-command">{stats['authenticated']}</div>
|
|
||||||
<br>
|
|
||||||
<div class="stat-title">Last generated message</div>
|
|
||||||
<div id="last-command">{stats['lastmsg']}</div>
|
|
||||||
<br>
|
|
||||||
<div class="stat-title">Version</div>
|
|
||||||
<div id="last-command">Installed Version: {stats['localversion']}</div>
|
|
||||||
<div id="last-command">Latest Version: {stats['latestversion']}</div>
|
|
||||||
<br>
|
|
||||||
<div class="stat-title">goober-central URL</div>
|
|
||||||
<div id="last-command">{VERSION_URL}</div>
|
|
||||||
<br>
|
|
||||||
<div class="stat-title">Change song</div>
|
|
||||||
<form action="/changesong" method="get">
|
|
||||||
<input type="text" name="song" placeholder="Enter song name...">
|
|
||||||
<button type="submit">
|
|
||||||
change song
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stat-container">
|
|
||||||
<div class="stat-title">Servers (<span id="guild-count">{stats['guild_count']}</span>)</div>
|
|
||||||
<div id="guild-list">
|
|
||||||
{guild_list_html}
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<div class="stat-title">Blacklisted Users (<span id="guild-count">{stats['bl_count']})</div>
|
|
||||||
<div id="blacklisted-users">
|
|
||||||
{blacklisted_users_html if stats['blacklisted_users'] else "<div>No blacklisted users</div>"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
window.addEventListener('load', function() {{
|
|
||||||
setTimeout(function() {{
|
|
||||||
const loadingScreen = document.getElementById('loading-screen');
|
|
||||||
loadingScreen.classList.add('fade-out');
|
|
||||||
setTimeout(function() {{
|
|
||||||
loadingScreen.remove();
|
|
||||||
}}, 1500);
|
|
||||||
}}, 1500);
|
|
||||||
}});
|
|
||||||
const ws = new WebSocket('ws://' + window.location.host + '/ws');
|
|
||||||
|
|
||||||
ws.onmessage = function(event) {{
|
|
||||||
const data = JSON.parse(event.data);
|
|
||||||
|
|
||||||
document.getElementById('ram-usage').innerHTML = `<span class="stat-title">RAM:</span> <span>${{data.ram_usage}}</span>`;
|
|
||||||
document.getElementById('cpu-usage').innerHTML = `<span class="stat-title">CPU:</span> <span>${{data.cpu_usage}}</span>`;
|
|
||||||
document.getElementById('system-cpu').innerHTML = `<span class="stat-title">System CPU:</span> <span>${{data.system_cpu}}</span>`;
|
|
||||||
document.getElementById('latency').innerHTML = `<span class="stat-title">Latency:</span> <span>${{data.latency}}</span>`;
|
|
||||||
document.getElementById('json-size').innerHTML = `<span class="stat-title">JSON Size:</span> <span>${{data.memory_json_size}}</span>`;
|
|
||||||
document.getElementById('uptime').innerHTML = `<span class="stat-title">Uptime:</span> <span>${{data.bot_uptime}}</span>`;
|
|
||||||
|
|
||||||
document.getElementById('bot-name').textContent = data.bot_name;
|
|
||||||
const botAvatar = document.getElementById('bot-avatar');
|
|
||||||
if (botAvatar.src !== data.bot_avatar_url) {{
|
|
||||||
botAvatar.src = data.bot_avatar_url;
|
|
||||||
}}
|
|
||||||
|
|
||||||
|
|
||||||
document.getElementById('last-command').textContent = data.last_command;
|
|
||||||
document.getElementById('last-command-time').textContent = `at ${{data.last_command_time}}`;
|
|
||||||
|
|
||||||
document.getElementById('guild-count').textContent = data.guild_count;
|
|
||||||
|
|
||||||
|
|
||||||
let guildListHtml = '';
|
|
||||||
data.guilds.forEach(guild => {{
|
|
||||||
const iconHtml = guild.icon_url
|
|
||||||
? `<img src="${{guild.icon_url}}" alt="guild icon" class="guild-icon">`
|
|
||||||
: '<div class="guild-icon-placeholder"></div>';
|
|
||||||
guildListHtml += `
|
|
||||||
<div class="guild-item">
|
|
||||||
${{iconHtml}}
|
|
||||||
<div class="guild-info">
|
|
||||||
<div class="guild-name">${{guild.name}}</div>
|
|
||||||
<div class="guild-members">${{guild.member_count}} members</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}});
|
|
||||||
document.getElementById('guild-list').innerHTML = guildListHtml;
|
|
||||||
|
|
||||||
let blacklistedUsersHtml = '';
|
|
||||||
if (data.blacklisted_users && data.blacklisted_users.length > 0) {{
|
|
||||||
data.blacklisted_users.forEach(user => {{
|
|
||||||
const avatarHtml = user.avatar_url
|
|
||||||
? `<img src="${{user.avatar_url}}" alt="user avatar" class="user-avatar">`
|
|
||||||
: '<div class="user-avatar-placeholder"></div>';
|
|
||||||
blacklistedUsersHtml += `
|
|
||||||
<div class="blacklisted-user">
|
|
||||||
${{avatarHtml}}
|
|
||||||
<div class="user-info">
|
|
||||||
<div class="user-name">${{user.name}}</div>
|
|
||||||
<div class="user-id">ID: ${{user.id}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}});
|
|
||||||
}} else {{
|
|
||||||
blacklistedUsersHtml = '<div>No blacklisted users</div>';
|
|
||||||
}}
|
|
||||||
document.getElementById('blacklisted-users').innerHTML = blacklistedUsersHtml;
|
|
||||||
}};
|
|
||||||
|
|
||||||
ws.onclose = function() {{
|
|
||||||
console.log('WebSocket disconnected');
|
|
||||||
}};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
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))
|
|
Binary file not shown.
138
bot.py → main.py
138
bot.py → main.py
|
@ -49,11 +49,9 @@ from modules.markovmemory import *
|
||||||
from modules.version import *
|
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
|
||||||
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()
|
||||||
# Type aliases
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
MessageContext = Union[commands.Context, discord.Interaction]
|
MessageContext = Union[commands.Context, discord.Interaction]
|
||||||
MessageReference = Union[Message, discord.WebhookMessage]
|
MessageReference = Union[Message, discord.WebhookMessage]
|
||||||
|
@ -75,6 +73,17 @@ if not markov_model:
|
||||||
generated_sentences: Set[str] = set()
|
generated_sentences: Set[str] = set()
|
||||||
used_words: 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"):
|
async def load_cogs_from_folder(bot, folder_name="assets/cogs"):
|
||||||
for filename in os.listdir(folder_name):
|
for filename in os.listdir(folder_name):
|
||||||
if filename.endswith(".py") and not filename.startswith("_"):
|
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}")
|
logger.error(f"{(_('cog_fail'))} {cog_name} {e}")
|
||||||
traceback.print_exc()
|
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
|
# Event: Called when the bot is ready
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_ready() -> None:
|
async def on_ready() -> None:
|
||||||
|
@ -113,7 +114,6 @@ async def on_ready() -> None:
|
||||||
logger.info(f"{_('synced_commands')} {len(synced)} {(_('synced_commands2'))}")
|
logger.info(f"{_('synced_commands')} {len(synced)} {(_('synced_commands2'))}")
|
||||||
slash_commands_enabled = True
|
slash_commands_enabled = True
|
||||||
logger.info(f"{(_('started')).format(name=NAME)}")
|
logger.info(f"{(_('started')).format(name=NAME)}")
|
||||||
bot.loop.create_task(send_alive_ping_periodically())
|
|
||||||
except discord.errors.Forbidden as perm_error:
|
except discord.errors.Forbidden as perm_error:
|
||||||
logger.error(f"Permission error while syncing commands: {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.")
|
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}"
|
context=f"Command: {ctx.command} | User: {ctx.author}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Command: Retrain the Markov model from memory
|
|
||||||
@bot.hybrid_command(description=f"{(_('command_desc_retrain'))}")
|
@bot.hybrid_command(description=f"{(_('command_desc_retrain'))}")
|
||||||
async def retrain(ctx: commands.Context) -> None:
|
async def retrain(ctx: commands.Context) -> None:
|
||||||
if ctx.author.id != ownerid:
|
if ctx.author.id != ownerid:
|
||||||
return
|
return
|
||||||
|
global markov_model
|
||||||
|
|
||||||
message_ref: MessageReference = await send_message(ctx, f"{(_('command_markov_retrain'))}")
|
message_ref: MessageReference = await send_message(ctx, f"{(_('command_markov_retrain'))}")
|
||||||
try:
|
try:
|
||||||
|
@ -166,22 +166,13 @@ async def retrain(ctx: commands.Context) -> None:
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
await send_message(ctx, f"{(_('command_markov_memory_is_corrupt'))}")
|
await send_message(ctx, f"{(_('command_markov_memory_is_corrupt'))}")
|
||||||
return
|
return
|
||||||
|
|
||||||
data_size: int = len(memory)
|
data_size: int = len(memory)
|
||||||
processed_data: int = 0
|
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)}")
|
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)
|
markov_model = train_markov_model(memory)
|
||||||
save_markov_model(markov_model)
|
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)
|
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'))}")
|
@bot.hybrid_command(description=f"{(_('command_desc_talk'))}")
|
||||||
async def talk(ctx: commands.Context, sentence_size: int = 5) -> None:
|
async def talk(ctx: commands.Context, sentence_size: int = 5) -> None:
|
||||||
if not markov_model:
|
if not markov_model:
|
||||||
|
@ -221,9 +212,8 @@ async def talk(ctx: commands.Context, sentence_size: int = 5) -> None:
|
||||||
async def ramusage(ctx):
|
async def ramusage(ctx):
|
||||||
process = psutil.Process(os.getpid())
|
process = psutil.Process(os.getpid())
|
||||||
mem = process.memory_info().rss
|
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'))}")
|
@bot.hybrid_command(description=f"{(_('command_desc_help'))}")
|
||||||
async def impact(ctx: commands.Context, text: Optional[str] = None) -> None:
|
async def impact(ctx: commands.Context, text: Optional[str] = None) -> None:
|
||||||
assets_folder: str = "assets/images"
|
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):
|
if temp_input and os.path.exists(temp_input):
|
||||||
os.remove(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')
|
bot.remove_command('help')
|
||||||
# Command: Show help information
|
# Command: Show help information
|
||||||
@bot.hybrid_command(description=f"{(_('command_desc_help'))}")
|
@bot.hybrid_command(description=f"{(_('command_desc_help'))}")
|
||||||
|
@ -362,11 +303,9 @@ async def setlanguage(ctx: commands.Context, locale: str) -> None:
|
||||||
set_language(locale)
|
set_language(locale)
|
||||||
await ctx.send(":thumbsup:")
|
await ctx.send(":thumbsup:")
|
||||||
|
|
||||||
# Event: Called on every message
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_message(message: discord.Message) -> None:
|
async def on_message(message: discord.Message) -> None:
|
||||||
global memory, markov_model
|
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:
|
if message.author.bot:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -383,38 +322,7 @@ async def on_message(message: discord.Message) -> None:
|
||||||
return
|
return
|
||||||
formatted_message: str = message.content
|
formatted_message: str = message.content
|
||||||
cleaned_message: str = formatted_message
|
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)
|
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}")
|
|
||||||
|
|
||||||
await bot.process_commands(message)
|
await bot.process_commands(message)
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
|
@ -425,8 +333,6 @@ async def on_interaction(interaction: discord.Interaction) -> None:
|
||||||
else:
|
else:
|
||||||
name = interaction.data['name']
|
name = interaction.data['name']
|
||||||
logger.info(f"{(_('command_ran_s')).format(interaction=interaction)}{name}")
|
logger.info(f"{(_('command_ran_s')).format(interaction=interaction)}{name}")
|
||||||
|
|
||||||
# Global check: Block blacklisted users from running commands
|
|
||||||
@bot.check
|
@bot.check
|
||||||
async def block_blacklisted(ctx: commands.Context) -> bool:
|
async def block_blacklisted(ctx: commands.Context) -> bool:
|
||||||
if str(ctx.author.id) in BLACKLISTED_USERS:
|
if str(ctx.author.id) in BLACKLISTED_USERS:
|
||||||
|
@ -442,8 +348,6 @@ async def block_blacklisted(ctx: commands.Context) -> bool:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Command: Show bot latency
|
|
||||||
@bot.hybrid_command(description=f"{(_('command_desc_ping'))}")
|
@bot.hybrid_command(description=f"{(_('command_desc_ping'))}")
|
||||||
async def ping(ctx: commands.Context) -> None:
|
async def ping(ctx: commands.Context) -> None:
|
||||||
await ctx.defer()
|
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)
|
LOLembed.set_footer(text=f"{(_('command_ping_footer'))} {ctx.author.name}", icon_url=ctx.author.avatar.url)
|
||||||
|
|
||||||
await ctx.send(embed=LOLembed)
|
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
|
# Command: Show about information
|
||||||
@bot.hybrid_command(description=f"{(_('command_about_desc'))}")
|
@bot.hybrid_command(description=f"{(_('command_about_desc'))}")
|
||||||
async def about(ctx: commands.Context) -> None:
|
async def about(ctx: commands.Context) -> None:
|
|
@ -56,7 +56,7 @@ latest_version = "0.0.0"
|
||||||
local_version = "2.3.5"
|
local_version = "2.3.5"
|
||||||
os.environ['gooberlocal_version'] = local_version
|
os.environ['gooberlocal_version'] = local_version
|
||||||
REACT = os.getenv("REACT")
|
REACT = os.getenv("REACT")
|
||||||
if get_git_branch() == "dev":
|
if get_git_branch() != "main":
|
||||||
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:
|
||||||
|
|
|
@ -12,10 +12,6 @@ generated_sentences = set()
|
||||||
|
|
||||||
def load_font(size):
|
def load_font(size):
|
||||||
return ImageFont.truetype("assets/fonts/Impact.ttf", size=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):
|
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)]
|
outline_offsets = [(-2, -2), (-2, 2), (2, -2), (2, 2), (0, -2), (0, 2), (-2, 0), (2, 0)]
|
||||||
for ox, oy in outline_offsets:
|
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)
|
draw_text_with_outline(draw, truncated, (width - text_width) / 2, 0, font)
|
||||||
img.save(input_image_path)
|
img.save(input_image_path)
|
||||||
return 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
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from modules.globalvars import *
|
||||||
from modules.volta.main import _
|
from modules.volta.main import _
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger("goober")
|
logger = logging.getLogger("goober")
|
||||||
# Get file size and line count for a given file path
|
|
||||||
def get_file_info(file_path):
|
def get_file_info(file_path):
|
||||||
try:
|
try:
|
||||||
file_size = os.path.getsize(file_path)
|
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)}
|
return {"file_size_bytes": file_size, "line_count": len(lines)}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"error": str(e)}
|
return {"error": str(e)}
|
||||||
|
|
||||||
# Load memory data from file, or use default dataset if not loaded yet
|
|
||||||
def load_memory():
|
def load_memory():
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
# Try to load data from MEMORY_FILE
|
|
||||||
try:
|
try:
|
||||||
with open(MEMORY_FILE, "r") as f:
|
with open(MEMORY_FILE, "r") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
@ -28,12 +24,9 @@ def load_memory():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
# Save memory data to MEMORY_FILE
|
|
||||||
def save_memory(memory):
|
def save_memory(memory):
|
||||||
with open(MEMORY_FILE, "w") as f:
|
with open(MEMORY_FILE, "w") as f:
|
||||||
json.dump(memory, f, indent=4)
|
json.dump(memory, f, indent=4)
|
||||||
|
|
||||||
def train_markov_model(memory, additional_data=None):
|
def train_markov_model(memory, additional_data=None):
|
||||||
if not memory:
|
if not memory:
|
||||||
return None
|
return None
|
||||||
|
@ -45,14 +38,9 @@ def train_markov_model(memory, additional_data=None):
|
||||||
text = "\n".join(filtered_memory)
|
text = "\n".join(filtered_memory)
|
||||||
model = markovify.NewlineText(text, state_size=2)
|
model = markovify.NewlineText(text, state_size=2)
|
||||||
return model
|
return model
|
||||||
|
|
||||||
# Save the Markov model to a pickle file
|
|
||||||
def save_markov_model(model, filename='markov_model.pkl'):
|
def save_markov_model(model, filename='markov_model.pkl'):
|
||||||
with open(filename, 'wb') as f:
|
with open(filename, 'wb') as f:
|
||||||
pickle.dump(model, 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'):
|
def load_markov_model(filename='markov_model.pkl'):
|
||||||
try:
|
try:
|
||||||
with open(filename, 'rb') as f:
|
with open(filename, 'rb') as f:
|
||||||
|
|
|
@ -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)
|
|
|
@ -29,7 +29,6 @@ def check_for_model():
|
||||||
else:
|
else:
|
||||||
logger.info("Model is not installed.")
|
logger.info("Model is not installed.")
|
||||||
|
|
||||||
|
|
||||||
def iscloned():
|
def iscloned():
|
||||||
if os.path.exists(".git"):
|
if os.path.exists(".git"):
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -6,18 +6,14 @@ from modules.volta.main import _
|
||||||
|
|
||||||
def handle_exception(exc_type, exc_value, exc_traceback, *, context=None):
|
def handle_exception(exc_type, exc_value, exc_traceback, *, context=None):
|
||||||
os.system('cls' if os.name == 'nt' else 'clear')
|
os.system('cls' if os.name == 'nt' else 'clear')
|
||||||
|
|
||||||
if issubclass(exc_type, KeyboardInterrupt):
|
if issubclass(exc_type, KeyboardInterrupt):
|
||||||
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
||||||
return
|
return
|
||||||
|
|
||||||
print(splashtext)
|
print(splashtext)
|
||||||
print(f"{RED}=====BEGINNING OF TRACEBACK====={RESET}")
|
print(f"{RED}=====BEGINNING OF TRACEBACK====={RESET}")
|
||||||
traceback.print_exception(exc_type, exc_value, exc_traceback)
|
traceback.print_exception(exc_type, exc_value, exc_traceback)
|
||||||
print(f"{RED}========END OF TRACEBACK========{RESET}")
|
print(f"{RED}========END OF TRACEBACK========{RESET}")
|
||||||
print(f"{RED}{_('unhandled_exception')}{RESET}")
|
print(f"{RED}{_('unhandled_exception')}{RESET}")
|
||||||
|
|
||||||
|
|
||||||
if context:
|
if context:
|
||||||
print(f"{RED}Context: {context}{RESET}")
|
print(f"{RED}Context: {context}{RESET}")
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ def is_remote_ahead(branch='main', remote='origin'):
|
||||||
count = run_cmd(f'git rev-list --count HEAD..{remote}/{branch}')
|
count = run_cmd(f'git rev-list --count HEAD..{remote}/{branch}')
|
||||||
return int(count) > 0
|
return int(count) > 0
|
||||||
|
|
||||||
# Automatically update the local repository if the remote is ahead
|
|
||||||
def auto_update(branch='main', remote='origin'):
|
def auto_update(branch='main', remote='origin'):
|
||||||
if launched == True:
|
if launched == True:
|
||||||
print(_("already_started"))
|
print(_("already_started"))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue