added permission wrapper

This commit is contained in:
ctih1 2025-07-23 10:19:08 +03:00
parent f7042ed8a7
commit f186e079da
29 changed files with 860 additions and 788 deletions

View file

@ -2,23 +2,23 @@ import discord
from discord.ext import commands
import discord.ext
import discord.ext.commands
from modules.permission import requires_admin
from modules.settings import Settings as SettingsManager
settings_manager = SettingsManager()
settings = settings_manager.settings
COG_PREFIX = "assets.cogs."
class CogManager(commands.Cog):
def __init__(self, bot):
self.bot = bot
@requires_admin()
@commands.command()
async def enable(self, ctx, cog_name: str):
if ctx.author.id not in settings["bot"]["owner_ids"]:
await ctx.send("You do not have permission to use this command.")
return
try:
await self.bot.load_extension(COG_PREFIX + cog_name)
await ctx.send(f"Loaded cog `{cog_name}` successfully.")
@ -28,17 +28,13 @@ class CogManager(commands.Cog):
except Exception as e:
await ctx.send(f"Error enabling cog `{cog_name}`: {e}")
@requires_admin()
@commands.command()
async def load(self, ctx, cog_name: str | None = None):
if ctx.author.id not in settings["bot"]["owner_ids"]:
await ctx.send("You do not have permission to use this command.")
return
if cog_name is None:
await ctx.send("Give cog_name")
return
if cog_name[:-3] not in settings["bot"]["enabled_cogs"]:
await ctx.send("Please enable the cog first!")
return
@ -51,11 +47,9 @@ class CogManager(commands.Cog):
except Exception as e:
await ctx.send(f"Error loading cog `{cog_name}`: {e}")
@requires_admin()
@commands.command()
async def unload(self, ctx, cog_name: str | None = None):
if ctx.author.id not in settings["bot"]["owner_ids"]:
await ctx.send("You do not have permission to use this command.")
return
if cog_name is None:
await ctx.send("Please provide the cog name to unload.")
return
@ -65,11 +59,9 @@ class CogManager(commands.Cog):
except Exception as e:
await ctx.send(f"Error unloading cog `{cog_name}`: {e}")
@requires_admin()
@commands.command()
async def disable(self, ctx, cog_name: str | None = None):
if ctx.author.id not in settings["bot"]["owner_ids"]:
await ctx.send("You do not have permission to use this command.")
return
if cog_name is None:
await ctx.send("Please provide the cog name to disable.")
return
@ -81,12 +73,9 @@ class CogManager(commands.Cog):
except Exception as e:
await ctx.send(f"Error unloading cog `{cog_name}`: {e}")
@requires_admin()
@commands.command()
async def reload(self, ctx, cog_name: str | None = None):
if ctx.author.id not in settings["bot"]["owner_ids"]:
await ctx.send("You do not have permission to use this command.")
return
if cog_name is None:
await ctx.send("Please provide the cog name to reload.")
return
@ -109,9 +98,13 @@ class CogManager(commands.Cog):
await ctx.send("No cogs are currently loaded.")
return
embed = discord.Embed(title="Loaded Cogs", description="Here is a list of all currently loaded cogs:")
embed = discord.Embed(
title="Loaded Cogs",
description="Here is a list of all currently loaded cogs:",
)
embed.add_field(name="Cogs", value="\n".join(cogs), inline=False)
await ctx.send(embed=embed)
async def setup(bot):
await bot.add_cog(CogManager(bot))

View file

