diff --git a/assets/cogs/filesharing.py b/assets/cogs/filesharing.py index ad6ac98..7229673 100644 --- a/assets/cogs/filesharing.py +++ b/assets/cogs/filesharing.py @@ -1,20 +1,17 @@ import discord from discord.ext import commands -from modules.globalvars import ownerid +from modules.permission import requires_admin class FileSync(commands.Cog): def __init__(self, bot): self.bot = bot self.mode = None self.peer_id = None self.awaiting_file = False - + @requires_admin() @commands.command() async def syncfile(self, ctx, mode: str, peer: discord.User): self.mode = mode.lower() self.peer_id = peer.id - if ctx.author.id != ownerid: - 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")) diff --git a/assets/cogs/internal/base_commands.py b/assets/cogs/internal/base_commands.py index 6be6405..ea1aaf8 100644 --- a/assets/cogs/internal/base_commands.py +++ b/assets/cogs/internal/base_commands.py @@ -1,14 +1,19 @@ import os +import platform import subprocess from typing import Dict, List import discord +from discord import Colour from discord.ext import commands import discord.ext import discord.ext.commands +from modules.globalvars import local_version from modules.volta.main import _ , set_language from modules.permission import requires_admin from modules.sentenceprocessing import send_message from modules.settings import instance as settings_manager +from modules.version import check_for_update + import requests settings = settings_manager.settings @@ -21,13 +26,21 @@ def get_git_origin_raw(): ) return result.stdout.strip() except Exception: - return "http://forgejo.expect.ovh/gooberinc/goober" + return "Failed to get git origin" class BaseCommands(commands.Cog): def __init__(self, bot): self.bot: discord.ext.commands.Bot = bot - + def get_git_origin_raw(): + try: + result = subprocess.run( + ["git", "config", "--get", "remote.origin.url"], + capture_output=True, text=True, check=True + ) + return result.stdout.strip() + except Exception: + return "https://forgejo.expect.ovh/gooberinc/goober" # fallback if git fails @commands.command() async def help(self, ctx: commands.Context) -> None: @@ -111,19 +124,13 @@ class BaseCommands(commands.Cog): @commands.command() async def about(self, ctx: commands.Context) -> None: - embed: discord.Embed = discord.Embed( - title=f"{_('command_about_embed_title')}", - description="", - color=discord.Colour(0x000000), - ) - - embed.add_field( - name=_('command_about_embed_field1'), - value=settings['name'], - inline=False, - ) - - embed.add_field(name="Git", value=get_git_origin_raw()) + latest_version: str = check_for_update(slient=True) + embed: discord.Embed = discord.Embed(title=f"{(_('command_about_embed_title'))}", description="", color=Colour(0x000000)) + embed.add_field(name=f"{(_('command_about_embed_field1'))}", value=f"{settings['name']}", inline=False) + embed.add_field(name=f"{(_('command_about_embed_field2name'))}", value=f"{(_('command_about_embed_field2value')).format(local_version=local_version, latest_version=latest_version)}", inline=False) + embed.add_field(name=f"Git", value=get_git_origin_raw()) + embed.add_field(name=f"OS", value=platform.platform()) + await send_message(ctx, embed=embed) @commands.command() diff --git a/assets/cogs/cogmanager.py b/assets/cogs/internal/cogmanager.py similarity index 82% rename from assets/cogs/cogmanager.py rename to assets/cogs/internal/cogmanager.py index 017f021..1843eab 100644 --- a/assets/cogs/cogmanager.py +++ b/assets/cogs/internal/cogmanager.py @@ -1,18 +1,15 @@ import discord from discord.ext import commands -from modules.globalvars import ownerid +from modules.permission import requires_admin COG_PREFIX = "assets.cogs." class CogManager(commands.Cog): def __init__(self, bot): self.bot = bot - + @requires_admin() @commands.command() async def load(self, ctx, cog_name: str = None): - if ctx.author.id != ownerid: - 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 load.") return @@ -21,12 +18,9 @@ class CogManager(commands.Cog): await ctx.send(f"Loaded cog `{cog_name}` successfully.") 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): - if ctx.author.id != ownerid: - 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 @@ -35,12 +29,9 @@ class CogManager(commands.Cog): await ctx.send(f"Unloaded cog `{cog_name}` successfully.") 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): - if ctx.author.id != ownerid: - 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 diff --git a/assets/cogs/internal/markov.py b/assets/cogs/internal/markov.py index 56f319a..274c8d9 100644 --- a/assets/cogs/internal/markov.py +++ b/assets/cogs/internal/markov.py @@ -32,11 +32,12 @@ settings = settings_manager.settings class Markov(commands.Cog): def __init__(self, bot): self.bot: discord.ext.commands.Bot = bot - self.model: markovify.NewlineText + @requires_admin() @commands.command() async def retrain(self, ctx: discord.ext.commands.Context): + markov_model: Optional[markovify.Text] = load_markov_model() message_ref: discord.Message | None = await send_message( ctx, f"{_('command_markov_retrain')}" ) @@ -71,8 +72,8 @@ class Markov(commands.Cog): await ctx.send("Failed to retrain!") return False - self.model = model - save_markov_model(self.model) + markov_model = model + save_markov_model(markov_model) logger.debug(f"Completed retraining in {round(time.time() - start_time,3)}s") @@ -86,33 +87,25 @@ class Markov(commands.Cog): @commands.command() async def talk(self, ctx: commands.Context, sentence_size: int = 5) -> None: - if not self.model: - await send_message(ctx, f"{_('command_markovcommand_talk_insufficent_text')}") + markov_model: Optional[markovify.Text] = load_markov_model() + if markov_model is None: + await send_message(ctx, _("command_markovcommand_talk_insufficent_text")) return - response: str = "" + raw_sentence = None if sentence_size == 1: - response = ( - self.model.make_short_sentence(max_chars=100, tries=100) - or _('command_markovcommand_talk_generation_fail') - ) - + raw_sentence = markov_model.make_short_sentence(max_chars=100, tries=100) else: - response = improve_sentence_coherence( - self.model.make_sentence(tries=100, max_words=sentence_size) - or _('command_talk_generation_fail') - ) + raw_sentence = markov_model.make_sentence(tries=100, max_words=sentence_size) + print(raw_sentence) - cleaned_response: str = re.sub(r"[^\w\s]", "", response).lower() - coherent_response: str = rephrase_for_coherence(cleaned_response) + if random.random() < 0.9 and is_positive(raw_sentence): + gif_url = random.choice(settings["bot"]["misc"]["positive_gifs"]) + raw_sentence = f"{raw_sentence}\n[jif]({gif_url})" - if random.random() < 0.9 and is_positive(coherent_response): - gif_url: str = random.choice(settings["bot"]["misc"]["positive_gifs"]) + os.environ["gooberlatestgen"] = raw_sentence + await send_message(ctx, raw_sentence) - coherent_response = f"{coherent_response}\n[jif]({gif_url})" - - os.environ["gooberlatestgen"] = coherent_response - await send_message(ctx, coherent_response) async def setup(bot): diff --git a/assets/cogs/internal/permission.py b/assets/cogs/internal/permission.py index bdb7f42..cf03af7 100644 --- a/assets/cogs/internal/permission.py +++ b/assets/cogs/internal/permission.py @@ -15,18 +15,7 @@ class PermissionManager(commands.Cog): @commands.command() async def add_owner(self, ctx: commands.Context, member: discord.Member): settings["bot"]["owner_ids"].append(member.id) - settings_manager.add_admin_log_event( - { - "action": "add", - "author": ctx.author.id, - "change": "owner_ids", - "messageId": ctx.message.id, - "target": member.id, - } - ) - settings_manager.commit() - embed = discord.Embed( title="Permissions", description=f"Set {member.name} as an owner", @@ -40,15 +29,6 @@ class PermissionManager(commands.Cog): async def remove_owner(self, ctx: commands.Context, member: discord.Member): try: settings["bot"]["owner_ids"].remove(member.id) - settings_manager.add_admin_log_event( - { - "action": "del", - "author": ctx.author.id, - "change": "owner_ids", - "messageId": ctx.message.id, - "target": member.id, - } - ) settings_manager.commit() except ValueError: await ctx.send("User is not an owner!") @@ -90,15 +70,6 @@ class PermissionManager(commands.Cog): async def unblacklist_user(self, ctx: commands.Context, member: discord.Member): try: settings["bot"]["blacklisted_users"].remove(member.id) - settings_manager.add_admin_log_event( - { - "action": "del", - "author": ctx.author.id, - "change": "blacklisted_users", - "messageId": ctx.message.id, - "target": member.id, - } - ) settings_manager.commit() except ValueError: diff --git a/main.py b/main.py index 4437ffb..45d8595 100644 --- a/main.py +++ b/main.py @@ -31,7 +31,8 @@ import logging from modules.settings import Settings as SettingsManager from modules.permission import requires_admin from modules.volta.main import _ - +from modules.version import check_for_update +check_for_update() logger = logging.getLogger("goober") logger.setLevel(logging.DEBUG) @@ -85,15 +86,18 @@ class MessageMetadata(TypedDict): # Constants with type hints positive_gifs: List[str] = settings["bot"]["misc"]["positive_gifs"] +name = settings["name"] currenthash: str = "" launched: bool = False slash_commands_enabled: bool = False + # Set up Discord bot intents and create bot instance intents: discord.Intents = discord.Intents.default() intents.messages = True +intents.presences = True +intents.members = True intents.message_content = True - bot: commands.Bot = commands.Bot( command_prefix=settings["bot"]["prefix"], intents=intents, @@ -104,11 +108,6 @@ bot: commands.Bot = commands.Bot( # Load memory and Markov model for text generation memory: List[str | Dict[Literal["_meta"], MessageMetadata]] = load_memory() -markov_model: Optional[markovify.Text] = load_markov_model() -if not markov_model: - logger.error(_('markov_model_not_found')) - memory = load_memory() - markov_model = train_markov_model(memory) generated_sentences: Set[str] = set() used_words: Set[str] = set() @@ -150,7 +149,7 @@ async def on_ready() -> None: synced: List[discord.app_commands.AppCommand] = await bot.tree.sync() logger.info(f"{_('synced_commands')} {len(synced)} {_('synced_commands2')}") - logger.info(_('started')) + logger.info(_('started').format(name=name)) except discord.errors.Forbidden as perm_error: logger.error(f"Permission error while syncing commands: {perm_error}") @@ -174,34 +173,6 @@ async def on_ready() -> None: launched = True bot.remove_command('help') -# Command: Show help information -@bot.hybrid_command(description=f"{(_('command_desc_help'))}") -async def help(ctx: commands.Context) -> None: - embed: discord.Embed = discord.Embed( - title=f"{(_('command_help_embed_title'))}", - description=f"{(_('command_help_embed_desc'))}", - color=Colour(0x000000) - ) - - command_categories: Dict[str, List[str]] = { - f"{(_('command_help_categories_general'))}": ["mem", "talk", "about", "ping", "impact", "demotivator", "help"], - f"{(_('command_help_categories_admin'))}": ["stats", "retrain", "setlanguage"] - } - - custom_commands: List[str] = [] - for cog_name, cog in bot.cogs.items(): - for command in cog.get_commands(): - if command.name not in command_categories[f"{(_('command_help_categories_general'))}"] and command.name not in command_categories[f"{(_('command_help_categories_admin'))}"]: - custom_commands.append(command.name) - - if custom_commands: - embed.add_field(name=f"{(_('command_help_categories_custom'))}", value="\n".join([f"{PREFIX}{command}" for command in custom_commands]), inline=False) - - for category, commands_list in command_categories.items(): - commands_in_category: str = "\n".join([f"{PREFIX}{command}" for command in commands_list]) - embed.add_field(name=category, value=commands_in_category, inline=False) - - await send_message(ctx, embed=embed) @bot.event async def on_command_error(ctx: commands.Context, error: commands.CommandError) -> None: @@ -226,7 +197,7 @@ async def on_command_error(ctx: commands.Context, error: commands.CommandError) # Event: Called on every message @bot.event async def on_message(message: discord.Message) -> None: - global memory, markov_model + global memory EMOJIS = [ "\U0001f604", "\U0001f44d", @@ -250,12 +221,6 @@ async def on_message(message: discord.Message) -> None: await bot.process_commands(message) return - if ( - profanity.contains_profanity(message.content) - and settings["bot"]["misc"]["block_profanity"] - ): - return - if message.content: if not settings["bot"]["user_training"]: return diff --git a/modules/globalvars.py b/modules/globalvars.py index e541c7e..3225605 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -1,80 +1,71 @@ import os import platform +from typing import Callable, List +from dotenv import load_dotenv import pathlib import subprocess -from dotenv import load_dotenv -import discord -from discord import Colour, Embed, File, Interaction, Message -from discord.ext import commands -from discord import app_commands -from discord.abc import Messageable -env_path = pathlib.Path(__file__).parent.parent / '.env' +def get_git_branch(): + try: + branch = ( + subprocess.check_output( + ["git", "rev-parse", "--abbrev-ref", "HEAD"], stderr=subprocess.DEVNULL + ) + .decode("utf-8") + .strip() + ) + return branch + except subprocess.CalledProcessError: + return None + + +env_path = pathlib.Path(__file__).parent.parent / ".env" load_dotenv(dotenv_path=env_path) -# ANSI colors +available_cogs: Callable[[], List[str]] = lambda: [ + file[:-3] for file in os.listdir("assets/cogs") if file.endswith(".py") +] + ANSI = "\033[" RED = f"{ANSI}31m" GREEN = f"{ANSI}32m" YELLOW = f"{ANSI}33m" PURPLE = f"{ANSI}35m" -DEBUG = f"{ANSI}1;30m" +DEBUG = f"{ANSI}90m" RESET = f"{ANSI}0m" VERSION_URL = "https://raw.githubusercontent.com/gooberinc/version/main" -UPDATE_URL = f"{VERSION_URL}/latest_version.json" +UPDATE_URL = VERSION_URL + "/latest_version.json" print(UPDATE_URL) - LOCAL_VERSION_FILE = "current_version.txt" -MEMORY_FILE = "memory.json" -MEMORY_LOADED_FILE = "MEMORY_LOADED" # used in markov module -local_version = "3.0.0" -latest_version = "0.0.0" -os.environ['gooberlocal_version'] = local_version -def get_git_branch() -> str | None: - try: - return subprocess.check_output( - ["git", "rev-parse", "--abbrev-ref", "HEAD"], - stderr=subprocess.DEVNULL - ).decode().strip() - except subprocess.CalledProcessError: - return None +# TOKEN = os.getenv("DISCORDBOTTOKEN", "0") +# PREFIX = os.getenv("BOTPREFIX", "g.") +# PING_LINE = os.getenv("PINGLINE") +# CHECKS_DISABLED = os.getenv("CHECKSDISABLED") +# LOCALE = os.getenv("LOCALE", "en") +# BLACKLISTED_USERS = os.getenv("BLACKLISTEDUSERS", "").split(",") +# USERTRAIN_ENABLED = os.getenv("USERTRAINENABLED", "true").lower() == "true" +# NAME = os.getenv("NAME") +# MEMORY_FILE = "memory.json" +# MEMORY_LOADED_FILE = "MEMORY_LOADED" # is this still even used?? okay just checked its used in the markov module +# ALIVEPING = os.getenv("ALIVEPING") +# AUTOUPDATE = os.getenv("AUTOUPDATE") +# REACT = os.getenv("REACT") -branch = get_git_branch() -beta = branch != "main" if branch else True +# gooberTOKEN = os.getenv("GOOBERTOKEN") +# splashtext = os.getenv("SPLASHTEXT") +# ownerid = int(os.getenv("OWNERID", "0")) +# showmemenabled = os.getenv("SHOWMEMENABLED") -TOKEN = os.getenv("DISCORDBOTTOKEN", "0") -PREFIX = os.getenv("BOTPREFIX", "g.") -PING_LINE = os.getenv("PINGLINE") -CHECKS_DISABLED = os.getenv("CHECKSDISABLED") -LOCALE = os.getenv("LOCALE", "en") -gooberTOKEN = os.getenv("GOOBERTOKEN") -splashtext = os.getenv("SPLASHTEXT") -ownerid = int(os.getenv("OWNERID", "0")) -status = os.getenv("STATUS") -showmemenabled = os.getenv("SHOWMEMENABLED") -BLACKLISTED_USERS = os.getenv("BLACKLISTEDUSERS", "").split(",") -USERTRAIN_ENABLED = os.getenv("USERTRAINENABLED", "true").lower() == "true" -NAME = os.getenv("NAME") -ALIVEPING = os.getenv("ALIVEPING") -AUTOUPDATE = os.getenv("AUTOUPDATE") -song = os.getenv("SONG") -REACT = os.getenv("REACT") - -intents = discord.Intents.default() -intents.messages = True -intents.presences = True -intents.members = True -intents.message_content = True - -bot = commands.Bot( - command_prefix=PREFIX, - intents=intents, - allowed_mentions=discord.AllowedMentions( - everyone=False, roles=False, users=False, replied_user=True - ) -) +# IGNOREWARNING = False # is this either??? i don't think so? +# song = os.getenv("song") +arch = platform.machine() +slash_commands_enabled = True # 100% broken, its a newer enough version so its probably enabled by default.... fix this at somepoint or hard code it in goober central code launched = False +latest_version = "0.0.0" +local_version = "3.0.0" +os.environ["gooberlocal_version"] = local_version +beta = get_git_branch() == "dev" \ No newline at end of file diff --git a/modules/markovmemory.py b/modules/markovmemory.py index 85ad6c1..d7ce6cd 100644 --- a/modules/markovmemory.py +++ b/modules/markovmemory.py @@ -3,55 +3,75 @@ import json import markovify import pickle from modules.globalvars import * -from modules.volta.main import _ import logging +from modules.volta.main import _ +from modules.settings import instance as settings_manager + +settings = settings_manager.settings + + logger = logging.getLogger("goober") -def get_file_info(file_path: str) -> dict: + +# Get file size and line count for a given file path +def get_file_info(file_path): try: file_size = os.path.getsize(file_path) with open(file_path, "r") as f: lines = f.readlines() - return { - "file_size_bytes": file_size, - "line_count": len(lines) - } + return {"file_size_bytes": file_size, "line_count": len(lines)} except Exception as e: return {"error": str(e)} -def load_memory() -> list: - try: - with open(MEMORY_FILE, "r") as f: - return json.load(f) - except FileNotFoundError: - return [] -def save_memory(memory: list) -> None: - with open(MEMORY_FILE, "w") as f: +# Load memory data from file, or use default dataset if not loaded yet +def load_memory(): + data = [] + + # Try to load data from MEMORY_FILE + try: + with open(settings["bot"]["active_memory"], "r") as f: + data = json.load(f) + except FileNotFoundError: + pass + + return data + + +# Save memory data to MEMORY_FILE +def save_memory(memory): + with open(settings["bot"]["active_memory"], "w") as f: json.dump(memory, f, indent=4) -def train_markov_model(memory: list, additional_data: list = None): - lines = [line for line in (memory or []) if isinstance(line, str)] - if additional_data: - lines.extend(line for line in additional_data if isinstance(line, str)) - - if not lines: +def train_markov_model(memory, additional_data=None) -> markovify.NewlineText | None: + if not memory: return None - text = "\n".join(lines) - return markovify.NewlineText(text, state_size=2) + filtered_memory = [line for line in memory if isinstance(line, str)] + if additional_data: + filtered_memory.extend( + line for line in additional_data if isinstance(line, str) + ) -def save_markov_model(model, filename: str = 'markov_model.pkl') -> None: - with open(filename, 'wb') as f: + if not filtered_memory: + return None + + text = "\n".join(filtered_memory) + model = markovify.NewlineText(text, state_size=2) + return model + +def save_markov_model(model, filename="markov_model.pkl"): + with open(filename, "wb") as f: pickle.dump(model, f) + logger.info(f"Markov model saved to {filename}.") -def load_markov_model(filename: str = 'markov_model.pkl'): +def load_markov_model(filename="markov_model.pkl"): try: - with open(filename, 'rb') as f: + with open(filename, "rb") as f: model = pickle.load(f) logger.info(f"{_('model_loaded')} {filename}.{RESET}") return model except FileNotFoundError: logger.error(f"{filename} {_('not_found')}{RESET}") - return None + return None \ No newline at end of file diff --git a/modules/permission.py b/modules/permission.py index 3d87d07..5434c61 100644 --- a/modules/permission.py +++ b/modules/permission.py @@ -19,11 +19,11 @@ class PermissionError(Exception): def requires_admin(): async def wrapper(ctx: discord.ext.commands.Context): + print(ctx.author.id) if ctx.author.id not in settings["bot"]["owner_ids"]: - await ctx.send( - "You don't have the necessary permissions to run this command!" - ) - return False + await ctx.send("You don't have the necessary permissions to run this command!") + return + command = ctx.command if not command: diff --git a/modules/prestartchecks.py b/modules/prestartchecks.py index d13e7bf..cd9fd72 100644 --- a/modules/prestartchecks.py +++ b/modules/prestartchecks.py @@ -1,4 +1,5 @@ from modules.globalvars import * +from modules.settings import Settings as SettingsManager from modules.volta.main import _, check_missing_translations import time import os @@ -14,6 +15,13 @@ import logging logger = logging.getLogger("goober") +settings_manager = SettingsManager() +settings = settings_manager.settings +MEMORY_FILE = settings["bot"]["active_memory"] +with open(settings["splash_text_loc"], "r", encoding="UTF-8") as f: + splash_text = "".join(f.readlines()) + + # import shutil psutilavaliable = True try: @@ -210,8 +218,8 @@ def presskey2skip(timeout): termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) beta = beta def start_checks(): - if CHECKS_DISABLED == "True": - logger.warning(f"{(_('checks_disabled'))}") + if settings["disable_checks"]: + logger.warning(f"{_('checks_disabled')}") return logger.info(_('running_prestart_checks')) check_for_model() @@ -233,5 +241,4 @@ def start_checks(): pass logger.info(_('continuing_in_seconds').format(seconds=5)) presskey2skip(timeout=5) - os.system('cls' if os.name == 'nt' else 'clear') - print(splashtext) \ No newline at end of file + os.system('cls' if os.name == 'nt' else 'clear') \ No newline at end of file diff --git a/modules/unhandledexception.py b/modules/unhandledexception.py index 93a3e73..c1dd5f6 100644 --- a/modules/unhandledexception.py +++ b/modules/unhandledexception.py @@ -1,6 +1,6 @@ import sys import traceback -from modules.globalvars import RED, RESET, splashtext +from modules.globalvars import RED, RESET from modules.volta.main import _ def handle_exception(exc_type, exc_value, exc_traceback, *, context=None):