@ -2,36 +2,40 @@ import random
import discord
from discord.ext import commands
class eightball(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.command()
async def eightball(self, ctx):
answer = random.choice([
"It is certain.",
"It is decidedly so.",
"Without a doubt.",
"Yes definitely.",
"You may rely on it.",
"As I see it, yes.",
"Most likely.",
"Outlook good.",
"Yes.",
"Signs point to yes.",
"Reply hazy, try again.",
"Ask again later.",
"Better not tell you now.",
"Cannot predict now.",
"Concentrate and ask again.",
"Don't count on it.",
"My reply is no.",
"My sources say no.",
"Outlook not so good.",
"Very doubtful."
])
answer = random.choice(
[
"It is certain.",
"It is decidedly so.",
"Without a doubt.",
"Yes definitely.",
"You may rely on it.",
"As I see it, yes.",
"Most likely.",
"Outlook good.",
"Yes.",
"Signs point to yes.",
"Reply hazy, try again.",
"Ask again later.",
"Better not tell you now.",
"Cannot predict now.",
"Concentrate and ask again.",
"Don't count on it.",
"My reply is no.",
"My sources say no.",
"Outlook not so good.",
"Very doubtful.",
]
)
await ctx.send(answer)
async def setup(bot):
await bot.add_cog(eightball(bot))

View file

@ -3,7 +3,9 @@ import discord.context_managers
from discord.ext import commands
import logging
from typing import Literal, get_args, cast
from modules.permission import requires_admin
from modules.settings import Settings as SettingsManager
settings_manager = SettingsManager()
settings = settings_manager.settings
@ -12,6 +14,7 @@ logger = logging.getLogger("goober")
AvailableModes = Literal["r", "s"]
class FileSync(commands.Cog):
def __init__(self, bot):
self.bot: discord.Client = bot
@ -19,24 +22,21 @@ class FileSync(commands.Cog):
self.peer_id = None
self.awaiting_file = False
@requires_admin()
@commands.command()
async def syncfile(self, ctx: commands.Context, mode: str, peer: discord.User):
if self.mode not in get_args(AvailableModes):
await ctx.send("Invalid mode, use 's' or 'r'.")
return
self.mode = cast(AvailableModes, mode.lower())
self.peer_id = peer.id
if ctx.author.id not in settings["bot"]["owner_ids"]:
await ctx.send("You don't have permission to execute this command.")
return
if self.mode == "s":
await ctx.send(f"<@{self.peer_id}> FILE_TRANSFER_REQUEST")
await ctx.send(file=discord.File("memory.json"))
await ctx.send("File sent in this channel.")
elif self.mode == "r":
await ctx.send("Waiting for incoming file...")
self.awaiting_file = True
@ -53,12 +53,11 @@ class FileSync(commands.Cog):
logger.info("Ping received. Awaiting file...")
if not message.attachments:
return
for attachment in message.attachments:
if not attachment.filename.endswith(".json"):
continue
filename = "received_memory.json"
with open(filename, "wb") as f:
await attachment.save(f)
@ -67,5 +66,6 @@ class FileSync(commands.Cog):
await message.channel.send("File received and saved.")
self.awaiting_file = False
async def setup(bot):
await bot.add_cog(FileSync(bot))

View file

@ -5,6 +5,7 @@ from PIL import Image, ImageEnhance, ImageFilter, ImageOps, ImageChops, ImageCol
import os, random, shutil, tempfile
import modules.keys as k
async def deepfryimage(path):
with Image.open(path).convert("RGB") as im:
# make it burn
@ -44,14 +45,17 @@ 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'))]
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))
@ -94,5 +98,6 @@ class whami(commands.Cog):
if temp_input and os.path.exists(temp_input):
os.remove(temp_input)
async def setup(bot):
await bot.add_cog(whami(bot))

View file

@ -6,11 +6,12 @@ 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
# 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
@ -34,7 +35,11 @@ class LastFmCog(commands.Cog):
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 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...")
@ -52,7 +57,11 @@ class LastFmCog(commands.Cog):
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 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):
@ -71,12 +80,13 @@ class LastFmCog(commands.Cog):
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')
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

View file

@ -2,10 +2,13 @@ from discord.ext import commands
import discord
from collections import defaultdict, Counter
import datetime
from modules.permission import requires_admin
from modules.settings import Settings as SettingsManager
settings_manager = SettingsManager()
settings = settings_manager.settings
class StatsCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
@ -32,38 +35,54 @@ class StatsCog(commands.Cog):
async def on_command(self, ctx):
self.command_usage[ctx.command.qualified_name] += 1
@requires_admin()
@commands.command()
async def spyware(self, ctx):
if ctx.author.id not in settings["bot"]["owner_ids"]:
return
uptime = datetime.datetime.utcnow() - self.start_time
hours_elapsed = max((uptime.total_seconds() / 3600), 1)
avg_per_hour = self.total_messages / hours_elapsed
if self.messages_per_hour:
peak_hour, peak_count = max(self.messages_per_hour.items(), key=lambda x: x[1])
peak_hour, peak_count = max(
self.messages_per_hour.items(), key=lambda x: x[1]
)
else:
peak_hour, peak_count = "N/A", 0
top_users = self.user_message_counts.most_common(5)
embed = discord.Embed(title="Community Stats", color=discord.Color.blue())
embed.add_field(name="Uptime", value=str(uptime).split('.')[0], inline=False)
embed.add_field(name="Total Messages", value=str(self.total_messages), inline=True)
embed.add_field(name="Active Users", value=str(len(self.active_users)), inline=True)
embed.add_field(name="Avg Messages/Hour", value=f"{avg_per_hour:.2f}", inline=True)
embed.add_field(name="Peak Hour (UTC)", value=f"{peak_hour}: {peak_count} messages", inline=True)
embed.add_field(name="Uptime", value=str(uptime).split(".")[0], inline=False)
embed.add_field(
name="Total Messages", value=str(self.total_messages), inline=True
)
embed.add_field(
name="Active Users", value=str(len(self.active_users)), inline=True
)
embed.add_field(
name="Avg Messages/Hour", value=f"{avg_per_hour:.2f}", inline=True
)
embed.add_field(
name="Peak Hour (UTC)",
value=f"{peak_hour}: {peak_count} messages",
inline=True,
)
top_str = "\n".join(
f"<@{user_id}>: {count} messages" for user_id, count in top_users
) or "No data"
top_str = (
"\n".join(f"<@{user_id}>: {count} messages" for user_id, count in top_users)
or "No data"
)
embed.add_field(name="Top Chatters", value=top_str, inline=False)
cmd_str = "\n".join(
f"{cmd}: {count}" for cmd, count in self.command_usage.most_common(5)
) or "No commands used yet"
cmd_str = (
"\n".join(
f"{cmd}: {count}" for cmd, count in self.command_usage.most_common(5)
)
or "No commands used yet"
)
embed.add_field(name="Top Commands", value=cmd_str, inline=False)
await ctx.send(embed=embed)
async def setup(bot):
await bot.add_cog(StatsCog(bot))

View file

@ -2,6 +2,7 @@ import discord
from discord.ext import commands
from discord import app_commands
class Ping(commands.Cog):
def __init__(self, bot):
self.bot = bot
@ -12,11 +13,15 @@ class Ping(commands.Cog):
exampleembed = discord.Embed(
title="Pong!!",
description="The Beretta fires fast and won't make you feel any better!",
color=discord.Color.blue()
color=discord.Color.blue(),
)
exampleembed.set_footer(
text=f"Requested by {interaction.user.name}",
icon_url=interaction.user.avatar.url,
)
exampleembed.set_footer(text=f"Requested by {interaction.user.name}", icon_url=interaction.user.avatar.url)
await interaction.followup.send(embed=exampleembed)
async def setup(bot):
await bot.add_cog(Ping(bot))

View file

@ -3,6 +3,7 @@ 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
@ -19,15 +20,22 @@ class songchange(commands.Cog):
@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}")
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}"))
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))

View file

@ -13,20 +13,22 @@ ready = True
MODEL_MATCH_STRING = r"[0-9]{2}_[0-9]{2}_[0-9]{4}-[0-9]{2}_[0-9]{2}"
try:
import tensorflow as tf
import tensorflow as tf
import keras
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential, load_model
from keras.layers import Embedding, LSTM, Dense
from keras.backend import clear_session
if tf.config.list_physical_devices('GPU'):
if tf.config.list_physical_devices("GPU"):
print("Using GPU acceleration")
elif tf.config.list_physical_devices('Metal'):
elif tf.config.list_physical_devices("Metal"):
print("Using Metal for macOS acceleration")
except ImportError:
print("ERROR: Failed to import TensorFlow. Ensure you have the correct dependencies:")
print(
"ERROR: Failed to import TensorFlow. Ensure you have the correct dependencies:"
)
print("tensorflow>=2.15.0")
print("For macOS (Apple Silicon): tensorflow-metal")
ready = False
@ -38,24 +40,39 @@ class TFCallback(keras.callbacks.Callback):
self.bot = bot
self.message = message
self.times = [time.time()]
async def send_message(self, message: str, description: str, **kwargs):
if "epoch" in kwargs:
self.times.append(time.time())
avg_epoch_time = np.mean(np.diff(self.times))
description = f"ETA: {round(avg_epoch_time)}s"
self.embed.add_field(name=f"<t:{round(time.time())}:t> - {message}", value=description, inline=False)
self.embed.add_field(
name=f"<t:{round(time.time())}:t> - {message}",
value=description,
inline=False,
)
await self.message.edit(embed=self.embed)
def on_train_end(self, logs=None):
self.bot.loop.create_task(self.send_message("Training stopped", "Training has been stopped."))
self.bot.loop.create_task(
self.send_message("Training stopped", "Training has been stopped.")
)
def on_epoch_begin(self, epoch, logs=None):
self.bot.loop.create_task(self.send_message(f"Starting epoch {epoch}", "This might take a while", epoch=True))
self.bot.loop.create_task(
self.send_message(
f"Starting epoch {epoch}", "This might take a while", epoch=True
)
)
def on_epoch_end(self, epoch, logs=None):
self.bot.loop.create_task(self.send_message(f"Epoch {epoch} ended", f"Accuracy: {round(logs.get('accuracy', 0.0), 4)}"))
self.bot.loop.create_task(
self.send_message(
f"Epoch {epoch} ended",
f"Accuracy: {round(logs.get('accuracy', 0.0), 4)}",
)
)
class Ai:
def __init__(self):
@ -63,11 +80,11 @@ class Ai:
if model_path:
self.__load_model(model_path)
self.is_loaded = model_path is not None
self.batch_size = 64
self.batch_size = 64
def generate_model_name(self):
return time.strftime('%d_%m_%Y-%H_%M', time.localtime())
return time.strftime("%d_%m_%Y-%H_%M", time.localtime())
def __load_model(self, model_path):
clear_session()
self.model = load_model(os.path.join(model_path, "model.h5"))
@ -81,7 +98,7 @@ class Ai:
with open("memory.json", "r") as f:
self.tokenizer.fit_on_texts(json.load(f))
self.is_loaded = True
def reload_model(self):
clear_session()
model_path = settings.get("model_path")
@ -90,9 +107,11 @@ class Ai:
self.is_loaded = True
async def run_async(self, func, bot, *args, **kwargs):
return await bot.loop.run_in_executor(None, functools.partial(func, *args, **kwargs))
return await bot.loop.run_in_executor(
None, functools.partial(func, *args, **kwargs)
)
class Learning(Ai):
def create_model(self, memory, epochs=2):
memory = memory[:2000]
@ -107,41 +126,58 @@ class Learning(Ai):
maxlen = max(map(len, X))
X = pad_sequences(X, maxlen=maxlen, padding="pre")
y = np.array(y)
model = Sequential([
Embedding(input_dim=VOCAB_SIZE, output_dim=128, input_length=maxlen),
LSTM(64),
Dense(VOCAB_SIZE, activation="softmax")
])
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])
model = Sequential(
[
Embedding(input_dim=VOCAB_SIZE, output_dim=128, input_length=maxlen),
LSTM(64),
Dense(VOCAB_SIZE, activation="softmax"),
]
)
model.compile(
optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
history = model.fit(X, y, epochs=epochs, batch_size=64, callbacks=[tf_callback])
self.save_model(model, tokenizer, history)
def save_model(self, model, tokenizer, history, name=None):
name = name or self.generate_model_name()
model_dir = os.path.join("models", name)
os.makedirs(model_dir, exist_ok=True)
with open(os.path.join(model_dir, "info.json"), "w") as f:
json.dump(history.history, f)
with open(os.path.join(model_dir, "tokenizer.pkl"), "wb") as f:
pickle.dump(tokenizer, f)
model.save(os.path.join(model_dir, "model.h5"))
class Generation(Ai):
def generate_sentence(self, word_amount, seed):
if not self.is_loaded:
return False
for _ in range(word_amount):
token_list = self.tokenizer.texts_to_sequences([seed])[0]
token_list = pad_sequences([token_list], maxlen=self.model.input_shape[1], padding="pre")
predicted_word_index = np.argmax(self.model.predict(token_list, verbose=0), axis=-1)[0]
output_word = next((w for w, i in self.tokenizer.word_index.items() if i == predicted_word_index), "")
token_list = pad_sequences(
[token_list], maxlen=self.model.input_shape[1], padding="pre"
)
predicted_word_index = np.argmax(
self.model.predict(token_list, verbose=0), axis=-1
)[0]
output_word = next(
(
w
for w, i in self.tokenizer.word_index.items()
if i == predicted_word_index
),
"",
)
seed += " " + output_word
return seed
VOCAB_SIZE = 100_000
settings = {}
@ -152,4 +188,4 @@ tf_callback = None
async def setup(bot):
await bot.add_cog(Tf(bot))
await bot.add_cog(Tf(bot))

View file

@ -5,10 +5,13 @@ from bs4 import BeautifulSoup
import json
import asyncio
from urllib.parse import urljoin
from modules.permission import requires_admin
from modules.settings import Settings as SettingsManager
settings_manager = SettingsManager()
settings = settings_manager.settings
class WebScraper(commands.Cog):
def __init__(self, bot):
self.bot = bot
@ -25,7 +28,7 @@ class WebScraper(commands.Cog):
def extract_sentences(self, text):
"""Extract sentences from text."""
sentences = text.split('.')
sentences = text.split(".")
return [sentence.strip() for sentence in sentences if sentence.strip()]
def save_to_json(self, sentences):
@ -52,7 +55,6 @@ class WebScraper(commands.Cog):
print("No data to undo.")
return False
data = data[:-1]
with open("memory.json", "w") as file:
@ -76,18 +78,14 @@ class WebScraper(commands.Cog):
soup = BeautifulSoup(html, "html.parser")
for paragraph in soup.find_all('p'):
for paragraph in soup.find_all("p"):
sentences = self.extract_sentences(paragraph.get_text())
self.save_to_json(sentences)
@requires_admin()
@commands.command()
async def start_scrape(self, ctx, start_url: str):
"""Command to start the scraping process."""
if ctx.author.id not in settings["bot"]["owner_ids"]:
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
@ -99,18 +97,16 @@ class WebScraper(commands.Cog):
await ctx.send("Scraping complete! Sentences saved to memory.json.")
@requires_admin()
@commands.command()
async def undo_scrape(self, ctx):
"""Command to undo the last scrape."""
if ctx.author.id not in settings["bot"]["owner_ids"]:
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))

View file

@ -14,6 +14,7 @@ from modules.globalvars import VERSION_URL
import sys
import subprocess
class GooberWeb(commands.Cog):
def __init__(self, bot):
self.bot = bot
@ -24,18 +25,20 @@ class GooberWeb(commands.Cog):
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.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()
@ -52,72 +55,82 @@ class GooberWeb(commands.Cog):
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
})
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
})
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
})
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)
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):
if self.site is None or self.runner is None:
return
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)
@ -126,62 +139,62 @@ class GooberWeb(commands.Cog):
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')
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}%",
@ -196,23 +209,29 @@ class GooberWeb(commands.Cog):
"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 "",
"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")
"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', '')
song = request.query.get("song", "")
if song:
await self.bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=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)
@ -224,36 +243,37 @@ class GooberWeb(commands.Cog):
async def read_env_file(self):
env_vars = {}
try:
with open('.env', 'r') as f:
with open(".env", "r") as f:
for line in f:
line = line.strip()
if not line or line.startswith('#') or '=' not in line:
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split('=', 1)
key, value = line.split("=", 1)
key = key.strip()
if key in ['splashtext', 'DISCORD_BOT_TOKEN']:
if key in ["splashtext", "DISCORD_BOT_TOKEN"]:
continue
env_vars[key] = value.strip('"\'')
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:
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('"')
if line.startswith("VERSION_URL"):
config_vars["VERSION_URL"] = (
line.split("=", 1)[1].strip().strip('"')
)
except FileNotFoundError:
pass
settings_html = """
<!DOCTYPE html>
<html>
@ -275,7 +295,7 @@ class GooberWeb(commands.Cog):
<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'>
@ -283,7 +303,7 @@ class GooberWeb(commands.Cog):
<input type='text' id='{key}' name='{key}' value='{value}'>
</div>
"""
for key, value in config_vars.items():
settings_html += f"""
<div class='form-group'>
@ -291,7 +311,7 @@ class GooberWeb(commands.Cog):
<input type='text' id='{key}' name='{key}' value='{value}'>
</div>
"""
settings_html += """
<button type='submit'>Save Settings</button>
</form>
@ -302,15 +322,15 @@ class GooberWeb(commands.Cog):
</body>
</html>
"""
return web.Response(text=settings_html, content_type='text/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:
with open(".env", "r") as f:
env_text = f.read()
except FileNotFoundError:
pass
@ -318,32 +338,42 @@ class GooberWeb(commands.Cog):
def replace_match(match):
key = match.group(1)
value = match.group(2)
if key in ['splashtext', 'DISCORD_BOT_TOKEN']:
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 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)
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')
with open(".env", "w") as f:
f.write(env_text.strip() + "\n")
if 'VERSION_URL' in data:
if "VERSION_URL" in data:
config_text = ""
try:
with open('config.py', 'r') as f:
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)
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')
with open("config.py", "w") as f:
f.write(config_text.strip() + "\n")
return aiohttp.web.Response(text="Settings updated successfully!")
@ -351,8 +381,12 @@ class GooberWeb(commands.Cog):
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>'
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}
@ -363,8 +397,12 @@ class GooberWeb(commands.Cog):
</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>'
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}
@ -375,11 +413,11 @@ class GooberWeb(commands.Cog):
</div>
"""
owner_id = stats.get('owner')
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))
@ -388,7 +426,6 @@ class GooberWeb(commands.Cog):
except:
pass
html_content = f"""
<!DOCTYPE html>
<html>
@ -869,15 +906,16 @@ class GooberWeb(commands.Cog):
</body>
</html>
"""
return web.Response(text=html_content, content_type='text/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))

View file

@ -1,6 +1,7 @@
import discord
from discord.ext import commands
class whoami(commands.Cog):
def __init__(self, bot):
self.bot = bot
@ -13,12 +14,13 @@ class whoami(commands.Cog):
embed = discord.Embed(
title="User Information",
description=f"Your User ID is: {user_id}\n"
f"Your username is: {username}\n"
f"Your nickname in this server is: <@{user_id}>",
color=discord.Color.blue()
f"Your username is: {username}\n"
f"Your nickname in this server is: <@{user_id}>",
color=discord.Color.blue(),
)
await ctx.send(embed=embed)
async def setup(bot):
await bot.add_cog(whoami(bot))