From fdf78f8d13b7c3a25755bfe4bf353130b01a67b2 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 01:18:51 +0200 Subject: [PATCH 01/84] quick update not sure if it even works --- modules/globalvars.py | 2 +- modules/version.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index cb44ae5..1226cc3 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -37,7 +37,7 @@ 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 = "2.0.0" +local_version = "2.0.1" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks diff --git a/modules/version.py b/modules/version.py index 9fafcac..ce38d98 100644 --- a/modules/version.py +++ b/modules/version.py @@ -4,6 +4,8 @@ import requests import subprocess import sys +launched = False + # Run a shell command and return its output def run_cmd(cmd): result = subprocess.run(cmd, shell=True, capture_output=True, text=True) @@ -17,6 +19,8 @@ def is_remote_ahead(branch='main', remote='origin'): # Automatically update the local repository if the remote is ahead def auto_update(branch='main', remote='origin'): + if launched == True: + return if launched == True: print(_("already_started")) return @@ -48,7 +52,7 @@ def get_latest_version_info(): def check_for_update(): if ALIVEPING != "True": return - global latest_version, local_version + global latest_version, local_version, launched latest_version_info = get_latest_version_info() if not latest_version_info: @@ -80,4 +84,5 @@ def check_for_update(): elif local_version == latest_version: print(f"{GREEN}{_('latest_version')} {local_version}{RESET}") print(f"{_('latest_version2').format(VERSION_URL=VERSION_URL)}\n\n") + launched = True return latest_version \ No newline at end of file From c23281980c14dfce011314901ed686dd86099ff8 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 12:13:25 +0200 Subject: [PATCH 02/84] moved missing translations check into volta --- modules/prestartchecks.py | 27 +---------------------- modules/volta/main.py | 45 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/modules/prestartchecks.py b/modules/prestartchecks.py index 803c1ca..98abd23 100644 --- a/modules/prestartchecks.py +++ b/modules/prestartchecks.py @@ -1,6 +1,5 @@ from modules.globalvars import * -from modules.volta.main import _, get_translation, load_translations, set_language, translations - +from modules.volta.main import _, check_missing_translations import time import os import sys @@ -27,30 +26,6 @@ def iscloned(): print(f"{RED}{(_('not_cloned'))}{RESET}") sys.exit(1) -def check_missing_translations(): - if LOCALE == "en": - print("Locale is English, skipping missing key check.") - return - load_translations() - - en_keys = set(translations.get("en", {}).keys()) - locale_keys = set(translations.get(LOCALE, {}).keys()) - - missing_keys = en_keys - locale_keys - total_keys = len(en_keys) - missing_count = len(missing_keys) - - if missing_count > 0: - percent_missing = (missing_count / total_keys) * 100 - print(f"{YELLOW}Warning: {missing_count}/{total_keys} keys missing in locale '{LOCALE}' ({percent_missing:.1f}%)!{RESET}") - for key in sorted(missing_keys): - print(f" - {key}") - time.sleep(5) - else: - print("All translation keys present for locale:", LOCALE) - - - def get_stdlib_modules(): stdlib_path = pathlib.Path(sysconfig.get_paths()['stdlib']) modules = set() diff --git a/modules/volta/main.py b/modules/volta/main.py index 677402a..a90ec53 100644 --- a/modules/volta/main.py +++ b/modules/volta/main.py @@ -24,6 +24,7 @@ working_dir = pathlib.Path.cwd() EXCLUDE_DIRS = {'.git', '__pycache__'} locales_dirs = [] +ENGLISH_MISSING = False def find_locales_dirs(base_path): found = [] @@ -79,14 +80,53 @@ def reload_if_changed(): translations.pop(lang_code, None) def set_language(lang: str): - global LOCALE + global LOCALE, ENGLISH_MISSING if lang in translations: LOCALE = lang else: print(f"[VOLTA] {RED}Language '{lang}' not found, defaulting to 'en'{RESET}") - LOCALE = "en" + if "en" in translations: + LOCALE = "en" + else: + print(f"[VOLTA] {RED}The English translations cannot be found! No fallback available.{RESET}") + ENGLISH_MISSING = True + +def check_missing_translations(): + global LOCALE, ENGLISH_MISSING + load_translations() + if "en" not in translations: + print(f"[VOLTA] {RED}English translations (en.json) missing from assets/locales. Exiting.{RESET}") + ENGLISH_MISSING = True + return + if LOCALE == "en": + print("Locale is English, skipping missing key check.") + return + + + en_keys = set(translations.get("en", {}).keys()) + locale_keys = set(translations.get(LOCALE, {}).keys()) + + missing_keys = en_keys - locale_keys + total_keys = len(en_keys) + missing_count = len(missing_keys) + + if missing_count > 0: + percent_missing = (missing_count / total_keys) * 100 + if percent_missing == 100: + print(f"[VOLTA] {RED}Warning: All keys are missing in locale '{LOCALE}'! Defaulting back to en{RESET}") + set_language("en") + elif percent_missing > 0: + print(f"{YELLOW}Warning: {missing_count}/{total_keys} keys missing in locale '{LOCALE}' ({percent_missing:.1f}%)!{RESET}") + for key in sorted(missing_keys): + print(f" - {key}") + time.sleep(2) + else: + print("All translation keys present for locale:", LOCALE) + def get_translation(lang: str, key: str): + if ENGLISH_MISSING: + return f"[VOLTA] {RED}No fallback available!{RESET}" lang_translations = translations.get(lang, {}) if key in lang_translations: return lang_translations[key] @@ -101,3 +141,4 @@ load_translations() watchdog_thread = threading.Thread(target=reload_if_changed, daemon=True) watchdog_thread.start() + From 0a044f349b6563168cb4fc8d7ff0572474a594e6 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 12:24:14 +0200 Subject: [PATCH 03/84] up to date with volta --- modules/globalvars.py | 2 +- modules/volta/main.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index 1226cc3..9431950 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -40,5 +40,5 @@ latest_version = "0.0.0" local_version = "2.0.1" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") -beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks +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 \ No newline at end of file diff --git a/modules/volta/main.py b/modules/volta/main.py index a90ec53..53f1224 100644 --- a/modules/volta/main.py +++ b/modules/volta/main.py @@ -130,6 +130,9 @@ def get_translation(lang: str, key: str): lang_translations = translations.get(lang, {}) if key in lang_translations: return lang_translations[key] + else: + if key not in translations.get("en", {}): + return f"[VOLTA] {RED}Missing key: '{key}' in en.json!{RESET}" fallback = translations.get("en", {}).get(key, key) print(f"[VOLTA] {RED}Missing key: '{key}' in language '{lang}', falling back to: '{fallback}'{RESET}") # yeah probably print this return fallback From c732049d79bf7f381dd0779528636741cf1fd7c0 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 12:38:32 +0200 Subject: [PATCH 04/84] up to date with volta --- modules/volta/main.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/modules/volta/main.py b/modules/volta/main.py index 53f1224..535cea6 100644 --- a/modules/volta/main.py +++ b/modules/volta/main.py @@ -25,7 +25,9 @@ EXCLUDE_DIRS = {'.git', '__pycache__'} locales_dirs = [] ENGLISH_MISSING = False - +FALLBACK_LOCALE = "en" +if os.getenv("fallback_locale"): + FALLBACK_LOCALE = os.getenv("fallback_locale") def find_locales_dirs(base_path): found = [] for root, dirs, files in os.walk(base_path): @@ -85,17 +87,17 @@ def set_language(lang: str): LOCALE = lang else: print(f"[VOLTA] {RED}Language '{lang}' not found, defaulting to 'en'{RESET}") - if "en" in translations: - LOCALE = "en" + if FALLBACK_LOCALE in translations: + LOCALE = FALLBACK_LOCALE else: - print(f"[VOLTA] {RED}The English translations cannot be found! No fallback available.{RESET}") + print(f"[VOLTA] {RED}The fallback translations cannot be found! No fallback available.{RESET}") ENGLISH_MISSING = True def check_missing_translations(): global LOCALE, ENGLISH_MISSING load_translations() - if "en" not in translations: - print(f"[VOLTA] {RED}English translations (en.json) missing from assets/locales. Exiting.{RESET}") + if FALLBACK_LOCALE not in translations: + print(f"[VOLTA] {RED}Fallback translations ({FALLBACK_LOCALE}.json) missing from assets/locales. Exiting.{RESET}") ENGLISH_MISSING = True return if LOCALE == "en": @@ -113,8 +115,8 @@ def check_missing_translations(): if missing_count > 0: percent_missing = (missing_count / total_keys) * 100 if percent_missing == 100: - print(f"[VOLTA] {RED}Warning: All keys are missing in locale '{LOCALE}'! Defaulting back to en{RESET}") - set_language("en") + print(f"[VOLTA] {RED}Warning: All keys are missing in locale '{LOCALE}'! Defaulting back to {FALLBACK_LOCALE}{RESET}") + set_language(FALLBACK_LOCALE) elif percent_missing > 0: print(f"{YELLOW}Warning: {missing_count}/{total_keys} keys missing in locale '{LOCALE}' ({percent_missing:.1f}%)!{RESET}") for key in sorted(missing_keys): @@ -131,10 +133,10 @@ def get_translation(lang: str, key: str): if key in lang_translations: return lang_translations[key] else: - if key not in translations.get("en", {}): - return f"[VOLTA] {RED}Missing key: '{key}' in en.json!{RESET}" - fallback = translations.get("en", {}).get(key, key) - print(f"[VOLTA] {RED}Missing key: '{key}' in language '{lang}', falling back to: '{fallback}'{RESET}") # yeah probably print this + if key not in translations.get(FALLBACK_LOCALE, {}): + return f"[VOLTA] {RED}Missing key: '{key}' in {FALLBACK_LOCALE}.json!{RESET}" + fallback = translations.get(FALLBACK_LOCALE, {}).get(key, key) + print(f"[VOLTA] {RED}Missing key: '{key}' in language '{lang}', falling back to: '{fallback}' using {FALLBACK_LOCALE}.json{RESET}") # yeah probably print this return fallback def _(key: str) -> str: @@ -145,3 +147,5 @@ load_translations() watchdog_thread = threading.Thread(target=reload_if_changed, daemon=True) watchdog_thread.start() +if __name__ == '__main__': + print("Volta should not be run directly! Please use it as a module..") From 6637ece3d8029ae0f31fcafd4b9bf0a3095166df Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 13:05:50 +0200 Subject: [PATCH 05/84] Please enter the commit message for your changes. Lines starting with '#' will be ignored, and an empty message aborts the commit. --- assets/locales/en.json | 1 + assets/locales/it.json | 1 + bot.py | 11 ++++++++++- modules/globalvars.py | 2 +- modules/version.py | 4 +--- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/assets/locales/en.json b/assets/locales/en.json index c169c95..cca8867 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -112,6 +112,7 @@ "command_ran": "Info: {message.author.name} ran {message.content}", "command_ran_s": "Info: {interaction.user} ran ", "command_desc_ping": "ping", + "command_desc_setlang": "Set a new language for the bot (temporarily)", "command_ping_embed_desc": "Bot Latency:", "command_ping_footer": "Requested by", "command_about_desc": "about", diff --git a/assets/locales/it.json b/assets/locales/it.json index b9f788d..ebf28aa 100644 --- a/assets/locales/it.json +++ b/assets/locales/it.json @@ -113,6 +113,7 @@ "command_ran": "Info: {message.author.name} ha eseguito {message.content}", "command_ran_s": "Info: {interaction.user} ha eseguito ", "command_desc_ping": "ping", + "command_desc_setlang": "Imposta una nuova lingua per il bot (temporaneamente)", "command_ping_embed_desc": "Latenza del bot:", "command_ping_footer": "Richiesto da", "command_about_desc": "informazioni", diff --git a/bot.py b/bot.py index 9a745bd..cce5e21 100644 --- a/bot.py +++ b/bot.py @@ -23,6 +23,7 @@ import requests import discord from discord.ext import commands +from discord import app_commands from discord import Colour, Embed, File, Interaction, Message from discord.abc import Messageable @@ -30,7 +31,7 @@ from better_profanity import profanity from discord.ext import commands from modules.central import ping_server -from modules.volta.main import _ +from modules.volta.main import _, set_language from modules.markovmemory import * from modules.version import * from modules.sentenceprocessing import * @@ -353,6 +354,14 @@ async def help(ctx: commands.Context) -> None: await send_message(ctx, embed=embed) +@bot.hybrid_command(description=f"{(_('command_desc_setlang'))}") +@app_commands.describe(locale="Choose your language") +async def setlanguage(ctx: commands.Context, locale: str) -> None: + await ctx.defer() + set_language(locale) + + await ctx.send(":thumbsup:") + # Event: Called on every message @bot.event async def on_message(message: discord.Message) -> None: diff --git a/modules/globalvars.py b/modules/globalvars.py index 9431950..56cb752 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -37,7 +37,7 @@ 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 = "2.0.1" +local_version = "2.0.2" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") 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 diff --git a/modules/version.py b/modules/version.py index ce38d98..f876123 100644 --- a/modules/version.py +++ b/modules/version.py @@ -19,8 +19,6 @@ def is_remote_ahead(branch='main', remote='origin'): # Automatically update the local repository if the remote is ahead def auto_update(branch='main', remote='origin'): - if launched == True: - return if launched == True: print(_("already_started")) return @@ -77,7 +75,7 @@ def check_for_update(): print(f"{YELLOW}{_('new_version').format(latest_version=latest_version, local_version=local_version)}{RESET}") print(f"{YELLOW}{_('changelog').format(VERSION_URL=VERSION_URL)}{RESET}") auto_update() - elif local_version > latest_version and beta == True: + elif beta == True: print(f"{YELLOW}You are running an \"unstable\" version of Goober, do not expect it to work properly.\nVersion {local_version}{RESET}") elif local_version > latest_version: print(f"{YELLOW}{_('modification_warning')}{RESET}") From 136e2e43506276a6fa596e542b731e897fbe6849 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 16:26:37 +0200 Subject: [PATCH 06/84] add the new commands to the help --- bot.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bot.py b/bot.py index cce5e21..7c2050c 100644 --- a/bot.py +++ b/bot.py @@ -335,8 +335,8 @@ async def help(ctx: commands.Context) -> None: ) command_categories: Dict[str, List[str]] = { - f"{(_('command_help_categories_general'))}": ["mem", "talk", "about", "ping", "image"], - f"{(_('command_help_categories_admin'))}": ["stats", "retrain"] + f"{(_('command_help_categories_general'))}": ["mem", "talk", "about", "ping", "impact", "demotivator", "help"], + f"{(_('command_help_categories_admin'))}": ["stats", "retrain", "setlanguage"] } custom_commands: List[str] = [] @@ -357,9 +357,11 @@ async def help(ctx: commands.Context) -> None: @bot.hybrid_command(description=f"{(_('command_desc_setlang'))}") @app_commands.describe(locale="Choose your language") async def setlanguage(ctx: commands.Context, locale: str) -> None: + if ctx.author.id != ownerid: + await ctx.send(":thumbsdown:") + return await ctx.defer() set_language(locale) - await ctx.send(":thumbsup:") # Event: Called on every message From 378aca4b0e5575e237b9926ead047e97ea4e7800 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 16:54:19 +0200 Subject: [PATCH 07/84] update with volta --- modules/prestartchecks.py | 1 + modules/volta/main.py | 24 ++++++++++++++++++++---- requirements.txt | 1 + 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/modules/prestartchecks.py b/modules/prestartchecks.py index 98abd23..f5ce96a 100644 --- a/modules/prestartchecks.py +++ b/modules/prestartchecks.py @@ -48,6 +48,7 @@ def check_requirements(): PACKAGE_ALIASES = { "discord": "discord.py", "better_profanity": "better-profanity", + "dotenv": "python-dotenv" } parent_dir = os.path.dirname(os.path.abspath(__file__)) diff --git a/modules/volta/main.py b/modules/volta/main.py index 535cea6..bb98e0a 100644 --- a/modules/volta/main.py +++ b/modules/volta/main.py @@ -39,6 +39,22 @@ def find_locales_dirs(base_path): dirs.remove('locales') return found +def find_dotenv(start_path: pathlib.Path) -> pathlib.Path | None: + current = start_path.resolve() + while current != current.parent: + candidate = current / ".env" + if candidate.exists(): + return candidate + current = current.parent + return None + +env_path = find_dotenv(pathlib.Path(__file__).parent) +if env_path: + load_dotenv(dotenv_path=env_path) + print(f"[VOLTA] {GREEN}Loaded .env from {env_path}{RESET}") +else: + print(f"[VOLTA] {YELLOW}No .env file found from {__file__} upwards.{RESET}") + locales_dirs.extend(find_locales_dirs(module_dir)) if working_dir != module_dir: locales_dirs.extend(find_locales_dirs(working_dir)) @@ -97,7 +113,7 @@ def check_missing_translations(): global LOCALE, ENGLISH_MISSING load_translations() if FALLBACK_LOCALE not in translations: - print(f"[VOLTA] {RED}Fallback translations ({FALLBACK_LOCALE}.json) missing from assets/locales. Exiting.{RESET}") + print(f"[VOLTA] {RED}Fallback translations ({FALLBACK_LOCALE}.json) missing from assets/locales.{RESET}") ENGLISH_MISSING = True return if LOCALE == "en": @@ -115,7 +131,7 @@ def check_missing_translations(): if missing_count > 0: percent_missing = (missing_count / total_keys) * 100 if percent_missing == 100: - print(f"[VOLTA] {RED}Warning: All keys are missing in locale '{LOCALE}'! Defaulting back to {FALLBACK_LOCALE}{RESET}") + print(f"[VOLTA] {YELLOW}Warning: All keys are missing in locale '{LOCALE}'! Defaulting back to {FALLBACK_LOCALE}{RESET}") set_language(FALLBACK_LOCALE) elif percent_missing > 0: print(f"{YELLOW}Warning: {missing_count}/{total_keys} keys missing in locale '{LOCALE}' ({percent_missing:.1f}%)!{RESET}") @@ -134,9 +150,9 @@ def get_translation(lang: str, key: str): return lang_translations[key] else: if key not in translations.get(FALLBACK_LOCALE, {}): - return f"[VOLTA] {RED}Missing key: '{key}' in {FALLBACK_LOCALE}.json!{RESET}" + return f"[VOLTA] {YELLOW}Missing key: '{key}' in {FALLBACK_LOCALE}.json!{RESET}" fallback = translations.get(FALLBACK_LOCALE, {}).get(key, key) - print(f"[VOLTA] {RED}Missing key: '{key}' in language '{lang}', falling back to: '{fallback}' using {FALLBACK_LOCALE}.json{RESET}") # yeah probably print this + print(f"[VOLTA] {YELLOW}Missing key: '{key}' in language '{lang}', falling back to: '{fallback}' using {FALLBACK_LOCALE}.json{RESET}") # yeah probably print this return fallback def _(key: str) -> str: diff --git a/requirements.txt b/requirements.txt index 1b9e42c..c53d758 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ requests psutil better_profanity python-dotenv +dotenv pillow \ No newline at end of file From 6db0081fab08176192ce61c876758bd1c29922a3 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:17:44 +0200 Subject: [PATCH 08/84] kill me --- modules/markovmemory.py | 10 ---------- modules/prestartchecks.py | 9 +++++++++ modules/sentenceprocessing.py | 8 +++++--- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/modules/markovmemory.py b/modules/markovmemory.py index f13b52d..eae003e 100644 --- a/modules/markovmemory.py +++ b/modules/markovmemory.py @@ -26,16 +26,6 @@ def load_memory(): except FileNotFoundError: pass - # If MEMORY_LOADED_FILE does not exist, load default data and mark as loaded - if not os.path.exists(MEMORY_LOADED_FILE): - try: - with open(DEFAULT_DATASET_FILE, "r") as f: - default_data = json.load(f) - data.extend(default_data) - except FileNotFoundError: - pass - with open(MEMORY_LOADED_FILE, "w") as f: - f.write("Data loaded") return data # Save memory data to MEMORY_FILE diff --git a/modules/prestartchecks.py b/modules/prestartchecks.py index f5ce96a..171a39a 100644 --- a/modules/prestartchecks.py +++ b/modules/prestartchecks.py @@ -8,6 +8,7 @@ import sysconfig import ast import json import re +from spacy.util import is_package import importlib.metadata # import shutil @@ -19,6 +20,13 @@ except ImportError: psutilavaliable = False print(RED, _('missing_requests_psutil'), RESET) +def check_for_model(): + if is_package("en_core_web_sm"): + print("Model is installed.") + else: + print("Model is not installed.") + + def iscloned(): if os.path.exists(".git"): return True @@ -260,6 +268,7 @@ def start_checks(): print(f"{YELLOW}{(_('checks_disabled'))}{RESET}") return print(_('running_prestart_checks')) + check_for_model() iscloned() check_missing_translations() check_requirements() diff --git a/modules/sentenceprocessing.py b/modules/sentenceprocessing.py index f073210..c470aef 100644 --- a/modules/sentenceprocessing.py +++ b/modules/sentenceprocessing.py @@ -5,9 +5,7 @@ from modules.volta.main import _ import spacy from spacy.tokens import Doc from spacytextblob.spacytextblob import SpacyTextBlob -nlp = spacy.load("en_core_web_sm") -nlp.add_pipe("spacytextblob") -Doc.set_extension("polarity", getter=lambda doc: doc._.blob.polarity) + def check_resources(): try: @@ -22,6 +20,10 @@ def check_resources(): check_resources() +nlp = spacy.load("en_core_web_sm") +nlp.add_pipe("spacytextblob") +Doc.set_extension("polarity", getter=lambda doc: doc._.blob.polarity) + def is_positive(sentence): doc = nlp(sentence) sentiment_score = doc._.polarity # from spacytextblob From f632536c80c2c24083f291abb5c87316dca894c2 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:18:29 +0200 Subject: [PATCH 09/84] fuck --- modules/globalvars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index 56cb752..7dd1c8a 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -40,5 +40,5 @@ latest_version = "0.0.0" local_version = "2.0.2" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") -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 +beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks \ No newline at end of file From d2e272fc3a6c4457cc17e21a874a1fd20bcc1e25 Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 7 Jul 2025 14:17:18 -0400 Subject: [PATCH 10/84] v2.1.0 - added fireboard cog and fixed a few tiny bugs/mistakes --- .gitignore | 3 + assets/cogs/fireboard.py | 1756 +++++++++++++++++++++++++++++++++++++ bot.py | 14 +- modules/central.py | 2 +- modules/globalvars.py | 2 +- modules/prestartchecks.py | 6 +- requirements.txt | 3 +- specialthanks.txt | 8 +- 8 files changed, 1784 insertions(+), 10 deletions(-) create mode 100644 assets/cogs/fireboard.py diff --git a/.gitignore b/.gitignore index 2c32b44..2bd2bfb 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ received_memory.json translation_report.txt translationcompleteness.py modules/volta +fireboard.db +fireboard.db-shm +fireboard.db-wal \ No newline at end of file diff --git a/assets/cogs/fireboard.py b/assets/cogs/fireboard.py new file mode 100644 index 0000000..bd38db4 --- /dev/null +++ b/assets/cogs/fireboard.py @@ -0,0 +1,1756 @@ +import asyncio +import random + +import asqlite +import discord +import discord.ext +import discord.ext.commands +from discord import Color, app_commands +from discord.ext import commands +from discord.ui import View + + +class Fireboard(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.locked_messages = [] + self.disabled = False + + # Check if the bot has the fireboard_pool attribute + if not hasattr(bot, 'fireboard_pool') or bot.fireboard_pool is None: + print("Warning: Bot does not have fireboard_pool initialized. Fireboard functionality will be disabled.") + self.disabled = True + return + + self.fireboard_pool: asqlite.Pool = bot.fireboard_pool + self.bot.loop.create_task(self.setup()) + + # SQL Setup + async def setup(self): + async with self.fireboard_pool.acquire() as sql: + if ( + await sql.fetchone( + "SELECT name FROM sqlite_master WHERE type='table' AND name='fireMessages';" + ) + is None + ): + # Fire Messages - messages that are active on the fireboard + await sql.execute( + "CREATE TABLE fireMessages (serverID int, msgID int, boardMsgID int, reactionAmount int)" + ) + + if ( + await sql.fetchone( + "SELECT name FROM sqlite_master WHERE type='table' AND name='fireSettings';" + ) + is None + ): + # Fire Settings - server properties for fireboard + await sql.execute( + "CREATE TABLE fireSettings (serverID int, reactionAmount int, emoji text, channelID int, ignoreBots int)" + ) + + if ( + await sql.fetchone( + "SELECT name FROM sqlite_master WHERE type='table' AND name='fireChannelBlacklist';" + ) + is None + ): + # Fire Channel Blacklist - blacklisted channels + await sql.execute( + "CREATE TABLE fireChannelBlacklist (serverID int, channelID int)" + ) + + if ( + await sql.fetchone( + "SELECT name FROM sqlite_master WHERE type='table' AND name='fireRoleBlacklist';" + ) + is None + ): + # Fire Role Blacklist - blacklisted roles + await sql.execute( + "CREATE TABLE fireRoleBlacklist (serverID int, roleID int)" + ) + + await sql.commit() + + await self.refresh_fire_lists() + + # List refresh function + async def refresh_fire_lists(self): + async with self.fireboard_pool.acquire() as sql: + self.fire_messages = await sql.fetchall("SELECT * FROM fireMessages") + self.fire_settings = await sql.fetchall("SELECT * FROM fireSettings") + self.fire_channel_blacklist = await sql.fetchall( + "SELECT * FROM fireChannelBlacklist" + ) + self.fire_role_blacklist = await sql.fetchall( + "SELECT * FROM fireRoleBlacklist" + ) + + # Listen for reactions + @commands.Cog.listener() + async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + queued = False + + # Lock system + if payload.message_id in self.locked_messages: + queued = True + + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + fetched = False + + # Find server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + react_minimum = server[1] + emoji = server[2] + channel_id = server[3] + ignore_bots = True if int(server[4]) == 1 else False + + fetched = True + + # Stop if server has no config (fireboard isn't enabled) + if not fetched: + return + + # Stop if message is by Titanium + if payload.message_author_id == self.bot.user.id: + return + + # Stop if emoji doesn't match + if str(payload.emoji) != emoji: + return + + # --- Edit board message if it already exists --- + if payload.message_id in [message[1] for message in self.fire_messages]: + message = [ + message + for message in self.fire_messages + if message[1] == payload.message_id + ][0] + + # Only fetch updated reaction count if I have queued or reaction amount is undefined + if queued or message[3] is None: + # Fetch message and channel + try: + msg_channel = await self.bot.fetch_channel(payload.channel_id) + message = await msg_channel.fetch_message(payload.message_id) + except discord.errors.NotFound: + return + + # Stop if not enough reactions + for reaction in message.reactions: + if str(reaction.emoji) == emoji: + react_count = reaction.count + break + + if react_count < react_minimum: + return + else: + react_count = None + + async with self.fireboard_pool.acquire() as sql: + # Set updated react count + if react_count is not None: + await sql.execute( + "UPDATE fireMessages SET reactionAmount = ? WHERE msgID = ?", + ( + react_count, + payload.message_id, + ), + ) + await self.refresh_fire_lists() + else: + await sql.execute( + "UPDATE fireMessages SET reactionAmount = reactionAmount + 1 WHERE msgID = ?", + (payload.message_id,), + ) + await self.refresh_fire_lists() + + # Get message from message list + message = [ + message + for message in self.fire_messages + if message[1] == payload.message_id + ][0] + + # Get board message + board_channel = await self.bot.fetch_channel(channel_id) + board_message = await board_channel.fetch_message(message[2]) + + await board_message.edit( + content=f"**{message[3]} {emoji}** | <@{payload.message_author_id}> | <#{payload.channel_id}>", + embeds=board_message.embeds, + ) + + return + + # Stop if message is in a blacklisted channel + if payload.channel_id in [ + channel[1] for channel in self.fire_channel_blacklist + ]: + return + + # Stop if message is by a blacklisted role + guild = await self.bot.fetch_guild(payload.guild_id) + member = await guild.fetch_member(payload.user_id) + + if any( + role[1] in [role.id for role in member.roles] + for role in self.fire_role_blacklist + ): + return + + # Fetch message and channel + try: + msg_channel = await self.bot.fetch_channel(payload.channel_id) + message = await msg_channel.fetch_message(payload.message_id) + except discord.errors.NotFound: + return + + # Stop if message is by a bot + if ignore_bots and message.author.bot: + return + + # Check if this is a thread + if isinstance(msg_channel, discord.Thread): + # Check if parent channel is NSFW + if msg_channel.parent.nsfw: + return + else: + # Stop if message is in an NSFW channel + if message.channel.nsfw: + return + + # Stop if not enough reactions + for reaction in message.reactions: + if str(reaction.emoji) == emoji: + react_count = reaction.count + break + + if react_count < react_minimum: + return + + # --- Send message to fireboard --- + + # Create embed + embed = discord.Embed(description=message.content, color=Color.random()) + embed.set_author( + name=message.author.name, icon_url=message.author.display_avatar.url + ) + embed.timestamp = message.created_at + + # Jump to message button + view = View() + view.add_item( + discord.ui.Button( + label="Jump to Message", + url=message.jump_url, + style=discord.ButtonStyle.url, + ) + ) + + embed_list = [embed] + + # Add reply embed + if message.reference: + try: + reply_message = await msg_channel.fetch_message( + message.reference.message_id + ) + + reply_embed = discord.Embed( + title="Replying To", + description=reply_message.content, + color=Color.random(), + ) + reply_embed.set_author( + name=reply_message.author.name, + icon_url=reply_message.author.display_avatar.url, + ) + reply_embed.timestamp = reply_message.created_at + + embed_list.insert(0, reply_embed) + except discord.errors.NotFound: + pass + + # Send message + board_channel = await self.bot.fetch_channel(channel_id) + board_message = await board_channel.send( + content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", + embeds=embed_list, + view=view, + files=[ + await attachment.to_file() for attachment in message.attachments + ], + ) + + async with self.fireboard_pool.acquire() as sql: + # Insert message to DB + await sql.execute( + "INSERT INTO fireMessages (serverID, msgID, boardMsgID, reactionAmount) VALUES (?, ?, ?, ?)", + ( + payload.guild_id, + payload.message_id, + board_message.id, + react_count, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for reaction removal + @commands.Cog.listener() + async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): + self.bot: discord.ext.commands.Bot + + queued = False + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + queued = True + + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + fetched = False + + # Find server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + react_minimum = server[1] + emoji = server[2] + channel_id = server[3] + ignore_bots = True if int(server[4]) == 1 else False + + fetched = True + + # Stop if server has no config (fireboard isn't enabled) + if not fetched: + return + + # Stop if message is by Titanium + if payload.message_author_id == self.bot.user.id: + return + + # Stop if emoji doesn't match + if str(payload.emoji) != emoji: + return + + # --- Edit board message if it already exists --- + if payload.message_id in [message[1] for message in self.fire_messages]: + prev_react_count = [ + message + for message in self.fire_messages + if message[1] == payload.message_id + ][0][3] + + # Only fetch updated reaction count if I have queued + if queued or prev_react_count is None: + # Fetch message and channel + try: + msg_channel = await self.bot.fetch_channel(payload.channel_id) + message = await msg_channel.fetch_message(payload.message_id) + except discord.errors.NotFound: + return + + # Stop if not enough reactions + for reaction in message.reactions: + if str(reaction.emoji) == emoji: + react_count = reaction.count + break + + if react_count < react_minimum: + return + else: + react_count = None + + async with self.fireboard_pool.acquire() as sql: + # Set updated react count + if react_count is not None: + await sql.execute( + "UPDATE fireMessages SET reactionAmount = ? WHERE msgID = ?", + ( + react_count, + payload.message_id, + ), + ) + await self.refresh_fire_lists() + else: + await sql.execute( + "UPDATE fireMessages SET reactionAmount = reactionAmount - 1 WHERE msgID = ?", + (payload.message_id,), + ) + await self.refresh_fire_lists() + + # Get message from message list + message = [ + message + for message in self.fire_messages + if message[1] == payload.message_id + ][0] + + # Get board message + board_channel = await self.bot.fetch_channel(channel_id) + board_message = await board_channel.fetch_message(message[2]) + + # Remove message if not enough reactions + if message[3] < react_minimum: + await board_message.delete() + + async with self.fireboard_pool.acquire() as sql: + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + + # Workaround for lack of message author ID + content = board_message.content + content = content.replace( + f"{prev_react_count} {emoji}", f"{message[3]} {emoji}" + ) + + await board_message.edit(content=content) + + return + + # Stop if message is in a blacklisted channel + if payload.channel_id in [ + channel[1] for channel in self.fire_channel_blacklist + ]: + return + + # Stop if message is by a blacklisted role + guild = await self.bot.fetch_guild(payload.guild_id) + member = await guild.fetch_member(payload.user_id) + + if any( + role[1] in [role.id for role in member.roles] + for role in self.fire_role_blacklist + ): + return + + # Fetch message and channel + try: + msg_channel = await self.bot.fetch_channel(payload.channel_id) + message = await msg_channel.fetch_message(payload.message_id) + except discord.errors.NotFound: + return + + # Stop if message is by a bot + if ignore_bots and message.author.bot: + return + + # Check if this is a thread + if isinstance(msg_channel, discord.Thread): + # Check if parent channel is NSFW + if msg_channel.parent.nsfw: + return + else: + # Stop if message is in an NSFW channel + if message.channel.nsfw: + return + + # Get reaction count + react_count = 0 + + for reaction in message.reactions: + if str(reaction.emoji) == emoji: + react_count = reaction.count + break + + # Stop if not enough reactions + if react_count < react_minimum: + return + + # --- Send message to fireboard --- + + # Create embed + embed = discord.Embed(description=message.content, color=Color.random()) + embed.set_author( + name=message.author.name, icon_url=message.author.display_avatar.url + ) + embed.timestamp = message.created_at + + # Jump to message button + view = View() + view.add_item( + discord.ui.Button( + label="Jump to Message", + url=message.jump_url, + style=discord.ButtonStyle.url, + ) + ) + + embed_list = [embed] + + # Add reply embed + if message.reference: + try: + reply_message = await msg_channel.fetch_message( + message.reference.message_id + ) + + reply_embed = discord.Embed( + title="Replying To", + description=reply_message.content, + color=Color.random(), + ) + reply_embed.set_author( + name=reply_message.author.name, + icon_url=reply_message.author.display_avatar.url, + ) + reply_embed.timestamp = reply_message.created_at + + embed_list.insert(0, reply_embed) + except discord.errors.NotFound: + pass + + # Send message + board_channel = await self.bot.fetch_channel(channel_id) + board_message = await board_channel.send( + content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", + embeds=embed_list, + view=view, + files=[ + await attachment.to_file() for attachment in message.attachments + ], + ) + + async with self.fireboard_pool.acquire() as sql: + # Insert message to DB + await sql.execute( + "INSERT INTO fireMessages (serverID, msgID, boardMsgID, reactionAmount) VALUES (?, ?, ?, ?)", + ( + payload.guild_id, + payload.message_id, + board_message.id, + react_count, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for message reaction clear + @commands.Cog.listener() + async def on_raw_reaction_clear(self, payload: discord.RawReactionClearEvent): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + # Only trigger if message is already in the fireboard DB + if payload.message_id in [message[1] for message in self.fire_messages]: + # Find server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + channel_id = server[3] + + # Get guild + try: + guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) + except discord.errors.NotFound: + return + + # Get message channel + channel: discord.abc.GuildChannel = await guild.fetch_channel( + payload.channel_id + ) + + # Get our message + message: discord.Message = await channel.fetch_message( + payload.message_id + ) + + # See if board message is already present + for fire_message in self.fire_messages: + if fire_message[1] == message.id: + async with self.fireboard_pool.acquire() as sql: + try: + # Delete message + try: + channel: discord.TextChannel = ( + await guild.fetch_channel(channel_id) + ) + except discord.errors.NotFound: + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (payload.guild_id,), + ) + await sql.commit() + self.locked_messages.remove(payload.message_id) + + return + + board_message = await channel.fetch_message( + fire_message[2] + ) + await board_message.delete() + + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (message.id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + except discord.errors.NotFound: + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (message.id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for specific emoji being cleared + @commands.Cog.listener() + async def on_raw_reaction_clear_emoji( + self, payload: discord.RawReactionClearEmojiEvent + ): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + # Only trigger if message is already in the fireboard DB + if payload.message_id in [message[1] for message in self.fire_messages]: + for server in self.fire_settings: + if server[0] == payload.guild_id: + emoji = server[2] + channel_id = server[3] + + # Only trigger if cleared emoji is our emoji + if str(payload.emoji) == emoji: + # Fetch server + try: + guild: discord.Guild = await self.bot.fetch_guild( + payload.guild_id + ) + except discord.errors.NotFound: + return + + # Get message channel + channel: discord.abc.GuildChannel = await guild.fetch_channel( + payload.channel_id + ) + + # See if board message is already present + for fire_message in self.fire_messages: + if fire_message[1] == payload.message_id: + async with self.fireboard_pool.acquire() as sql: + try: + # Fetch fireboard channel + try: + channel: discord.TextChannel = ( + await guild.fetch_channel(channel_id) + ) + except discord.errors.NotFound: + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (payload.guild_id,), + ) + await sql.commit() + + return + + # Delete message + board_message = await channel.fetch_message( + fire_message[2] + ) + await board_message.delete() + + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + except discord.errors.NotFound: + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for message being deleted + @commands.Cog.listener() + async def on_raw_message_delete(self, payload: discord.RawMessageDeleteEvent): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + # Only trigger if message is already in the fireboard DB + if payload.message_id in [message[1] for message in self.fire_messages]: + # Fetch server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + channel_id = server[3] + + # Fetch server + try: + guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) + except discord.errors.NotFound: + return + + # Get message channel + channel: discord.abc.GuildChannel = await guild.fetch_channel( + payload.channel_id + ) + + # See if board message is already present + for fire_message in self.fire_messages: + if fire_message[1] == payload.message_id: + async with self.fireboard_pool.acquire() as sql: + try: + # Fetch fireboard channel + try: + channel: discord.TextChannel = ( + await guild.fetch_channel(channel_id) + ) + except discord.errors.NotFound: + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (payload.guild_id,), + ) + await sql.commit() + + return + + # Delete message + board_message = await channel.fetch_message( + fire_message[2] + ) + await board_message.delete() + + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + except discord.errors.NotFound: + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for message being edited + @commands.Cog.listener() + async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + # Only trigger if message is already in the fireboard DB + if payload.message_id in [message[1] for message in self.fire_messages]: + # Fetch server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + channel_id = server[3] + + # Fetch server + try: + guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) + except discord.errors.NotFound: + return + + # Get message channel + channel: discord.abc.GuildChannel = await guild.fetch_channel( + payload.channel_id + ) + + # Get our message + message: discord.Message = await channel.fetch_message( + payload.message_id + ) + + embed = discord.Embed(description=message.content, color=Color.random()) + embed.set_author( + name=message.author.name, icon_url=message.author.display_avatar.url + ) + embed.timestamp = message.created_at + + # Jump to message button + view = View() + view.add_item( + discord.ui.Button( + label="Jump to Message", + url=message.jump_url, + style=discord.ButtonStyle.url, + ) + ) + + embed_list = [embed] + + # Add reply embed + if message.reference: + try: + reply_message = await channel.fetch_message( + message.reference.message_id + ) + + reply_embed = discord.Embed( + title="Replying To", + description=reply_message.content, + color=Color.random(), + ) + reply_embed.set_author( + name=reply_message.author.name, + icon_url=reply_message.author.display_avatar.url, + ) + reply_embed.timestamp = reply_message.created_at + + embed_list.insert(0, reply_embed) + except discord.errors.NotFound: + pass + + try: + channel: discord.TextChannel = await guild.fetch_channel(channel_id) + except discord.errors.NotFound: + async with self.fireboard_pool.acquire() as sql: + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (payload.guild_id,), + ) + await sql.commit() + + return + + # Find previous fireboard message + try: + for fire_message in self.fire_messages: + if ( + fire_message[0] == payload.guild_id + and fire_message[1] == payload.message_id + ): + # Edit with updated embed - reaction amount stays the same + board_message = await channel.fetch_message(fire_message[2]) + + await board_message.edit( + embeds=embed_list, attachments=message.attachments + ) + except discord.errors.NotFound: # Message not found + async with self.fireboard_pool.acquire() as sql: + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for fireboard channel delete + @commands.Cog.listener() + async def on_guild_channel_delete(self, channel: discord.abc.GuildChannel): + # Only trigger if server has fireboard enabled + if channel.guild.id in [guild[0] for guild in self.fire_settings]: + for server in self.fire_settings: + if server[0] == channel.guild.id: + if server[3] == channel.id: + async with self.fireboard_pool.acquire() as sql: + # Delete fireboard config + await sql.execute( + "DELETE FROM fireMessages WHERE serverID = ?", + (channel.guild.id,), + ) + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (channel.guild.id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + + # Listen for server being left / deleted + @commands.Cog.listener() + async def on_guild_remove(self, guild: discord.Guild): + # Only trigger if server has fireboard enabled + if guild.id in [guild[0] for guild in self.fire_settings]: + for server in self.fire_settings: + if server[0] == guild.id: + async with self.fireboard_pool.acquire() as sql: + # Delete fireboard config + await sql.execute( + "DELETE FROM fireMessages WHERE serverID = ?", (guild.id,) + ) + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", (guild.id,) + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + + # Command group setup + context = discord.app_commands.AppCommandContext( + guild=True, dm_channel=False, private_channel=False + ) + installs = discord.app_commands.AppInstallationType(guild=True, user=False) + fireGroup = app_commands.Group( + name="fireboard", + description="Fireboard related commands.", + allowed_contexts=context, + allowed_installs=installs, + ) + + # Random fireboard message command + @fireGroup.command( + name="random", description="Get a random message from the fireboard." + ) + @app_commands.describe( + ephemeral="Optional: whether to send the command output as a dismissible message only visible to you. Defaults to false." + ) + async def random_fireboard( + self, interaction: discord.Interaction, ephemeral: bool = False + ): + await interaction.response.defer(ephemeral=ephemeral) + + channel_id = None + + # Find server config + for server in self.fire_settings: + if server[0] == interaction.guild_id: + channel_id = server[3] + + if channel_id is None: + embed = discord.Embed( + title="Error", + description="Fireboard is not enabled in this server.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=ephemeral) + + return + + # Fetch channel + try: + channel = await interaction.guild.fetch_channel(channel_id) + except discord.errors.NotFound: + embed = discord.Embed( + title="Error", + description="Can't find the fireboard channel. Please contact a server admin.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=ephemeral) + + return + + # Fetch messages + async with self.fireboard_pool.acquire() as sql: + messages = await sql.fetchall( + "SELECT * FROM fireMessages WHERE serverID = ?", (interaction.guild_id,) + ) + + if not messages: + embed = discord.Embed( + title="Error", + description="No messages found in the fireboard.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=ephemeral) + + return + else: + while messages != []: + message = random.choice(messages) + + try: + board_message = await channel.fetch_message(message[2]) + + view = View().from_message(board_message) + files = [ + await attachment.to_file() + for attachment in board_message.attachments + ] + + await interaction.followup.send( + content=board_message.content, + embeds=board_message.embeds, + view=view, + ephemeral=ephemeral, + files=files, + allowed_mentions=discord.AllowedMentions.none(), + ) + + return + except discord.errors.NotFound: + messages.remove(message) + + embed = discord.Embed( + title="Error", + description="No messages found in the fireboard.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=ephemeral) + + # Command group setup + context = discord.app_commands.AppCommandContext( + guild=True, dm_channel=False, private_channel=False + ) + installs = discord.app_commands.AppInstallationType(guild=True, user=False) + perms = discord.Permissions(manage_guild=True) + fireSetupGroup = app_commands.Group( + name="fireboard-setup", + description="Control the fireboard.", + allowed_contexts=context, + allowed_installs=installs, + default_permissions=perms, + ) + + # Fireboard enable command + @fireSetupGroup.command( + name="enable", description="Enable the fireboard in the current channel." + ) + async def enable_fireboard(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Fireboard is already enabled.", color=Color.green() + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + # Default settings + react_minimum = 3 + emoji = "🔥" + channel_id = interaction.channel_id + ignore_bots = True + + embed = discord.Embed( + title="Fireboard", + description="This channel has been configured as the server fireboard.", + color=Color.random(), + ) + embed.set_footer(text="Feel free to delete this message!") + + try: + channel = await interaction.guild.fetch_channel(channel_id) + await channel.send(embed=embed) + except discord.errors.Forbidden or discord.errors.NotFound: + embed = discord.Embed( + title="Error", + description="Looks like I can't send messages in this channel. Check permissions and try again.", + color=Color.random(), + ) + await interaction.followup.send(embed=embed, ephemeral=True) + + return + + async with self.fireboard_pool.acquire() as sql: + # Insert to DB, refresh lists + await sql.execute( + "INSERT INTO fireSettings (serverID, reactionAmount, emoji, channelID, ignoreBots) VALUES (?, ?, ?, ?, ?)", + ( + interaction.guild_id, + react_minimum, + emoji, + channel_id, + ignore_bots, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Enabled", + description="Fireboard has been enabled in the current channel.", + color=Color.green(), + ) + embed.add_field( + name="Info", + value=f"**Reaction Requirement:** `{react_minimum} reactions`\n**Fireboard Channel:** <#{channel_id}>\n**Emoji:** {emoji}\n**Ignore Bots:** `{ignore_bots}`", + ) + + await interaction.followup.send(embed=embed, ephemeral=True) + + class ConfirmDisableView(View): + def __init__(self): + super().__init__(timeout=60) + + async def disable_fireboard( + self, interaction: discord.Interaction, pool: asqlite.Pool + ): + async with pool.acquire() as sql: + try: + await sql.execute( + "DELETE FROM fireMessages WHERE serverID = ?", + (interaction.guild_id,), + ) + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (interaction.guild_id,), + ) + await sql.execute( + "DELETE FROM fireChannelBlacklist WHERE serverID = ?", + (interaction.guild_id,), + ) + await sql.execute( + "DELETE FROM fireRoleBlacklist WHERE serverID = ?", + (interaction.guild_id,), + ) + await sql.commit() + return True + except Exception: + return False + + @discord.ui.button(label="Disable", style=discord.ButtonStyle.red) + async def confirm( + self, interaction: discord.Interaction, button: discord.ui.Button + ): + await interaction.response.defer(ephemeral=True) + + success = await self.disable_fireboard(interaction, self.pool) + + if success: + embed = discord.Embed( + title="Done!", + description="Fireboard was disabled.", + color=Color.green(), + ) + await self.cog.refresh_fire_lists() # pylint: disable=no-member + else: + embed = discord.Embed( + title="Error", + description="Failed to disable fireboard.", + color=Color.red(), + ) + + await interaction.edit_original_response(embed=embed, view=None) + self.stop() + + async def on_timeout(self): + for item in self.children: + item.disabled = True + + embed = discord.Embed( + title="Timeout", + description="You didn't press the button in time.", + color=Color.red(), + ) + await self.message.edit(embed=embed, view=self) + + # Fireboard disable command + @fireSetupGroup.command(name="disable", description="Disable the server fireboard.") + async def disable_fireboard(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + + if interaction.guild_id in [guild[0] for guild in self.fire_settings]: + view = self.ConfirmDisableView() + view.pool = self.fireboard_pool + view.cog = self + + embed = discord.Embed( + title="Are you sure?", + description="All data about this server's fireboard will be deleted. This cannot be undone!", + color=Color.orange(), + ) + + message = await interaction.followup.send( + embed=embed, view=view, ephemeral=True, wait=True + ) + view.message = message + else: + await interaction.followup.send( + "Fireboard is not enabled in this server!", ephemeral=True + ) + + # Fireboard server info command + @fireSetupGroup.command( + name="info", description="View fireboard config for this server." + ) + async def fireboard_info(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + # Fetch server settings + for server in self.fire_settings: + if server[0] == interaction.guild_id: + react_minimum = server[1] + emoji = server[2] + channel_id = server[3] + ignore_bots = True if int(server[4]) == 1 else False + + embed = discord.Embed( + title="Server Fireboard Settings", + description=f"**Reaction Requirement:** `{react_minimum} reactions`\n**Fireboard Channel:** <#{channel_id}>\n**Emoji:** {emoji}\n**Ignore Bots:** `{ignore_bots}`", + color=Color.random(), + ) + + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard set emoji command + @fireSetupGroup.command(name="emoji", description="Set a custom fireboard emoji.") + async def fireboard_emoji(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=False) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Waiting for Reaction", + description="🔄 React with this message with your target emoji to set the fireboard emoji.", + color=Color.orange(), + ) + + msg = await interaction.followup.send(embed=embed, ephemeral=False) + + def check(reaction, user): + return user == interaction.user and reaction.message.id == msg.id + + # Wait for a reaction + try: + reaction, user = await self.bot.wait_for( + "reaction_add", timeout=60.0, check=check + ) + + reaction: discord.Reaction = reaction + + async with self.fireboard_pool.acquire() as sql: + # Change emoji in DB, refresh lists + await sql.execute( + "UPDATE fireSettings SET emoji = ? WHERE serverID = ?", + ( + str(reaction.emoji), + interaction.guild_id, + ), + ) + await sql.commit() + + embed = discord.Embed( + title="Emoji Set", + description=f"Set emoji to **{str(reaction.emoji)}.**", + color=Color.green(), + ) + + await self.refresh_fire_lists() + await interaction.edit_original_response(embed=embed) + except asyncio.TimeoutError: # Timed out + embed = discord.Embed( + title="Timed Out", + description="You didn't react in time.", + color=Color.red(), + ) + + await interaction.edit_original_response(embed=embed) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=False) + + # Fireboard set channel command + @fireSetupGroup.command( + name="channel", + description="Set the channel for fireboard messages to be sent in.", + ) + async def fireboard_channel( + self, interaction: discord.Interaction, channel: discord.TextChannel + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Fireboard", + description="This channel has been configured as the server fireboard.", + color=Color.random(), + ) + embed.set_footer(text="Feel free to delete this message!") + + try: + await channel.send(embed=embed) + except discord.errors.NotFound: + embed = discord.Embed( + title="Error", + description="Looks like I can't find that channel. Check permissions and try again.", + color=Color.random(), + ) + await interaction.followup.send(embed=embed, ephemeral=True) + + return + except discord.errors.Forbidden as e: + embed = discord.Embed( + title="Error", + description="Looks like I can't send messages in that channel. Check permissions and try again.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=True) + + return + + async with self.fireboard_pool.acquire() as sql: + # Update channel in DB, refresh lists + await sql.execute( + "UPDATE fireSettings SET channelID = ? WHERE serverID = ?", + ( + channel.id, + interaction.guild_id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Channel Set", + description=f"Fireboard channel has been set to **{channel.mention}.**", + color=Color.green(), + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard set requirement command + @fireSetupGroup.command( + name="requirement", + description="Set required reaction amount for message to be posted on the fireboard.", + ) + async def fireboard_requirement( + self, interaction: discord.Interaction, amount: int + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Set", + description=f"Reaction requirement has been set to **{amount} reactions.**", + color=Color.green(), + ) + + async with self.fireboard_pool.acquire() as sql: + # Update reaction requirement in DB, refresh lists + await sql.execute( + "UPDATE fireSettings SET reactionAmount = ? WHERE serverID = ?", + ( + amount, + interaction.guild_id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard ignore bots command + @fireSetupGroup.command( + name="ignore-bots", + description="Whether bot messages are ignored in the fireboard. Defaults to true.", + ) + async def fireboard_ignore_bots( + self, interaction: discord.Interaction, value: bool + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Set", + description=f"Bot messages will **{'be ignored.' if value else 'not be ignored.'}**", + color=Color.green(), + ) + + async with self.fireboard_pool.acquire() as sql: + # Update setting in DB, refresh lists + await sql.execute( + "UPDATE fireSettings SET ignoreBots = ? WHERE serverID = ?", + ( + value, + interaction.guild_id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard role blacklist + @fireSetupGroup.command( + name="channel-blacklist", + description="Toggle the blacklist for a channel. NSFW channels are always blacklisted.", + ) + async def fireboard_channel_blacklist( + self, interaction: discord.Interaction, channel: discord.abc.GuildChannel + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild_id in [guild[0] for guild in self.fire_settings]: + async with self.fireboard_pool.acquire() as sql: + if channel.id in [ + channelEntry[1] for channelEntry in self.fire_channel_blacklist + ]: + await sql.execute( + "DELETE FROM fireChannelBlacklist WHERE serverID = ? AND channelID = ?", + ( + interaction.guild_id, + channel.id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Set", + description=f"Removed {channel.mention} from the channel blacklist.", + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + await sql.execute( + "INSERT INTO fireChannelBlacklist (serverID, channelID) VALUES (?, ?)", + ( + interaction.guild_id, + channel.id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Set", + description=f"Added {channel.mention} to the channel blacklist.", + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard role blacklist + @fireSetupGroup.command( + name="role-blacklist", description="Toggle the blacklist for a role." + ) + async def fireboard_role_blacklist( + self, interaction: discord.Interaction, role: discord.Role + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild_id in [guild[0] for guild in self.fire_settings]: + async with self.fireboard_pool.acquire() as sql: + if role.id in [roleEntry[1] for roleEntry in self.fire_role_blacklist]: + await sql.execute( + "DELETE FROM fireRoleBlacklist WHERE serverID = ? AND roleID = ?", + ( + interaction.guild_id, + role.id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Set", + description=f"Removed {role.mention} from the role blacklist.", + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + await sql.execute( + "INSERT INTO fireRoleBlacklist (serverID, roleID) VALUES (?, ?)", + ( + interaction.guild_id, + role.id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Set", + description=f"Added {role.mention} to the role blacklist.", + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard role blacklist + @fireSetupGroup.command( + name="blacklists", description="View this server's role and channel blacklists." + ) + async def fireboard_blacklists(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild_id in [guild[0] for guild in self.fire_settings]: + + class BlacklistViewer(View): + def __init__(self): + super().__init__(timeout=240) + + self.fire_channel_blacklist: list + self.fire_role_blacklist: list + self.interaction: discord.Interaction + + async def on_timeout(self) -> None: + for item in self.children: + item.disabled = True + + await self.interaction.edit_original_response(view=self) + + @discord.ui.button( + label="Role Blacklist", + style=discord.ButtonStyle.gray, + row=0, + custom_id="role", + disabled=True, + ) + async def role( + self, interaction: discord.Interaction, button: discord.ui.Button + ): + await interaction.response.defer(ephemeral=True) + + for item in self.children: + if item.custom_id == "channel": + item.disabled = False + else: + item.disabled = True + + my_roles = [] + + for role in self.fire_role_blacklist: + if role[0] == interaction.guild_id: + my_roles.append(f"<@&{role[1]}>") + + if my_roles != []: + embed = discord.Embed( + title="Role Blacklist", + description="\n".join(my_roles), + color=Color.random(), + ) + await interaction.edit_original_response(embed=embed, view=self) + else: + embed = discord.Embed( + title="Role Blacklist", + description="No roles have been blacklisted.", + color=Color.random(), + ) + await interaction.edit_original_response(embed=embed, view=self) + + @discord.ui.button( + label="Channel Blacklist", + style=discord.ButtonStyle.gray, + row=0, + custom_id="channel", + ) + async def channel( + self, interaction: discord.Interaction, button: discord.ui.Button + ): + await interaction.response.defer(ephemeral=True) + + for item in self.children: + if item.custom_id == "role": + item.disabled = False + else: + item.disabled = True + + my_channels = [] + + for channel in self.fire_channel_blacklist: + if channel[0] == interaction.guild_id: + my_channels.append(f"<#{channel[1]}>") + + if my_channels != []: + embed = discord.Embed( + title="Channel Blacklist", + description="\n".join(my_channels), + color=Color.random(), + ) + await interaction.edit_original_response(embed=embed, view=self) + else: + embed = discord.Embed( + title="Channel Blacklist", + description="No channels have been blacklisted.", + color=Color.random(), + ) + await interaction.edit_original_response(embed=embed, view=self) + + view_instance = BlacklistViewer() + view_instance.fire_channel_blacklist = self.fire_channel_blacklist + view_instance.fire_role_blacklist = self.fire_role_blacklist + view_instance.interaction = interaction + + my_roles = [] + + for role in self.fire_role_blacklist: + if role[0] == interaction.guild_id: + my_roles.append(f"<@&{role[1]}>") + + if my_roles != []: + embed = discord.Embed( + title="Role Blacklist", + description="\n".join(my_roles), + color=Color.random(), + ) + await interaction.followup.send( + embed=embed, view=view_instance, ephemeral=True + ) + else: + embed = discord.Embed( + title="Role Blacklist", + description="No roles have been blacklisted.", + color=Color.random(), + ) + await interaction.followup.send( + embed=embed, view=view_instance, ephemeral=True + ) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + +async def setup(bot): + await bot.add_cog(Fireboard(bot)) \ No newline at end of file diff --git a/bot.py b/bot.py index 7c2050c..315d193 100644 --- a/bot.py +++ b/bot.py @@ -20,6 +20,7 @@ print(splashtext) # Print splash text (from modules/globalvars.py) start_checks() import requests +import asqlite import discord from discord.ext import commands @@ -62,6 +63,9 @@ bot: commands.Bot = commands.Bot( allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False, replied_user=True) ) +# Initialize database pool for fireboard functionality +bot.fireboard_pool = None + # Load memory and Markov model for text generation memory: List[str] = load_memory() markov_model: Optional[markovify.Text] = load_markov_model() @@ -114,6 +118,14 @@ async def on_ready() -> None: folder_name: str = "cogs" if launched: return + + # Initialize database pool for fireboard functionality + try: + bot.fireboard_pool = await asqlite.create_pool("fireboard.db") + print(f"{GREEN}Database pool initialized successfully{RESET}") + except Exception as e: + print(f"{RED}Failed to initialize database pool: {e}{RESET}") + bot.fireboard_pool = None await load_cogs_from_folder(bot) try: @@ -473,7 +485,7 @@ async def stats(ctx: commands.Context) -> None: embed: discord.Embed = discord.Embed(title=f"{(_('command_stats_embed_title'))}", description=f"{(_('command_stats_embed_desc'))}", color=Colour(0x000000)) embed.add_field(name=f"{(_('command_stats_embed_field1name'))}", value=f"{(_('command_stats_embed_field1value')).format(file_size=file_size, line_count=line_count)}", inline=False) embed.add_field(name=f"{(_('command_stats_embed_field2name'))}", value=f"{(_('command_stats_embed_field2value')).format(local_version=local_version, latest_version=latest_version)}", inline=False) - embed.add_field(name=f"{(_('command_stats_embed_field3name'))}", value=f"{(_('command_stats_embed_field3value')).format(NAME=NAME, PREFIX=PREFIX, ownerid=ownerid, cooldown_time=cooldown_time, PING_LINE=PING_LINE, showmemenabled=showmemenabled, USERTRAIN_ENABLED=USERTRAIN_ENABLED, song=song, splashtext=splashtext)}", inline=False) + embed.add_field(name=f"{(_('command_stats_embed_field3name'))}", value=f"{(_('command_stats_embed_field3value')).format(NAME=NAME, PREFIX=PREFIX, ownerid=ownerid, PING_LINE=PING_LINE, showmemenabled=showmemenabled, USERTRAIN_ENABLED=USERTRAIN_ENABLED, song=song, splashtext=splashtext)}", inline=False) await send_message(ctx, embed=embed) diff --git a/modules/central.py b/modules/central.py index edde8cc..335e6ef 100644 --- a/modules/central.py +++ b/modules/central.py @@ -70,7 +70,7 @@ def register_name(NAME): if os.getenv("gooberTOKEN"): return # Name taken: print error and exit - print(f"{RED}{(_('name_taken'))}{gv.RESET}") + print(f"{gv.RED}{(_('name_taken'))}{gv.RESET}") quit() # Register the name response = requests.post(f"{gv.VERSION_URL}/register", json={"name": NAME}, headers={"Content-Type": "application/json"}) diff --git a/modules/globalvars.py b/modules/globalvars.py index 7dd1c8a..f1520e7 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -37,7 +37,7 @@ 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 = "2.0.2" +local_version = "2.1.0" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks diff --git a/modules/prestartchecks.py b/modules/prestartchecks.py index 171a39a..1f4b736 100644 --- a/modules/prestartchecks.py +++ b/modules/prestartchecks.py @@ -127,7 +127,7 @@ def check_requirements(): "token": gooberTOKEN } try: - requests.post(VERSION_URL + "/ping", json=payload) + requests.post(VERSION_URL + "/ping", json=payload) # type: ignore except Exception as e: print(f"{RED}{(_('failed_to_contact')).format(url=VERSION_URL, error=e)}{RESET}") sys.exit(1) @@ -173,7 +173,7 @@ def check_memory(): if psutilavaliable == False: return try: - memory_info = psutil.virtual_memory() + memory_info = psutil.virtual_memory() # type: ignore total_memory = memory_info.total / (1024 ** 3) used_memory = memory_info.used / (1024 ** 3) free_memory = memory_info.available / (1024 ** 3) @@ -193,7 +193,7 @@ def check_cpu(): if psutilavaliable == False: return print((_('measuring_cpu'))) - cpu_per_core = psutil.cpu_percent(interval=1, percpu=True) + cpu_per_core = psutil.cpu_percent(interval=1, percpu=True) # type: ignore for idx, core_usage in enumerate(cpu_per_core): bar_length = int(core_usage / 5) bar = '█' * bar_length + '-' * (20 - bar_length) diff --git a/requirements.txt b/requirements.txt index c53d758..0dca484 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ psutil better_profanity python-dotenv dotenv -pillow \ No newline at end of file +pillow +asqlite \ No newline at end of file diff --git a/specialthanks.txt b/specialthanks.txt index 514448e..ce6a09e 100644 --- a/specialthanks.txt +++ b/specialthanks.txt @@ -1,3 +1,5 @@ -The catbox.moe team -Charlie's Computers -ctih1 +- The catbox.moe team for memory.json file uploads +- Charlie's Computers +- ctih1 +- RestartB (for the Fireboard cog from his Titanium bot) +- Claude 4 Sonnet (for slightly modifying main.py and fireboard.py to work with Goober) From 74848a2cede9cd92be54050bf06285fb0c3239c2 Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 7 Jul 2025 14:25:11 -0400 Subject: [PATCH 11/84] stats command should work now --- assets/locales/en.json | 2 +- assets/locales/es.json | 2 +- assets/locales/et.json | 2 +- assets/locales/fi.json | 2 +- assets/locales/fr.json | 2 +- assets/locales/fy.json | 2 +- assets/locales/it.json | 2 +- assets/locales/mt.json | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/assets/locales/en.json b/assets/locales/en.json index cca8867..d196eaf 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -128,6 +128,6 @@ "command_stats_embed_field2name": "Version", "command_stats_embed_field2value": "Local: {local_version} \nLatest: {latest_version}", "command_stats_embed_field3name": "Variable Info", - "command_stats_embed_field3value": "Name: {NAME} \nPrefix: {PREFIX} \nOwner ID: {ownerid} \nCooldown: {cooldown_time} \nPing line: {PING_LINE} \nMemory Sharing Enabled: {showmemenabled} \nUser Training Enabled: {USERTRAIN_ENABLED}\nSong: {song} \nSplashtext: ```{splashtext}```" + "command_stats_embed_field3value": "Name: {NAME} \nPrefix: {PREFIX} \nOwner ID: {ownerid}\nPing line: {PING_LINE} \nMemory Sharing Enabled: {showmemenabled} \nUser Training Enabled: {USERTRAIN_ENABLED}\nSong: {song} \nSplashtext: ```{splashtext}```" } diff --git a/assets/locales/es.json b/assets/locales/es.json index eb7b743..4444a9c 100644 --- a/assets/locales/es.json +++ b/assets/locales/es.json @@ -73,5 +73,5 @@ "command_stats_embed_field2name": "Version", "command_stats_embed_field2value": "Version local: {local_version} \nUltima version: {latest_version}", "command_stats_embed_field3name": "informacion sobre las variables", - "command_stats_embed_field3value": "Nombre: {NAME} \nPrefijo: {PREFIX} \nID del propietario: {ownerid} \nTiempo de reutilizacion: {cooldown_time} \nLinea de ping: {PING_LINE} \nCompartir memoria habilitada: {showmemenabled} \nEntrenamiento de usuario habilitado: {USERTRAIN_ENABLED} \nCancion: {song} \nTexto de bienvenida: ```{splashtext}```" + "command_stats_embed_field3value": "Nombre: {NAME} \nPrefijo: {PREFIX} \nID del propietario: {ownerid}\nLinea de ping: {PING_LINE} \nCompartir memoria habilitada: {showmemenabled} \nEntrenamiento de usuario habilitado: {USERTRAIN_ENABLED} \nCancion: {song} \nTexto de bienvenida: ```{splashtext}```" } \ No newline at end of file diff --git a/assets/locales/et.json b/assets/locales/et.json index 9e8b20c..a169d05 100644 --- a/assets/locales/et.json +++ b/assets/locales/et.json @@ -126,6 +126,6 @@ "command_stats_embed_field2name": "Versioon", "command_stats_embed_field2value": "Kohalik: {local_version} \nViimane: {latest_version}", "command_stats_embed_field3name": "Muutuja info", - "command_stats_embed_field3value": "Nimi: {NAME} \nPrefiks: {PREFIX} \nOmaniku ID: {ownerid} \nJahtumisaeg: {cooldown_time} \nPingirida: {PING_LINE} \nMälu jagamine lubatud: {showmemenabled} \nKasutajaõpe lubatud: {USERTRAIN_ENABLED}\nLaul: {song} \nSplashtekst: ```{splashtext}```" + "command_stats_embed_field3value": "Nimi: {NAME} \nPrefiks: {PREFIX} \nOmaniku ID: {ownerid}\nPingirida: {PING_LINE} \nMälu jagamine lubatud: {showmemenabled} \nKasutajaõpe lubatud: {USERTRAIN_ENABLED}\nLaul: {song} \nSplashtekst: ```{splashtext}```" } diff --git a/assets/locales/fi.json b/assets/locales/fi.json index bdc1544..ea6adf0 100644 --- a/assets/locales/fi.json +++ b/assets/locales/fi.json @@ -128,6 +128,6 @@ "command_stats_embed_field2name": "Versio", "command_stats_embed_field2value": "Paikallinen: {local_version} \nUusin: {latest_version}", "command_stats_embed_field3name": "Muuttajainformaatio", - "command_stats_embed_field3value": "Nimi: {NAME} \nEtuliite: {PREFIX} \nOmistajan ID: {ownerid} \nJäähtymisaika: {cooldown_time} \nPing-linja: {PING_LINE} \nMuistin jako päällä: {showmemenabled} \nOppiminen käyttäjistä: {USERTRAIN_ENABLED}\nLaulu: {song} \nRoisketeksti: ```{splashtext}```" + "command_stats_embed_field3value": "Nimi: {NAME} \nEtuliite: {PREFIX} \nOmistajan ID: {ownerid}\nPing-linja: {PING_LINE} \nMuistin jako päällä: {showmemenabled} \nOppiminen käyttäjistä: {USERTRAIN_ENABLED}\nLaulu: {song} \nRoisketeksti: ```{splashtext}```" } \ No newline at end of file diff --git a/assets/locales/fr.json b/assets/locales/fr.json index 4561819..572d2f4 100644 --- a/assets/locales/fr.json +++ b/assets/locales/fr.json @@ -126,5 +126,5 @@ "command_stats_embed_field2name": "Version", "command_stats_embed_field2value": "Locale : {local_version} \nDernière : {latest_version}", "command_stats_embed_field3name": "Informations variables", - "command_stats_embed_field3value": "Nom : {NAME} \nPréfixe : {PREFIX} \nID du propriétaire : {ownerid} \nTemps de recharge : {cooldown_time} \nLigne de ping : {PING_LINE} \nPartage de mémoire activé : {showmemenabled} \nEntraînement utilisateur activé : {USERTRAIN_ENABLED} \nChanson : {song} \nTexte de démarrage : ```{splashtext}```" + "command_stats_embed_field3value": "Nom : {NAME} \nPréfixe : {PREFIX} \nID du propriétaire : {ownerid}\nLigne de ping : {PING_LINE} \nPartage de mémoire activé : {showmemenabled} \nEntraînement utilisateur activé : {USERTRAIN_ENABLED} \nChanson : {song} \nTexte de démarrage : ```{splashtext}```" } \ No newline at end of file diff --git a/assets/locales/fy.json b/assets/locales/fy.json index 2779729..5881b5b 100644 --- a/assets/locales/fy.json +++ b/assets/locales/fy.json @@ -121,5 +121,5 @@ "command_stats_embed_field2name": "Ferzje", "command_stats_embed_field2value": "Lokaal: {local_version} \nLêste: {latest_version}", "command_stats_embed_field3name": "Fariabel Info", - "command_stats_embed_field3value": "Namme: {NAME} \nFoarheaksel: {PREFIX} \nEigner ID: {ownerid} \nCooldown: {cooldown_time} \nPingrigel: {PING_LINE} \nUnthâld Dielen Ynskeakele: {showmemenabled} \nBrûkerstraining Ynskeakele: {USERTRAIN_ENABLED}\nLiet: {song} \nSplashtekst: ```{splashtext}```" + "command_stats_embed_field3value": "Namme: {NAME} \nFoarheaksel: {PREFIX} \nEigner ID: {ownerid}\nPingrigel: {PING_LINE} \nUnthâld Dielen Ynskeakele: {showmemenabled} \nBrûkerstraining Ynskeakele: {USERTRAIN_ENABLED}\nLiet: {song} \nSplashtekst: ```{splashtext}```" } diff --git a/assets/locales/it.json b/assets/locales/it.json index ebf28aa..f86fc96 100644 --- a/assets/locales/it.json +++ b/assets/locales/it.json @@ -129,6 +129,6 @@ "command_stats_embed_field2name": "Versione", "command_stats_embed_field2value": "Locale: {local_version} \nUltima: {latest_version}", "command_stats_embed_field3name": "Informazioni sulle variabili", - "command_stats_embed_field3value": "Nome: {NAME} \nPrefisso: {PREFIX} \nID Proprietario: {ownerid} \nCooldown: {cooldown_time} \nLinea ping: {PING_LINE} \nMemoria Condivisa Abilitata: {showmemenabled} \nAddestramento Utente Abilitato: {USERTRAIN_ENABLED}\nCanzone: {song} \nSplashtext: ```{splashtext}```" + "command_stats_embed_field3value": "Nome: {NAME} \nPrefisso: {PREFIX} \nID Proprietario: {ownerid}\nLinea ping: {PING_LINE} \nMemoria Condivisa Abilitata: {showmemenabled} \nAddestramento Utente Abilitato: {USERTRAIN_ENABLED}\nCanzone: {song} \nSplashtext: ```{splashtext}```" } diff --git a/assets/locales/mt.json b/assets/locales/mt.json index 5e7e9bd..cb41722 100644 --- a/assets/locales/mt.json +++ b/assets/locales/mt.json @@ -121,5 +121,5 @@ "command_stats_embed_field2name": "Verżjoni", "command_stats_embed_field2value": "Lokali: {local_version} \nĠdida: {latest_version}", "command_stats_embed_field3name": "Informazzjoni Varjabbli", - "command_stats_embed_field3value": "Isem: {NAME} \nPrefiss: {PREFIX} \nID ta’ Sid: {ownerid} \nCooldown: {cooldown_time} \nPing line: {PING_LINE} \nImoħħar Memenja: {showmemenabled} \nUser Training Attiva: {USERTRAIN_ENABLED}\nKan ta’: {song} \nSplashtext: ```{splashtext}```" + "command_stats_embed_field3value": "Isem: {NAME} \nPrefiss: {PREFIX} \nID ta’ Sid: {ownerid}\nPing line: {PING_LINE} \nImoħħar Memenja: {showmemenabled} \nUser Training Attiva: {USERTRAIN_ENABLED}\nKan ta’: {song} \nSplashtext: ```{splashtext}```" } From 6d24bd8d68e686971df73311c76c4ec9b67809a8 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 20:39:39 +0200 Subject: [PATCH 12/84] up to date with volta --- modules/volta/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/volta/main.py b/modules/volta/main.py index bb98e0a..b8e88ea 100644 --- a/modules/volta/main.py +++ b/modules/volta/main.py @@ -117,7 +117,7 @@ def check_missing_translations(): ENGLISH_MISSING = True return if LOCALE == "en": - print("Locale is English, skipping missing key check.") + print("[VOLTA] Locale is English, skipping missing key check.") return @@ -134,12 +134,12 @@ def check_missing_translations(): print(f"[VOLTA] {YELLOW}Warning: All keys are missing in locale '{LOCALE}'! Defaulting back to {FALLBACK_LOCALE}{RESET}") set_language(FALLBACK_LOCALE) elif percent_missing > 0: - print(f"{YELLOW}Warning: {missing_count}/{total_keys} keys missing in locale '{LOCALE}' ({percent_missing:.1f}%)!{RESET}") + print(f"[VOLTA] {YELLOW}Warning: {missing_count}/{total_keys} keys missing in locale '{LOCALE}' ({percent_missing:.1f}%)!{RESET}") for key in sorted(missing_keys): print(f" - {key}") time.sleep(2) else: - print("All translation keys present for locale:", LOCALE) + print(f"[VOLTA] All translation keys present for locale: {LOCALE}") def get_translation(lang: str, key: str): From fb861e26717efac2bd11684a19c6510be173f156 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 20:41:24 +0200 Subject: [PATCH 13/84] no no FUCKING NO --- .gitignore | 3 + assets/cogs/fireboard.py | 1756 +++++++++++++++++++++++++++++++++++++ assets/locales/en.json | 2 +- assets/locales/es.json | 2 +- assets/locales/et.json | 2 +- assets/locales/fi.json | 2 +- assets/locales/fr.json | 2 +- assets/locales/fy.json | 2 +- assets/locales/it.json | 2 +- assets/locales/mt.json | 2 +- bot.py | 14 +- modules/central.py | 2 +- modules/globalvars.py | 2 +- modules/prestartchecks.py | 6 +- requirements.txt | 3 +- specialthanks.txt | 8 +- 16 files changed, 1792 insertions(+), 18 deletions(-) create mode 100644 assets/cogs/fireboard.py diff --git a/.gitignore b/.gitignore index 2c32b44..2bd2bfb 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ received_memory.json translation_report.txt translationcompleteness.py modules/volta +fireboard.db +fireboard.db-shm +fireboard.db-wal \ No newline at end of file diff --git a/assets/cogs/fireboard.py b/assets/cogs/fireboard.py new file mode 100644 index 0000000..bd38db4 --- /dev/null +++ b/assets/cogs/fireboard.py @@ -0,0 +1,1756 @@ +import asyncio +import random + +import asqlite +import discord +import discord.ext +import discord.ext.commands +from discord import Color, app_commands +from discord.ext import commands +from discord.ui import View + + +class Fireboard(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.locked_messages = [] + self.disabled = False + + # Check if the bot has the fireboard_pool attribute + if not hasattr(bot, 'fireboard_pool') or bot.fireboard_pool is None: + print("Warning: Bot does not have fireboard_pool initialized. Fireboard functionality will be disabled.") + self.disabled = True + return + + self.fireboard_pool: asqlite.Pool = bot.fireboard_pool + self.bot.loop.create_task(self.setup()) + + # SQL Setup + async def setup(self): + async with self.fireboard_pool.acquire() as sql: + if ( + await sql.fetchone( + "SELECT name FROM sqlite_master WHERE type='table' AND name='fireMessages';" + ) + is None + ): + # Fire Messages - messages that are active on the fireboard + await sql.execute( + "CREATE TABLE fireMessages (serverID int, msgID int, boardMsgID int, reactionAmount int)" + ) + + if ( + await sql.fetchone( + "SELECT name FROM sqlite_master WHERE type='table' AND name='fireSettings';" + ) + is None + ): + # Fire Settings - server properties for fireboard + await sql.execute( + "CREATE TABLE fireSettings (serverID int, reactionAmount int, emoji text, channelID int, ignoreBots int)" + ) + + if ( + await sql.fetchone( + "SELECT name FROM sqlite_master WHERE type='table' AND name='fireChannelBlacklist';" + ) + is None + ): + # Fire Channel Blacklist - blacklisted channels + await sql.execute( + "CREATE TABLE fireChannelBlacklist (serverID int, channelID int)" + ) + + if ( + await sql.fetchone( + "SELECT name FROM sqlite_master WHERE type='table' AND name='fireRoleBlacklist';" + ) + is None + ): + # Fire Role Blacklist - blacklisted roles + await sql.execute( + "CREATE TABLE fireRoleBlacklist (serverID int, roleID int)" + ) + + await sql.commit() + + await self.refresh_fire_lists() + + # List refresh function + async def refresh_fire_lists(self): + async with self.fireboard_pool.acquire() as sql: + self.fire_messages = await sql.fetchall("SELECT * FROM fireMessages") + self.fire_settings = await sql.fetchall("SELECT * FROM fireSettings") + self.fire_channel_blacklist = await sql.fetchall( + "SELECT * FROM fireChannelBlacklist" + ) + self.fire_role_blacklist = await sql.fetchall( + "SELECT * FROM fireRoleBlacklist" + ) + + # Listen for reactions + @commands.Cog.listener() + async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + queued = False + + # Lock system + if payload.message_id in self.locked_messages: + queued = True + + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + fetched = False + + # Find server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + react_minimum = server[1] + emoji = server[2] + channel_id = server[3] + ignore_bots = True if int(server[4]) == 1 else False + + fetched = True + + # Stop if server has no config (fireboard isn't enabled) + if not fetched: + return + + # Stop if message is by Titanium + if payload.message_author_id == self.bot.user.id: + return + + # Stop if emoji doesn't match + if str(payload.emoji) != emoji: + return + + # --- Edit board message if it already exists --- + if payload.message_id in [message[1] for message in self.fire_messages]: + message = [ + message + for message in self.fire_messages + if message[1] == payload.message_id + ][0] + + # Only fetch updated reaction count if I have queued or reaction amount is undefined + if queued or message[3] is None: + # Fetch message and channel + try: + msg_channel = await self.bot.fetch_channel(payload.channel_id) + message = await msg_channel.fetch_message(payload.message_id) + except discord.errors.NotFound: + return + + # Stop if not enough reactions + for reaction in message.reactions: + if str(reaction.emoji) == emoji: + react_count = reaction.count + break + + if react_count < react_minimum: + return + else: + react_count = None + + async with self.fireboard_pool.acquire() as sql: + # Set updated react count + if react_count is not None: + await sql.execute( + "UPDATE fireMessages SET reactionAmount = ? WHERE msgID = ?", + ( + react_count, + payload.message_id, + ), + ) + await self.refresh_fire_lists() + else: + await sql.execute( + "UPDATE fireMessages SET reactionAmount = reactionAmount + 1 WHERE msgID = ?", + (payload.message_id,), + ) + await self.refresh_fire_lists() + + # Get message from message list + message = [ + message + for message in self.fire_messages + if message[1] == payload.message_id + ][0] + + # Get board message + board_channel = await self.bot.fetch_channel(channel_id) + board_message = await board_channel.fetch_message(message[2]) + + await board_message.edit( + content=f"**{message[3]} {emoji}** | <@{payload.message_author_id}> | <#{payload.channel_id}>", + embeds=board_message.embeds, + ) + + return + + # Stop if message is in a blacklisted channel + if payload.channel_id in [ + channel[1] for channel in self.fire_channel_blacklist + ]: + return + + # Stop if message is by a blacklisted role + guild = await self.bot.fetch_guild(payload.guild_id) + member = await guild.fetch_member(payload.user_id) + + if any( + role[1] in [role.id for role in member.roles] + for role in self.fire_role_blacklist + ): + return + + # Fetch message and channel + try: + msg_channel = await self.bot.fetch_channel(payload.channel_id) + message = await msg_channel.fetch_message(payload.message_id) + except discord.errors.NotFound: + return + + # Stop if message is by a bot + if ignore_bots and message.author.bot: + return + + # Check if this is a thread + if isinstance(msg_channel, discord.Thread): + # Check if parent channel is NSFW + if msg_channel.parent.nsfw: + return + else: + # Stop if message is in an NSFW channel + if message.channel.nsfw: + return + + # Stop if not enough reactions + for reaction in message.reactions: + if str(reaction.emoji) == emoji: + react_count = reaction.count + break + + if react_count < react_minimum: + return + + # --- Send message to fireboard --- + + # Create embed + embed = discord.Embed(description=message.content, color=Color.random()) + embed.set_author( + name=message.author.name, icon_url=message.author.display_avatar.url + ) + embed.timestamp = message.created_at + + # Jump to message button + view = View() + view.add_item( + discord.ui.Button( + label="Jump to Message", + url=message.jump_url, + style=discord.ButtonStyle.url, + ) + ) + + embed_list = [embed] + + # Add reply embed + if message.reference: + try: + reply_message = await msg_channel.fetch_message( + message.reference.message_id + ) + + reply_embed = discord.Embed( + title="Replying To", + description=reply_message.content, + color=Color.random(), + ) + reply_embed.set_author( + name=reply_message.author.name, + icon_url=reply_message.author.display_avatar.url, + ) + reply_embed.timestamp = reply_message.created_at + + embed_list.insert(0, reply_embed) + except discord.errors.NotFound: + pass + + # Send message + board_channel = await self.bot.fetch_channel(channel_id) + board_message = await board_channel.send( + content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", + embeds=embed_list, + view=view, + files=[ + await attachment.to_file() for attachment in message.attachments + ], + ) + + async with self.fireboard_pool.acquire() as sql: + # Insert message to DB + await sql.execute( + "INSERT INTO fireMessages (serverID, msgID, boardMsgID, reactionAmount) VALUES (?, ?, ?, ?)", + ( + payload.guild_id, + payload.message_id, + board_message.id, + react_count, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for reaction removal + @commands.Cog.listener() + async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): + self.bot: discord.ext.commands.Bot + + queued = False + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + queued = True + + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + fetched = False + + # Find server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + react_minimum = server[1] + emoji = server[2] + channel_id = server[3] + ignore_bots = True if int(server[4]) == 1 else False + + fetched = True + + # Stop if server has no config (fireboard isn't enabled) + if not fetched: + return + + # Stop if message is by Titanium + if payload.message_author_id == self.bot.user.id: + return + + # Stop if emoji doesn't match + if str(payload.emoji) != emoji: + return + + # --- Edit board message if it already exists --- + if payload.message_id in [message[1] for message in self.fire_messages]: + prev_react_count = [ + message + for message in self.fire_messages + if message[1] == payload.message_id + ][0][3] + + # Only fetch updated reaction count if I have queued + if queued or prev_react_count is None: + # Fetch message and channel + try: + msg_channel = await self.bot.fetch_channel(payload.channel_id) + message = await msg_channel.fetch_message(payload.message_id) + except discord.errors.NotFound: + return + + # Stop if not enough reactions + for reaction in message.reactions: + if str(reaction.emoji) == emoji: + react_count = reaction.count + break + + if react_count < react_minimum: + return + else: + react_count = None + + async with self.fireboard_pool.acquire() as sql: + # Set updated react count + if react_count is not None: + await sql.execute( + "UPDATE fireMessages SET reactionAmount = ? WHERE msgID = ?", + ( + react_count, + payload.message_id, + ), + ) + await self.refresh_fire_lists() + else: + await sql.execute( + "UPDATE fireMessages SET reactionAmount = reactionAmount - 1 WHERE msgID = ?", + (payload.message_id,), + ) + await self.refresh_fire_lists() + + # Get message from message list + message = [ + message + for message in self.fire_messages + if message[1] == payload.message_id + ][0] + + # Get board message + board_channel = await self.bot.fetch_channel(channel_id) + board_message = await board_channel.fetch_message(message[2]) + + # Remove message if not enough reactions + if message[3] < react_minimum: + await board_message.delete() + + async with self.fireboard_pool.acquire() as sql: + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + + # Workaround for lack of message author ID + content = board_message.content + content = content.replace( + f"{prev_react_count} {emoji}", f"{message[3]} {emoji}" + ) + + await board_message.edit(content=content) + + return + + # Stop if message is in a blacklisted channel + if payload.channel_id in [ + channel[1] for channel in self.fire_channel_blacklist + ]: + return + + # Stop if message is by a blacklisted role + guild = await self.bot.fetch_guild(payload.guild_id) + member = await guild.fetch_member(payload.user_id) + + if any( + role[1] in [role.id for role in member.roles] + for role in self.fire_role_blacklist + ): + return + + # Fetch message and channel + try: + msg_channel = await self.bot.fetch_channel(payload.channel_id) + message = await msg_channel.fetch_message(payload.message_id) + except discord.errors.NotFound: + return + + # Stop if message is by a bot + if ignore_bots and message.author.bot: + return + + # Check if this is a thread + if isinstance(msg_channel, discord.Thread): + # Check if parent channel is NSFW + if msg_channel.parent.nsfw: + return + else: + # Stop if message is in an NSFW channel + if message.channel.nsfw: + return + + # Get reaction count + react_count = 0 + + for reaction in message.reactions: + if str(reaction.emoji) == emoji: + react_count = reaction.count + break + + # Stop if not enough reactions + if react_count < react_minimum: + return + + # --- Send message to fireboard --- + + # Create embed + embed = discord.Embed(description=message.content, color=Color.random()) + embed.set_author( + name=message.author.name, icon_url=message.author.display_avatar.url + ) + embed.timestamp = message.created_at + + # Jump to message button + view = View() + view.add_item( + discord.ui.Button( + label="Jump to Message", + url=message.jump_url, + style=discord.ButtonStyle.url, + ) + ) + + embed_list = [embed] + + # Add reply embed + if message.reference: + try: + reply_message = await msg_channel.fetch_message( + message.reference.message_id + ) + + reply_embed = discord.Embed( + title="Replying To", + description=reply_message.content, + color=Color.random(), + ) + reply_embed.set_author( + name=reply_message.author.name, + icon_url=reply_message.author.display_avatar.url, + ) + reply_embed.timestamp = reply_message.created_at + + embed_list.insert(0, reply_embed) + except discord.errors.NotFound: + pass + + # Send message + board_channel = await self.bot.fetch_channel(channel_id) + board_message = await board_channel.send( + content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", + embeds=embed_list, + view=view, + files=[ + await attachment.to_file() for attachment in message.attachments + ], + ) + + async with self.fireboard_pool.acquire() as sql: + # Insert message to DB + await sql.execute( + "INSERT INTO fireMessages (serverID, msgID, boardMsgID, reactionAmount) VALUES (?, ?, ?, ?)", + ( + payload.guild_id, + payload.message_id, + board_message.id, + react_count, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for message reaction clear + @commands.Cog.listener() + async def on_raw_reaction_clear(self, payload: discord.RawReactionClearEvent): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + # Only trigger if message is already in the fireboard DB + if payload.message_id in [message[1] for message in self.fire_messages]: + # Find server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + channel_id = server[3] + + # Get guild + try: + guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) + except discord.errors.NotFound: + return + + # Get message channel + channel: discord.abc.GuildChannel = await guild.fetch_channel( + payload.channel_id + ) + + # Get our message + message: discord.Message = await channel.fetch_message( + payload.message_id + ) + + # See if board message is already present + for fire_message in self.fire_messages: + if fire_message[1] == message.id: + async with self.fireboard_pool.acquire() as sql: + try: + # Delete message + try: + channel: discord.TextChannel = ( + await guild.fetch_channel(channel_id) + ) + except discord.errors.NotFound: + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (payload.guild_id,), + ) + await sql.commit() + self.locked_messages.remove(payload.message_id) + + return + + board_message = await channel.fetch_message( + fire_message[2] + ) + await board_message.delete() + + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (message.id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + except discord.errors.NotFound: + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (message.id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for specific emoji being cleared + @commands.Cog.listener() + async def on_raw_reaction_clear_emoji( + self, payload: discord.RawReactionClearEmojiEvent + ): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + # Only trigger if message is already in the fireboard DB + if payload.message_id in [message[1] for message in self.fire_messages]: + for server in self.fire_settings: + if server[0] == payload.guild_id: + emoji = server[2] + channel_id = server[3] + + # Only trigger if cleared emoji is our emoji + if str(payload.emoji) == emoji: + # Fetch server + try: + guild: discord.Guild = await self.bot.fetch_guild( + payload.guild_id + ) + except discord.errors.NotFound: + return + + # Get message channel + channel: discord.abc.GuildChannel = await guild.fetch_channel( + payload.channel_id + ) + + # See if board message is already present + for fire_message in self.fire_messages: + if fire_message[1] == payload.message_id: + async with self.fireboard_pool.acquire() as sql: + try: + # Fetch fireboard channel + try: + channel: discord.TextChannel = ( + await guild.fetch_channel(channel_id) + ) + except discord.errors.NotFound: + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (payload.guild_id,), + ) + await sql.commit() + + return + + # Delete message + board_message = await channel.fetch_message( + fire_message[2] + ) + await board_message.delete() + + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + except discord.errors.NotFound: + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for message being deleted + @commands.Cog.listener() + async def on_raw_message_delete(self, payload: discord.RawMessageDeleteEvent): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + # Only trigger if message is already in the fireboard DB + if payload.message_id in [message[1] for message in self.fire_messages]: + # Fetch server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + channel_id = server[3] + + # Fetch server + try: + guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) + except discord.errors.NotFound: + return + + # Get message channel + channel: discord.abc.GuildChannel = await guild.fetch_channel( + payload.channel_id + ) + + # See if board message is already present + for fire_message in self.fire_messages: + if fire_message[1] == payload.message_id: + async with self.fireboard_pool.acquire() as sql: + try: + # Fetch fireboard channel + try: + channel: discord.TextChannel = ( + await guild.fetch_channel(channel_id) + ) + except discord.errors.NotFound: + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (payload.guild_id,), + ) + await sql.commit() + + return + + # Delete message + board_message = await channel.fetch_message( + fire_message[2] + ) + await board_message.delete() + + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + except discord.errors.NotFound: + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for message being edited + @commands.Cog.listener() + async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + # Only trigger if message is already in the fireboard DB + if payload.message_id in [message[1] for message in self.fire_messages]: + # Fetch server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + channel_id = server[3] + + # Fetch server + try: + guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) + except discord.errors.NotFound: + return + + # Get message channel + channel: discord.abc.GuildChannel = await guild.fetch_channel( + payload.channel_id + ) + + # Get our message + message: discord.Message = await channel.fetch_message( + payload.message_id + ) + + embed = discord.Embed(description=message.content, color=Color.random()) + embed.set_author( + name=message.author.name, icon_url=message.author.display_avatar.url + ) + embed.timestamp = message.created_at + + # Jump to message button + view = View() + view.add_item( + discord.ui.Button( + label="Jump to Message", + url=message.jump_url, + style=discord.ButtonStyle.url, + ) + ) + + embed_list = [embed] + + # Add reply embed + if message.reference: + try: + reply_message = await channel.fetch_message( + message.reference.message_id + ) + + reply_embed = discord.Embed( + title="Replying To", + description=reply_message.content, + color=Color.random(), + ) + reply_embed.set_author( + name=reply_message.author.name, + icon_url=reply_message.author.display_avatar.url, + ) + reply_embed.timestamp = reply_message.created_at + + embed_list.insert(0, reply_embed) + except discord.errors.NotFound: + pass + + try: + channel: discord.TextChannel = await guild.fetch_channel(channel_id) + except discord.errors.NotFound: + async with self.fireboard_pool.acquire() as sql: + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (payload.guild_id,), + ) + await sql.commit() + + return + + # Find previous fireboard message + try: + for fire_message in self.fire_messages: + if ( + fire_message[0] == payload.guild_id + and fire_message[1] == payload.message_id + ): + # Edit with updated embed - reaction amount stays the same + board_message = await channel.fetch_message(fire_message[2]) + + await board_message.edit( + embeds=embed_list, attachments=message.attachments + ) + except discord.errors.NotFound: # Message not found + async with self.fireboard_pool.acquire() as sql: + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for fireboard channel delete + @commands.Cog.listener() + async def on_guild_channel_delete(self, channel: discord.abc.GuildChannel): + # Only trigger if server has fireboard enabled + if channel.guild.id in [guild[0] for guild in self.fire_settings]: + for server in self.fire_settings: + if server[0] == channel.guild.id: + if server[3] == channel.id: + async with self.fireboard_pool.acquire() as sql: + # Delete fireboard config + await sql.execute( + "DELETE FROM fireMessages WHERE serverID = ?", + (channel.guild.id,), + ) + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (channel.guild.id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + + # Listen for server being left / deleted + @commands.Cog.listener() + async def on_guild_remove(self, guild: discord.Guild): + # Only trigger if server has fireboard enabled + if guild.id in [guild[0] for guild in self.fire_settings]: + for server in self.fire_settings: + if server[0] == guild.id: + async with self.fireboard_pool.acquire() as sql: + # Delete fireboard config + await sql.execute( + "DELETE FROM fireMessages WHERE serverID = ?", (guild.id,) + ) + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", (guild.id,) + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + + # Command group setup + context = discord.app_commands.AppCommandContext( + guild=True, dm_channel=False, private_channel=False + ) + installs = discord.app_commands.AppInstallationType(guild=True, user=False) + fireGroup = app_commands.Group( + name="fireboard", + description="Fireboard related commands.", + allowed_contexts=context, + allowed_installs=installs, + ) + + # Random fireboard message command + @fireGroup.command( + name="random", description="Get a random message from the fireboard." + ) + @app_commands.describe( + ephemeral="Optional: whether to send the command output as a dismissible message only visible to you. Defaults to false." + ) + async def random_fireboard( + self, interaction: discord.Interaction, ephemeral: bool = False + ): + await interaction.response.defer(ephemeral=ephemeral) + + channel_id = None + + # Find server config + for server in self.fire_settings: + if server[0] == interaction.guild_id: + channel_id = server[3] + + if channel_id is None: + embed = discord.Embed( + title="Error", + description="Fireboard is not enabled in this server.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=ephemeral) + + return + + # Fetch channel + try: + channel = await interaction.guild.fetch_channel(channel_id) + except discord.errors.NotFound: + embed = discord.Embed( + title="Error", + description="Can't find the fireboard channel. Please contact a server admin.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=ephemeral) + + return + + # Fetch messages + async with self.fireboard_pool.acquire() as sql: + messages = await sql.fetchall( + "SELECT * FROM fireMessages WHERE serverID = ?", (interaction.guild_id,) + ) + + if not messages: + embed = discord.Embed( + title="Error", + description="No messages found in the fireboard.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=ephemeral) + + return + else: + while messages != []: + message = random.choice(messages) + + try: + board_message = await channel.fetch_message(message[2]) + + view = View().from_message(board_message) + files = [ + await attachment.to_file() + for attachment in board_message.attachments + ] + + await interaction.followup.send( + content=board_message.content, + embeds=board_message.embeds, + view=view, + ephemeral=ephemeral, + files=files, + allowed_mentions=discord.AllowedMentions.none(), + ) + + return + except discord.errors.NotFound: + messages.remove(message) + + embed = discord.Embed( + title="Error", + description="No messages found in the fireboard.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=ephemeral) + + # Command group setup + context = discord.app_commands.AppCommandContext( + guild=True, dm_channel=False, private_channel=False + ) + installs = discord.app_commands.AppInstallationType(guild=True, user=False) + perms = discord.Permissions(manage_guild=True) + fireSetupGroup = app_commands.Group( + name="fireboard-setup", + description="Control the fireboard.", + allowed_contexts=context, + allowed_installs=installs, + default_permissions=perms, + ) + + # Fireboard enable command + @fireSetupGroup.command( + name="enable", description="Enable the fireboard in the current channel." + ) + async def enable_fireboard(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Fireboard is already enabled.", color=Color.green() + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + # Default settings + react_minimum = 3 + emoji = "🔥" + channel_id = interaction.channel_id + ignore_bots = True + + embed = discord.Embed( + title="Fireboard", + description="This channel has been configured as the server fireboard.", + color=Color.random(), + ) + embed.set_footer(text="Feel free to delete this message!") + + try: + channel = await interaction.guild.fetch_channel(channel_id) + await channel.send(embed=embed) + except discord.errors.Forbidden or discord.errors.NotFound: + embed = discord.Embed( + title="Error", + description="Looks like I can't send messages in this channel. Check permissions and try again.", + color=Color.random(), + ) + await interaction.followup.send(embed=embed, ephemeral=True) + + return + + async with self.fireboard_pool.acquire() as sql: + # Insert to DB, refresh lists + await sql.execute( + "INSERT INTO fireSettings (serverID, reactionAmount, emoji, channelID, ignoreBots) VALUES (?, ?, ?, ?, ?)", + ( + interaction.guild_id, + react_minimum, + emoji, + channel_id, + ignore_bots, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Enabled", + description="Fireboard has been enabled in the current channel.", + color=Color.green(), + ) + embed.add_field( + name="Info", + value=f"**Reaction Requirement:** `{react_minimum} reactions`\n**Fireboard Channel:** <#{channel_id}>\n**Emoji:** {emoji}\n**Ignore Bots:** `{ignore_bots}`", + ) + + await interaction.followup.send(embed=embed, ephemeral=True) + + class ConfirmDisableView(View): + def __init__(self): + super().__init__(timeout=60) + + async def disable_fireboard( + self, interaction: discord.Interaction, pool: asqlite.Pool + ): + async with pool.acquire() as sql: + try: + await sql.execute( + "DELETE FROM fireMessages WHERE serverID = ?", + (interaction.guild_id,), + ) + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (interaction.guild_id,), + ) + await sql.execute( + "DELETE FROM fireChannelBlacklist WHERE serverID = ?", + (interaction.guild_id,), + ) + await sql.execute( + "DELETE FROM fireRoleBlacklist WHERE serverID = ?", + (interaction.guild_id,), + ) + await sql.commit() + return True + except Exception: + return False + + @discord.ui.button(label="Disable", style=discord.ButtonStyle.red) + async def confirm( + self, interaction: discord.Interaction, button: discord.ui.Button + ): + await interaction.response.defer(ephemeral=True) + + success = await self.disable_fireboard(interaction, self.pool) + + if success: + embed = discord.Embed( + title="Done!", + description="Fireboard was disabled.", + color=Color.green(), + ) + await self.cog.refresh_fire_lists() # pylint: disable=no-member + else: + embed = discord.Embed( + title="Error", + description="Failed to disable fireboard.", + color=Color.red(), + ) + + await interaction.edit_original_response(embed=embed, view=None) + self.stop() + + async def on_timeout(self): + for item in self.children: + item.disabled = True + + embed = discord.Embed( + title="Timeout", + description="You didn't press the button in time.", + color=Color.red(), + ) + await self.message.edit(embed=embed, view=self) + + # Fireboard disable command + @fireSetupGroup.command(name="disable", description="Disable the server fireboard.") + async def disable_fireboard(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + + if interaction.guild_id in [guild[0] for guild in self.fire_settings]: + view = self.ConfirmDisableView() + view.pool = self.fireboard_pool + view.cog = self + + embed = discord.Embed( + title="Are you sure?", + description="All data about this server's fireboard will be deleted. This cannot be undone!", + color=Color.orange(), + ) + + message = await interaction.followup.send( + embed=embed, view=view, ephemeral=True, wait=True + ) + view.message = message + else: + await interaction.followup.send( + "Fireboard is not enabled in this server!", ephemeral=True + ) + + # Fireboard server info command + @fireSetupGroup.command( + name="info", description="View fireboard config for this server." + ) + async def fireboard_info(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + # Fetch server settings + for server in self.fire_settings: + if server[0] == interaction.guild_id: + react_minimum = server[1] + emoji = server[2] + channel_id = server[3] + ignore_bots = True if int(server[4]) == 1 else False + + embed = discord.Embed( + title="Server Fireboard Settings", + description=f"**Reaction Requirement:** `{react_minimum} reactions`\n**Fireboard Channel:** <#{channel_id}>\n**Emoji:** {emoji}\n**Ignore Bots:** `{ignore_bots}`", + color=Color.random(), + ) + + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard set emoji command + @fireSetupGroup.command(name="emoji", description="Set a custom fireboard emoji.") + async def fireboard_emoji(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=False) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Waiting for Reaction", + description="🔄 React with this message with your target emoji to set the fireboard emoji.", + color=Color.orange(), + ) + + msg = await interaction.followup.send(embed=embed, ephemeral=False) + + def check(reaction, user): + return user == interaction.user and reaction.message.id == msg.id + + # Wait for a reaction + try: + reaction, user = await self.bot.wait_for( + "reaction_add", timeout=60.0, check=check + ) + + reaction: discord.Reaction = reaction + + async with self.fireboard_pool.acquire() as sql: + # Change emoji in DB, refresh lists + await sql.execute( + "UPDATE fireSettings SET emoji = ? WHERE serverID = ?", + ( + str(reaction.emoji), + interaction.guild_id, + ), + ) + await sql.commit() + + embed = discord.Embed( + title="Emoji Set", + description=f"Set emoji to **{str(reaction.emoji)}.**", + color=Color.green(), + ) + + await self.refresh_fire_lists() + await interaction.edit_original_response(embed=embed) + except asyncio.TimeoutError: # Timed out + embed = discord.Embed( + title="Timed Out", + description="You didn't react in time.", + color=Color.red(), + ) + + await interaction.edit_original_response(embed=embed) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=False) + + # Fireboard set channel command + @fireSetupGroup.command( + name="channel", + description="Set the channel for fireboard messages to be sent in.", + ) + async def fireboard_channel( + self, interaction: discord.Interaction, channel: discord.TextChannel + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Fireboard", + description="This channel has been configured as the server fireboard.", + color=Color.random(), + ) + embed.set_footer(text="Feel free to delete this message!") + + try: + await channel.send(embed=embed) + except discord.errors.NotFound: + embed = discord.Embed( + title="Error", + description="Looks like I can't find that channel. Check permissions and try again.", + color=Color.random(), + ) + await interaction.followup.send(embed=embed, ephemeral=True) + + return + except discord.errors.Forbidden as e: + embed = discord.Embed( + title="Error", + description="Looks like I can't send messages in that channel. Check permissions and try again.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=True) + + return + + async with self.fireboard_pool.acquire() as sql: + # Update channel in DB, refresh lists + await sql.execute( + "UPDATE fireSettings SET channelID = ? WHERE serverID = ?", + ( + channel.id, + interaction.guild_id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Channel Set", + description=f"Fireboard channel has been set to **{channel.mention}.**", + color=Color.green(), + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard set requirement command + @fireSetupGroup.command( + name="requirement", + description="Set required reaction amount for message to be posted on the fireboard.", + ) + async def fireboard_requirement( + self, interaction: discord.Interaction, amount: int + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Set", + description=f"Reaction requirement has been set to **{amount} reactions.**", + color=Color.green(), + ) + + async with self.fireboard_pool.acquire() as sql: + # Update reaction requirement in DB, refresh lists + await sql.execute( + "UPDATE fireSettings SET reactionAmount = ? WHERE serverID = ?", + ( + amount, + interaction.guild_id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard ignore bots command + @fireSetupGroup.command( + name="ignore-bots", + description="Whether bot messages are ignored in the fireboard. Defaults to true.", + ) + async def fireboard_ignore_bots( + self, interaction: discord.Interaction, value: bool + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Set", + description=f"Bot messages will **{'be ignored.' if value else 'not be ignored.'}**", + color=Color.green(), + ) + + async with self.fireboard_pool.acquire() as sql: + # Update setting in DB, refresh lists + await sql.execute( + "UPDATE fireSettings SET ignoreBots = ? WHERE serverID = ?", + ( + value, + interaction.guild_id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard role blacklist + @fireSetupGroup.command( + name="channel-blacklist", + description="Toggle the blacklist for a channel. NSFW channels are always blacklisted.", + ) + async def fireboard_channel_blacklist( + self, interaction: discord.Interaction, channel: discord.abc.GuildChannel + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild_id in [guild[0] for guild in self.fire_settings]: + async with self.fireboard_pool.acquire() as sql: + if channel.id in [ + channelEntry[1] for channelEntry in self.fire_channel_blacklist + ]: + await sql.execute( + "DELETE FROM fireChannelBlacklist WHERE serverID = ? AND channelID = ?", + ( + interaction.guild_id, + channel.id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Set", + description=f"Removed {channel.mention} from the channel blacklist.", + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + await sql.execute( + "INSERT INTO fireChannelBlacklist (serverID, channelID) VALUES (?, ?)", + ( + interaction.guild_id, + channel.id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Set", + description=f"Added {channel.mention} to the channel blacklist.", + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard role blacklist + @fireSetupGroup.command( + name="role-blacklist", description="Toggle the blacklist for a role." + ) + async def fireboard_role_blacklist( + self, interaction: discord.Interaction, role: discord.Role + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild_id in [guild[0] for guild in self.fire_settings]: + async with self.fireboard_pool.acquire() as sql: + if role.id in [roleEntry[1] for roleEntry in self.fire_role_blacklist]: + await sql.execute( + "DELETE FROM fireRoleBlacklist WHERE serverID = ? AND roleID = ?", + ( + interaction.guild_id, + role.id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Set", + description=f"Removed {role.mention} from the role blacklist.", + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + await sql.execute( + "INSERT INTO fireRoleBlacklist (serverID, roleID) VALUES (?, ?)", + ( + interaction.guild_id, + role.id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Set", + description=f"Added {role.mention} to the role blacklist.", + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard role blacklist + @fireSetupGroup.command( + name="blacklists", description="View this server's role and channel blacklists." + ) + async def fireboard_blacklists(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild_id in [guild[0] for guild in self.fire_settings]: + + class BlacklistViewer(View): + def __init__(self): + super().__init__(timeout=240) + + self.fire_channel_blacklist: list + self.fire_role_blacklist: list + self.interaction: discord.Interaction + + async def on_timeout(self) -> None: + for item in self.children: + item.disabled = True + + await self.interaction.edit_original_response(view=self) + + @discord.ui.button( + label="Role Blacklist", + style=discord.ButtonStyle.gray, + row=0, + custom_id="role", + disabled=True, + ) + async def role( + self, interaction: discord.Interaction, button: discord.ui.Button + ): + await interaction.response.defer(ephemeral=True) + + for item in self.children: + if item.custom_id == "channel": + item.disabled = False + else: + item.disabled = True + + my_roles = [] + + for role in self.fire_role_blacklist: + if role[0] == interaction.guild_id: + my_roles.append(f"<@&{role[1]}>") + + if my_roles != []: + embed = discord.Embed( + title="Role Blacklist", + description="\n".join(my_roles), + color=Color.random(), + ) + await interaction.edit_original_response(embed=embed, view=self) + else: + embed = discord.Embed( + title="Role Blacklist", + description="No roles have been blacklisted.", + color=Color.random(), + ) + await interaction.edit_original_response(embed=embed, view=self) + + @discord.ui.button( + label="Channel Blacklist", + style=discord.ButtonStyle.gray, + row=0, + custom_id="channel", + ) + async def channel( + self, interaction: discord.Interaction, button: discord.ui.Button + ): + await interaction.response.defer(ephemeral=True) + + for item in self.children: + if item.custom_id == "role": + item.disabled = False + else: + item.disabled = True + + my_channels = [] + + for channel in self.fire_channel_blacklist: + if channel[0] == interaction.guild_id: + my_channels.append(f"<#{channel[1]}>") + + if my_channels != []: + embed = discord.Embed( + title="Channel Blacklist", + description="\n".join(my_channels), + color=Color.random(), + ) + await interaction.edit_original_response(embed=embed, view=self) + else: + embed = discord.Embed( + title="Channel Blacklist", + description="No channels have been blacklisted.", + color=Color.random(), + ) + await interaction.edit_original_response(embed=embed, view=self) + + view_instance = BlacklistViewer() + view_instance.fire_channel_blacklist = self.fire_channel_blacklist + view_instance.fire_role_blacklist = self.fire_role_blacklist + view_instance.interaction = interaction + + my_roles = [] + + for role in self.fire_role_blacklist: + if role[0] == interaction.guild_id: + my_roles.append(f"<@&{role[1]}>") + + if my_roles != []: + embed = discord.Embed( + title="Role Blacklist", + description="\n".join(my_roles), + color=Color.random(), + ) + await interaction.followup.send( + embed=embed, view=view_instance, ephemeral=True + ) + else: + embed = discord.Embed( + title="Role Blacklist", + description="No roles have been blacklisted.", + color=Color.random(), + ) + await interaction.followup.send( + embed=embed, view=view_instance, ephemeral=True + ) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + +async def setup(bot): + await bot.add_cog(Fireboard(bot)) \ No newline at end of file diff --git a/assets/locales/en.json b/assets/locales/en.json index cca8867..d196eaf 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -128,6 +128,6 @@ "command_stats_embed_field2name": "Version", "command_stats_embed_field2value": "Local: {local_version} \nLatest: {latest_version}", "command_stats_embed_field3name": "Variable Info", - "command_stats_embed_field3value": "Name: {NAME} \nPrefix: {PREFIX} \nOwner ID: {ownerid} \nCooldown: {cooldown_time} \nPing line: {PING_LINE} \nMemory Sharing Enabled: {showmemenabled} \nUser Training Enabled: {USERTRAIN_ENABLED}\nSong: {song} \nSplashtext: ```{splashtext}```" + "command_stats_embed_field3value": "Name: {NAME} \nPrefix: {PREFIX} \nOwner ID: {ownerid}\nPing line: {PING_LINE} \nMemory Sharing Enabled: {showmemenabled} \nUser Training Enabled: {USERTRAIN_ENABLED}\nSong: {song} \nSplashtext: ```{splashtext}```" } diff --git a/assets/locales/es.json b/assets/locales/es.json index eb7b743..4444a9c 100644 --- a/assets/locales/es.json +++ b/assets/locales/es.json @@ -73,5 +73,5 @@ "command_stats_embed_field2name": "Version", "command_stats_embed_field2value": "Version local: {local_version} \nUltima version: {latest_version}", "command_stats_embed_field3name": "informacion sobre las variables", - "command_stats_embed_field3value": "Nombre: {NAME} \nPrefijo: {PREFIX} \nID del propietario: {ownerid} \nTiempo de reutilizacion: {cooldown_time} \nLinea de ping: {PING_LINE} \nCompartir memoria habilitada: {showmemenabled} \nEntrenamiento de usuario habilitado: {USERTRAIN_ENABLED} \nCancion: {song} \nTexto de bienvenida: ```{splashtext}```" + "command_stats_embed_field3value": "Nombre: {NAME} \nPrefijo: {PREFIX} \nID del propietario: {ownerid}\nLinea de ping: {PING_LINE} \nCompartir memoria habilitada: {showmemenabled} \nEntrenamiento de usuario habilitado: {USERTRAIN_ENABLED} \nCancion: {song} \nTexto de bienvenida: ```{splashtext}```" } \ No newline at end of file diff --git a/assets/locales/et.json b/assets/locales/et.json index 9e8b20c..a169d05 100644 --- a/assets/locales/et.json +++ b/assets/locales/et.json @@ -126,6 +126,6 @@ "command_stats_embed_field2name": "Versioon", "command_stats_embed_field2value": "Kohalik: {local_version} \nViimane: {latest_version}", "command_stats_embed_field3name": "Muutuja info", - "command_stats_embed_field3value": "Nimi: {NAME} \nPrefiks: {PREFIX} \nOmaniku ID: {ownerid} \nJahtumisaeg: {cooldown_time} \nPingirida: {PING_LINE} \nMälu jagamine lubatud: {showmemenabled} \nKasutajaõpe lubatud: {USERTRAIN_ENABLED}\nLaul: {song} \nSplashtekst: ```{splashtext}```" + "command_stats_embed_field3value": "Nimi: {NAME} \nPrefiks: {PREFIX} \nOmaniku ID: {ownerid}\nPingirida: {PING_LINE} \nMälu jagamine lubatud: {showmemenabled} \nKasutajaõpe lubatud: {USERTRAIN_ENABLED}\nLaul: {song} \nSplashtekst: ```{splashtext}```" } diff --git a/assets/locales/fi.json b/assets/locales/fi.json index bdc1544..ea6adf0 100644 --- a/assets/locales/fi.json +++ b/assets/locales/fi.json @@ -128,6 +128,6 @@ "command_stats_embed_field2name": "Versio", "command_stats_embed_field2value": "Paikallinen: {local_version} \nUusin: {latest_version}", "command_stats_embed_field3name": "Muuttajainformaatio", - "command_stats_embed_field3value": "Nimi: {NAME} \nEtuliite: {PREFIX} \nOmistajan ID: {ownerid} \nJäähtymisaika: {cooldown_time} \nPing-linja: {PING_LINE} \nMuistin jako päällä: {showmemenabled} \nOppiminen käyttäjistä: {USERTRAIN_ENABLED}\nLaulu: {song} \nRoisketeksti: ```{splashtext}```" + "command_stats_embed_field3value": "Nimi: {NAME} \nEtuliite: {PREFIX} \nOmistajan ID: {ownerid}\nPing-linja: {PING_LINE} \nMuistin jako päällä: {showmemenabled} \nOppiminen käyttäjistä: {USERTRAIN_ENABLED}\nLaulu: {song} \nRoisketeksti: ```{splashtext}```" } \ No newline at end of file diff --git a/assets/locales/fr.json b/assets/locales/fr.json index 4561819..572d2f4 100644 --- a/assets/locales/fr.json +++ b/assets/locales/fr.json @@ -126,5 +126,5 @@ "command_stats_embed_field2name": "Version", "command_stats_embed_field2value": "Locale : {local_version} \nDernière : {latest_version}", "command_stats_embed_field3name": "Informations variables", - "command_stats_embed_field3value": "Nom : {NAME} \nPréfixe : {PREFIX} \nID du propriétaire : {ownerid} \nTemps de recharge : {cooldown_time} \nLigne de ping : {PING_LINE} \nPartage de mémoire activé : {showmemenabled} \nEntraînement utilisateur activé : {USERTRAIN_ENABLED} \nChanson : {song} \nTexte de démarrage : ```{splashtext}```" + "command_stats_embed_field3value": "Nom : {NAME} \nPréfixe : {PREFIX} \nID du propriétaire : {ownerid}\nLigne de ping : {PING_LINE} \nPartage de mémoire activé : {showmemenabled} \nEntraînement utilisateur activé : {USERTRAIN_ENABLED} \nChanson : {song} \nTexte de démarrage : ```{splashtext}```" } \ No newline at end of file diff --git a/assets/locales/fy.json b/assets/locales/fy.json index 2779729..5881b5b 100644 --- a/assets/locales/fy.json +++ b/assets/locales/fy.json @@ -121,5 +121,5 @@ "command_stats_embed_field2name": "Ferzje", "command_stats_embed_field2value": "Lokaal: {local_version} \nLêste: {latest_version}", "command_stats_embed_field3name": "Fariabel Info", - "command_stats_embed_field3value": "Namme: {NAME} \nFoarheaksel: {PREFIX} \nEigner ID: {ownerid} \nCooldown: {cooldown_time} \nPingrigel: {PING_LINE} \nUnthâld Dielen Ynskeakele: {showmemenabled} \nBrûkerstraining Ynskeakele: {USERTRAIN_ENABLED}\nLiet: {song} \nSplashtekst: ```{splashtext}```" + "command_stats_embed_field3value": "Namme: {NAME} \nFoarheaksel: {PREFIX} \nEigner ID: {ownerid}\nPingrigel: {PING_LINE} \nUnthâld Dielen Ynskeakele: {showmemenabled} \nBrûkerstraining Ynskeakele: {USERTRAIN_ENABLED}\nLiet: {song} \nSplashtekst: ```{splashtext}```" } diff --git a/assets/locales/it.json b/assets/locales/it.json index ebf28aa..f86fc96 100644 --- a/assets/locales/it.json +++ b/assets/locales/it.json @@ -129,6 +129,6 @@ "command_stats_embed_field2name": "Versione", "command_stats_embed_field2value": "Locale: {local_version} \nUltima: {latest_version}", "command_stats_embed_field3name": "Informazioni sulle variabili", - "command_stats_embed_field3value": "Nome: {NAME} \nPrefisso: {PREFIX} \nID Proprietario: {ownerid} \nCooldown: {cooldown_time} \nLinea ping: {PING_LINE} \nMemoria Condivisa Abilitata: {showmemenabled} \nAddestramento Utente Abilitato: {USERTRAIN_ENABLED}\nCanzone: {song} \nSplashtext: ```{splashtext}```" + "command_stats_embed_field3value": "Nome: {NAME} \nPrefisso: {PREFIX} \nID Proprietario: {ownerid}\nLinea ping: {PING_LINE} \nMemoria Condivisa Abilitata: {showmemenabled} \nAddestramento Utente Abilitato: {USERTRAIN_ENABLED}\nCanzone: {song} \nSplashtext: ```{splashtext}```" } diff --git a/assets/locales/mt.json b/assets/locales/mt.json index 5e7e9bd..cb41722 100644 --- a/assets/locales/mt.json +++ b/assets/locales/mt.json @@ -121,5 +121,5 @@ "command_stats_embed_field2name": "Verżjoni", "command_stats_embed_field2value": "Lokali: {local_version} \nĠdida: {latest_version}", "command_stats_embed_field3name": "Informazzjoni Varjabbli", - "command_stats_embed_field3value": "Isem: {NAME} \nPrefiss: {PREFIX} \nID ta’ Sid: {ownerid} \nCooldown: {cooldown_time} \nPing line: {PING_LINE} \nImoħħar Memenja: {showmemenabled} \nUser Training Attiva: {USERTRAIN_ENABLED}\nKan ta’: {song} \nSplashtext: ```{splashtext}```" + "command_stats_embed_field3value": "Isem: {NAME} \nPrefiss: {PREFIX} \nID ta’ Sid: {ownerid}\nPing line: {PING_LINE} \nImoħħar Memenja: {showmemenabled} \nUser Training Attiva: {USERTRAIN_ENABLED}\nKan ta’: {song} \nSplashtext: ```{splashtext}```" } diff --git a/bot.py b/bot.py index 7c2050c..315d193 100644 --- a/bot.py +++ b/bot.py @@ -20,6 +20,7 @@ print(splashtext) # Print splash text (from modules/globalvars.py) start_checks() import requests +import asqlite import discord from discord.ext import commands @@ -62,6 +63,9 @@ bot: commands.Bot = commands.Bot( allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False, replied_user=True) ) +# Initialize database pool for fireboard functionality +bot.fireboard_pool = None + # Load memory and Markov model for text generation memory: List[str] = load_memory() markov_model: Optional[markovify.Text] = load_markov_model() @@ -114,6 +118,14 @@ async def on_ready() -> None: folder_name: str = "cogs" if launched: return + + # Initialize database pool for fireboard functionality + try: + bot.fireboard_pool = await asqlite.create_pool("fireboard.db") + print(f"{GREEN}Database pool initialized successfully{RESET}") + except Exception as e: + print(f"{RED}Failed to initialize database pool: {e}{RESET}") + bot.fireboard_pool = None await load_cogs_from_folder(bot) try: @@ -473,7 +485,7 @@ async def stats(ctx: commands.Context) -> None: embed: discord.Embed = discord.Embed(title=f"{(_('command_stats_embed_title'))}", description=f"{(_('command_stats_embed_desc'))}", color=Colour(0x000000)) embed.add_field(name=f"{(_('command_stats_embed_field1name'))}", value=f"{(_('command_stats_embed_field1value')).format(file_size=file_size, line_count=line_count)}", inline=False) embed.add_field(name=f"{(_('command_stats_embed_field2name'))}", value=f"{(_('command_stats_embed_field2value')).format(local_version=local_version, latest_version=latest_version)}", inline=False) - embed.add_field(name=f"{(_('command_stats_embed_field3name'))}", value=f"{(_('command_stats_embed_field3value')).format(NAME=NAME, PREFIX=PREFIX, ownerid=ownerid, cooldown_time=cooldown_time, PING_LINE=PING_LINE, showmemenabled=showmemenabled, USERTRAIN_ENABLED=USERTRAIN_ENABLED, song=song, splashtext=splashtext)}", inline=False) + embed.add_field(name=f"{(_('command_stats_embed_field3name'))}", value=f"{(_('command_stats_embed_field3value')).format(NAME=NAME, PREFIX=PREFIX, ownerid=ownerid, PING_LINE=PING_LINE, showmemenabled=showmemenabled, USERTRAIN_ENABLED=USERTRAIN_ENABLED, song=song, splashtext=splashtext)}", inline=False) await send_message(ctx, embed=embed) diff --git a/modules/central.py b/modules/central.py index edde8cc..335e6ef 100644 --- a/modules/central.py +++ b/modules/central.py @@ -70,7 +70,7 @@ def register_name(NAME): if os.getenv("gooberTOKEN"): return # Name taken: print error and exit - print(f"{RED}{(_('name_taken'))}{gv.RESET}") + print(f"{gv.RED}{(_('name_taken'))}{gv.RESET}") quit() # Register the name response = requests.post(f"{gv.VERSION_URL}/register", json={"name": NAME}, headers={"Content-Type": "application/json"}) diff --git a/modules/globalvars.py b/modules/globalvars.py index 7dd1c8a..f1520e7 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -37,7 +37,7 @@ 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 = "2.0.2" +local_version = "2.1.0" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks diff --git a/modules/prestartchecks.py b/modules/prestartchecks.py index 171a39a..1f4b736 100644 --- a/modules/prestartchecks.py +++ b/modules/prestartchecks.py @@ -127,7 +127,7 @@ def check_requirements(): "token": gooberTOKEN } try: - requests.post(VERSION_URL + "/ping", json=payload) + requests.post(VERSION_URL + "/ping", json=payload) # type: ignore except Exception as e: print(f"{RED}{(_('failed_to_contact')).format(url=VERSION_URL, error=e)}{RESET}") sys.exit(1) @@ -173,7 +173,7 @@ def check_memory(): if psutilavaliable == False: return try: - memory_info = psutil.virtual_memory() + memory_info = psutil.virtual_memory() # type: ignore total_memory = memory_info.total / (1024 ** 3) used_memory = memory_info.used / (1024 ** 3) free_memory = memory_info.available / (1024 ** 3) @@ -193,7 +193,7 @@ def check_cpu(): if psutilavaliable == False: return print((_('measuring_cpu'))) - cpu_per_core = psutil.cpu_percent(interval=1, percpu=True) + cpu_per_core = psutil.cpu_percent(interval=1, percpu=True) # type: ignore for idx, core_usage in enumerate(cpu_per_core): bar_length = int(core_usage / 5) bar = '█' * bar_length + '-' * (20 - bar_length) diff --git a/requirements.txt b/requirements.txt index c53d758..0dca484 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ psutil better_profanity python-dotenv dotenv -pillow \ No newline at end of file +pillow +asqlite \ No newline at end of file diff --git a/specialthanks.txt b/specialthanks.txt index 514448e..ce6a09e 100644 --- a/specialthanks.txt +++ b/specialthanks.txt @@ -1,3 +1,5 @@ -The catbox.moe team -Charlie's Computers -ctih1 +- The catbox.moe team for memory.json file uploads +- Charlie's Computers +- ctih1 +- RestartB (for the Fireboard cog from his Titanium bot) +- Claude 4 Sonnet (for slightly modifying main.py and fireboard.py to work with Goober) From 92624a4c127d05fe86720441941acb79e1745429 Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 7 Jul 2025 15:48:16 -0400 Subject: [PATCH 14/84] revert fireboard, it doesnt work idk what the version should be i just did `2.1.1` --- .gitignore | 3 - assets/cogs/fireboard.py | 1756 -------------------------------------- bot.py | 11 - modules/globalvars.py | 2 +- specialthanks.txt | 2 - 5 files changed, 1 insertion(+), 1773 deletions(-) delete mode 100644 assets/cogs/fireboard.py diff --git a/.gitignore b/.gitignore index 2bd2bfb..2c32b44 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,3 @@ received_memory.json translation_report.txt translationcompleteness.py modules/volta -fireboard.db -fireboard.db-shm -fireboard.db-wal \ No newline at end of file diff --git a/assets/cogs/fireboard.py b/assets/cogs/fireboard.py deleted file mode 100644 index bd38db4..0000000 --- a/assets/cogs/fireboard.py +++ /dev/null @@ -1,1756 +0,0 @@ -import asyncio -import random - -import asqlite -import discord -import discord.ext -import discord.ext.commands -from discord import Color, app_commands -from discord.ext import commands -from discord.ui import View - - -class Fireboard(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.locked_messages = [] - self.disabled = False - - # Check if the bot has the fireboard_pool attribute - if not hasattr(bot, 'fireboard_pool') or bot.fireboard_pool is None: - print("Warning: Bot does not have fireboard_pool initialized. Fireboard functionality will be disabled.") - self.disabled = True - return - - self.fireboard_pool: asqlite.Pool = bot.fireboard_pool - self.bot.loop.create_task(self.setup()) - - # SQL Setup - async def setup(self): - async with self.fireboard_pool.acquire() as sql: - if ( - await sql.fetchone( - "SELECT name FROM sqlite_master WHERE type='table' AND name='fireMessages';" - ) - is None - ): - # Fire Messages - messages that are active on the fireboard - await sql.execute( - "CREATE TABLE fireMessages (serverID int, msgID int, boardMsgID int, reactionAmount int)" - ) - - if ( - await sql.fetchone( - "SELECT name FROM sqlite_master WHERE type='table' AND name='fireSettings';" - ) - is None - ): - # Fire Settings - server properties for fireboard - await sql.execute( - "CREATE TABLE fireSettings (serverID int, reactionAmount int, emoji text, channelID int, ignoreBots int)" - ) - - if ( - await sql.fetchone( - "SELECT name FROM sqlite_master WHERE type='table' AND name='fireChannelBlacklist';" - ) - is None - ): - # Fire Channel Blacklist - blacklisted channels - await sql.execute( - "CREATE TABLE fireChannelBlacklist (serverID int, channelID int)" - ) - - if ( - await sql.fetchone( - "SELECT name FROM sqlite_master WHERE type='table' AND name='fireRoleBlacklist';" - ) - is None - ): - # Fire Role Blacklist - blacklisted roles - await sql.execute( - "CREATE TABLE fireRoleBlacklist (serverID int, roleID int)" - ) - - await sql.commit() - - await self.refresh_fire_lists() - - # List refresh function - async def refresh_fire_lists(self): - async with self.fireboard_pool.acquire() as sql: - self.fire_messages = await sql.fetchall("SELECT * FROM fireMessages") - self.fire_settings = await sql.fetchall("SELECT * FROM fireSettings") - self.fire_channel_blacklist = await sql.fetchall( - "SELECT * FROM fireChannelBlacklist" - ) - self.fire_role_blacklist = await sql.fetchall( - "SELECT * FROM fireRoleBlacklist" - ) - - # Listen for reactions - @commands.Cog.listener() - async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - queued = False - - # Lock system - if payload.message_id in self.locked_messages: - queued = True - - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - fetched = False - - # Find server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - react_minimum = server[1] - emoji = server[2] - channel_id = server[3] - ignore_bots = True if int(server[4]) == 1 else False - - fetched = True - - # Stop if server has no config (fireboard isn't enabled) - if not fetched: - return - - # Stop if message is by Titanium - if payload.message_author_id == self.bot.user.id: - return - - # Stop if emoji doesn't match - if str(payload.emoji) != emoji: - return - - # --- Edit board message if it already exists --- - if payload.message_id in [message[1] for message in self.fire_messages]: - message = [ - message - for message in self.fire_messages - if message[1] == payload.message_id - ][0] - - # Only fetch updated reaction count if I have queued or reaction amount is undefined - if queued or message[3] is None: - # Fetch message and channel - try: - msg_channel = await self.bot.fetch_channel(payload.channel_id) - message = await msg_channel.fetch_message(payload.message_id) - except discord.errors.NotFound: - return - - # Stop if not enough reactions - for reaction in message.reactions: - if str(reaction.emoji) == emoji: - react_count = reaction.count - break - - if react_count < react_minimum: - return - else: - react_count = None - - async with self.fireboard_pool.acquire() as sql: - # Set updated react count - if react_count is not None: - await sql.execute( - "UPDATE fireMessages SET reactionAmount = ? WHERE msgID = ?", - ( - react_count, - payload.message_id, - ), - ) - await self.refresh_fire_lists() - else: - await sql.execute( - "UPDATE fireMessages SET reactionAmount = reactionAmount + 1 WHERE msgID = ?", - (payload.message_id,), - ) - await self.refresh_fire_lists() - - # Get message from message list - message = [ - message - for message in self.fire_messages - if message[1] == payload.message_id - ][0] - - # Get board message - board_channel = await self.bot.fetch_channel(channel_id) - board_message = await board_channel.fetch_message(message[2]) - - await board_message.edit( - content=f"**{message[3]} {emoji}** | <@{payload.message_author_id}> | <#{payload.channel_id}>", - embeds=board_message.embeds, - ) - - return - - # Stop if message is in a blacklisted channel - if payload.channel_id in [ - channel[1] for channel in self.fire_channel_blacklist - ]: - return - - # Stop if message is by a blacklisted role - guild = await self.bot.fetch_guild(payload.guild_id) - member = await guild.fetch_member(payload.user_id) - - if any( - role[1] in [role.id for role in member.roles] - for role in self.fire_role_blacklist - ): - return - - # Fetch message and channel - try: - msg_channel = await self.bot.fetch_channel(payload.channel_id) - message = await msg_channel.fetch_message(payload.message_id) - except discord.errors.NotFound: - return - - # Stop if message is by a bot - if ignore_bots and message.author.bot: - return - - # Check if this is a thread - if isinstance(msg_channel, discord.Thread): - # Check if parent channel is NSFW - if msg_channel.parent.nsfw: - return - else: - # Stop if message is in an NSFW channel - if message.channel.nsfw: - return - - # Stop if not enough reactions - for reaction in message.reactions: - if str(reaction.emoji) == emoji: - react_count = reaction.count - break - - if react_count < react_minimum: - return - - # --- Send message to fireboard --- - - # Create embed - embed = discord.Embed(description=message.content, color=Color.random()) - embed.set_author( - name=message.author.name, icon_url=message.author.display_avatar.url - ) - embed.timestamp = message.created_at - - # Jump to message button - view = View() - view.add_item( - discord.ui.Button( - label="Jump to Message", - url=message.jump_url, - style=discord.ButtonStyle.url, - ) - ) - - embed_list = [embed] - - # Add reply embed - if message.reference: - try: - reply_message = await msg_channel.fetch_message( - message.reference.message_id - ) - - reply_embed = discord.Embed( - title="Replying To", - description=reply_message.content, - color=Color.random(), - ) - reply_embed.set_author( - name=reply_message.author.name, - icon_url=reply_message.author.display_avatar.url, - ) - reply_embed.timestamp = reply_message.created_at - - embed_list.insert(0, reply_embed) - except discord.errors.NotFound: - pass - - # Send message - board_channel = await self.bot.fetch_channel(channel_id) - board_message = await board_channel.send( - content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", - embeds=embed_list, - view=view, - files=[ - await attachment.to_file() for attachment in message.attachments - ], - ) - - async with self.fireboard_pool.acquire() as sql: - # Insert message to DB - await sql.execute( - "INSERT INTO fireMessages (serverID, msgID, boardMsgID, reactionAmount) VALUES (?, ?, ?, ?)", - ( - payload.guild_id, - payload.message_id, - board_message.id, - react_count, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for reaction removal - @commands.Cog.listener() - async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): - self.bot: discord.ext.commands.Bot - - queued = False - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - queued = True - - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - fetched = False - - # Find server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - react_minimum = server[1] - emoji = server[2] - channel_id = server[3] - ignore_bots = True if int(server[4]) == 1 else False - - fetched = True - - # Stop if server has no config (fireboard isn't enabled) - if not fetched: - return - - # Stop if message is by Titanium - if payload.message_author_id == self.bot.user.id: - return - - # Stop if emoji doesn't match - if str(payload.emoji) != emoji: - return - - # --- Edit board message if it already exists --- - if payload.message_id in [message[1] for message in self.fire_messages]: - prev_react_count = [ - message - for message in self.fire_messages - if message[1] == payload.message_id - ][0][3] - - # Only fetch updated reaction count if I have queued - if queued or prev_react_count is None: - # Fetch message and channel - try: - msg_channel = await self.bot.fetch_channel(payload.channel_id) - message = await msg_channel.fetch_message(payload.message_id) - except discord.errors.NotFound: - return - - # Stop if not enough reactions - for reaction in message.reactions: - if str(reaction.emoji) == emoji: - react_count = reaction.count - break - - if react_count < react_minimum: - return - else: - react_count = None - - async with self.fireboard_pool.acquire() as sql: - # Set updated react count - if react_count is not None: - await sql.execute( - "UPDATE fireMessages SET reactionAmount = ? WHERE msgID = ?", - ( - react_count, - payload.message_id, - ), - ) - await self.refresh_fire_lists() - else: - await sql.execute( - "UPDATE fireMessages SET reactionAmount = reactionAmount - 1 WHERE msgID = ?", - (payload.message_id,), - ) - await self.refresh_fire_lists() - - # Get message from message list - message = [ - message - for message in self.fire_messages - if message[1] == payload.message_id - ][0] - - # Get board message - board_channel = await self.bot.fetch_channel(channel_id) - board_message = await board_channel.fetch_message(message[2]) - - # Remove message if not enough reactions - if message[3] < react_minimum: - await board_message.delete() - - async with self.fireboard_pool.acquire() as sql: - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - - # Workaround for lack of message author ID - content = board_message.content - content = content.replace( - f"{prev_react_count} {emoji}", f"{message[3]} {emoji}" - ) - - await board_message.edit(content=content) - - return - - # Stop if message is in a blacklisted channel - if payload.channel_id in [ - channel[1] for channel in self.fire_channel_blacklist - ]: - return - - # Stop if message is by a blacklisted role - guild = await self.bot.fetch_guild(payload.guild_id) - member = await guild.fetch_member(payload.user_id) - - if any( - role[1] in [role.id for role in member.roles] - for role in self.fire_role_blacklist - ): - return - - # Fetch message and channel - try: - msg_channel = await self.bot.fetch_channel(payload.channel_id) - message = await msg_channel.fetch_message(payload.message_id) - except discord.errors.NotFound: - return - - # Stop if message is by a bot - if ignore_bots and message.author.bot: - return - - # Check if this is a thread - if isinstance(msg_channel, discord.Thread): - # Check if parent channel is NSFW - if msg_channel.parent.nsfw: - return - else: - # Stop if message is in an NSFW channel - if message.channel.nsfw: - return - - # Get reaction count - react_count = 0 - - for reaction in message.reactions: - if str(reaction.emoji) == emoji: - react_count = reaction.count - break - - # Stop if not enough reactions - if react_count < react_minimum: - return - - # --- Send message to fireboard --- - - # Create embed - embed = discord.Embed(description=message.content, color=Color.random()) - embed.set_author( - name=message.author.name, icon_url=message.author.display_avatar.url - ) - embed.timestamp = message.created_at - - # Jump to message button - view = View() - view.add_item( - discord.ui.Button( - label="Jump to Message", - url=message.jump_url, - style=discord.ButtonStyle.url, - ) - ) - - embed_list = [embed] - - # Add reply embed - if message.reference: - try: - reply_message = await msg_channel.fetch_message( - message.reference.message_id - ) - - reply_embed = discord.Embed( - title="Replying To", - description=reply_message.content, - color=Color.random(), - ) - reply_embed.set_author( - name=reply_message.author.name, - icon_url=reply_message.author.display_avatar.url, - ) - reply_embed.timestamp = reply_message.created_at - - embed_list.insert(0, reply_embed) - except discord.errors.NotFound: - pass - - # Send message - board_channel = await self.bot.fetch_channel(channel_id) - board_message = await board_channel.send( - content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", - embeds=embed_list, - view=view, - files=[ - await attachment.to_file() for attachment in message.attachments - ], - ) - - async with self.fireboard_pool.acquire() as sql: - # Insert message to DB - await sql.execute( - "INSERT INTO fireMessages (serverID, msgID, boardMsgID, reactionAmount) VALUES (?, ?, ?, ?)", - ( - payload.guild_id, - payload.message_id, - board_message.id, - react_count, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for message reaction clear - @commands.Cog.listener() - async def on_raw_reaction_clear(self, payload: discord.RawReactionClearEvent): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - # Only trigger if message is already in the fireboard DB - if payload.message_id in [message[1] for message in self.fire_messages]: - # Find server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - channel_id = server[3] - - # Get guild - try: - guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) - except discord.errors.NotFound: - return - - # Get message channel - channel: discord.abc.GuildChannel = await guild.fetch_channel( - payload.channel_id - ) - - # Get our message - message: discord.Message = await channel.fetch_message( - payload.message_id - ) - - # See if board message is already present - for fire_message in self.fire_messages: - if fire_message[1] == message.id: - async with self.fireboard_pool.acquire() as sql: - try: - # Delete message - try: - channel: discord.TextChannel = ( - await guild.fetch_channel(channel_id) - ) - except discord.errors.NotFound: - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (payload.guild_id,), - ) - await sql.commit() - self.locked_messages.remove(payload.message_id) - - return - - board_message = await channel.fetch_message( - fire_message[2] - ) - await board_message.delete() - - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (message.id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - except discord.errors.NotFound: - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (message.id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for specific emoji being cleared - @commands.Cog.listener() - async def on_raw_reaction_clear_emoji( - self, payload: discord.RawReactionClearEmojiEvent - ): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - # Only trigger if message is already in the fireboard DB - if payload.message_id in [message[1] for message in self.fire_messages]: - for server in self.fire_settings: - if server[0] == payload.guild_id: - emoji = server[2] - channel_id = server[3] - - # Only trigger if cleared emoji is our emoji - if str(payload.emoji) == emoji: - # Fetch server - try: - guild: discord.Guild = await self.bot.fetch_guild( - payload.guild_id - ) - except discord.errors.NotFound: - return - - # Get message channel - channel: discord.abc.GuildChannel = await guild.fetch_channel( - payload.channel_id - ) - - # See if board message is already present - for fire_message in self.fire_messages: - if fire_message[1] == payload.message_id: - async with self.fireboard_pool.acquire() as sql: - try: - # Fetch fireboard channel - try: - channel: discord.TextChannel = ( - await guild.fetch_channel(channel_id) - ) - except discord.errors.NotFound: - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (payload.guild_id,), - ) - await sql.commit() - - return - - # Delete message - board_message = await channel.fetch_message( - fire_message[2] - ) - await board_message.delete() - - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - except discord.errors.NotFound: - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for message being deleted - @commands.Cog.listener() - async def on_raw_message_delete(self, payload: discord.RawMessageDeleteEvent): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - # Only trigger if message is already in the fireboard DB - if payload.message_id in [message[1] for message in self.fire_messages]: - # Fetch server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - channel_id = server[3] - - # Fetch server - try: - guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) - except discord.errors.NotFound: - return - - # Get message channel - channel: discord.abc.GuildChannel = await guild.fetch_channel( - payload.channel_id - ) - - # See if board message is already present - for fire_message in self.fire_messages: - if fire_message[1] == payload.message_id: - async with self.fireboard_pool.acquire() as sql: - try: - # Fetch fireboard channel - try: - channel: discord.TextChannel = ( - await guild.fetch_channel(channel_id) - ) - except discord.errors.NotFound: - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (payload.guild_id,), - ) - await sql.commit() - - return - - # Delete message - board_message = await channel.fetch_message( - fire_message[2] - ) - await board_message.delete() - - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - except discord.errors.NotFound: - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for message being edited - @commands.Cog.listener() - async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - # Only trigger if message is already in the fireboard DB - if payload.message_id in [message[1] for message in self.fire_messages]: - # Fetch server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - channel_id = server[3] - - # Fetch server - try: - guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) - except discord.errors.NotFound: - return - - # Get message channel - channel: discord.abc.GuildChannel = await guild.fetch_channel( - payload.channel_id - ) - - # Get our message - message: discord.Message = await channel.fetch_message( - payload.message_id - ) - - embed = discord.Embed(description=message.content, color=Color.random()) - embed.set_author( - name=message.author.name, icon_url=message.author.display_avatar.url - ) - embed.timestamp = message.created_at - - # Jump to message button - view = View() - view.add_item( - discord.ui.Button( - label="Jump to Message", - url=message.jump_url, - style=discord.ButtonStyle.url, - ) - ) - - embed_list = [embed] - - # Add reply embed - if message.reference: - try: - reply_message = await channel.fetch_message( - message.reference.message_id - ) - - reply_embed = discord.Embed( - title="Replying To", - description=reply_message.content, - color=Color.random(), - ) - reply_embed.set_author( - name=reply_message.author.name, - icon_url=reply_message.author.display_avatar.url, - ) - reply_embed.timestamp = reply_message.created_at - - embed_list.insert(0, reply_embed) - except discord.errors.NotFound: - pass - - try: - channel: discord.TextChannel = await guild.fetch_channel(channel_id) - except discord.errors.NotFound: - async with self.fireboard_pool.acquire() as sql: - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (payload.guild_id,), - ) - await sql.commit() - - return - - # Find previous fireboard message - try: - for fire_message in self.fire_messages: - if ( - fire_message[0] == payload.guild_id - and fire_message[1] == payload.message_id - ): - # Edit with updated embed - reaction amount stays the same - board_message = await channel.fetch_message(fire_message[2]) - - await board_message.edit( - embeds=embed_list, attachments=message.attachments - ) - except discord.errors.NotFound: # Message not found - async with self.fireboard_pool.acquire() as sql: - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for fireboard channel delete - @commands.Cog.listener() - async def on_guild_channel_delete(self, channel: discord.abc.GuildChannel): - # Only trigger if server has fireboard enabled - if channel.guild.id in [guild[0] for guild in self.fire_settings]: - for server in self.fire_settings: - if server[0] == channel.guild.id: - if server[3] == channel.id: - async with self.fireboard_pool.acquire() as sql: - # Delete fireboard config - await sql.execute( - "DELETE FROM fireMessages WHERE serverID = ?", - (channel.guild.id,), - ) - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (channel.guild.id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - - # Listen for server being left / deleted - @commands.Cog.listener() - async def on_guild_remove(self, guild: discord.Guild): - # Only trigger if server has fireboard enabled - if guild.id in [guild[0] for guild in self.fire_settings]: - for server in self.fire_settings: - if server[0] == guild.id: - async with self.fireboard_pool.acquire() as sql: - # Delete fireboard config - await sql.execute( - "DELETE FROM fireMessages WHERE serverID = ?", (guild.id,) - ) - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", (guild.id,) - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - - # Command group setup - context = discord.app_commands.AppCommandContext( - guild=True, dm_channel=False, private_channel=False - ) - installs = discord.app_commands.AppInstallationType(guild=True, user=False) - fireGroup = app_commands.Group( - name="fireboard", - description="Fireboard related commands.", - allowed_contexts=context, - allowed_installs=installs, - ) - - # Random fireboard message command - @fireGroup.command( - name="random", description="Get a random message from the fireboard." - ) - @app_commands.describe( - ephemeral="Optional: whether to send the command output as a dismissible message only visible to you. Defaults to false." - ) - async def random_fireboard( - self, interaction: discord.Interaction, ephemeral: bool = False - ): - await interaction.response.defer(ephemeral=ephemeral) - - channel_id = None - - # Find server config - for server in self.fire_settings: - if server[0] == interaction.guild_id: - channel_id = server[3] - - if channel_id is None: - embed = discord.Embed( - title="Error", - description="Fireboard is not enabled in this server.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=ephemeral) - - return - - # Fetch channel - try: - channel = await interaction.guild.fetch_channel(channel_id) - except discord.errors.NotFound: - embed = discord.Embed( - title="Error", - description="Can't find the fireboard channel. Please contact a server admin.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=ephemeral) - - return - - # Fetch messages - async with self.fireboard_pool.acquire() as sql: - messages = await sql.fetchall( - "SELECT * FROM fireMessages WHERE serverID = ?", (interaction.guild_id,) - ) - - if not messages: - embed = discord.Embed( - title="Error", - description="No messages found in the fireboard.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=ephemeral) - - return - else: - while messages != []: - message = random.choice(messages) - - try: - board_message = await channel.fetch_message(message[2]) - - view = View().from_message(board_message) - files = [ - await attachment.to_file() - for attachment in board_message.attachments - ] - - await interaction.followup.send( - content=board_message.content, - embeds=board_message.embeds, - view=view, - ephemeral=ephemeral, - files=files, - allowed_mentions=discord.AllowedMentions.none(), - ) - - return - except discord.errors.NotFound: - messages.remove(message) - - embed = discord.Embed( - title="Error", - description="No messages found in the fireboard.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=ephemeral) - - # Command group setup - context = discord.app_commands.AppCommandContext( - guild=True, dm_channel=False, private_channel=False - ) - installs = discord.app_commands.AppInstallationType(guild=True, user=False) - perms = discord.Permissions(manage_guild=True) - fireSetupGroup = app_commands.Group( - name="fireboard-setup", - description="Control the fireboard.", - allowed_contexts=context, - allowed_installs=installs, - default_permissions=perms, - ) - - # Fireboard enable command - @fireSetupGroup.command( - name="enable", description="Enable the fireboard in the current channel." - ) - async def enable_fireboard(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Fireboard is already enabled.", color=Color.green() - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - # Default settings - react_minimum = 3 - emoji = "🔥" - channel_id = interaction.channel_id - ignore_bots = True - - embed = discord.Embed( - title="Fireboard", - description="This channel has been configured as the server fireboard.", - color=Color.random(), - ) - embed.set_footer(text="Feel free to delete this message!") - - try: - channel = await interaction.guild.fetch_channel(channel_id) - await channel.send(embed=embed) - except discord.errors.Forbidden or discord.errors.NotFound: - embed = discord.Embed( - title="Error", - description="Looks like I can't send messages in this channel. Check permissions and try again.", - color=Color.random(), - ) - await interaction.followup.send(embed=embed, ephemeral=True) - - return - - async with self.fireboard_pool.acquire() as sql: - # Insert to DB, refresh lists - await sql.execute( - "INSERT INTO fireSettings (serverID, reactionAmount, emoji, channelID, ignoreBots) VALUES (?, ?, ?, ?, ?)", - ( - interaction.guild_id, - react_minimum, - emoji, - channel_id, - ignore_bots, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Enabled", - description="Fireboard has been enabled in the current channel.", - color=Color.green(), - ) - embed.add_field( - name="Info", - value=f"**Reaction Requirement:** `{react_minimum} reactions`\n**Fireboard Channel:** <#{channel_id}>\n**Emoji:** {emoji}\n**Ignore Bots:** `{ignore_bots}`", - ) - - await interaction.followup.send(embed=embed, ephemeral=True) - - class ConfirmDisableView(View): - def __init__(self): - super().__init__(timeout=60) - - async def disable_fireboard( - self, interaction: discord.Interaction, pool: asqlite.Pool - ): - async with pool.acquire() as sql: - try: - await sql.execute( - "DELETE FROM fireMessages WHERE serverID = ?", - (interaction.guild_id,), - ) - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (interaction.guild_id,), - ) - await sql.execute( - "DELETE FROM fireChannelBlacklist WHERE serverID = ?", - (interaction.guild_id,), - ) - await sql.execute( - "DELETE FROM fireRoleBlacklist WHERE serverID = ?", - (interaction.guild_id,), - ) - await sql.commit() - return True - except Exception: - return False - - @discord.ui.button(label="Disable", style=discord.ButtonStyle.red) - async def confirm( - self, interaction: discord.Interaction, button: discord.ui.Button - ): - await interaction.response.defer(ephemeral=True) - - success = await self.disable_fireboard(interaction, self.pool) - - if success: - embed = discord.Embed( - title="Done!", - description="Fireboard was disabled.", - color=Color.green(), - ) - await self.cog.refresh_fire_lists() # pylint: disable=no-member - else: - embed = discord.Embed( - title="Error", - description="Failed to disable fireboard.", - color=Color.red(), - ) - - await interaction.edit_original_response(embed=embed, view=None) - self.stop() - - async def on_timeout(self): - for item in self.children: - item.disabled = True - - embed = discord.Embed( - title="Timeout", - description="You didn't press the button in time.", - color=Color.red(), - ) - await self.message.edit(embed=embed, view=self) - - # Fireboard disable command - @fireSetupGroup.command(name="disable", description="Disable the server fireboard.") - async def disable_fireboard(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - - if interaction.guild_id in [guild[0] for guild in self.fire_settings]: - view = self.ConfirmDisableView() - view.pool = self.fireboard_pool - view.cog = self - - embed = discord.Embed( - title="Are you sure?", - description="All data about this server's fireboard will be deleted. This cannot be undone!", - color=Color.orange(), - ) - - message = await interaction.followup.send( - embed=embed, view=view, ephemeral=True, wait=True - ) - view.message = message - else: - await interaction.followup.send( - "Fireboard is not enabled in this server!", ephemeral=True - ) - - # Fireboard server info command - @fireSetupGroup.command( - name="info", description="View fireboard config for this server." - ) - async def fireboard_info(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - # Fetch server settings - for server in self.fire_settings: - if server[0] == interaction.guild_id: - react_minimum = server[1] - emoji = server[2] - channel_id = server[3] - ignore_bots = True if int(server[4]) == 1 else False - - embed = discord.Embed( - title="Server Fireboard Settings", - description=f"**Reaction Requirement:** `{react_minimum} reactions`\n**Fireboard Channel:** <#{channel_id}>\n**Emoji:** {emoji}\n**Ignore Bots:** `{ignore_bots}`", - color=Color.random(), - ) - - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard set emoji command - @fireSetupGroup.command(name="emoji", description="Set a custom fireboard emoji.") - async def fireboard_emoji(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=False) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Waiting for Reaction", - description="🔄 React with this message with your target emoji to set the fireboard emoji.", - color=Color.orange(), - ) - - msg = await interaction.followup.send(embed=embed, ephemeral=False) - - def check(reaction, user): - return user == interaction.user and reaction.message.id == msg.id - - # Wait for a reaction - try: - reaction, user = await self.bot.wait_for( - "reaction_add", timeout=60.0, check=check - ) - - reaction: discord.Reaction = reaction - - async with self.fireboard_pool.acquire() as sql: - # Change emoji in DB, refresh lists - await sql.execute( - "UPDATE fireSettings SET emoji = ? WHERE serverID = ?", - ( - str(reaction.emoji), - interaction.guild_id, - ), - ) - await sql.commit() - - embed = discord.Embed( - title="Emoji Set", - description=f"Set emoji to **{str(reaction.emoji)}.**", - color=Color.green(), - ) - - await self.refresh_fire_lists() - await interaction.edit_original_response(embed=embed) - except asyncio.TimeoutError: # Timed out - embed = discord.Embed( - title="Timed Out", - description="You didn't react in time.", - color=Color.red(), - ) - - await interaction.edit_original_response(embed=embed) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=False) - - # Fireboard set channel command - @fireSetupGroup.command( - name="channel", - description="Set the channel for fireboard messages to be sent in.", - ) - async def fireboard_channel( - self, interaction: discord.Interaction, channel: discord.TextChannel - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Fireboard", - description="This channel has been configured as the server fireboard.", - color=Color.random(), - ) - embed.set_footer(text="Feel free to delete this message!") - - try: - await channel.send(embed=embed) - except discord.errors.NotFound: - embed = discord.Embed( - title="Error", - description="Looks like I can't find that channel. Check permissions and try again.", - color=Color.random(), - ) - await interaction.followup.send(embed=embed, ephemeral=True) - - return - except discord.errors.Forbidden as e: - embed = discord.Embed( - title="Error", - description="Looks like I can't send messages in that channel. Check permissions and try again.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=True) - - return - - async with self.fireboard_pool.acquire() as sql: - # Update channel in DB, refresh lists - await sql.execute( - "UPDATE fireSettings SET channelID = ? WHERE serverID = ?", - ( - channel.id, - interaction.guild_id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Channel Set", - description=f"Fireboard channel has been set to **{channel.mention}.**", - color=Color.green(), - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard set requirement command - @fireSetupGroup.command( - name="requirement", - description="Set required reaction amount for message to be posted on the fireboard.", - ) - async def fireboard_requirement( - self, interaction: discord.Interaction, amount: int - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Set", - description=f"Reaction requirement has been set to **{amount} reactions.**", - color=Color.green(), - ) - - async with self.fireboard_pool.acquire() as sql: - # Update reaction requirement in DB, refresh lists - await sql.execute( - "UPDATE fireSettings SET reactionAmount = ? WHERE serverID = ?", - ( - amount, - interaction.guild_id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard ignore bots command - @fireSetupGroup.command( - name="ignore-bots", - description="Whether bot messages are ignored in the fireboard. Defaults to true.", - ) - async def fireboard_ignore_bots( - self, interaction: discord.Interaction, value: bool - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Set", - description=f"Bot messages will **{'be ignored.' if value else 'not be ignored.'}**", - color=Color.green(), - ) - - async with self.fireboard_pool.acquire() as sql: - # Update setting in DB, refresh lists - await sql.execute( - "UPDATE fireSettings SET ignoreBots = ? WHERE serverID = ?", - ( - value, - interaction.guild_id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard role blacklist - @fireSetupGroup.command( - name="channel-blacklist", - description="Toggle the blacklist for a channel. NSFW channels are always blacklisted.", - ) - async def fireboard_channel_blacklist( - self, interaction: discord.Interaction, channel: discord.abc.GuildChannel - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild_id in [guild[0] for guild in self.fire_settings]: - async with self.fireboard_pool.acquire() as sql: - if channel.id in [ - channelEntry[1] for channelEntry in self.fire_channel_blacklist - ]: - await sql.execute( - "DELETE FROM fireChannelBlacklist WHERE serverID = ? AND channelID = ?", - ( - interaction.guild_id, - channel.id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Set", - description=f"Removed {channel.mention} from the channel blacklist.", - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - await sql.execute( - "INSERT INTO fireChannelBlacklist (serverID, channelID) VALUES (?, ?)", - ( - interaction.guild_id, - channel.id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Set", - description=f"Added {channel.mention} to the channel blacklist.", - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard role blacklist - @fireSetupGroup.command( - name="role-blacklist", description="Toggle the blacklist for a role." - ) - async def fireboard_role_blacklist( - self, interaction: discord.Interaction, role: discord.Role - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild_id in [guild[0] for guild in self.fire_settings]: - async with self.fireboard_pool.acquire() as sql: - if role.id in [roleEntry[1] for roleEntry in self.fire_role_blacklist]: - await sql.execute( - "DELETE FROM fireRoleBlacklist WHERE serverID = ? AND roleID = ?", - ( - interaction.guild_id, - role.id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Set", - description=f"Removed {role.mention} from the role blacklist.", - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - await sql.execute( - "INSERT INTO fireRoleBlacklist (serverID, roleID) VALUES (?, ?)", - ( - interaction.guild_id, - role.id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Set", - description=f"Added {role.mention} to the role blacklist.", - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard role blacklist - @fireSetupGroup.command( - name="blacklists", description="View this server's role and channel blacklists." - ) - async def fireboard_blacklists(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild_id in [guild[0] for guild in self.fire_settings]: - - class BlacklistViewer(View): - def __init__(self): - super().__init__(timeout=240) - - self.fire_channel_blacklist: list - self.fire_role_blacklist: list - self.interaction: discord.Interaction - - async def on_timeout(self) -> None: - for item in self.children: - item.disabled = True - - await self.interaction.edit_original_response(view=self) - - @discord.ui.button( - label="Role Blacklist", - style=discord.ButtonStyle.gray, - row=0, - custom_id="role", - disabled=True, - ) - async def role( - self, interaction: discord.Interaction, button: discord.ui.Button - ): - await interaction.response.defer(ephemeral=True) - - for item in self.children: - if item.custom_id == "channel": - item.disabled = False - else: - item.disabled = True - - my_roles = [] - - for role in self.fire_role_blacklist: - if role[0] == interaction.guild_id: - my_roles.append(f"<@&{role[1]}>") - - if my_roles != []: - embed = discord.Embed( - title="Role Blacklist", - description="\n".join(my_roles), - color=Color.random(), - ) - await interaction.edit_original_response(embed=embed, view=self) - else: - embed = discord.Embed( - title="Role Blacklist", - description="No roles have been blacklisted.", - color=Color.random(), - ) - await interaction.edit_original_response(embed=embed, view=self) - - @discord.ui.button( - label="Channel Blacklist", - style=discord.ButtonStyle.gray, - row=0, - custom_id="channel", - ) - async def channel( - self, interaction: discord.Interaction, button: discord.ui.Button - ): - await interaction.response.defer(ephemeral=True) - - for item in self.children: - if item.custom_id == "role": - item.disabled = False - else: - item.disabled = True - - my_channels = [] - - for channel in self.fire_channel_blacklist: - if channel[0] == interaction.guild_id: - my_channels.append(f"<#{channel[1]}>") - - if my_channels != []: - embed = discord.Embed( - title="Channel Blacklist", - description="\n".join(my_channels), - color=Color.random(), - ) - await interaction.edit_original_response(embed=embed, view=self) - else: - embed = discord.Embed( - title="Channel Blacklist", - description="No channels have been blacklisted.", - color=Color.random(), - ) - await interaction.edit_original_response(embed=embed, view=self) - - view_instance = BlacklistViewer() - view_instance.fire_channel_blacklist = self.fire_channel_blacklist - view_instance.fire_role_blacklist = self.fire_role_blacklist - view_instance.interaction = interaction - - my_roles = [] - - for role in self.fire_role_blacklist: - if role[0] == interaction.guild_id: - my_roles.append(f"<@&{role[1]}>") - - if my_roles != []: - embed = discord.Embed( - title="Role Blacklist", - description="\n".join(my_roles), - color=Color.random(), - ) - await interaction.followup.send( - embed=embed, view=view_instance, ephemeral=True - ) - else: - embed = discord.Embed( - title="Role Blacklist", - description="No roles have been blacklisted.", - color=Color.random(), - ) - await interaction.followup.send( - embed=embed, view=view_instance, ephemeral=True - ) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - -async def setup(bot): - await bot.add_cog(Fireboard(bot)) \ No newline at end of file diff --git a/bot.py b/bot.py index 315d193..571c6ba 100644 --- a/bot.py +++ b/bot.py @@ -63,9 +63,6 @@ bot: commands.Bot = commands.Bot( allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False, replied_user=True) ) -# Initialize database pool for fireboard functionality -bot.fireboard_pool = None - # Load memory and Markov model for text generation memory: List[str] = load_memory() markov_model: Optional[markovify.Text] = load_markov_model() @@ -118,14 +115,6 @@ async def on_ready() -> None: folder_name: str = "cogs" if launched: return - - # Initialize database pool for fireboard functionality - try: - bot.fireboard_pool = await asqlite.create_pool("fireboard.db") - print(f"{GREEN}Database pool initialized successfully{RESET}") - except Exception as e: - print(f"{RED}Failed to initialize database pool: {e}{RESET}") - bot.fireboard_pool = None await load_cogs_from_folder(bot) try: diff --git a/modules/globalvars.py b/modules/globalvars.py index f1520e7..f28548a 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -37,7 +37,7 @@ 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 = "2.1.0" +local_version = "2.1.1" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks diff --git a/specialthanks.txt b/specialthanks.txt index ce6a09e..c509086 100644 --- a/specialthanks.txt +++ b/specialthanks.txt @@ -1,5 +1,3 @@ - The catbox.moe team for memory.json file uploads - Charlie's Computers - ctih1 -- RestartB (for the Fireboard cog from his Titanium bot) -- Claude 4 Sonnet (for slightly modifying main.py and fireboard.py to work with Goober) From 48bee2dd88e747c3b5884b266a48748aa6b8c240 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:41:23 +0200 Subject: [PATCH 15/84] did i not push this --- assets/cogs/fireboard.py | 1923 ++++++++++++++++++++++++++++++++++++++ assets/locales/en.json | 1 + assets/locales/it.json | 1 + bot.py | 2 +- modules/globalvars.py | 2 +- 5 files changed, 1927 insertions(+), 2 deletions(-) create mode 100644 assets/cogs/fireboard.py diff --git a/assets/cogs/fireboard.py b/assets/cogs/fireboard.py new file mode 100644 index 0000000..8d199b0 --- /dev/null +++ b/assets/cogs/fireboard.py @@ -0,0 +1,1923 @@ +import asyncio +import random + +import asqlite +import discord +import discord.ext +import discord.ext.commands +from discord import Color, app_commands +from discord.ext import commands +from discord.ui import View + + +class Fireboard(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.locked_messages = [] + self.disabled = False + + # Check if the bot has the fireboard_pool attribute + if not hasattr(bot, 'fireboard_pool') or bot.fireboard_pool is None: + print("Warning: Bot does not have fireboard_pool initialized. Fireboard functionality will be disabled.") + self.disabled = True + return + + self.fireboard_pool: asqlite.Pool = bot.fireboard_pool + self.bot.loop.create_task(self.setup()) + + # SQL Setup + async def setup(self): + async with self.fireboard_pool.acquire() as sql: + if ( + await sql.fetchone( + "SELECT name FROM sqlite_master WHERE type='table' AND name='fireMessages';" + ) + is None + ): + # Fire Messages - messages that are active on the fireboard + await sql.execute( + "CREATE TABLE fireMessages (serverID int, msgID int, boardMsgID int, reactionAmount int)" + ) + + if ( + await sql.fetchone( + "SELECT name FROM sqlite_master WHERE type='table' AND name='fireSettings';" + ) + is None + ): + # Fire Settings - server properties for fireboard + await sql.execute( + "CREATE TABLE fireSettings (serverID int, reactionAmount int, emoji text, channelID int, ignoreBots int)" + ) + + if ( + await sql.fetchone( + "SELECT name FROM sqlite_master WHERE type='table' AND name='fireChannelBlacklist';" + ) + is None + ): + # Fire Channel Blacklist - blacklisted channels + await sql.execute( + "CREATE TABLE fireChannelBlacklist (serverID int, channelID int)" + ) + + if ( + await sql.fetchone( + "SELECT name FROM sqlite_master WHERE type='table' AND name='fireRoleBlacklist';" + ) + is None + ): + # Fire Role Blacklist - blacklisted roles + await sql.execute( + "CREATE TABLE fireRoleBlacklist (serverID int, roleID int)" + ) + + await sql.commit() + + await self.refresh_fire_lists() + + # List refresh function + async def refresh_fire_lists(self): + async with self.fireboard_pool.acquire() as sql: + self.fire_messages = await sql.fetchall("SELECT * FROM fireMessages") + self.fire_settings = await sql.fetchall("SELECT * FROM fireSettings") + self.fire_channel_blacklist = await sql.fetchall( + "SELECT * FROM fireChannelBlacklist" + ) + self.fire_role_blacklist = await sql.fetchall( + "SELECT * FROM fireRoleBlacklist" + ) + + # Listen for reactions + @commands.Cog.listener() + async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + queued = False + + # Lock system + if payload.message_id in self.locked_messages: + queued = True + + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + fetched = False + + # Find server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + react_minimum = server[1] + emoji = server[2] + channel_id = server[3] + ignore_bots = True if int(server[4]) == 1 else False + + fetched = True + + # Stop if server has no config (fireboard isn't enabled) + if not fetched: + return + + # Stop if message is by Titanium + if payload.message_author_id == self.bot.user.id: + return + + # Stop if emoji doesn't match + if str(payload.emoji) != emoji: + return + + # --- Edit board message if it already exists --- + if payload.message_id in [message[1] for message in self.fire_messages]: + message = [ + message + for message in self.fire_messages + if message[1] == payload.message_id + ][0] + + # Only fetch updated reaction count if I have queued or reaction amount is undefined + if queued or message[3] is None: + # Fetch message and channel + try: + msg_channel = await self.bot.fetch_channel(payload.channel_id) + message = await msg_channel.fetch_message(payload.message_id) + except discord.errors.NotFound: + return + + # Stop if not enough reactions + for reaction in message.reactions: + if str(reaction.emoji) == emoji: + react_count = reaction.count + break + + if react_count < react_minimum: + return + else: + react_count = None + + async with self.fireboard_pool.acquire() as sql: + # Set updated react count + if react_count is not None: + await sql.execute( + "UPDATE fireMessages SET reactionAmount = ? WHERE msgID = ?", + ( + react_count, + payload.message_id, + ), + ) + await self.refresh_fire_lists() + else: + await sql.execute( + "UPDATE fireMessages SET reactionAmount = reactionAmount + 1 WHERE msgID = ?", + (payload.message_id,), + ) + await self.refresh_fire_lists() + + # Get message from message list + message = [ + message + for message in self.fire_messages + if message[1] == payload.message_id + ][0] + + # Get board message + board_channel = await self.bot.fetch_channel(channel_id) + board_message = await board_channel.fetch_message(message[2]) + + await board_message.edit( + content=f"**{message[3]} {emoji}** | <@{payload.message_author_id}> | <#{payload.channel_id}>", + embeds=board_message.embeds, + ) + + return + + # Stop if message is in a blacklisted channel + if payload.channel_id in [ + channel[1] for channel in self.fire_channel_blacklist + ]: + return + + # Stop if message is by a blacklisted role + guild = await self.bot.fetch_guild(payload.guild_id) + member = await guild.fetch_member(payload.user_id) + + if any( + role[1] in [role.id for role in member.roles] + for role in self.fire_role_blacklist + ): + return + + # Fetch message and channel + try: + msg_channel = await self.bot.fetch_channel(payload.channel_id) + message = await msg_channel.fetch_message(payload.message_id) + except discord.errors.NotFound: + return + + # Stop if message is by a bot + if ignore_bots and message.author.bot: + return + + # Check if this is a thread + if isinstance(msg_channel, discord.Thread): + # Check if parent channel is NSFW + if msg_channel.parent.nsfw: + return + else: + # Stop if message is in an NSFW channel + if message.channel.nsfw: + return + + # Stop if not enough reactions + for reaction in message.reactions: + if str(reaction.emoji) == emoji: + react_count = reaction.count + break + + if react_count < react_minimum: + return + + # --- Send message to fireboard --- + + # Create embed + embed = discord.Embed(description=message.content, color=Color.random()) + embed.set_author( + name=message.author.name, icon_url=message.author.display_avatar.url + ) + embed.timestamp = message.created_at + + # Jump to message button + view = View() + view.add_item( + discord.ui.Button( + label="Jump to Message", + url=message.jump_url, + style=discord.ButtonStyle.url, + ) + ) + + embed_list = [embed] + + # Add reply embed + if message.reference: + try: + reply_message = await msg_channel.fetch_message( + message.reference.message_id + ) + + reply_embed = discord.Embed( + title="Replying To", + description=reply_message.content, + color=Color.random(), + ) + reply_embed.set_author( + name=reply_message.author.name, + icon_url=reply_message.author.display_avatar.url, + ) + reply_embed.timestamp = reply_message.created_at + + embed_list.insert(0, reply_embed) + except discord.errors.NotFound: + pass + + # Send message + board_channel = await self.bot.fetch_channel(channel_id) + board_message = await board_channel.send( + content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", + embeds=embed_list, + view=view, + files=[ + await attachment.to_file() for attachment in message.attachments + ], + ) + + async with self.fireboard_pool.acquire() as sql: + # Insert message to DB + await sql.execute( + "INSERT INTO fireMessages (serverID, msgID, boardMsgID, reactionAmount) VALUES (?, ?, ?, ?)", + ( + payload.guild_id, + payload.message_id, + board_message.id, + react_count, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for reaction removal + @commands.Cog.listener() + async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): + self.bot: discord.ext.commands.Bot + + queued = False + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + queued = True + + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + fetched = False + + # Find server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + react_minimum = server[1] + emoji = server[2] + channel_id = server[3] + ignore_bots = True if int(server[4]) == 1 else False + + fetched = True + + # Stop if server has no config (fireboard isn't enabled) + if not fetched: + return + + # Stop if message is by Titanium + if payload.message_author_id == self.bot.user.id: + return + + # Stop if emoji doesn't match + if str(payload.emoji) != emoji: + return + + # --- Edit board message if it already exists --- + if payload.message_id in [message[1] for message in self.fire_messages]: + prev_react_count = [ + message + for message in self.fire_messages + if message[1] == payload.message_id + ][0][3] + + # Only fetch updated reaction count if I have queued + if queued or prev_react_count is None: + # Fetch message and channel + try: + msg_channel = await self.bot.fetch_channel(payload.channel_id) + message = await msg_channel.fetch_message(payload.message_id) + except discord.errors.NotFound: + return + + # Stop if not enough reactions + for reaction in message.reactions: + if str(reaction.emoji) == emoji: + react_count = reaction.count + break + + if react_count < react_minimum: + return + else: + react_count = None + + async with self.fireboard_pool.acquire() as sql: + # Set updated react count + if react_count is not None: + await sql.execute( + "UPDATE fireMessages SET reactionAmount = ? WHERE msgID = ?", + ( + react_count, + payload.message_id, + ), + ) + await self.refresh_fire_lists() + else: + await sql.execute( + "UPDATE fireMessages SET reactionAmount = reactionAmount - 1 WHERE msgID = ?", + (payload.message_id,), + ) + await self.refresh_fire_lists() + + # Get message from message list + message = [ + message + for message in self.fire_messages + if message[1] == payload.message_id + ][0] + + # Get board message + board_channel = await self.bot.fetch_channel(channel_id) + board_message = await board_channel.fetch_message(message[2]) + + # Remove message if not enough reactions + if message[3] < react_minimum: + await board_message.delete() + + async with self.fireboard_pool.acquire() as sql: + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + + # Workaround for lack of message author ID + content = board_message.content + content = content.replace( + f"{prev_react_count} {emoji}", f"{message[3]} {emoji}" + ) + + await board_message.edit(content=content) + + return + + # Stop if message is in a blacklisted channel + if payload.channel_id in [ + channel[1] for channel in self.fire_channel_blacklist + ]: + return + + # Stop if message is by a blacklisted role + guild = await self.bot.fetch_guild(payload.guild_id) + member = await guild.fetch_member(payload.user_id) + + if any( + role[1] in [role.id for role in member.roles] + for role in self.fire_role_blacklist + ): + return + + # Fetch message and channel + try: + msg_channel = await self.bot.fetch_channel(payload.channel_id) + message = await msg_channel.fetch_message(payload.message_id) + except discord.errors.NotFound: + return + + # Stop if message is by a bot + if ignore_bots and message.author.bot: + return + + # Check if this is a thread + if isinstance(msg_channel, discord.Thread): + # Check if parent channel is NSFW + if msg_channel.parent.nsfw: + return + else: + # Stop if message is in an NSFW channel + if message.channel.nsfw: + return + + # Get reaction count + react_count = 0 + + for reaction in message.reactions: + if str(reaction.emoji) == emoji: + react_count = reaction.count + break + + # Stop if not enough reactions + if react_count < react_minimum: + return + + # --- Send message to fireboard --- + + # Create embed + embed = discord.Embed(description=message.content, color=Color.random()) + embed.set_author( + name=message.author.name, icon_url=message.author.display_avatar.url + ) + embed.timestamp = message.created_at + + # Jump to message button + view = View() + view.add_item( + discord.ui.Button( + label="Jump to Message", + url=message.jump_url, + style=discord.ButtonStyle.url, + ) + ) + + embed_list = [embed] + + # Add reply embed + if message.reference: + try: + reply_message = await msg_channel.fetch_message( + message.reference.message_id + ) + + reply_embed = discord.Embed( + title="Replying To", + description=reply_message.content, + color=Color.random(), + ) + reply_embed.set_author( + name=reply_message.author.name, + icon_url=reply_message.author.display_avatar.url, + ) + reply_embed.timestamp = reply_message.created_at + + embed_list.insert(0, reply_embed) + except discord.errors.NotFound: + pass + + # Send message + board_channel = await self.bot.fetch_channel(channel_id) + board_message = await board_channel.send( + content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", + embeds=embed_list, + view=view, + files=[ + await attachment.to_file() for attachment in message.attachments + ], + ) + + async with self.fireboard_pool.acquire() as sql: + # Insert message to DB + await sql.execute( + "INSERT INTO fireMessages (serverID, msgID, boardMsgID, reactionAmount) VALUES (?, ?, ?, ?)", + ( + payload.guild_id, + payload.message_id, + board_message.id, + react_count, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for message reaction clear + @commands.Cog.listener() + async def on_raw_reaction_clear(self, payload: discord.RawReactionClearEvent): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + # Only trigger if message is already in the fireboard DB + if payload.message_id in [message[1] for message in self.fire_messages]: + # Find server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + channel_id = server[3] + + # Get guild + try: + guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) + except discord.errors.NotFound: + return + + # Get message channel + channel: discord.abc.GuildChannel = await guild.fetch_channel( + payload.channel_id + ) + + # Get our message + message: discord.Message = await channel.fetch_message( + payload.message_id + ) + + # See if board message is already present + for fire_message in self.fire_messages: + if fire_message[1] == message.id: + async with self.fireboard_pool.acquire() as sql: + try: + # Delete message + try: + channel: discord.TextChannel = ( + await guild.fetch_channel(channel_id) + ) + except discord.errors.NotFound: + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (payload.guild_id,), + ) + await sql.commit() + self.locked_messages.remove(payload.message_id) + + return + + board_message = await channel.fetch_message( + fire_message[2] + ) + await board_message.delete() + + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (message.id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + except discord.errors.NotFound: + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (message.id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for specific emoji being cleared + @commands.Cog.listener() + async def on_raw_reaction_clear_emoji( + self, payload: discord.RawReactionClearEmojiEvent + ): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + # Only trigger if message is already in the fireboard DB + if payload.message_id in [message[1] for message in self.fire_messages]: + for server in self.fire_settings: + if server[0] == payload.guild_id: + emoji = server[2] + channel_id = server[3] + + # Only trigger if cleared emoji is our emoji + if str(payload.emoji) == emoji: + # Fetch server + try: + guild: discord.Guild = await self.bot.fetch_guild( + payload.guild_id + ) + except discord.errors.NotFound: + return + + # Get message channel + channel: discord.abc.GuildChannel = await guild.fetch_channel( + payload.channel_id + ) + + # See if board message is already present + for fire_message in self.fire_messages: + if fire_message[1] == payload.message_id: + async with self.fireboard_pool.acquire() as sql: + try: + # Fetch fireboard channel + try: + channel: discord.TextChannel = ( + await guild.fetch_channel(channel_id) + ) + except discord.errors.NotFound: + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (payload.guild_id,), + ) + await sql.commit() + + return + + # Delete message + board_message = await channel.fetch_message( + fire_message[2] + ) + await board_message.delete() + + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + except discord.errors.NotFound: + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for message being deleted + @commands.Cog.listener() + async def on_raw_message_delete(self, payload: discord.RawMessageDeleteEvent): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + # Only trigger if message is already in the fireboard DB + if payload.message_id in [message[1] for message in self.fire_messages]: + # Fetch server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + channel_id = server[3] + + # Fetch server + try: + guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) + except discord.errors.NotFound: + return + + # Get message channel + channel: discord.abc.GuildChannel = await guild.fetch_channel( + payload.channel_id + ) + + # See if board message is already present + for fire_message in self.fire_messages: + if fire_message[1] == payload.message_id: + async with self.fireboard_pool.acquire() as sql: + try: + # Fetch fireboard channel + try: + channel: discord.TextChannel = ( + await guild.fetch_channel(channel_id) + ) + except discord.errors.NotFound: + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (payload.guild_id,), + ) + await sql.commit() + + return + + # Delete message + board_message = await channel.fetch_message( + fire_message[2] + ) + await board_message.delete() + + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + except discord.errors.NotFound: + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for message being edited + @commands.Cog.listener() + async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent): + self.bot: discord.ext.commands.Bot + + # Stop if this is a DM + if payload.guild_id is None: + return + + # Lock system + if payload.message_id in self.locked_messages: + while payload.message_id in self.locked_messages: + await asyncio.sleep(0.5) + + self.locked_messages.append(payload.message_id) + + try: + # Only trigger if message is already in the fireboard DB + if payload.message_id in [message[1] for message in self.fire_messages]: + # Fetch server config + for server in self.fire_settings: + if server[0] == payload.guild_id: + channel_id = server[3] + + # Fetch server + try: + guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) + except discord.errors.NotFound: + return + + # Get message channel + channel: discord.abc.GuildChannel = await guild.fetch_channel( + payload.channel_id + ) + + # Get our message + message: discord.Message = await channel.fetch_message( + payload.message_id + ) + + embed = discord.Embed(description=message.content, color=Color.random()) + embed.set_author( + name=message.author.name, icon_url=message.author.display_avatar.url + ) + embed.timestamp = message.created_at + + # Jump to message button + view = View() + view.add_item( + discord.ui.Button( + label="Jump to Message", + url=message.jump_url, + style=discord.ButtonStyle.url, + ) + ) + + embed_list = [embed] + + # Add reply embed + if message.reference: + try: + reply_message = await channel.fetch_message( + message.reference.message_id + ) + + reply_embed = discord.Embed( + title="Replying To", + description=reply_message.content, + color=Color.random(), + ) + reply_embed.set_author( + name=reply_message.author.name, + icon_url=reply_message.author.display_avatar.url, + ) + reply_embed.timestamp = reply_message.created_at + + embed_list.insert(0, reply_embed) + except discord.errors.NotFound: + pass + + try: + channel: discord.TextChannel = await guild.fetch_channel(channel_id) + except discord.errors.NotFound: + async with self.fireboard_pool.acquire() as sql: + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (payload.guild_id,), + ) + await sql.commit() + + return + + # Find previous fireboard message + try: + for fire_message in self.fire_messages: + if ( + fire_message[0] == payload.guild_id + and fire_message[1] == payload.message_id + ): + # Edit with updated embed - reaction amount stays the same + board_message = await channel.fetch_message(fire_message[2]) + + await board_message.edit( + embeds=embed_list, attachments=message.attachments + ) + except discord.errors.NotFound: # Message not found + async with self.fireboard_pool.acquire() as sql: + # Delete message from DB + await sql.execute( + "DELETE FROM fireMessages WHERE msgID = ?", + (payload.message_id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + except Exception as e: + raise e + finally: + self.locked_messages.remove(payload.message_id) + + # Listen for fireboard channel delete + @commands.Cog.listener() + async def on_guild_channel_delete(self, channel: discord.abc.GuildChannel): + # Only trigger if server has fireboard enabled + if channel.guild.id in [guild[0] for guild in self.fire_settings]: + for server in self.fire_settings: + if server[0] == channel.guild.id: + if server[3] == channel.id: + async with self.fireboard_pool.acquire() as sql: + # Delete fireboard config + await sql.execute( + "DELETE FROM fireMessages WHERE serverID = ?", + (channel.guild.id,), + ) + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (channel.guild.id,), + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + + # Listen for server being left / deleted + @commands.Cog.listener() + async def on_guild_remove(self, guild: discord.Guild): + # Only trigger if server has fireboard enabled + if guild.id in [guild[0] for guild in self.fire_settings]: + for server in self.fire_settings: + if server[0] == guild.id: + async with self.fireboard_pool.acquire() as sql: + # Delete fireboard config + await sql.execute( + "DELETE FROM fireMessages WHERE serverID = ?", (guild.id,) + ) + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", (guild.id,) + ) + await sql.commit() + + await self.refresh_fire_lists() + + return + else: + return + + # Command group setup + context = discord.app_commands.AppCommandContext( + guild=True, dm_channel=False, private_channel=False + ) + installs = discord.app_commands.AppInstallationType(guild=True, user=False) + fireGroup = app_commands.Group( + name="fireboard", + description="Fireboard related commands.", + allowed_contexts=context, + allowed_installs=installs, + ) + + # Random fireboard message command + @fireGroup.command( + name="random", description="Get a random message from the fireboard." + ) + @app_commands.describe( + ephemeral="Optional: whether to send the command output as a dismissible message only visible to you. Defaults to false." + ) + async def random_fireboard( + self, interaction: discord.Interaction, ephemeral: bool = False + ): + await interaction.response.defer(ephemeral=ephemeral) + + channel_id = None + + # Find server config + for server in self.fire_settings: + if server[0] == interaction.guild_id: + channel_id = server[3] + + if channel_id is None: + embed = discord.Embed( + title="Error", + description="Fireboard is not enabled in this server.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=ephemeral) + + return + + # Fetch channel + try: + channel = await interaction.guild.fetch_channel(channel_id) + except discord.errors.NotFound: + embed = discord.Embed( + title="Error", + description="Can't find the fireboard channel. Please contact a server admin.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=ephemeral) + + return + + # Fetch messages + async with self.fireboard_pool.acquire() as sql: + messages = await sql.fetchall( + "SELECT * FROM fireMessages WHERE serverID = ?", (interaction.guild_id,) + ) + + if not messages: + embed = discord.Embed( + title="Error", + description="No messages found in the fireboard.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=ephemeral) + + return + else: + while messages != []: + message = random.choice(messages) + + try: + board_message = await channel.fetch_message(message[2]) + + view = View().from_message(board_message) + files = [ + await attachment.to_file() + for attachment in board_message.attachments + ] + + await interaction.followup.send( + content=board_message.content, + embeds=board_message.embeds, + view=view, + ephemeral=ephemeral, + files=files, + allowed_mentions=discord.AllowedMentions.none(), + ) + + return + except discord.errors.NotFound: + messages.remove(message) + + embed = discord.Embed( + title="Error", + description="No messages found in the fireboard.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=ephemeral) + + # Command group setup + context = discord.app_commands.AppCommandContext( + guild=True, dm_channel=False, private_channel=False + ) + installs = discord.app_commands.AppInstallationType(guild=True, user=False) + perms = discord.Permissions(manage_guild=True) + fireSetupGroup = app_commands.Group( + name="fireboard-setup", + description="Control the fireboard.", + allowed_contexts=context, + allowed_installs=installs, + default_permissions=perms, + ) + + # Fireboard enable command + @fireSetupGroup.command( + name="enable", description="Enable the fireboard in the current channel." + ) + async def enable_fireboard(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Fireboard is already enabled.", color=Color.green() + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + # Default settings + react_minimum = 3 + emoji = "🔥" + channel_id = interaction.channel_id + ignore_bots = True + + embed = discord.Embed( + title="Fireboard", + description="This channel has been configured as the server fireboard.", + color=Color.random(), + ) + embed.set_footer(text="Feel free to delete this message!") + + try: + channel = await interaction.guild.fetch_channel(channel_id) + await channel.send(embed=embed) + except discord.errors.Forbidden or discord.errors.NotFound: + embed = discord.Embed( + title="Error", + description="Looks like I can't send messages in this channel. Check permissions and try again.", + color=Color.random(), + ) + await interaction.followup.send(embed=embed, ephemeral=True) + + return + + async with self.fireboard_pool.acquire() as sql: + # Insert to DB, refresh lists + await sql.execute( + "INSERT INTO fireSettings (serverID, reactionAmount, emoji, channelID, ignoreBots) VALUES (?, ?, ?, ?, ?)", + ( + interaction.guild_id, + react_minimum, + emoji, + channel_id, + ignore_bots, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Enabled", + description="Fireboard has been enabled in the current channel.", + color=Color.green(), + ) + embed.add_field( + name="Info", + value=f"**Reaction Requirement:** `{react_minimum} reactions`\n**Fireboard Channel:** <#{channel_id}>\n**Emoji:** {emoji}\n**Ignore Bots:** `{ignore_bots}`", + ) + + await interaction.followup.send(embed=embed, ephemeral=True) + + class ConfirmDisableView(View): + def __init__(self): + super().__init__(timeout=60) + + async def disable_fireboard( + self, interaction: discord.Interaction, pool: asqlite.Pool + ): + async with pool.acquire() as sql: + try: + await sql.execute( + "DELETE FROM fireMessages WHERE serverID = ?", + (interaction.guild_id,), + ) + await sql.execute( + "DELETE FROM fireSettings WHERE serverID = ?", + (interaction.guild_id,), + ) + await sql.execute( + "DELETE FROM fireChannelBlacklist WHERE serverID = ?", + (interaction.guild_id,), + ) + await sql.execute( + "DELETE FROM fireRoleBlacklist WHERE serverID = ?", + (interaction.guild_id,), + ) + await sql.commit() + return True + except Exception: + return False + + @discord.ui.button(label="Disable", style=discord.ButtonStyle.red) + async def confirm( + self, interaction: discord.Interaction, button: discord.ui.Button + ): + await interaction.response.defer(ephemeral=True) + + success = await self.disable_fireboard(interaction, self.pool) + + if success: + embed = discord.Embed( + title="Done!", + description="Fireboard was disabled.", + color=Color.green(), + ) + await self.cog.refresh_fire_lists() # pylint: disable=no-member + else: + embed = discord.Embed( + title="Error", + description="Failed to disable fireboard.", + color=Color.red(), + ) + + await interaction.edit_original_response(embed=embed, view=None) + self.stop() + + async def on_timeout(self): + for item in self.children: + item.disabled = True + + embed = discord.Embed( + title="Timeout", + description="You didn't press the button in time.", + color=Color.red(), + ) + await self.message.edit(embed=embed, view=self) + + # Fireboard disable command + @fireSetupGroup.command(name="disable", description="Disable the server fireboard.") + async def disable_fireboard(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + + if interaction.guild_id in [guild[0] for guild in self.fire_settings]: + view = self.ConfirmDisableView() + view.pool = self.fireboard_pool + view.cog = self + + embed = discord.Embed( + title="Are you sure?", + description="All data about this server's fireboard will be deleted. This cannot be undone!", + color=Color.orange(), + ) + + message = await interaction.followup.send( + embed=embed, view=view, ephemeral=True, wait=True + ) + view.message = message + else: + await interaction.followup.send( + "Fireboard is not enabled in this server!", ephemeral=True + ) + + # Fireboard server info command + @fireSetupGroup.command( + name="info", description="View fireboard config for this server." + ) + async def fireboard_info(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + # Fetch server settings + for server in self.fire_settings: + if server[0] == interaction.guild_id: + react_minimum = server[1] + emoji = server[2] + channel_id = server[3] + ignore_bots = True if int(server[4]) == 1 else False + + embed = discord.Embed( + title="Server Fireboard Settings", + description=f"**Reaction Requirement:** `{react_minimum} reactions`\n**Fireboard Channel:** <#{channel_id}>\n**Emoji:** {emoji}\n**Ignore Bots:** `{ignore_bots}`", + color=Color.random(), + ) + + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard set emoji command + @fireSetupGroup.command(name="emoji", description="Set a custom fireboard emoji.") + async def fireboard_emoji(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=False) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Waiting for Reaction", + description="🔄 React with this message with your target emoji to set the fireboard emoji.", + color=Color.orange(), + ) + + msg = await interaction.followup.send(embed=embed, ephemeral=False) + + def check(reaction, user): + return user == interaction.user and reaction.message.id == msg.id + + # Wait for a reaction + try: + reaction, user = await self.bot.wait_for( + "reaction_add", timeout=60.0, check=check + ) + + reaction: discord.Reaction = reaction + + async with self.fireboard_pool.acquire() as sql: + # Change emoji in DB, refresh lists + await sql.execute( + "UPDATE fireSettings SET emoji = ? WHERE serverID = ?", + ( + str(reaction.emoji), + interaction.guild_id, + ), + ) + await sql.commit() + + embed = discord.Embed( + title="Emoji Set", + description=f"Set emoji to **{str(reaction.emoji)}.**", + color=Color.green(), + ) + + await self.refresh_fire_lists() + await interaction.edit_original_response(embed=embed) + except asyncio.TimeoutError: # Timed out + embed = discord.Embed( + title="Timed Out", + description="You didn't react in time.", + color=Color.red(), + ) + + await interaction.edit_original_response(embed=embed) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=False) + + # Fireboard set channel command + @fireSetupGroup.command( + name="channel", + description="Set the channel for fireboard messages to be sent in.", + ) + async def fireboard_channel( + self, interaction: discord.Interaction, channel: discord.TextChannel + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Fireboard", + description="This channel has been configured as the server fireboard.", + color=Color.random(), + ) + embed.set_footer(text="Feel free to delete this message!") + + try: + await channel.send(embed=embed) + except discord.errors.NotFound: + embed = discord.Embed( + title="Error", + description="Looks like I can't find that channel. Check permissions and try again.", + color=Color.random(), + ) + await interaction.followup.send(embed=embed, ephemeral=True) + + return + except discord.errors.Forbidden as e: + embed = discord.Embed( + title="Error", + description="Looks like I can't send messages in that channel. Check permissions and try again.", + color=Color.red(), + ) + await interaction.followup.send(embed=embed, ephemeral=True) + + return + + async with self.fireboard_pool.acquire() as sql: + # Update channel in DB, refresh lists + await sql.execute( + "UPDATE fireSettings SET channelID = ? WHERE serverID = ?", + ( + channel.id, + interaction.guild_id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Channel Set", + description=f"Fireboard channel has been set to **{channel.mention}.**", + color=Color.green(), + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard set requirement command + @fireSetupGroup.command( + name="requirement", + description="Set required reaction amount for message to be posted on the fireboard.", + ) + async def fireboard_requirement( + self, interaction: discord.Interaction, amount: int + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Set", + description=f"Reaction requirement has been set to **{amount} reactions.**", + color=Color.green(), + ) + + async with self.fireboard_pool.acquire() as sql: + # Update reaction requirement in DB, refresh lists + await sql.execute( + "UPDATE fireSettings SET reactionAmount = ? WHERE serverID = ?", + ( + amount, + interaction.guild_id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard ignore bots command + @fireSetupGroup.command( + name="ignore-bots", + description="Whether bot messages are ignored in the fireboard. Defaults to true.", + ) + async def fireboard_ignore_bots( + self, interaction: discord.Interaction, value: bool + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild.id in [guild[0] for guild in self.fire_settings]: + embed = discord.Embed( + title="Set", + description=f"Bot messages will **{'be ignored.' if value else 'not be ignored.'}**", + color=Color.green(), + ) + + async with self.fireboard_pool.acquire() as sql: + # Update setting in DB, refresh lists + await sql.execute( + "UPDATE fireSettings SET ignoreBots = ? WHERE serverID = ?", + ( + value, + interaction.guild_id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard role blacklist + @fireSetupGroup.command( + name="channel-blacklist", + description="Toggle the blacklist for a channel. NSFW channels are always blacklisted.", + ) + async def fireboard_channel_blacklist( + self, interaction: discord.Interaction, channel: discord.abc.GuildChannel + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild_id in [guild[0] for guild in self.fire_settings]: + async with self.fireboard_pool.acquire() as sql: + if channel.id in [ + channelEntry[1] for channelEntry in self.fire_channel_blacklist + ]: + await sql.execute( + "DELETE FROM fireChannelBlacklist WHERE serverID = ? AND channelID = ?", + ( + interaction.guild_id, + channel.id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Set", + description=f"Removed {channel.mention} from the channel blacklist.", + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + await sql.execute( + "INSERT INTO fireChannelBlacklist (serverID, channelID) VALUES (?, ?)", + ( + interaction.guild_id, + channel.id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Set", + description=f"Added {channel.mention} to the channel blacklist.", + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard role blacklist + @fireSetupGroup.command( + name="role-blacklist", description="Toggle the blacklist for a role." + ) + async def fireboard_role_blacklist( + self, interaction: discord.Interaction, role: discord.Role + ): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild_id in [guild[0] for guild in self.fire_settings]: + async with self.fireboard_pool.acquire() as sql: + if role.id in [roleEntry[1] for roleEntry in self.fire_role_blacklist]: + await sql.execute( + "DELETE FROM fireRoleBlacklist WHERE serverID = ? AND roleID = ?", + ( + interaction.guild_id, + role.id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Set", + description=f"Removed {role.mention} from the role blacklist.", + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + await sql.execute( + "INSERT INTO fireRoleBlacklist (serverID, roleID) VALUES (?, ?)", + ( + interaction.guild_id, + role.id, + ), + ) + await sql.commit() + + await self.refresh_fire_lists() + + embed = discord.Embed( + title="Set", + description=f"Added {role.mention} to the role blacklist.", + ) + await interaction.followup.send(embed=embed, ephemeral=True) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + # Fireboard role blacklist + @fireSetupGroup.command( + name="blacklists", description="View this server's role and channel blacklists." + ) + async def fireboard_blacklists(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + + # Check fireboard status + if interaction.guild_id in [guild[0] for guild in self.fire_settings]: + + class BlacklistViewer(View): + def __init__(self): + super().__init__(timeout=240) + + self.fire_channel_blacklist: list + self.fire_role_blacklist: list + self.interaction: discord.Interaction + + async def on_timeout(self) -> None: + for item in self.children: + item.disabled = True + + await self.interaction.edit_original_response(view=self) + + @discord.ui.button( + label="Role Blacklist", + style=discord.ButtonStyle.gray, + row=0, + custom_id="role", + disabled=True, + ) + async def role( + self, interaction: discord.Interaction, button: discord.ui.Button + ): + await interaction.response.defer(ephemeral=True) + + for item in self.children: + if item.custom_id == "channel": + item.disabled = False + else: + item.disabled = True + + my_roles = [] + + for role in self.fire_role_blacklist: + if role[0] == interaction.guild_id: + my_roles.append(f"<@&{role[1]}>") + + if my_roles != []: + embed = discord.Embed( + title="Role Blacklist", + description="\n".join(my_roles), + color=Color.random(), + ) + await interaction.edit_original_response(embed=embed, view=self) + else: + embed = discord.Embed( + title="Role Blacklist", + description="No roles have been blacklisted.", + color=Color.random(), + ) + await interaction.edit_original_response(embed=embed, view=self) + + @discord.ui.button( + label="Channel Blacklist", + style=discord.ButtonStyle.gray, + row=0, + custom_id="channel", + ) + async def channel( + self, interaction: discord.Interaction, button: discord.ui.Button + ): + await interaction.response.defer(ephemeral=True) + + for item in self.children: + if item.custom_id == "role": + item.disabled = False + else: + item.disabled = True + + my_channels = [] + + for channel in self.fire_channel_blacklist: + if channel[0] == interaction.guild_id: + my_channels.append(f"<#{channel[1]}>") + + if my_channels != []: + embed = discord.Embed( + title="Channel Blacklist", + description="\n".join(my_channels), + color=Color.random(), + ) + await interaction.edit_original_response(embed=embed, view=self) + else: + embed = discord.Embed( + title="Channel Blacklist", + description="No channels have been blacklisted.", + color=Color.random(), + ) + await interaction.edit_original_response(embed=embed, view=self) + + view_instance = BlacklistViewer() + view_instance.fire_channel_blacklist = self.fire_channel_blacklist + view_instance.fire_role_blacklist = self.fire_role_blacklist + view_instance.interaction = interaction + + my_roles = [] + + for role in self.fire_role_blacklist: + if role[0] == interaction.guild_id: + my_roles.append(f"<@&{role[1]}>") + + if my_roles != []: + embed = discord.Embed( + title="Role Blacklist", + description="\n".join(my_roles), + color=Color.random(), + ) + await interaction.followup.send( + embed=embed, view=view_instance, ephemeral=True + ) + else: + embed = discord.Embed( + title="Role Blacklist", + description="No roles have been blacklisted.", + color=Color.random(), + ) + await interaction.followup.send( + embed=embed, view=view_instance, ephemeral=True + ) + else: + embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) + + await interaction.followup.send(embed=embed, ephemeral=True) + + +async def setup(bot):import asyncio +import random +import json +import os + +import discord +from discord import Color, app_commands +from discord.ext import commands +from discord.ui import View + +FIREBOARD_PATH = "assets/cogs/files/fireboard.json" + +def load_data(): + if not os.path.exists(FIREBOARD_PATH): + return {} + with open(FIREBOARD_PATH, "r") as f: + return json.load(f) + +def save_data(data): + os.makedirs(os.path.dirname(FIREBOARD_PATH), exist_ok=True) + with open(FIREBOARD_PATH, "w") as f: + json.dump(data, f, indent=2) + +class Fireboard(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.data = load_data() + self.locked_messages = [] + + def save(self): + save_data(self.data) + + def get_guild(self, guild_id): + return self.data.setdefault(str(guild_id), { + "settings": None, + "messages": [], + "role_blacklist": [], + "channel_blacklist": [] + }) + + @commands.Cog.listener() + async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): + if payload.guild_id is None: + return + guild_data = self.get_guild(payload.guild_id) + settings = guild_data["settings"] + if not settings: + return + react_minimum = settings["reactionAmount"] + emoji = settings["emoji"] + channel_id = settings["channelID"] + ignore_bots = settings["ignoreBots"] + + if str(payload.emoji) != emoji: + return + if payload.channel_id in guild_data["channel_blacklist"]: + return + if payload.message_id in self.locked_messages: + return + self.locked_messages.append(payload.message_id) + try: + channel = await self.bot.fetch_channel(payload.channel_id) + message = await channel.fetch_message(payload.message_id) + if ignore_bots and message.author.bot: + return + if hasattr(message.channel, "nsfw") and message.channel.nsfw: + return + react_count = 0 + for reaction in message.reactions: + if str(reaction.emoji) == emoji: + react_count = reaction.count + break + if react_count < react_minimum: + return + # Already on fireboard? + for m in guild_data["messages"]: + if m["msgID"] == payload.message_id: + return + # Send to fireboard + board_channel = await self.bot.fetch_channel(channel_id) + embed = discord.Embed(description=message.content, color=Color.random()) + embed.set_author(name=message.author.name, icon_url=message.author.display_avatar.url) + embed.timestamp = message.created_at + view = View() + view.add_item(discord.ui.Button(label="Jump to Message", url=message.jump_url, style=discord.ButtonStyle.url)) + files = [await a.to_file() for a in message.attachments] + board_message = await board_channel.send( + content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", + embed=embed, view=view, files=files + ) + guild_data["messages"].append({ + "msgID": payload.message_id, + "boardMsgID": board_message.id, + "reactionAmount": react_count + }) + self.save() + finally: + if payload.message_id in self.locked_messages: + self.locked_messages.remove(payload.message_id) + + @commands.Cog.listener() + async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): + if payload.guild_id is None: + return + guild_data = self.get_guild(payload.guild_id) + settings = guild_data["settings"] + if not settings: + return + emoji = settings["emoji"] + channel_id = settings["channelID"] + if str(payload.emoji) != emoji: + return + for m in guild_data["messages"]: + if m["msgID"] == payload.message_id: + channel = await self.bot.fetch_channel(channel_id) + try: + board_message = await channel.fetch_message(m["boardMsgID"]) + await board_message.delete() + except Exception: + pass + guild_data["messages"].remove(m) + self.save() + break + + @app_commands.command(name="fireboard_enable", description="Enable the fireboard in this channel.") + async def fireboard_enable(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + guild_data = self.get_guild(interaction.guild_id) + if guild_data["settings"]: + await interaction.followup.send("Fireboard is already enabled.", ephemeral=True) + return + guild_data["settings"] = { + "reactionAmount": 3, + "emoji": "🔥", + "channelID": interaction.channel_id, + "ignoreBots": True + } + self.save() + await interaction.followup.send("Fireboard enabled in this channel.", ephemeral=True) + + @app_commands.command(name="fireboard_disable", description="Disable the fireboard.") + async def fireboard_disable(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + guild_data = self.get_guild(interaction.guild_id) + guild_data["settings"] = None + guild_data["messages"] = [] + self.save() + await interaction.followup.send("Fireboard disabled.", ephemeral=True) + + @app_commands.command(name="fireboard_info", description="Show fireboard settings.") + async def fireboard_info(self, interaction: discord.Interaction): + await interaction.response.defer(ephemeral=True) + guild_data = self.get_guild(interaction.guild_id) + settings = guild_data["settings"] + if not settings: + await interaction.followup.send("Fireboard is not enabled.", ephemeral=True) + return + embed = discord.Embed( + title="Fireboard Settings", + description=f"**Reaction Requirement:** `{settings['reactionAmount']}`\n" + f"**Fireboard Channel:** <#{settings['channelID']}>\n" + f"**Emoji:** {settings['emoji']}\n" + f"**Ignore Bots:** `{settings['ignoreBots']}`", + color=Color.random() + ) + await interaction.followup.send(embed=embed, ephemeral=True) + +async def setup(bot): + await bot.add_cog(Fireboard(bot)) \ No newline at end of file diff --git a/assets/locales/en.json b/assets/locales/en.json index cca8867..238e172 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -1,4 +1,5 @@ { + "psutil_not_installed": "Memory check skipped.", "not_cloned": "Goober is not cloned! Please clone it from GitHub.", "checks_disabled": "Checks are disabled!", "unhandled_exception": "An unhandled exception occurred. Please report this issue on GitHub.", diff --git a/assets/locales/it.json b/assets/locales/it.json index ebf28aa..54d1071 100644 --- a/assets/locales/it.json +++ b/assets/locales/it.json @@ -1,4 +1,5 @@ { + "psutil_not_installed": "Controllo memoria saltato.", "not_cloned": "Goober non è stato clonato! Clonalo da GitHub.", "checks_disabled": "I controlli sono disabilitati!", "unhandled_exception": "Si è verificata un'eccezione non gestita. Segnala questo problema su GitHub, per favore.", diff --git a/bot.py b/bot.py index 7c2050c..015f9f3 100644 --- a/bot.py +++ b/bot.py @@ -473,7 +473,7 @@ async def stats(ctx: commands.Context) -> None: embed: discord.Embed = discord.Embed(title=f"{(_('command_stats_embed_title'))}", description=f"{(_('command_stats_embed_desc'))}", color=Colour(0x000000)) embed.add_field(name=f"{(_('command_stats_embed_field1name'))}", value=f"{(_('command_stats_embed_field1value')).format(file_size=file_size, line_count=line_count)}", inline=False) embed.add_field(name=f"{(_('command_stats_embed_field2name'))}", value=f"{(_('command_stats_embed_field2value')).format(local_version=local_version, latest_version=latest_version)}", inline=False) - embed.add_field(name=f"{(_('command_stats_embed_field3name'))}", value=f"{(_('command_stats_embed_field3value')).format(NAME=NAME, PREFIX=PREFIX, ownerid=ownerid, cooldown_time=cooldown_time, PING_LINE=PING_LINE, showmemenabled=showmemenabled, USERTRAIN_ENABLED=USERTRAIN_ENABLED, song=song, splashtext=splashtext)}", inline=False) + embed.add_field(name=f"{(_('command_stats_embed_field3name'))}", value=f"{(_('command_stats_embed_field3value')).format(NAME=NAME, PREFIX=PREFIX, ownerid=ownerid, PING_LINE=PING_LINE, showmemenabled=showmemenabled, USERTRAIN_ENABLED=USERTRAIN_ENABLED, song=song, splashtext=splashtext)}", inline=False) await send_message(ctx, embed=embed) diff --git a/modules/globalvars.py b/modules/globalvars.py index 7dd1c8a..998267b 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -37,7 +37,7 @@ 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 = "2.0.2" +local_version = "2.1.2" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks From 06d98091caae7e5568494e378464cdd04a3024c5 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:44:55 +0200 Subject: [PATCH 16/84] fuck fireboard --- assets/cogs/fireboard.py | 1923 -------------------------------------- 1 file changed, 1923 deletions(-) delete mode 100644 assets/cogs/fireboard.py diff --git a/assets/cogs/fireboard.py b/assets/cogs/fireboard.py deleted file mode 100644 index 8d199b0..0000000 --- a/assets/cogs/fireboard.py +++ /dev/null @@ -1,1923 +0,0 @@ -import asyncio -import random - -import asqlite -import discord -import discord.ext -import discord.ext.commands -from discord import Color, app_commands -from discord.ext import commands -from discord.ui import View - - -class Fireboard(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.locked_messages = [] - self.disabled = False - - # Check if the bot has the fireboard_pool attribute - if not hasattr(bot, 'fireboard_pool') or bot.fireboard_pool is None: - print("Warning: Bot does not have fireboard_pool initialized. Fireboard functionality will be disabled.") - self.disabled = True - return - - self.fireboard_pool: asqlite.Pool = bot.fireboard_pool - self.bot.loop.create_task(self.setup()) - - # SQL Setup - async def setup(self): - async with self.fireboard_pool.acquire() as sql: - if ( - await sql.fetchone( - "SELECT name FROM sqlite_master WHERE type='table' AND name='fireMessages';" - ) - is None - ): - # Fire Messages - messages that are active on the fireboard - await sql.execute( - "CREATE TABLE fireMessages (serverID int, msgID int, boardMsgID int, reactionAmount int)" - ) - - if ( - await sql.fetchone( - "SELECT name FROM sqlite_master WHERE type='table' AND name='fireSettings';" - ) - is None - ): - # Fire Settings - server properties for fireboard - await sql.execute( - "CREATE TABLE fireSettings (serverID int, reactionAmount int, emoji text, channelID int, ignoreBots int)" - ) - - if ( - await sql.fetchone( - "SELECT name FROM sqlite_master WHERE type='table' AND name='fireChannelBlacklist';" - ) - is None - ): - # Fire Channel Blacklist - blacklisted channels - await sql.execute( - "CREATE TABLE fireChannelBlacklist (serverID int, channelID int)" - ) - - if ( - await sql.fetchone( - "SELECT name FROM sqlite_master WHERE type='table' AND name='fireRoleBlacklist';" - ) - is None - ): - # Fire Role Blacklist - blacklisted roles - await sql.execute( - "CREATE TABLE fireRoleBlacklist (serverID int, roleID int)" - ) - - await sql.commit() - - await self.refresh_fire_lists() - - # List refresh function - async def refresh_fire_lists(self): - async with self.fireboard_pool.acquire() as sql: - self.fire_messages = await sql.fetchall("SELECT * FROM fireMessages") - self.fire_settings = await sql.fetchall("SELECT * FROM fireSettings") - self.fire_channel_blacklist = await sql.fetchall( - "SELECT * FROM fireChannelBlacklist" - ) - self.fire_role_blacklist = await sql.fetchall( - "SELECT * FROM fireRoleBlacklist" - ) - - # Listen for reactions - @commands.Cog.listener() - async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - queued = False - - # Lock system - if payload.message_id in self.locked_messages: - queued = True - - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - fetched = False - - # Find server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - react_minimum = server[1] - emoji = server[2] - channel_id = server[3] - ignore_bots = True if int(server[4]) == 1 else False - - fetched = True - - # Stop if server has no config (fireboard isn't enabled) - if not fetched: - return - - # Stop if message is by Titanium - if payload.message_author_id == self.bot.user.id: - return - - # Stop if emoji doesn't match - if str(payload.emoji) != emoji: - return - - # --- Edit board message if it already exists --- - if payload.message_id in [message[1] for message in self.fire_messages]: - message = [ - message - for message in self.fire_messages - if message[1] == payload.message_id - ][0] - - # Only fetch updated reaction count if I have queued or reaction amount is undefined - if queued or message[3] is None: - # Fetch message and channel - try: - msg_channel = await self.bot.fetch_channel(payload.channel_id) - message = await msg_channel.fetch_message(payload.message_id) - except discord.errors.NotFound: - return - - # Stop if not enough reactions - for reaction in message.reactions: - if str(reaction.emoji) == emoji: - react_count = reaction.count - break - - if react_count < react_minimum: - return - else: - react_count = None - - async with self.fireboard_pool.acquire() as sql: - # Set updated react count - if react_count is not None: - await sql.execute( - "UPDATE fireMessages SET reactionAmount = ? WHERE msgID = ?", - ( - react_count, - payload.message_id, - ), - ) - await self.refresh_fire_lists() - else: - await sql.execute( - "UPDATE fireMessages SET reactionAmount = reactionAmount + 1 WHERE msgID = ?", - (payload.message_id,), - ) - await self.refresh_fire_lists() - - # Get message from message list - message = [ - message - for message in self.fire_messages - if message[1] == payload.message_id - ][0] - - # Get board message - board_channel = await self.bot.fetch_channel(channel_id) - board_message = await board_channel.fetch_message(message[2]) - - await board_message.edit( - content=f"**{message[3]} {emoji}** | <@{payload.message_author_id}> | <#{payload.channel_id}>", - embeds=board_message.embeds, - ) - - return - - # Stop if message is in a blacklisted channel - if payload.channel_id in [ - channel[1] for channel in self.fire_channel_blacklist - ]: - return - - # Stop if message is by a blacklisted role - guild = await self.bot.fetch_guild(payload.guild_id) - member = await guild.fetch_member(payload.user_id) - - if any( - role[1] in [role.id for role in member.roles] - for role in self.fire_role_blacklist - ): - return - - # Fetch message and channel - try: - msg_channel = await self.bot.fetch_channel(payload.channel_id) - message = await msg_channel.fetch_message(payload.message_id) - except discord.errors.NotFound: - return - - # Stop if message is by a bot - if ignore_bots and message.author.bot: - return - - # Check if this is a thread - if isinstance(msg_channel, discord.Thread): - # Check if parent channel is NSFW - if msg_channel.parent.nsfw: - return - else: - # Stop if message is in an NSFW channel - if message.channel.nsfw: - return - - # Stop if not enough reactions - for reaction in message.reactions: - if str(reaction.emoji) == emoji: - react_count = reaction.count - break - - if react_count < react_minimum: - return - - # --- Send message to fireboard --- - - # Create embed - embed = discord.Embed(description=message.content, color=Color.random()) - embed.set_author( - name=message.author.name, icon_url=message.author.display_avatar.url - ) - embed.timestamp = message.created_at - - # Jump to message button - view = View() - view.add_item( - discord.ui.Button( - label="Jump to Message", - url=message.jump_url, - style=discord.ButtonStyle.url, - ) - ) - - embed_list = [embed] - - # Add reply embed - if message.reference: - try: - reply_message = await msg_channel.fetch_message( - message.reference.message_id - ) - - reply_embed = discord.Embed( - title="Replying To", - description=reply_message.content, - color=Color.random(), - ) - reply_embed.set_author( - name=reply_message.author.name, - icon_url=reply_message.author.display_avatar.url, - ) - reply_embed.timestamp = reply_message.created_at - - embed_list.insert(0, reply_embed) - except discord.errors.NotFound: - pass - - # Send message - board_channel = await self.bot.fetch_channel(channel_id) - board_message = await board_channel.send( - content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", - embeds=embed_list, - view=view, - files=[ - await attachment.to_file() for attachment in message.attachments - ], - ) - - async with self.fireboard_pool.acquire() as sql: - # Insert message to DB - await sql.execute( - "INSERT INTO fireMessages (serverID, msgID, boardMsgID, reactionAmount) VALUES (?, ?, ?, ?)", - ( - payload.guild_id, - payload.message_id, - board_message.id, - react_count, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for reaction removal - @commands.Cog.listener() - async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): - self.bot: discord.ext.commands.Bot - - queued = False - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - queued = True - - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - fetched = False - - # Find server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - react_minimum = server[1] - emoji = server[2] - channel_id = server[3] - ignore_bots = True if int(server[4]) == 1 else False - - fetched = True - - # Stop if server has no config (fireboard isn't enabled) - if not fetched: - return - - # Stop if message is by Titanium - if payload.message_author_id == self.bot.user.id: - return - - # Stop if emoji doesn't match - if str(payload.emoji) != emoji: - return - - # --- Edit board message if it already exists --- - if payload.message_id in [message[1] for message in self.fire_messages]: - prev_react_count = [ - message - for message in self.fire_messages - if message[1] == payload.message_id - ][0][3] - - # Only fetch updated reaction count if I have queued - if queued or prev_react_count is None: - # Fetch message and channel - try: - msg_channel = await self.bot.fetch_channel(payload.channel_id) - message = await msg_channel.fetch_message(payload.message_id) - except discord.errors.NotFound: - return - - # Stop if not enough reactions - for reaction in message.reactions: - if str(reaction.emoji) == emoji: - react_count = reaction.count - break - - if react_count < react_minimum: - return - else: - react_count = None - - async with self.fireboard_pool.acquire() as sql: - # Set updated react count - if react_count is not None: - await sql.execute( - "UPDATE fireMessages SET reactionAmount = ? WHERE msgID = ?", - ( - react_count, - payload.message_id, - ), - ) - await self.refresh_fire_lists() - else: - await sql.execute( - "UPDATE fireMessages SET reactionAmount = reactionAmount - 1 WHERE msgID = ?", - (payload.message_id,), - ) - await self.refresh_fire_lists() - - # Get message from message list - message = [ - message - for message in self.fire_messages - if message[1] == payload.message_id - ][0] - - # Get board message - board_channel = await self.bot.fetch_channel(channel_id) - board_message = await board_channel.fetch_message(message[2]) - - # Remove message if not enough reactions - if message[3] < react_minimum: - await board_message.delete() - - async with self.fireboard_pool.acquire() as sql: - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - - # Workaround for lack of message author ID - content = board_message.content - content = content.replace( - f"{prev_react_count} {emoji}", f"{message[3]} {emoji}" - ) - - await board_message.edit(content=content) - - return - - # Stop if message is in a blacklisted channel - if payload.channel_id in [ - channel[1] for channel in self.fire_channel_blacklist - ]: - return - - # Stop if message is by a blacklisted role - guild = await self.bot.fetch_guild(payload.guild_id) - member = await guild.fetch_member(payload.user_id) - - if any( - role[1] in [role.id for role in member.roles] - for role in self.fire_role_blacklist - ): - return - - # Fetch message and channel - try: - msg_channel = await self.bot.fetch_channel(payload.channel_id) - message = await msg_channel.fetch_message(payload.message_id) - except discord.errors.NotFound: - return - - # Stop if message is by a bot - if ignore_bots and message.author.bot: - return - - # Check if this is a thread - if isinstance(msg_channel, discord.Thread): - # Check if parent channel is NSFW - if msg_channel.parent.nsfw: - return - else: - # Stop if message is in an NSFW channel - if message.channel.nsfw: - return - - # Get reaction count - react_count = 0 - - for reaction in message.reactions: - if str(reaction.emoji) == emoji: - react_count = reaction.count - break - - # Stop if not enough reactions - if react_count < react_minimum: - return - - # --- Send message to fireboard --- - - # Create embed - embed = discord.Embed(description=message.content, color=Color.random()) - embed.set_author( - name=message.author.name, icon_url=message.author.display_avatar.url - ) - embed.timestamp = message.created_at - - # Jump to message button - view = View() - view.add_item( - discord.ui.Button( - label="Jump to Message", - url=message.jump_url, - style=discord.ButtonStyle.url, - ) - ) - - embed_list = [embed] - - # Add reply embed - if message.reference: - try: - reply_message = await msg_channel.fetch_message( - message.reference.message_id - ) - - reply_embed = discord.Embed( - title="Replying To", - description=reply_message.content, - color=Color.random(), - ) - reply_embed.set_author( - name=reply_message.author.name, - icon_url=reply_message.author.display_avatar.url, - ) - reply_embed.timestamp = reply_message.created_at - - embed_list.insert(0, reply_embed) - except discord.errors.NotFound: - pass - - # Send message - board_channel = await self.bot.fetch_channel(channel_id) - board_message = await board_channel.send( - content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", - embeds=embed_list, - view=view, - files=[ - await attachment.to_file() for attachment in message.attachments - ], - ) - - async with self.fireboard_pool.acquire() as sql: - # Insert message to DB - await sql.execute( - "INSERT INTO fireMessages (serverID, msgID, boardMsgID, reactionAmount) VALUES (?, ?, ?, ?)", - ( - payload.guild_id, - payload.message_id, - board_message.id, - react_count, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for message reaction clear - @commands.Cog.listener() - async def on_raw_reaction_clear(self, payload: discord.RawReactionClearEvent): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - # Only trigger if message is already in the fireboard DB - if payload.message_id in [message[1] for message in self.fire_messages]: - # Find server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - channel_id = server[3] - - # Get guild - try: - guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) - except discord.errors.NotFound: - return - - # Get message channel - channel: discord.abc.GuildChannel = await guild.fetch_channel( - payload.channel_id - ) - - # Get our message - message: discord.Message = await channel.fetch_message( - payload.message_id - ) - - # See if board message is already present - for fire_message in self.fire_messages: - if fire_message[1] == message.id: - async with self.fireboard_pool.acquire() as sql: - try: - # Delete message - try: - channel: discord.TextChannel = ( - await guild.fetch_channel(channel_id) - ) - except discord.errors.NotFound: - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (payload.guild_id,), - ) - await sql.commit() - self.locked_messages.remove(payload.message_id) - - return - - board_message = await channel.fetch_message( - fire_message[2] - ) - await board_message.delete() - - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (message.id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - except discord.errors.NotFound: - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (message.id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for specific emoji being cleared - @commands.Cog.listener() - async def on_raw_reaction_clear_emoji( - self, payload: discord.RawReactionClearEmojiEvent - ): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - # Only trigger if message is already in the fireboard DB - if payload.message_id in [message[1] for message in self.fire_messages]: - for server in self.fire_settings: - if server[0] == payload.guild_id: - emoji = server[2] - channel_id = server[3] - - # Only trigger if cleared emoji is our emoji - if str(payload.emoji) == emoji: - # Fetch server - try: - guild: discord.Guild = await self.bot.fetch_guild( - payload.guild_id - ) - except discord.errors.NotFound: - return - - # Get message channel - channel: discord.abc.GuildChannel = await guild.fetch_channel( - payload.channel_id - ) - - # See if board message is already present - for fire_message in self.fire_messages: - if fire_message[1] == payload.message_id: - async with self.fireboard_pool.acquire() as sql: - try: - # Fetch fireboard channel - try: - channel: discord.TextChannel = ( - await guild.fetch_channel(channel_id) - ) - except discord.errors.NotFound: - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (payload.guild_id,), - ) - await sql.commit() - - return - - # Delete message - board_message = await channel.fetch_message( - fire_message[2] - ) - await board_message.delete() - - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - except discord.errors.NotFound: - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for message being deleted - @commands.Cog.listener() - async def on_raw_message_delete(self, payload: discord.RawMessageDeleteEvent): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - # Only trigger if message is already in the fireboard DB - if payload.message_id in [message[1] for message in self.fire_messages]: - # Fetch server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - channel_id = server[3] - - # Fetch server - try: - guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) - except discord.errors.NotFound: - return - - # Get message channel - channel: discord.abc.GuildChannel = await guild.fetch_channel( - payload.channel_id - ) - - # See if board message is already present - for fire_message in self.fire_messages: - if fire_message[1] == payload.message_id: - async with self.fireboard_pool.acquire() as sql: - try: - # Fetch fireboard channel - try: - channel: discord.TextChannel = ( - await guild.fetch_channel(channel_id) - ) - except discord.errors.NotFound: - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (payload.guild_id,), - ) - await sql.commit() - - return - - # Delete message - board_message = await channel.fetch_message( - fire_message[2] - ) - await board_message.delete() - - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - except discord.errors.NotFound: - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for message being edited - @commands.Cog.listener() - async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - # Only trigger if message is already in the fireboard DB - if payload.message_id in [message[1] for message in self.fire_messages]: - # Fetch server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - channel_id = server[3] - - # Fetch server - try: - guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) - except discord.errors.NotFound: - return - - # Get message channel - channel: discord.abc.GuildChannel = await guild.fetch_channel( - payload.channel_id - ) - - # Get our message - message: discord.Message = await channel.fetch_message( - payload.message_id - ) - - embed = discord.Embed(description=message.content, color=Color.random()) - embed.set_author( - name=message.author.name, icon_url=message.author.display_avatar.url - ) - embed.timestamp = message.created_at - - # Jump to message button - view = View() - view.add_item( - discord.ui.Button( - label="Jump to Message", - url=message.jump_url, - style=discord.ButtonStyle.url, - ) - ) - - embed_list = [embed] - - # Add reply embed - if message.reference: - try: - reply_message = await channel.fetch_message( - message.reference.message_id - ) - - reply_embed = discord.Embed( - title="Replying To", - description=reply_message.content, - color=Color.random(), - ) - reply_embed.set_author( - name=reply_message.author.name, - icon_url=reply_message.author.display_avatar.url, - ) - reply_embed.timestamp = reply_message.created_at - - embed_list.insert(0, reply_embed) - except discord.errors.NotFound: - pass - - try: - channel: discord.TextChannel = await guild.fetch_channel(channel_id) - except discord.errors.NotFound: - async with self.fireboard_pool.acquire() as sql: - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (payload.guild_id,), - ) - await sql.commit() - - return - - # Find previous fireboard message - try: - for fire_message in self.fire_messages: - if ( - fire_message[0] == payload.guild_id - and fire_message[1] == payload.message_id - ): - # Edit with updated embed - reaction amount stays the same - board_message = await channel.fetch_message(fire_message[2]) - - await board_message.edit( - embeds=embed_list, attachments=message.attachments - ) - except discord.errors.NotFound: # Message not found - async with self.fireboard_pool.acquire() as sql: - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for fireboard channel delete - @commands.Cog.listener() - async def on_guild_channel_delete(self, channel: discord.abc.GuildChannel): - # Only trigger if server has fireboard enabled - if channel.guild.id in [guild[0] for guild in self.fire_settings]: - for server in self.fire_settings: - if server[0] == channel.guild.id: - if server[3] == channel.id: - async with self.fireboard_pool.acquire() as sql: - # Delete fireboard config - await sql.execute( - "DELETE FROM fireMessages WHERE serverID = ?", - (channel.guild.id,), - ) - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (channel.guild.id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - - # Listen for server being left / deleted - @commands.Cog.listener() - async def on_guild_remove(self, guild: discord.Guild): - # Only trigger if server has fireboard enabled - if guild.id in [guild[0] for guild in self.fire_settings]: - for server in self.fire_settings: - if server[0] == guild.id: - async with self.fireboard_pool.acquire() as sql: - # Delete fireboard config - await sql.execute( - "DELETE FROM fireMessages WHERE serverID = ?", (guild.id,) - ) - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", (guild.id,) - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - - # Command group setup - context = discord.app_commands.AppCommandContext( - guild=True, dm_channel=False, private_channel=False - ) - installs = discord.app_commands.AppInstallationType(guild=True, user=False) - fireGroup = app_commands.Group( - name="fireboard", - description="Fireboard related commands.", - allowed_contexts=context, - allowed_installs=installs, - ) - - # Random fireboard message command - @fireGroup.command( - name="random", description="Get a random message from the fireboard." - ) - @app_commands.describe( - ephemeral="Optional: whether to send the command output as a dismissible message only visible to you. Defaults to false." - ) - async def random_fireboard( - self, interaction: discord.Interaction, ephemeral: bool = False - ): - await interaction.response.defer(ephemeral=ephemeral) - - channel_id = None - - # Find server config - for server in self.fire_settings: - if server[0] == interaction.guild_id: - channel_id = server[3] - - if channel_id is None: - embed = discord.Embed( - title="Error", - description="Fireboard is not enabled in this server.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=ephemeral) - - return - - # Fetch channel - try: - channel = await interaction.guild.fetch_channel(channel_id) - except discord.errors.NotFound: - embed = discord.Embed( - title="Error", - description="Can't find the fireboard channel. Please contact a server admin.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=ephemeral) - - return - - # Fetch messages - async with self.fireboard_pool.acquire() as sql: - messages = await sql.fetchall( - "SELECT * FROM fireMessages WHERE serverID = ?", (interaction.guild_id,) - ) - - if not messages: - embed = discord.Embed( - title="Error", - description="No messages found in the fireboard.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=ephemeral) - - return - else: - while messages != []: - message = random.choice(messages) - - try: - board_message = await channel.fetch_message(message[2]) - - view = View().from_message(board_message) - files = [ - await attachment.to_file() - for attachment in board_message.attachments - ] - - await interaction.followup.send( - content=board_message.content, - embeds=board_message.embeds, - view=view, - ephemeral=ephemeral, - files=files, - allowed_mentions=discord.AllowedMentions.none(), - ) - - return - except discord.errors.NotFound: - messages.remove(message) - - embed = discord.Embed( - title="Error", - description="No messages found in the fireboard.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=ephemeral) - - # Command group setup - context = discord.app_commands.AppCommandContext( - guild=True, dm_channel=False, private_channel=False - ) - installs = discord.app_commands.AppInstallationType(guild=True, user=False) - perms = discord.Permissions(manage_guild=True) - fireSetupGroup = app_commands.Group( - name="fireboard-setup", - description="Control the fireboard.", - allowed_contexts=context, - allowed_installs=installs, - default_permissions=perms, - ) - - # Fireboard enable command - @fireSetupGroup.command( - name="enable", description="Enable the fireboard in the current channel." - ) - async def enable_fireboard(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Fireboard is already enabled.", color=Color.green() - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - # Default settings - react_minimum = 3 - emoji = "🔥" - channel_id = interaction.channel_id - ignore_bots = True - - embed = discord.Embed( - title="Fireboard", - description="This channel has been configured as the server fireboard.", - color=Color.random(), - ) - embed.set_footer(text="Feel free to delete this message!") - - try: - channel = await interaction.guild.fetch_channel(channel_id) - await channel.send(embed=embed) - except discord.errors.Forbidden or discord.errors.NotFound: - embed = discord.Embed( - title="Error", - description="Looks like I can't send messages in this channel. Check permissions and try again.", - color=Color.random(), - ) - await interaction.followup.send(embed=embed, ephemeral=True) - - return - - async with self.fireboard_pool.acquire() as sql: - # Insert to DB, refresh lists - await sql.execute( - "INSERT INTO fireSettings (serverID, reactionAmount, emoji, channelID, ignoreBots) VALUES (?, ?, ?, ?, ?)", - ( - interaction.guild_id, - react_minimum, - emoji, - channel_id, - ignore_bots, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Enabled", - description="Fireboard has been enabled in the current channel.", - color=Color.green(), - ) - embed.add_field( - name="Info", - value=f"**Reaction Requirement:** `{react_minimum} reactions`\n**Fireboard Channel:** <#{channel_id}>\n**Emoji:** {emoji}\n**Ignore Bots:** `{ignore_bots}`", - ) - - await interaction.followup.send(embed=embed, ephemeral=True) - - class ConfirmDisableView(View): - def __init__(self): - super().__init__(timeout=60) - - async def disable_fireboard( - self, interaction: discord.Interaction, pool: asqlite.Pool - ): - async with pool.acquire() as sql: - try: - await sql.execute( - "DELETE FROM fireMessages WHERE serverID = ?", - (interaction.guild_id,), - ) - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (interaction.guild_id,), - ) - await sql.execute( - "DELETE FROM fireChannelBlacklist WHERE serverID = ?", - (interaction.guild_id,), - ) - await sql.execute( - "DELETE FROM fireRoleBlacklist WHERE serverID = ?", - (interaction.guild_id,), - ) - await sql.commit() - return True - except Exception: - return False - - @discord.ui.button(label="Disable", style=discord.ButtonStyle.red) - async def confirm( - self, interaction: discord.Interaction, button: discord.ui.Button - ): - await interaction.response.defer(ephemeral=True) - - success = await self.disable_fireboard(interaction, self.pool) - - if success: - embed = discord.Embed( - title="Done!", - description="Fireboard was disabled.", - color=Color.green(), - ) - await self.cog.refresh_fire_lists() # pylint: disable=no-member - else: - embed = discord.Embed( - title="Error", - description="Failed to disable fireboard.", - color=Color.red(), - ) - - await interaction.edit_original_response(embed=embed, view=None) - self.stop() - - async def on_timeout(self): - for item in self.children: - item.disabled = True - - embed = discord.Embed( - title="Timeout", - description="You didn't press the button in time.", - color=Color.red(), - ) - await self.message.edit(embed=embed, view=self) - - # Fireboard disable command - @fireSetupGroup.command(name="disable", description="Disable the server fireboard.") - async def disable_fireboard(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - - if interaction.guild_id in [guild[0] for guild in self.fire_settings]: - view = self.ConfirmDisableView() - view.pool = self.fireboard_pool - view.cog = self - - embed = discord.Embed( - title="Are you sure?", - description="All data about this server's fireboard will be deleted. This cannot be undone!", - color=Color.orange(), - ) - - message = await interaction.followup.send( - embed=embed, view=view, ephemeral=True, wait=True - ) - view.message = message - else: - await interaction.followup.send( - "Fireboard is not enabled in this server!", ephemeral=True - ) - - # Fireboard server info command - @fireSetupGroup.command( - name="info", description="View fireboard config for this server." - ) - async def fireboard_info(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - # Fetch server settings - for server in self.fire_settings: - if server[0] == interaction.guild_id: - react_minimum = server[1] - emoji = server[2] - channel_id = server[3] - ignore_bots = True if int(server[4]) == 1 else False - - embed = discord.Embed( - title="Server Fireboard Settings", - description=f"**Reaction Requirement:** `{react_minimum} reactions`\n**Fireboard Channel:** <#{channel_id}>\n**Emoji:** {emoji}\n**Ignore Bots:** `{ignore_bots}`", - color=Color.random(), - ) - - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard set emoji command - @fireSetupGroup.command(name="emoji", description="Set a custom fireboard emoji.") - async def fireboard_emoji(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=False) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Waiting for Reaction", - description="🔄 React with this message with your target emoji to set the fireboard emoji.", - color=Color.orange(), - ) - - msg = await interaction.followup.send(embed=embed, ephemeral=False) - - def check(reaction, user): - return user == interaction.user and reaction.message.id == msg.id - - # Wait for a reaction - try: - reaction, user = await self.bot.wait_for( - "reaction_add", timeout=60.0, check=check - ) - - reaction: discord.Reaction = reaction - - async with self.fireboard_pool.acquire() as sql: - # Change emoji in DB, refresh lists - await sql.execute( - "UPDATE fireSettings SET emoji = ? WHERE serverID = ?", - ( - str(reaction.emoji), - interaction.guild_id, - ), - ) - await sql.commit() - - embed = discord.Embed( - title="Emoji Set", - description=f"Set emoji to **{str(reaction.emoji)}.**", - color=Color.green(), - ) - - await self.refresh_fire_lists() - await interaction.edit_original_response(embed=embed) - except asyncio.TimeoutError: # Timed out - embed = discord.Embed( - title="Timed Out", - description="You didn't react in time.", - color=Color.red(), - ) - - await interaction.edit_original_response(embed=embed) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=False) - - # Fireboard set channel command - @fireSetupGroup.command( - name="channel", - description="Set the channel for fireboard messages to be sent in.", - ) - async def fireboard_channel( - self, interaction: discord.Interaction, channel: discord.TextChannel - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Fireboard", - description="This channel has been configured as the server fireboard.", - color=Color.random(), - ) - embed.set_footer(text="Feel free to delete this message!") - - try: - await channel.send(embed=embed) - except discord.errors.NotFound: - embed = discord.Embed( - title="Error", - description="Looks like I can't find that channel. Check permissions and try again.", - color=Color.random(), - ) - await interaction.followup.send(embed=embed, ephemeral=True) - - return - except discord.errors.Forbidden as e: - embed = discord.Embed( - title="Error", - description="Looks like I can't send messages in that channel. Check permissions and try again.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=True) - - return - - async with self.fireboard_pool.acquire() as sql: - # Update channel in DB, refresh lists - await sql.execute( - "UPDATE fireSettings SET channelID = ? WHERE serverID = ?", - ( - channel.id, - interaction.guild_id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Channel Set", - description=f"Fireboard channel has been set to **{channel.mention}.**", - color=Color.green(), - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard set requirement command - @fireSetupGroup.command( - name="requirement", - description="Set required reaction amount for message to be posted on the fireboard.", - ) - async def fireboard_requirement( - self, interaction: discord.Interaction, amount: int - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Set", - description=f"Reaction requirement has been set to **{amount} reactions.**", - color=Color.green(), - ) - - async with self.fireboard_pool.acquire() as sql: - # Update reaction requirement in DB, refresh lists - await sql.execute( - "UPDATE fireSettings SET reactionAmount = ? WHERE serverID = ?", - ( - amount, - interaction.guild_id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard ignore bots command - @fireSetupGroup.command( - name="ignore-bots", - description="Whether bot messages are ignored in the fireboard. Defaults to true.", - ) - async def fireboard_ignore_bots( - self, interaction: discord.Interaction, value: bool - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Set", - description=f"Bot messages will **{'be ignored.' if value else 'not be ignored.'}**", - color=Color.green(), - ) - - async with self.fireboard_pool.acquire() as sql: - # Update setting in DB, refresh lists - await sql.execute( - "UPDATE fireSettings SET ignoreBots = ? WHERE serverID = ?", - ( - value, - interaction.guild_id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard role blacklist - @fireSetupGroup.command( - name="channel-blacklist", - description="Toggle the blacklist for a channel. NSFW channels are always blacklisted.", - ) - async def fireboard_channel_blacklist( - self, interaction: discord.Interaction, channel: discord.abc.GuildChannel - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild_id in [guild[0] for guild in self.fire_settings]: - async with self.fireboard_pool.acquire() as sql: - if channel.id in [ - channelEntry[1] for channelEntry in self.fire_channel_blacklist - ]: - await sql.execute( - "DELETE FROM fireChannelBlacklist WHERE serverID = ? AND channelID = ?", - ( - interaction.guild_id, - channel.id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Set", - description=f"Removed {channel.mention} from the channel blacklist.", - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - await sql.execute( - "INSERT INTO fireChannelBlacklist (serverID, channelID) VALUES (?, ?)", - ( - interaction.guild_id, - channel.id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Set", - description=f"Added {channel.mention} to the channel blacklist.", - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard role blacklist - @fireSetupGroup.command( - name="role-blacklist", description="Toggle the blacklist for a role." - ) - async def fireboard_role_blacklist( - self, interaction: discord.Interaction, role: discord.Role - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild_id in [guild[0] for guild in self.fire_settings]: - async with self.fireboard_pool.acquire() as sql: - if role.id in [roleEntry[1] for roleEntry in self.fire_role_blacklist]: - await sql.execute( - "DELETE FROM fireRoleBlacklist WHERE serverID = ? AND roleID = ?", - ( - interaction.guild_id, - role.id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Set", - description=f"Removed {role.mention} from the role blacklist.", - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - await sql.execute( - "INSERT INTO fireRoleBlacklist (serverID, roleID) VALUES (?, ?)", - ( - interaction.guild_id, - role.id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Set", - description=f"Added {role.mention} to the role blacklist.", - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard role blacklist - @fireSetupGroup.command( - name="blacklists", description="View this server's role and channel blacklists." - ) - async def fireboard_blacklists(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild_id in [guild[0] for guild in self.fire_settings]: - - class BlacklistViewer(View): - def __init__(self): - super().__init__(timeout=240) - - self.fire_channel_blacklist: list - self.fire_role_blacklist: list - self.interaction: discord.Interaction - - async def on_timeout(self) -> None: - for item in self.children: - item.disabled = True - - await self.interaction.edit_original_response(view=self) - - @discord.ui.button( - label="Role Blacklist", - style=discord.ButtonStyle.gray, - row=0, - custom_id="role", - disabled=True, - ) - async def role( - self, interaction: discord.Interaction, button: discord.ui.Button - ): - await interaction.response.defer(ephemeral=True) - - for item in self.children: - if item.custom_id == "channel": - item.disabled = False - else: - item.disabled = True - - my_roles = [] - - for role in self.fire_role_blacklist: - if role[0] == interaction.guild_id: - my_roles.append(f"<@&{role[1]}>") - - if my_roles != []: - embed = discord.Embed( - title="Role Blacklist", - description="\n".join(my_roles), - color=Color.random(), - ) - await interaction.edit_original_response(embed=embed, view=self) - else: - embed = discord.Embed( - title="Role Blacklist", - description="No roles have been blacklisted.", - color=Color.random(), - ) - await interaction.edit_original_response(embed=embed, view=self) - - @discord.ui.button( - label="Channel Blacklist", - style=discord.ButtonStyle.gray, - row=0, - custom_id="channel", - ) - async def channel( - self, interaction: discord.Interaction, button: discord.ui.Button - ): - await interaction.response.defer(ephemeral=True) - - for item in self.children: - if item.custom_id == "role": - item.disabled = False - else: - item.disabled = True - - my_channels = [] - - for channel in self.fire_channel_blacklist: - if channel[0] == interaction.guild_id: - my_channels.append(f"<#{channel[1]}>") - - if my_channels != []: - embed = discord.Embed( - title="Channel Blacklist", - description="\n".join(my_channels), - color=Color.random(), - ) - await interaction.edit_original_response(embed=embed, view=self) - else: - embed = discord.Embed( - title="Channel Blacklist", - description="No channels have been blacklisted.", - color=Color.random(), - ) - await interaction.edit_original_response(embed=embed, view=self) - - view_instance = BlacklistViewer() - view_instance.fire_channel_blacklist = self.fire_channel_blacklist - view_instance.fire_role_blacklist = self.fire_role_blacklist - view_instance.interaction = interaction - - my_roles = [] - - for role in self.fire_role_blacklist: - if role[0] == interaction.guild_id: - my_roles.append(f"<@&{role[1]}>") - - if my_roles != []: - embed = discord.Embed( - title="Role Blacklist", - description="\n".join(my_roles), - color=Color.random(), - ) - await interaction.followup.send( - embed=embed, view=view_instance, ephemeral=True - ) - else: - embed = discord.Embed( - title="Role Blacklist", - description="No roles have been blacklisted.", - color=Color.random(), - ) - await interaction.followup.send( - embed=embed, view=view_instance, ephemeral=True - ) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - -async def setup(bot):import asyncio -import random -import json -import os - -import discord -from discord import Color, app_commands -from discord.ext import commands -from discord.ui import View - -FIREBOARD_PATH = "assets/cogs/files/fireboard.json" - -def load_data(): - if not os.path.exists(FIREBOARD_PATH): - return {} - with open(FIREBOARD_PATH, "r") as f: - return json.load(f) - -def save_data(data): - os.makedirs(os.path.dirname(FIREBOARD_PATH), exist_ok=True) - with open(FIREBOARD_PATH, "w") as f: - json.dump(data, f, indent=2) - -class Fireboard(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.data = load_data() - self.locked_messages = [] - - def save(self): - save_data(self.data) - - def get_guild(self, guild_id): - return self.data.setdefault(str(guild_id), { - "settings": None, - "messages": [], - "role_blacklist": [], - "channel_blacklist": [] - }) - - @commands.Cog.listener() - async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): - if payload.guild_id is None: - return - guild_data = self.get_guild(payload.guild_id) - settings = guild_data["settings"] - if not settings: - return - react_minimum = settings["reactionAmount"] - emoji = settings["emoji"] - channel_id = settings["channelID"] - ignore_bots = settings["ignoreBots"] - - if str(payload.emoji) != emoji: - return - if payload.channel_id in guild_data["channel_blacklist"]: - return - if payload.message_id in self.locked_messages: - return - self.locked_messages.append(payload.message_id) - try: - channel = await self.bot.fetch_channel(payload.channel_id) - message = await channel.fetch_message(payload.message_id) - if ignore_bots and message.author.bot: - return - if hasattr(message.channel, "nsfw") and message.channel.nsfw: - return - react_count = 0 - for reaction in message.reactions: - if str(reaction.emoji) == emoji: - react_count = reaction.count - break - if react_count < react_minimum: - return - # Already on fireboard? - for m in guild_data["messages"]: - if m["msgID"] == payload.message_id: - return - # Send to fireboard - board_channel = await self.bot.fetch_channel(channel_id) - embed = discord.Embed(description=message.content, color=Color.random()) - embed.set_author(name=message.author.name, icon_url=message.author.display_avatar.url) - embed.timestamp = message.created_at - view = View() - view.add_item(discord.ui.Button(label="Jump to Message", url=message.jump_url, style=discord.ButtonStyle.url)) - files = [await a.to_file() for a in message.attachments] - board_message = await board_channel.send( - content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", - embed=embed, view=view, files=files - ) - guild_data["messages"].append({ - "msgID": payload.message_id, - "boardMsgID": board_message.id, - "reactionAmount": react_count - }) - self.save() - finally: - if payload.message_id in self.locked_messages: - self.locked_messages.remove(payload.message_id) - - @commands.Cog.listener() - async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): - if payload.guild_id is None: - return - guild_data = self.get_guild(payload.guild_id) - settings = guild_data["settings"] - if not settings: - return - emoji = settings["emoji"] - channel_id = settings["channelID"] - if str(payload.emoji) != emoji: - return - for m in guild_data["messages"]: - if m["msgID"] == payload.message_id: - channel = await self.bot.fetch_channel(channel_id) - try: - board_message = await channel.fetch_message(m["boardMsgID"]) - await board_message.delete() - except Exception: - pass - guild_data["messages"].remove(m) - self.save() - break - - @app_commands.command(name="fireboard_enable", description="Enable the fireboard in this channel.") - async def fireboard_enable(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - guild_data = self.get_guild(interaction.guild_id) - if guild_data["settings"]: - await interaction.followup.send("Fireboard is already enabled.", ephemeral=True) - return - guild_data["settings"] = { - "reactionAmount": 3, - "emoji": "🔥", - "channelID": interaction.channel_id, - "ignoreBots": True - } - self.save() - await interaction.followup.send("Fireboard enabled in this channel.", ephemeral=True) - - @app_commands.command(name="fireboard_disable", description="Disable the fireboard.") - async def fireboard_disable(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - guild_data = self.get_guild(interaction.guild_id) - guild_data["settings"] = None - guild_data["messages"] = [] - self.save() - await interaction.followup.send("Fireboard disabled.", ephemeral=True) - - @app_commands.command(name="fireboard_info", description="Show fireboard settings.") - async def fireboard_info(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - guild_data = self.get_guild(interaction.guild_id) - settings = guild_data["settings"] - if not settings: - await interaction.followup.send("Fireboard is not enabled.", ephemeral=True) - return - embed = discord.Embed( - title="Fireboard Settings", - description=f"**Reaction Requirement:** `{settings['reactionAmount']}`\n" - f"**Fireboard Channel:** <#{settings['channelID']}>\n" - f"**Emoji:** {settings['emoji']}\n" - f"**Ignore Bots:** `{settings['ignoreBots']}`", - color=Color.random() - ) - await interaction.followup.send(embed=embed, ephemeral=True) - -async def setup(bot): - await bot.add_cog(Fireboard(bot)) \ No newline at end of file From 89510b594a2edd750daa5c370fb2b770d319f593 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:47:56 +0200 Subject: [PATCH 17/84] undo fuckup --- bot.py | 2 -- requirements.txt | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/bot.py b/bot.py index 571c6ba..9755f8e 100644 --- a/bot.py +++ b/bot.py @@ -20,8 +20,6 @@ print(splashtext) # Print splash text (from modules/globalvars.py) start_checks() import requests -import asqlite - import discord from discord.ext import commands from discord import app_commands diff --git a/requirements.txt b/requirements.txt index 0dca484..c53d758 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,4 @@ psutil better_profanity python-dotenv dotenv -pillow -asqlite \ No newline at end of file +pillow \ No newline at end of file From 8f8d6b941b37483b5e7ae21082121ab3e8d58bab Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:48:47 +0200 Subject: [PATCH 18/84] no fireboard --- assets/cogs/fireboard.py | 1923 -------------------------------------- 1 file changed, 1923 deletions(-) delete mode 100644 assets/cogs/fireboard.py diff --git a/assets/cogs/fireboard.py b/assets/cogs/fireboard.py deleted file mode 100644 index 8d199b0..0000000 --- a/assets/cogs/fireboard.py +++ /dev/null @@ -1,1923 +0,0 @@ -import asyncio -import random - -import asqlite -import discord -import discord.ext -import discord.ext.commands -from discord import Color, app_commands -from discord.ext import commands -from discord.ui import View - - -class Fireboard(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.locked_messages = [] - self.disabled = False - - # Check if the bot has the fireboard_pool attribute - if not hasattr(bot, 'fireboard_pool') or bot.fireboard_pool is None: - print("Warning: Bot does not have fireboard_pool initialized. Fireboard functionality will be disabled.") - self.disabled = True - return - - self.fireboard_pool: asqlite.Pool = bot.fireboard_pool - self.bot.loop.create_task(self.setup()) - - # SQL Setup - async def setup(self): - async with self.fireboard_pool.acquire() as sql: - if ( - await sql.fetchone( - "SELECT name FROM sqlite_master WHERE type='table' AND name='fireMessages';" - ) - is None - ): - # Fire Messages - messages that are active on the fireboard - await sql.execute( - "CREATE TABLE fireMessages (serverID int, msgID int, boardMsgID int, reactionAmount int)" - ) - - if ( - await sql.fetchone( - "SELECT name FROM sqlite_master WHERE type='table' AND name='fireSettings';" - ) - is None - ): - # Fire Settings - server properties for fireboard - await sql.execute( - "CREATE TABLE fireSettings (serverID int, reactionAmount int, emoji text, channelID int, ignoreBots int)" - ) - - if ( - await sql.fetchone( - "SELECT name FROM sqlite_master WHERE type='table' AND name='fireChannelBlacklist';" - ) - is None - ): - # Fire Channel Blacklist - blacklisted channels - await sql.execute( - "CREATE TABLE fireChannelBlacklist (serverID int, channelID int)" - ) - - if ( - await sql.fetchone( - "SELECT name FROM sqlite_master WHERE type='table' AND name='fireRoleBlacklist';" - ) - is None - ): - # Fire Role Blacklist - blacklisted roles - await sql.execute( - "CREATE TABLE fireRoleBlacklist (serverID int, roleID int)" - ) - - await sql.commit() - - await self.refresh_fire_lists() - - # List refresh function - async def refresh_fire_lists(self): - async with self.fireboard_pool.acquire() as sql: - self.fire_messages = await sql.fetchall("SELECT * FROM fireMessages") - self.fire_settings = await sql.fetchall("SELECT * FROM fireSettings") - self.fire_channel_blacklist = await sql.fetchall( - "SELECT * FROM fireChannelBlacklist" - ) - self.fire_role_blacklist = await sql.fetchall( - "SELECT * FROM fireRoleBlacklist" - ) - - # Listen for reactions - @commands.Cog.listener() - async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - queued = False - - # Lock system - if payload.message_id in self.locked_messages: - queued = True - - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - fetched = False - - # Find server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - react_minimum = server[1] - emoji = server[2] - channel_id = server[3] - ignore_bots = True if int(server[4]) == 1 else False - - fetched = True - - # Stop if server has no config (fireboard isn't enabled) - if not fetched: - return - - # Stop if message is by Titanium - if payload.message_author_id == self.bot.user.id: - return - - # Stop if emoji doesn't match - if str(payload.emoji) != emoji: - return - - # --- Edit board message if it already exists --- - if payload.message_id in [message[1] for message in self.fire_messages]: - message = [ - message - for message in self.fire_messages - if message[1] == payload.message_id - ][0] - - # Only fetch updated reaction count if I have queued or reaction amount is undefined - if queued or message[3] is None: - # Fetch message and channel - try: - msg_channel = await self.bot.fetch_channel(payload.channel_id) - message = await msg_channel.fetch_message(payload.message_id) - except discord.errors.NotFound: - return - - # Stop if not enough reactions - for reaction in message.reactions: - if str(reaction.emoji) == emoji: - react_count = reaction.count - break - - if react_count < react_minimum: - return - else: - react_count = None - - async with self.fireboard_pool.acquire() as sql: - # Set updated react count - if react_count is not None: - await sql.execute( - "UPDATE fireMessages SET reactionAmount = ? WHERE msgID = ?", - ( - react_count, - payload.message_id, - ), - ) - await self.refresh_fire_lists() - else: - await sql.execute( - "UPDATE fireMessages SET reactionAmount = reactionAmount + 1 WHERE msgID = ?", - (payload.message_id,), - ) - await self.refresh_fire_lists() - - # Get message from message list - message = [ - message - for message in self.fire_messages - if message[1] == payload.message_id - ][0] - - # Get board message - board_channel = await self.bot.fetch_channel(channel_id) - board_message = await board_channel.fetch_message(message[2]) - - await board_message.edit( - content=f"**{message[3]} {emoji}** | <@{payload.message_author_id}> | <#{payload.channel_id}>", - embeds=board_message.embeds, - ) - - return - - # Stop if message is in a blacklisted channel - if payload.channel_id in [ - channel[1] for channel in self.fire_channel_blacklist - ]: - return - - # Stop if message is by a blacklisted role - guild = await self.bot.fetch_guild(payload.guild_id) - member = await guild.fetch_member(payload.user_id) - - if any( - role[1] in [role.id for role in member.roles] - for role in self.fire_role_blacklist - ): - return - - # Fetch message and channel - try: - msg_channel = await self.bot.fetch_channel(payload.channel_id) - message = await msg_channel.fetch_message(payload.message_id) - except discord.errors.NotFound: - return - - # Stop if message is by a bot - if ignore_bots and message.author.bot: - return - - # Check if this is a thread - if isinstance(msg_channel, discord.Thread): - # Check if parent channel is NSFW - if msg_channel.parent.nsfw: - return - else: - # Stop if message is in an NSFW channel - if message.channel.nsfw: - return - - # Stop if not enough reactions - for reaction in message.reactions: - if str(reaction.emoji) == emoji: - react_count = reaction.count - break - - if react_count < react_minimum: - return - - # --- Send message to fireboard --- - - # Create embed - embed = discord.Embed(description=message.content, color=Color.random()) - embed.set_author( - name=message.author.name, icon_url=message.author.display_avatar.url - ) - embed.timestamp = message.created_at - - # Jump to message button - view = View() - view.add_item( - discord.ui.Button( - label="Jump to Message", - url=message.jump_url, - style=discord.ButtonStyle.url, - ) - ) - - embed_list = [embed] - - # Add reply embed - if message.reference: - try: - reply_message = await msg_channel.fetch_message( - message.reference.message_id - ) - - reply_embed = discord.Embed( - title="Replying To", - description=reply_message.content, - color=Color.random(), - ) - reply_embed.set_author( - name=reply_message.author.name, - icon_url=reply_message.author.display_avatar.url, - ) - reply_embed.timestamp = reply_message.created_at - - embed_list.insert(0, reply_embed) - except discord.errors.NotFound: - pass - - # Send message - board_channel = await self.bot.fetch_channel(channel_id) - board_message = await board_channel.send( - content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", - embeds=embed_list, - view=view, - files=[ - await attachment.to_file() for attachment in message.attachments - ], - ) - - async with self.fireboard_pool.acquire() as sql: - # Insert message to DB - await sql.execute( - "INSERT INTO fireMessages (serverID, msgID, boardMsgID, reactionAmount) VALUES (?, ?, ?, ?)", - ( - payload.guild_id, - payload.message_id, - board_message.id, - react_count, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for reaction removal - @commands.Cog.listener() - async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): - self.bot: discord.ext.commands.Bot - - queued = False - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - queued = True - - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - fetched = False - - # Find server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - react_minimum = server[1] - emoji = server[2] - channel_id = server[3] - ignore_bots = True if int(server[4]) == 1 else False - - fetched = True - - # Stop if server has no config (fireboard isn't enabled) - if not fetched: - return - - # Stop if message is by Titanium - if payload.message_author_id == self.bot.user.id: - return - - # Stop if emoji doesn't match - if str(payload.emoji) != emoji: - return - - # --- Edit board message if it already exists --- - if payload.message_id in [message[1] for message in self.fire_messages]: - prev_react_count = [ - message - for message in self.fire_messages - if message[1] == payload.message_id - ][0][3] - - # Only fetch updated reaction count if I have queued - if queued or prev_react_count is None: - # Fetch message and channel - try: - msg_channel = await self.bot.fetch_channel(payload.channel_id) - message = await msg_channel.fetch_message(payload.message_id) - except discord.errors.NotFound: - return - - # Stop if not enough reactions - for reaction in message.reactions: - if str(reaction.emoji) == emoji: - react_count = reaction.count - break - - if react_count < react_minimum: - return - else: - react_count = None - - async with self.fireboard_pool.acquire() as sql: - # Set updated react count - if react_count is not None: - await sql.execute( - "UPDATE fireMessages SET reactionAmount = ? WHERE msgID = ?", - ( - react_count, - payload.message_id, - ), - ) - await self.refresh_fire_lists() - else: - await sql.execute( - "UPDATE fireMessages SET reactionAmount = reactionAmount - 1 WHERE msgID = ?", - (payload.message_id,), - ) - await self.refresh_fire_lists() - - # Get message from message list - message = [ - message - for message in self.fire_messages - if message[1] == payload.message_id - ][0] - - # Get board message - board_channel = await self.bot.fetch_channel(channel_id) - board_message = await board_channel.fetch_message(message[2]) - - # Remove message if not enough reactions - if message[3] < react_minimum: - await board_message.delete() - - async with self.fireboard_pool.acquire() as sql: - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - - # Workaround for lack of message author ID - content = board_message.content - content = content.replace( - f"{prev_react_count} {emoji}", f"{message[3]} {emoji}" - ) - - await board_message.edit(content=content) - - return - - # Stop if message is in a blacklisted channel - if payload.channel_id in [ - channel[1] for channel in self.fire_channel_blacklist - ]: - return - - # Stop if message is by a blacklisted role - guild = await self.bot.fetch_guild(payload.guild_id) - member = await guild.fetch_member(payload.user_id) - - if any( - role[1] in [role.id for role in member.roles] - for role in self.fire_role_blacklist - ): - return - - # Fetch message and channel - try: - msg_channel = await self.bot.fetch_channel(payload.channel_id) - message = await msg_channel.fetch_message(payload.message_id) - except discord.errors.NotFound: - return - - # Stop if message is by a bot - if ignore_bots and message.author.bot: - return - - # Check if this is a thread - if isinstance(msg_channel, discord.Thread): - # Check if parent channel is NSFW - if msg_channel.parent.nsfw: - return - else: - # Stop if message is in an NSFW channel - if message.channel.nsfw: - return - - # Get reaction count - react_count = 0 - - for reaction in message.reactions: - if str(reaction.emoji) == emoji: - react_count = reaction.count - break - - # Stop if not enough reactions - if react_count < react_minimum: - return - - # --- Send message to fireboard --- - - # Create embed - embed = discord.Embed(description=message.content, color=Color.random()) - embed.set_author( - name=message.author.name, icon_url=message.author.display_avatar.url - ) - embed.timestamp = message.created_at - - # Jump to message button - view = View() - view.add_item( - discord.ui.Button( - label="Jump to Message", - url=message.jump_url, - style=discord.ButtonStyle.url, - ) - ) - - embed_list = [embed] - - # Add reply embed - if message.reference: - try: - reply_message = await msg_channel.fetch_message( - message.reference.message_id - ) - - reply_embed = discord.Embed( - title="Replying To", - description=reply_message.content, - color=Color.random(), - ) - reply_embed.set_author( - name=reply_message.author.name, - icon_url=reply_message.author.display_avatar.url, - ) - reply_embed.timestamp = reply_message.created_at - - embed_list.insert(0, reply_embed) - except discord.errors.NotFound: - pass - - # Send message - board_channel = await self.bot.fetch_channel(channel_id) - board_message = await board_channel.send( - content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", - embeds=embed_list, - view=view, - files=[ - await attachment.to_file() for attachment in message.attachments - ], - ) - - async with self.fireboard_pool.acquire() as sql: - # Insert message to DB - await sql.execute( - "INSERT INTO fireMessages (serverID, msgID, boardMsgID, reactionAmount) VALUES (?, ?, ?, ?)", - ( - payload.guild_id, - payload.message_id, - board_message.id, - react_count, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for message reaction clear - @commands.Cog.listener() - async def on_raw_reaction_clear(self, payload: discord.RawReactionClearEvent): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - # Only trigger if message is already in the fireboard DB - if payload.message_id in [message[1] for message in self.fire_messages]: - # Find server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - channel_id = server[3] - - # Get guild - try: - guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) - except discord.errors.NotFound: - return - - # Get message channel - channel: discord.abc.GuildChannel = await guild.fetch_channel( - payload.channel_id - ) - - # Get our message - message: discord.Message = await channel.fetch_message( - payload.message_id - ) - - # See if board message is already present - for fire_message in self.fire_messages: - if fire_message[1] == message.id: - async with self.fireboard_pool.acquire() as sql: - try: - # Delete message - try: - channel: discord.TextChannel = ( - await guild.fetch_channel(channel_id) - ) - except discord.errors.NotFound: - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (payload.guild_id,), - ) - await sql.commit() - self.locked_messages.remove(payload.message_id) - - return - - board_message = await channel.fetch_message( - fire_message[2] - ) - await board_message.delete() - - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (message.id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - except discord.errors.NotFound: - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (message.id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for specific emoji being cleared - @commands.Cog.listener() - async def on_raw_reaction_clear_emoji( - self, payload: discord.RawReactionClearEmojiEvent - ): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - # Only trigger if message is already in the fireboard DB - if payload.message_id in [message[1] for message in self.fire_messages]: - for server in self.fire_settings: - if server[0] == payload.guild_id: - emoji = server[2] - channel_id = server[3] - - # Only trigger if cleared emoji is our emoji - if str(payload.emoji) == emoji: - # Fetch server - try: - guild: discord.Guild = await self.bot.fetch_guild( - payload.guild_id - ) - except discord.errors.NotFound: - return - - # Get message channel - channel: discord.abc.GuildChannel = await guild.fetch_channel( - payload.channel_id - ) - - # See if board message is already present - for fire_message in self.fire_messages: - if fire_message[1] == payload.message_id: - async with self.fireboard_pool.acquire() as sql: - try: - # Fetch fireboard channel - try: - channel: discord.TextChannel = ( - await guild.fetch_channel(channel_id) - ) - except discord.errors.NotFound: - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (payload.guild_id,), - ) - await sql.commit() - - return - - # Delete message - board_message = await channel.fetch_message( - fire_message[2] - ) - await board_message.delete() - - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - except discord.errors.NotFound: - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for message being deleted - @commands.Cog.listener() - async def on_raw_message_delete(self, payload: discord.RawMessageDeleteEvent): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - # Only trigger if message is already in the fireboard DB - if payload.message_id in [message[1] for message in self.fire_messages]: - # Fetch server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - channel_id = server[3] - - # Fetch server - try: - guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) - except discord.errors.NotFound: - return - - # Get message channel - channel: discord.abc.GuildChannel = await guild.fetch_channel( - payload.channel_id - ) - - # See if board message is already present - for fire_message in self.fire_messages: - if fire_message[1] == payload.message_id: - async with self.fireboard_pool.acquire() as sql: - try: - # Fetch fireboard channel - try: - channel: discord.TextChannel = ( - await guild.fetch_channel(channel_id) - ) - except discord.errors.NotFound: - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (payload.guild_id,), - ) - await sql.commit() - - return - - # Delete message - board_message = await channel.fetch_message( - fire_message[2] - ) - await board_message.delete() - - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - except discord.errors.NotFound: - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for message being edited - @commands.Cog.listener() - async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent): - self.bot: discord.ext.commands.Bot - - # Stop if this is a DM - if payload.guild_id is None: - return - - # Lock system - if payload.message_id in self.locked_messages: - while payload.message_id in self.locked_messages: - await asyncio.sleep(0.5) - - self.locked_messages.append(payload.message_id) - - try: - # Only trigger if message is already in the fireboard DB - if payload.message_id in [message[1] for message in self.fire_messages]: - # Fetch server config - for server in self.fire_settings: - if server[0] == payload.guild_id: - channel_id = server[3] - - # Fetch server - try: - guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) - except discord.errors.NotFound: - return - - # Get message channel - channel: discord.abc.GuildChannel = await guild.fetch_channel( - payload.channel_id - ) - - # Get our message - message: discord.Message = await channel.fetch_message( - payload.message_id - ) - - embed = discord.Embed(description=message.content, color=Color.random()) - embed.set_author( - name=message.author.name, icon_url=message.author.display_avatar.url - ) - embed.timestamp = message.created_at - - # Jump to message button - view = View() - view.add_item( - discord.ui.Button( - label="Jump to Message", - url=message.jump_url, - style=discord.ButtonStyle.url, - ) - ) - - embed_list = [embed] - - # Add reply embed - if message.reference: - try: - reply_message = await channel.fetch_message( - message.reference.message_id - ) - - reply_embed = discord.Embed( - title="Replying To", - description=reply_message.content, - color=Color.random(), - ) - reply_embed.set_author( - name=reply_message.author.name, - icon_url=reply_message.author.display_avatar.url, - ) - reply_embed.timestamp = reply_message.created_at - - embed_list.insert(0, reply_embed) - except discord.errors.NotFound: - pass - - try: - channel: discord.TextChannel = await guild.fetch_channel(channel_id) - except discord.errors.NotFound: - async with self.fireboard_pool.acquire() as sql: - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (payload.guild_id,), - ) - await sql.commit() - - return - - # Find previous fireboard message - try: - for fire_message in self.fire_messages: - if ( - fire_message[0] == payload.guild_id - and fire_message[1] == payload.message_id - ): - # Edit with updated embed - reaction amount stays the same - board_message = await channel.fetch_message(fire_message[2]) - - await board_message.edit( - embeds=embed_list, attachments=message.attachments - ) - except discord.errors.NotFound: # Message not found - async with self.fireboard_pool.acquire() as sql: - # Delete message from DB - await sql.execute( - "DELETE FROM fireMessages WHERE msgID = ?", - (payload.message_id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - except Exception as e: - raise e - finally: - self.locked_messages.remove(payload.message_id) - - # Listen for fireboard channel delete - @commands.Cog.listener() - async def on_guild_channel_delete(self, channel: discord.abc.GuildChannel): - # Only trigger if server has fireboard enabled - if channel.guild.id in [guild[0] for guild in self.fire_settings]: - for server in self.fire_settings: - if server[0] == channel.guild.id: - if server[3] == channel.id: - async with self.fireboard_pool.acquire() as sql: - # Delete fireboard config - await sql.execute( - "DELETE FROM fireMessages WHERE serverID = ?", - (channel.guild.id,), - ) - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (channel.guild.id,), - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - - # Listen for server being left / deleted - @commands.Cog.listener() - async def on_guild_remove(self, guild: discord.Guild): - # Only trigger if server has fireboard enabled - if guild.id in [guild[0] for guild in self.fire_settings]: - for server in self.fire_settings: - if server[0] == guild.id: - async with self.fireboard_pool.acquire() as sql: - # Delete fireboard config - await sql.execute( - "DELETE FROM fireMessages WHERE serverID = ?", (guild.id,) - ) - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", (guild.id,) - ) - await sql.commit() - - await self.refresh_fire_lists() - - return - else: - return - - # Command group setup - context = discord.app_commands.AppCommandContext( - guild=True, dm_channel=False, private_channel=False - ) - installs = discord.app_commands.AppInstallationType(guild=True, user=False) - fireGroup = app_commands.Group( - name="fireboard", - description="Fireboard related commands.", - allowed_contexts=context, - allowed_installs=installs, - ) - - # Random fireboard message command - @fireGroup.command( - name="random", description="Get a random message from the fireboard." - ) - @app_commands.describe( - ephemeral="Optional: whether to send the command output as a dismissible message only visible to you. Defaults to false." - ) - async def random_fireboard( - self, interaction: discord.Interaction, ephemeral: bool = False - ): - await interaction.response.defer(ephemeral=ephemeral) - - channel_id = None - - # Find server config - for server in self.fire_settings: - if server[0] == interaction.guild_id: - channel_id = server[3] - - if channel_id is None: - embed = discord.Embed( - title="Error", - description="Fireboard is not enabled in this server.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=ephemeral) - - return - - # Fetch channel - try: - channel = await interaction.guild.fetch_channel(channel_id) - except discord.errors.NotFound: - embed = discord.Embed( - title="Error", - description="Can't find the fireboard channel. Please contact a server admin.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=ephemeral) - - return - - # Fetch messages - async with self.fireboard_pool.acquire() as sql: - messages = await sql.fetchall( - "SELECT * FROM fireMessages WHERE serverID = ?", (interaction.guild_id,) - ) - - if not messages: - embed = discord.Embed( - title="Error", - description="No messages found in the fireboard.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=ephemeral) - - return - else: - while messages != []: - message = random.choice(messages) - - try: - board_message = await channel.fetch_message(message[2]) - - view = View().from_message(board_message) - files = [ - await attachment.to_file() - for attachment in board_message.attachments - ] - - await interaction.followup.send( - content=board_message.content, - embeds=board_message.embeds, - view=view, - ephemeral=ephemeral, - files=files, - allowed_mentions=discord.AllowedMentions.none(), - ) - - return - except discord.errors.NotFound: - messages.remove(message) - - embed = discord.Embed( - title="Error", - description="No messages found in the fireboard.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=ephemeral) - - # Command group setup - context = discord.app_commands.AppCommandContext( - guild=True, dm_channel=False, private_channel=False - ) - installs = discord.app_commands.AppInstallationType(guild=True, user=False) - perms = discord.Permissions(manage_guild=True) - fireSetupGroup = app_commands.Group( - name="fireboard-setup", - description="Control the fireboard.", - allowed_contexts=context, - allowed_installs=installs, - default_permissions=perms, - ) - - # Fireboard enable command - @fireSetupGroup.command( - name="enable", description="Enable the fireboard in the current channel." - ) - async def enable_fireboard(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Fireboard is already enabled.", color=Color.green() - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - # Default settings - react_minimum = 3 - emoji = "🔥" - channel_id = interaction.channel_id - ignore_bots = True - - embed = discord.Embed( - title="Fireboard", - description="This channel has been configured as the server fireboard.", - color=Color.random(), - ) - embed.set_footer(text="Feel free to delete this message!") - - try: - channel = await interaction.guild.fetch_channel(channel_id) - await channel.send(embed=embed) - except discord.errors.Forbidden or discord.errors.NotFound: - embed = discord.Embed( - title="Error", - description="Looks like I can't send messages in this channel. Check permissions and try again.", - color=Color.random(), - ) - await interaction.followup.send(embed=embed, ephemeral=True) - - return - - async with self.fireboard_pool.acquire() as sql: - # Insert to DB, refresh lists - await sql.execute( - "INSERT INTO fireSettings (serverID, reactionAmount, emoji, channelID, ignoreBots) VALUES (?, ?, ?, ?, ?)", - ( - interaction.guild_id, - react_minimum, - emoji, - channel_id, - ignore_bots, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Enabled", - description="Fireboard has been enabled in the current channel.", - color=Color.green(), - ) - embed.add_field( - name="Info", - value=f"**Reaction Requirement:** `{react_minimum} reactions`\n**Fireboard Channel:** <#{channel_id}>\n**Emoji:** {emoji}\n**Ignore Bots:** `{ignore_bots}`", - ) - - await interaction.followup.send(embed=embed, ephemeral=True) - - class ConfirmDisableView(View): - def __init__(self): - super().__init__(timeout=60) - - async def disable_fireboard( - self, interaction: discord.Interaction, pool: asqlite.Pool - ): - async with pool.acquire() as sql: - try: - await sql.execute( - "DELETE FROM fireMessages WHERE serverID = ?", - (interaction.guild_id,), - ) - await sql.execute( - "DELETE FROM fireSettings WHERE serverID = ?", - (interaction.guild_id,), - ) - await sql.execute( - "DELETE FROM fireChannelBlacklist WHERE serverID = ?", - (interaction.guild_id,), - ) - await sql.execute( - "DELETE FROM fireRoleBlacklist WHERE serverID = ?", - (interaction.guild_id,), - ) - await sql.commit() - return True - except Exception: - return False - - @discord.ui.button(label="Disable", style=discord.ButtonStyle.red) - async def confirm( - self, interaction: discord.Interaction, button: discord.ui.Button - ): - await interaction.response.defer(ephemeral=True) - - success = await self.disable_fireboard(interaction, self.pool) - - if success: - embed = discord.Embed( - title="Done!", - description="Fireboard was disabled.", - color=Color.green(), - ) - await self.cog.refresh_fire_lists() # pylint: disable=no-member - else: - embed = discord.Embed( - title="Error", - description="Failed to disable fireboard.", - color=Color.red(), - ) - - await interaction.edit_original_response(embed=embed, view=None) - self.stop() - - async def on_timeout(self): - for item in self.children: - item.disabled = True - - embed = discord.Embed( - title="Timeout", - description="You didn't press the button in time.", - color=Color.red(), - ) - await self.message.edit(embed=embed, view=self) - - # Fireboard disable command - @fireSetupGroup.command(name="disable", description="Disable the server fireboard.") - async def disable_fireboard(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - - if interaction.guild_id in [guild[0] for guild in self.fire_settings]: - view = self.ConfirmDisableView() - view.pool = self.fireboard_pool - view.cog = self - - embed = discord.Embed( - title="Are you sure?", - description="All data about this server's fireboard will be deleted. This cannot be undone!", - color=Color.orange(), - ) - - message = await interaction.followup.send( - embed=embed, view=view, ephemeral=True, wait=True - ) - view.message = message - else: - await interaction.followup.send( - "Fireboard is not enabled in this server!", ephemeral=True - ) - - # Fireboard server info command - @fireSetupGroup.command( - name="info", description="View fireboard config for this server." - ) - async def fireboard_info(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - # Fetch server settings - for server in self.fire_settings: - if server[0] == interaction.guild_id: - react_minimum = server[1] - emoji = server[2] - channel_id = server[3] - ignore_bots = True if int(server[4]) == 1 else False - - embed = discord.Embed( - title="Server Fireboard Settings", - description=f"**Reaction Requirement:** `{react_minimum} reactions`\n**Fireboard Channel:** <#{channel_id}>\n**Emoji:** {emoji}\n**Ignore Bots:** `{ignore_bots}`", - color=Color.random(), - ) - - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard set emoji command - @fireSetupGroup.command(name="emoji", description="Set a custom fireboard emoji.") - async def fireboard_emoji(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=False) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Waiting for Reaction", - description="🔄 React with this message with your target emoji to set the fireboard emoji.", - color=Color.orange(), - ) - - msg = await interaction.followup.send(embed=embed, ephemeral=False) - - def check(reaction, user): - return user == interaction.user and reaction.message.id == msg.id - - # Wait for a reaction - try: - reaction, user = await self.bot.wait_for( - "reaction_add", timeout=60.0, check=check - ) - - reaction: discord.Reaction = reaction - - async with self.fireboard_pool.acquire() as sql: - # Change emoji in DB, refresh lists - await sql.execute( - "UPDATE fireSettings SET emoji = ? WHERE serverID = ?", - ( - str(reaction.emoji), - interaction.guild_id, - ), - ) - await sql.commit() - - embed = discord.Embed( - title="Emoji Set", - description=f"Set emoji to **{str(reaction.emoji)}.**", - color=Color.green(), - ) - - await self.refresh_fire_lists() - await interaction.edit_original_response(embed=embed) - except asyncio.TimeoutError: # Timed out - embed = discord.Embed( - title="Timed Out", - description="You didn't react in time.", - color=Color.red(), - ) - - await interaction.edit_original_response(embed=embed) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=False) - - # Fireboard set channel command - @fireSetupGroup.command( - name="channel", - description="Set the channel for fireboard messages to be sent in.", - ) - async def fireboard_channel( - self, interaction: discord.Interaction, channel: discord.TextChannel - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Fireboard", - description="This channel has been configured as the server fireboard.", - color=Color.random(), - ) - embed.set_footer(text="Feel free to delete this message!") - - try: - await channel.send(embed=embed) - except discord.errors.NotFound: - embed = discord.Embed( - title="Error", - description="Looks like I can't find that channel. Check permissions and try again.", - color=Color.random(), - ) - await interaction.followup.send(embed=embed, ephemeral=True) - - return - except discord.errors.Forbidden as e: - embed = discord.Embed( - title="Error", - description="Looks like I can't send messages in that channel. Check permissions and try again.", - color=Color.red(), - ) - await interaction.followup.send(embed=embed, ephemeral=True) - - return - - async with self.fireboard_pool.acquire() as sql: - # Update channel in DB, refresh lists - await sql.execute( - "UPDATE fireSettings SET channelID = ? WHERE serverID = ?", - ( - channel.id, - interaction.guild_id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Channel Set", - description=f"Fireboard channel has been set to **{channel.mention}.**", - color=Color.green(), - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard set requirement command - @fireSetupGroup.command( - name="requirement", - description="Set required reaction amount for message to be posted on the fireboard.", - ) - async def fireboard_requirement( - self, interaction: discord.Interaction, amount: int - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Set", - description=f"Reaction requirement has been set to **{amount} reactions.**", - color=Color.green(), - ) - - async with self.fireboard_pool.acquire() as sql: - # Update reaction requirement in DB, refresh lists - await sql.execute( - "UPDATE fireSettings SET reactionAmount = ? WHERE serverID = ?", - ( - amount, - interaction.guild_id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard ignore bots command - @fireSetupGroup.command( - name="ignore-bots", - description="Whether bot messages are ignored in the fireboard. Defaults to true.", - ) - async def fireboard_ignore_bots( - self, interaction: discord.Interaction, value: bool - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild.id in [guild[0] for guild in self.fire_settings]: - embed = discord.Embed( - title="Set", - description=f"Bot messages will **{'be ignored.' if value else 'not be ignored.'}**", - color=Color.green(), - ) - - async with self.fireboard_pool.acquire() as sql: - # Update setting in DB, refresh lists - await sql.execute( - "UPDATE fireSettings SET ignoreBots = ? WHERE serverID = ?", - ( - value, - interaction.guild_id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard role blacklist - @fireSetupGroup.command( - name="channel-blacklist", - description="Toggle the blacklist for a channel. NSFW channels are always blacklisted.", - ) - async def fireboard_channel_blacklist( - self, interaction: discord.Interaction, channel: discord.abc.GuildChannel - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild_id in [guild[0] for guild in self.fire_settings]: - async with self.fireboard_pool.acquire() as sql: - if channel.id in [ - channelEntry[1] for channelEntry in self.fire_channel_blacklist - ]: - await sql.execute( - "DELETE FROM fireChannelBlacklist WHERE serverID = ? AND channelID = ?", - ( - interaction.guild_id, - channel.id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Set", - description=f"Removed {channel.mention} from the channel blacklist.", - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - await sql.execute( - "INSERT INTO fireChannelBlacklist (serverID, channelID) VALUES (?, ?)", - ( - interaction.guild_id, - channel.id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Set", - description=f"Added {channel.mention} to the channel blacklist.", - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard role blacklist - @fireSetupGroup.command( - name="role-blacklist", description="Toggle the blacklist for a role." - ) - async def fireboard_role_blacklist( - self, interaction: discord.Interaction, role: discord.Role - ): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild_id in [guild[0] for guild in self.fire_settings]: - async with self.fireboard_pool.acquire() as sql: - if role.id in [roleEntry[1] for roleEntry in self.fire_role_blacklist]: - await sql.execute( - "DELETE FROM fireRoleBlacklist WHERE serverID = ? AND roleID = ?", - ( - interaction.guild_id, - role.id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Set", - description=f"Removed {role.mention} from the role blacklist.", - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - await sql.execute( - "INSERT INTO fireRoleBlacklist (serverID, roleID) VALUES (?, ?)", - ( - interaction.guild_id, - role.id, - ), - ) - await sql.commit() - - await self.refresh_fire_lists() - - embed = discord.Embed( - title="Set", - description=f"Added {role.mention} to the role blacklist.", - ) - await interaction.followup.send(embed=embed, ephemeral=True) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - # Fireboard role blacklist - @fireSetupGroup.command( - name="blacklists", description="View this server's role and channel blacklists." - ) - async def fireboard_blacklists(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - - # Check fireboard status - if interaction.guild_id in [guild[0] for guild in self.fire_settings]: - - class BlacklistViewer(View): - def __init__(self): - super().__init__(timeout=240) - - self.fire_channel_blacklist: list - self.fire_role_blacklist: list - self.interaction: discord.Interaction - - async def on_timeout(self) -> None: - for item in self.children: - item.disabled = True - - await self.interaction.edit_original_response(view=self) - - @discord.ui.button( - label="Role Blacklist", - style=discord.ButtonStyle.gray, - row=0, - custom_id="role", - disabled=True, - ) - async def role( - self, interaction: discord.Interaction, button: discord.ui.Button - ): - await interaction.response.defer(ephemeral=True) - - for item in self.children: - if item.custom_id == "channel": - item.disabled = False - else: - item.disabled = True - - my_roles = [] - - for role in self.fire_role_blacklist: - if role[0] == interaction.guild_id: - my_roles.append(f"<@&{role[1]}>") - - if my_roles != []: - embed = discord.Embed( - title="Role Blacklist", - description="\n".join(my_roles), - color=Color.random(), - ) - await interaction.edit_original_response(embed=embed, view=self) - else: - embed = discord.Embed( - title="Role Blacklist", - description="No roles have been blacklisted.", - color=Color.random(), - ) - await interaction.edit_original_response(embed=embed, view=self) - - @discord.ui.button( - label="Channel Blacklist", - style=discord.ButtonStyle.gray, - row=0, - custom_id="channel", - ) - async def channel( - self, interaction: discord.Interaction, button: discord.ui.Button - ): - await interaction.response.defer(ephemeral=True) - - for item in self.children: - if item.custom_id == "role": - item.disabled = False - else: - item.disabled = True - - my_channels = [] - - for channel in self.fire_channel_blacklist: - if channel[0] == interaction.guild_id: - my_channels.append(f"<#{channel[1]}>") - - if my_channels != []: - embed = discord.Embed( - title="Channel Blacklist", - description="\n".join(my_channels), - color=Color.random(), - ) - await interaction.edit_original_response(embed=embed, view=self) - else: - embed = discord.Embed( - title="Channel Blacklist", - description="No channels have been blacklisted.", - color=Color.random(), - ) - await interaction.edit_original_response(embed=embed, view=self) - - view_instance = BlacklistViewer() - view_instance.fire_channel_blacklist = self.fire_channel_blacklist - view_instance.fire_role_blacklist = self.fire_role_blacklist - view_instance.interaction = interaction - - my_roles = [] - - for role in self.fire_role_blacklist: - if role[0] == interaction.guild_id: - my_roles.append(f"<@&{role[1]}>") - - if my_roles != []: - embed = discord.Embed( - title="Role Blacklist", - description="\n".join(my_roles), - color=Color.random(), - ) - await interaction.followup.send( - embed=embed, view=view_instance, ephemeral=True - ) - else: - embed = discord.Embed( - title="Role Blacklist", - description="No roles have been blacklisted.", - color=Color.random(), - ) - await interaction.followup.send( - embed=embed, view=view_instance, ephemeral=True - ) - else: - embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) - - await interaction.followup.send(embed=embed, ephemeral=True) - - -async def setup(bot):import asyncio -import random -import json -import os - -import discord -from discord import Color, app_commands -from discord.ext import commands -from discord.ui import View - -FIREBOARD_PATH = "assets/cogs/files/fireboard.json" - -def load_data(): - if not os.path.exists(FIREBOARD_PATH): - return {} - with open(FIREBOARD_PATH, "r") as f: - return json.load(f) - -def save_data(data): - os.makedirs(os.path.dirname(FIREBOARD_PATH), exist_ok=True) - with open(FIREBOARD_PATH, "w") as f: - json.dump(data, f, indent=2) - -class Fireboard(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.data = load_data() - self.locked_messages = [] - - def save(self): - save_data(self.data) - - def get_guild(self, guild_id): - return self.data.setdefault(str(guild_id), { - "settings": None, - "messages": [], - "role_blacklist": [], - "channel_blacklist": [] - }) - - @commands.Cog.listener() - async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): - if payload.guild_id is None: - return - guild_data = self.get_guild(payload.guild_id) - settings = guild_data["settings"] - if not settings: - return - react_minimum = settings["reactionAmount"] - emoji = settings["emoji"] - channel_id = settings["channelID"] - ignore_bots = settings["ignoreBots"] - - if str(payload.emoji) != emoji: - return - if payload.channel_id in guild_data["channel_blacklist"]: - return - if payload.message_id in self.locked_messages: - return - self.locked_messages.append(payload.message_id) - try: - channel = await self.bot.fetch_channel(payload.channel_id) - message = await channel.fetch_message(payload.message_id) - if ignore_bots and message.author.bot: - return - if hasattr(message.channel, "nsfw") and message.channel.nsfw: - return - react_count = 0 - for reaction in message.reactions: - if str(reaction.emoji) == emoji: - react_count = reaction.count - break - if react_count < react_minimum: - return - # Already on fireboard? - for m in guild_data["messages"]: - if m["msgID"] == payload.message_id: - return - # Send to fireboard - board_channel = await self.bot.fetch_channel(channel_id) - embed = discord.Embed(description=message.content, color=Color.random()) - embed.set_author(name=message.author.name, icon_url=message.author.display_avatar.url) - embed.timestamp = message.created_at - view = View() - view.add_item(discord.ui.Button(label="Jump to Message", url=message.jump_url, style=discord.ButtonStyle.url)) - files = [await a.to_file() for a in message.attachments] - board_message = await board_channel.send( - content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", - embed=embed, view=view, files=files - ) - guild_data["messages"].append({ - "msgID": payload.message_id, - "boardMsgID": board_message.id, - "reactionAmount": react_count - }) - self.save() - finally: - if payload.message_id in self.locked_messages: - self.locked_messages.remove(payload.message_id) - - @commands.Cog.listener() - async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): - if payload.guild_id is None: - return - guild_data = self.get_guild(payload.guild_id) - settings = guild_data["settings"] - if not settings: - return - emoji = settings["emoji"] - channel_id = settings["channelID"] - if str(payload.emoji) != emoji: - return - for m in guild_data["messages"]: - if m["msgID"] == payload.message_id: - channel = await self.bot.fetch_channel(channel_id) - try: - board_message = await channel.fetch_message(m["boardMsgID"]) - await board_message.delete() - except Exception: - pass - guild_data["messages"].remove(m) - self.save() - break - - @app_commands.command(name="fireboard_enable", description="Enable the fireboard in this channel.") - async def fireboard_enable(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - guild_data = self.get_guild(interaction.guild_id) - if guild_data["settings"]: - await interaction.followup.send("Fireboard is already enabled.", ephemeral=True) - return - guild_data["settings"] = { - "reactionAmount": 3, - "emoji": "🔥", - "channelID": interaction.channel_id, - "ignoreBots": True - } - self.save() - await interaction.followup.send("Fireboard enabled in this channel.", ephemeral=True) - - @app_commands.command(name="fireboard_disable", description="Disable the fireboard.") - async def fireboard_disable(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - guild_data = self.get_guild(interaction.guild_id) - guild_data["settings"] = None - guild_data["messages"] = [] - self.save() - await interaction.followup.send("Fireboard disabled.", ephemeral=True) - - @app_commands.command(name="fireboard_info", description="Show fireboard settings.") - async def fireboard_info(self, interaction: discord.Interaction): - await interaction.response.defer(ephemeral=True) - guild_data = self.get_guild(interaction.guild_id) - settings = guild_data["settings"] - if not settings: - await interaction.followup.send("Fireboard is not enabled.", ephemeral=True) - return - embed = discord.Embed( - title="Fireboard Settings", - description=f"**Reaction Requirement:** `{settings['reactionAmount']}`\n" - f"**Fireboard Channel:** <#{settings['channelID']}>\n" - f"**Emoji:** {settings['emoji']}\n" - f"**Ignore Bots:** `{settings['ignoreBots']}`", - color=Color.random() - ) - await interaction.followup.send(embed=embed, ephemeral=True) - -async def setup(bot): - await bot.add_cog(Fireboard(bot)) \ No newline at end of file From 5bcd7473f2d99f479907139f2e07c2b9f18a7ece Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 7 Jul 2025 18:37:27 -0400 Subject: [PATCH 19/84] =?UTF-8?q?discord=20user=20discriminators=20are=20s?= =?UTF-8?q?o=202020=20=F0=9F=92=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/cogs/webserver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/cogs/webserver.py b/assets/cogs/webserver.py index 57f6ca0..110c20d 100644 --- a/assets/cogs/webserver.py +++ b/assets/cogs/webserver.py @@ -60,7 +60,7 @@ class GooberWeb(commands.Cog): try: user = await self.bot.fetch_user(int(user_id)) blacklisted_users.append({ - "name": f"{user.name}#{user.discriminator}", + "name": f"{user.name}", "avatar_url": str(user.avatar.url) if user.avatar else str(user.default_avatar.url), "id": user.id }) @@ -158,7 +158,7 @@ class GooberWeb(commands.Cog): 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}#{user.discriminator})" + 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()) @@ -380,7 +380,7 @@ class GooberWeb(commands.Cog): if owner_id: try: owner = await self.bot.fetch_user(int(owner_id)) - owner_username = f"{owner.name}#{owner.discriminator}" + owner_username = f"{owner.name}" owner_pfp = str(owner.avatar.url) if owner and owner.avatar else "" except: pass @@ -877,4 +877,4 @@ class GooberWeb(commands.Cog): return web.json_response(stats) async def setup(bot): - await bot.add_cog(GooberWeb(bot)) \ No newline at end of file + await bot.add_cog(GooberWeb(bot)) From 30627db7309a3d18676d2cc449e81d947b015423 Mon Sep 17 00:00:00 2001 From: ctih1 Date: Tue, 8 Jul 2025 17:02:10 +0300 Subject: [PATCH 20/84] added translations and implemented somewhere but its way too painful so im quitting --- bot.py | 57 ++++++++++++++++++-------- log.txt | 37 +++++++++++++++++ modules/globalvars.py | 1 + modules/logger.py | 25 ++++++++++++ modules/prestartchecks.py | 85 ++++++++++++++++++++------------------- 5 files changed, 147 insertions(+), 58 deletions(-) create mode 100644 log.txt create mode 100644 modules/logger.py diff --git a/bot.py b/bot.py index 9755f8e..5beb003 100644 --- a/bot.py +++ b/bot.py @@ -11,9 +11,32 @@ import uuid import asyncio import sys from typing import List, Dict, Set, Optional, Tuple, Any, Union, Callable, Coroutine, TypeVar, Type - +import logging from modules.globalvars import * from modules.prestartchecks import start_checks +from modules.logger import GooberFormatter +import logging + +logger = logging.getLogger("goober") +logger.setLevel(logging.DEBUG) + +console_handler = logging.StreamHandler() +console_handler.setLevel(logging.DEBUG) +console_handler.setFormatter(GooberFormatter()) + +file_handler = logging.FileHandler("log.txt", mode="w+", encoding="UTF-8") +file_handler.setLevel(logging.DEBUG) +file_handler.setFormatter(GooberFormatter(colors=False)) + +logger.addHandler(console_handler) +logger.addHandler(file_handler) + +logger.debug("Testing logging") +logger.info("Testing logging") +logger.warning("Testing logging") +logger.error("Testing logging") +logger.critical("Testing logging") + # Print splash text and check for updates print(splashtext) # Print splash text (from modules/globalvars.py) @@ -65,7 +88,7 @@ bot: commands.Bot = commands.Bot( memory: List[str] = load_memory() markov_model: Optional[markovify.Text] = load_markov_model() if not markov_model: - print(f"{RED}{(_('markov_model_not_found'))}{RESET}") + logger.error(_('markov_model_not_found')) memory = load_memory() markov_model = train_markov_model(memory) @@ -79,9 +102,9 @@ async def load_cogs_from_folder(bot, folder_name="assets/cogs"): module_path = folder_name.replace("/", ".").replace("\\", ".") + f".{cog_name}" try: await bot.load_extension(module_path) - print(f"{GREEN}{(_('loaded_cog'))} {cog_name}{RESET}") + logger.info(f"{(_('loaded_cog'))} {cog_name}") except Exception as e: - print(f"{RED}{(_('cog_fail'))} {cog_name} {e}{RESET}") + logger.error(f"{(_('cog_fail'))} {cog_name} {e}") traceback.print_exc() async def fetch_active_users() -> str: @@ -92,7 +115,7 @@ async def fetch_active_users() -> str: else: return "?" except Exception as e: - print(f"{RED}{(_('error_fetching_active_users'))}{RESET} {e}") + logger.e(f"{_('error_fetching_active_users')} {RESET} {e}") return "?" async def send_alive_ping_periodically() -> None: @@ -100,7 +123,7 @@ async def send_alive_ping_periodically() -> None: try: requests.post(f"{VERSION_URL}/aliveping", json={"name": NAME}) except Exception as e: - print(f"{RED}{(_('error_sending_alive_ping'))}{RESET} {e}") + logger.error(f"{(_('error_sending_alive_ping'))}{RESET} {e}") await asyncio.sleep(60) # Event: Called when the bot is ready @@ -117,21 +140,21 @@ async def on_ready() -> None: await load_cogs_from_folder(bot) try: synced: List[discord.app_commands.AppCommand] = await bot.tree.sync() - print(f"{GREEN}{_('synced_commands')} {len(synced)} {(_('synced_commands2'))} {RESET}") + logger.info(f"{_('synced_commands')} {len(synced)} {(_('synced_commands2'))}") slash_commands_enabled = True ping_server() # ping_server from modules/central.py active_users: str = await fetch_active_users() - print(f"{GREEN}{(_('active_users:'))} {active_users}{RESET}") - print(f"{GREEN}{(_('started')).format(name=NAME)}{RESET}") + logger.info(f"{(_('active_users:'))} {active_users}") + logger.info(f"{(_('started')).format(name=NAME)}") bot.loop.create_task(send_alive_ping_periodically()) except discord.errors.Forbidden as perm_error: - print(f"{RED}Permission error while syncing commands: {perm_error}{RESET}") - print(f"{RED}Make sure the bot has the 'applications.commands' scope and is invited with the correct permissions.{RESET}") + 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.") quit() except Exception as e: - print(f"{RED}{(_('fail_commands_sync'))} {e}{RESET}") + logger.error(f"{_('fail_commands_sync')} {e}") traceback.print_exc() quit() @@ -219,7 +242,7 @@ async def talk(ctx: commands.Context, sentence_size: int = 5) -> None: combined_message: str = f"{coherent_response}\n[jif]({gif_url})" else: combined_message: str = coherent_response - print(combined_message) + logger.info(combined_message) os.environ['gooberlatestgen'] = combined_message await send_message(ctx, combined_message) else: @@ -375,7 +398,7 @@ async def on_message(message: discord.Message) -> None: return if message.content.startswith((f"{PREFIX}talk", f"{PREFIX}mem", f"{PREFIX}help", f"{PREFIX}stats", f"{PREFIX}")): - print(f"{(_('command_ran')).format(message=message)}") + logger.info(f"{(_('command_ran')).format(message=message)}") await bot.process_commands(message) return @@ -399,14 +422,14 @@ async def on_message(message: discord.Message) -> None: try: await message.add_reaction(emoji) except Exception as e: - print(f"Failed to react with emoji: {e}") + logger.info(f"Failed to react with emoji: {e}") await bot.process_commands(message) # Event: Called on every interaction (slash command, etc.) @bot.event async def on_interaction(interaction: discord.Interaction) -> None: - print(f"{(_('command_ran_s')).format(interaction=interaction)}{interaction.data['name']}") + logger.info(f"{(_('command_ran_s')).format(interaction=interaction)}{interaction.data['name']}") # Global check: Block blacklisted users from running commands @bot.check @@ -483,7 +506,7 @@ async def mem(ctx: commands.Context) -> None: return command: str = """curl -F "reqtype=fileupload" -F "time=1h" -F "fileToUpload=@memory.json" https://litterbox.catbox.moe/resources/internals/api.php""" memorylitter: subprocess.CompletedProcess = subprocess.run(command, shell=True, capture_output=True, text=True) - print(memorylitter) + logger.debug(memorylitter) await send_message(ctx, memorylitter.stdout.strip()) # Helper: Improve sentence coherence (simple capitalization fix) diff --git a/log.txt b/log.txt new file mode 100644 index 0000000..f0b1bba --- /dev/null +++ b/log.txt @@ -0,0 +1,37 @@ +[ DEBUG ]: Testing logging  [07/08/25 17:01:45.827] (bot.py:)  +[ INFO ]: Testing logging  [07/08/25 17:01:45.828] (bot.py:)  +[ WARNING ]: Testing logging  [07/08/25 17:01:45.828] (bot.py:)  +[ ERROR ]: Testing logging  [07/08/25 17:01:45.829] (bot.py:)  +[ CRITICAL ]: Testing logging  [07/08/25 17:01:45.829] (bot.py:)  +[ INFO ]: Suoritetaan esikäynnistystarkistuksia...  [07/08/25 17:01:45.830] (prestartchecks.py:start_checks)  +[ INFO ]: Model is installed.  [07/08/25 17:01:45.832] (prestartchecks.py:check_for_model)  +[ INFO ]: OK aiohttp  [07/08/25 17:01:48.091] (prestartchecks.py:check_requirements)  +[ INFO ]: OK better-profanity  [07/08/25 17:01:48.091] (prestartchecks.py:check_requirements)  +[ INFO ]: OK discord.py  [07/08/25 17:01:48.092] (prestartchecks.py:check_requirements)  +[ INFO ]: OK discord.py  [07/08/25 17:01:48.092] (prestartchecks.py:check_requirements)  +[ INFO ]: OK python-dotenv  [07/08/25 17:01:48.093] (prestartchecks.py:check_requirements)  +[ INFO ]: OK markovify  [07/08/25 17:01:48.093] (prestartchecks.py:check_requirements)  +[ INFO ]: OK pillow  [07/08/25 17:01:48.093] (prestartchecks.py:check_requirements)  +[ INFO ]: OK psutil  [07/08/25 17:01:48.094] (prestartchecks.py:check_requirements)  +[ INFO ]: OK python-dotenv  [07/08/25 17:01:48.094] (prestartchecks.py:check_requirements)  +[ INFO ]: OK requests  [07/08/25 17:01:48.095] (prestartchecks.py:check_requirements)  +[ INFO ]: OK spacy  [07/08/25 17:01:48.095] (prestartchecks.py:check_requirements)  +[ INFO ]: OK spacytextblob  [07/08/25 17:01:48.095] (prestartchecks.py:check_requirements)  +[ INFO ]: Kaikki vaatimukset täyttyvät.  [07/08/25 17:01:48.096] (prestartchecks.py:check_requirements)  +[ INFO ]: Ping osoitteeseen 1.1.1.1: 117.0 ms  [07/08/25 17:01:48.240] (prestartchecks.py:check_latency)  +[ INFO ]: Muistin käyttö: 12.796562194824219 Gt / 15.846412658691406 Gt (80.753685205879%)  [07/08/25 17:01:48.248] (prestartchecks.py:check_memory)  +[ INFO ]: Kokonaismuisti: 15.846412658691406 Gt  [07/08/25 17:01:48.249] (prestartchecks.py:check_memory)  +[ INFO ]: Käytetty muisti: 12.796562194824219 Gt  [07/08/25 17:01:48.249] (prestartchecks.py:check_memory)  +[ INFO ]: Muistitiedosto: 0.00022029876708984375 Mt  [07/08/25 17:01:48.250] (prestartchecks.py:check_memoryjson)  +[ INFO ]: Mitataan suorittimen käyttöä ytimittäin...  [07/08/25 17:01:48.250] (prestartchecks.py:check_cpu)  +[ INFO ]: Ydin 0: [██------------------] 14.1%  [07/08/25 17:01:49.251] (prestartchecks.py:check_cpu)  +[ INFO ]: Ydin 1: [█-------------------] 9.4%  [07/08/25 17:01:49.252] (prestartchecks.py:check_cpu)  +[ INFO ]: Ydin 2: [███-----------------] 17.2%  [07/08/25 17:01:49.252] (prestartchecks.py:check_cpu)  +[ INFO ]: Ydin 3: [███-----------------] 18.8%  [07/08/25 17:01:49.253] (prestartchecks.py:check_cpu)  +[ INFO ]: Ydin 4: [███████-------------] 35.4%  [07/08/25 17:01:49.253] (prestartchecks.py:check_cpu)  +[ INFO ]: Ydin 5: [██████--------------] 31.8%  [07/08/25 17:01:49.253] (prestartchecks.py:check_cpu)  +[ INFO ]: Ydin 6: [██████████----------] 54.4%  [07/08/25 17:01:49.254] (prestartchecks.py:check_cpu)  +[ INFO ]: Ydin 7: [███████-------------] 39.1%  [07/08/25 17:01:49.254] (prestartchecks.py:check_cpu)  +[ INFO ]: Kokonaisprosessorin käyttö: 27.525%  [07/08/25 17:01:49.255] (prestartchecks.py:check_cpu)  +[ INFO ]: Jatketaan 5 sekunnin kuluttua... Paina mitä tahansa näppäintä ohittaaksesi.  [07/08/25 17:01:49.255] (prestartchecks.py:start_checks)  +[ ERROR ]: [VOLTA] Missing key: 'markov_model_not_found' in en.json!  [07/08/25 17:01:59.630] (bot.py:)  diff --git a/modules/globalvars.py b/modules/globalvars.py index 998267b..a705f87 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -10,6 +10,7 @@ ANSI = "\033[" RED = f"{ANSI}31m" GREEN = f"{ANSI}32m" YELLOW = f"{ANSI}33m" +PURPLE = f"{ANSI}35m" DEBUG = f"{ANSI}1;30m" RESET = f"{ANSI}0m" VERSION_URL = "https://goober.expect.ovh" diff --git a/modules/logger.py b/modules/logger.py new file mode 100644 index 0000000..76f5f10 --- /dev/null +++ b/modules/logger.py @@ -0,0 +1,25 @@ +import logging +from modules.globalvars import * + +class GooberFormatter(logging.Formatter): + def __init__(self, colors: bool = True): # Disable colors for TXT output + self.colors = colors + + self._format = f"[ %(levelname)-8s ]: %(message)s {DEBUG} [%(asctime)s.%(msecs)03d] (%(filename)s:%(funcName)s) {RESET}" + + self.FORMATS = { + logging.DEBUG: DEBUG + self._format + RESET, + logging.INFO: self._format.replace("%(levelname)-8s", f"{GREEN}%(levelname)-8s{RESET}"), + logging.WARNING: YELLOW + self._format + RESET, + logging.ERROR: RED + self._format + RESET, + logging.CRITICAL: PURPLE + self._format + RESET + } + + def format(self, record: logging.LogRecord): + if self.colors: + log_fmt = self.FORMATS.get(record.levelno) # Add colors + else: + log_fmt = self._format # Just use the default format + + formatter = logging.Formatter(log_fmt, datefmt="%m/%d/%y %H:%M:%S") + return formatter.format(record) diff --git a/modules/prestartchecks.py b/modules/prestartchecks.py index 1f4b736..60c1d76 100644 --- a/modules/prestartchecks.py +++ b/modules/prestartchecks.py @@ -10,6 +10,9 @@ import json import re from spacy.util import is_package import importlib.metadata +import logging + +logger = logging.getLogger("goober") # import shutil psutilavaliable = True @@ -18,20 +21,20 @@ try: import psutil except ImportError: psutilavaliable = False - print(RED, _('missing_requests_psutil'), RESET) + logger.error(_('missing_requests_psutil')) def check_for_model(): if is_package("en_core_web_sm"): - print("Model is installed.") + logger.info("Model is installed.") else: - print("Model is not installed.") + logger.info("Model is not installed.") def iscloned(): if os.path.exists(".git"): return True else: - print(f"{RED}{(_('not_cloned'))}{RESET}") + logger.error(f"{_('not_cloned')}") sys.exit(1) def get_stdlib_modules(): @@ -63,7 +66,7 @@ def check_requirements(): requirements_path = os.path.abspath(os.path.join(parent_dir, '..', 'requirements.txt')) if not os.path.exists(requirements_path): - print(f"{RED}{(_('requirements_not_found')).format(path=requirements_path)}{RESET}") + logger.error(f"{(_('requirements_not_found')).format(path=requirements_path)}") return with open(requirements_path, 'r') as f: @@ -95,9 +98,9 @@ def check_requirements(): continue requirements.add(pkg) except Exception as e: - print(f"{YELLOW}{(_('warning_failed_parse_imports')).format(filename=filename, error=e)}{RESET}") + logger.warning(f"{(_('warning_failed_parse_imports')).format(filename=filename, error=e)}") else: - print(f"{YELLOW}{(_('cogs_dir_not_found')).format(path=cogs_dir)}{RESET}") + logger.warning(f"{(_('cogs_dir_not_found')).format(path=cogs_dir)}") installed_packages = {dist.metadata['Name'].lower() for dist in importlib.metadata.distributions()} missing = [] @@ -110,16 +113,16 @@ def check_requirements(): check_name = PACKAGE_ALIASES.get(req, req).lower() if check_name in installed_packages: - print(f"[ {GREEN}{(_('ok_installed')).format(package=check_name)}{RESET} ] {check_name}") + logger.info(f"{_('ok_installed').format(package=check_name)} {check_name}") else: - print(f"[ {RED}{(_('missing_package')).format(package=check_name)}{RESET} ] {check_name} {(_('missing_package2'))}") + logger.error(f"{(_('missing_package')).format(package=check_name)} {check_name} {(_('missing_package2'))}") missing.append(check_name) if missing: - print(RED, _('missing_packages_detected'), RESET) + logger.error(_('missing_packages_detected')) for pkg in missing: print(f" - {pkg}") - print((_('telling_goober_central')).format(url=VERSION_URL)) + logger.info((_('telling_goober_central')).format(url=VERSION_URL)) payload = { "name": NAME, "version": local_version, @@ -129,10 +132,10 @@ def check_requirements(): try: requests.post(VERSION_URL + "/ping", json=payload) # type: ignore except Exception as e: - print(f"{RED}{(_('failed_to_contact')).format(url=VERSION_URL, error=e)}{RESET}") + logger.error(f"{(_('failed_to_contact')).format(url=VERSION_URL, error=e)}") sys.exit(1) else: - print(_('all_requirements_satisfied')) + logger.info(_('all_requirements_satisfied')) def check_latency(): host = "1.1.1.1" @@ -158,16 +161,16 @@ def check_latency(): match = re.search(latency_pattern, result.stdout) if match: latency_ms = float(match.group(1)) - print((_('ping_to')).format(host=host, latency=latency_ms)) + logger.info((_('ping_to')).format(host=host, latency=latency_ms)) if latency_ms > 300: - print(f"{YELLOW}{(_('high_latency'))}{RESET}") + logger.warning(f"{(_('high_latency'))}") else: - print(f"{YELLOW}{(_('could_not_parse_latency'))}{RESET}") + logger.warning((_('could_not_parse_latency'))) else: print(result.stderr) - print(f"{RED}{(_('ping_failed')).format(host=host)}{RESET}") + logger.error(f"{(_('ping_failed')).format(host=host)}{RESET}") except Exception as e: - print(f"{RED}{(_('error_running_ping')).format(error=e)}{RESET}") + logger.error((_('error_running_ping')).format(error=e)) def check_memory(): if psutilavaliable == False: @@ -178,21 +181,21 @@ def check_memory(): used_memory = memory_info.used / (1024 ** 3) free_memory = memory_info.available / (1024 ** 3) - print((_('memory_usage')).format(used=used_memory, total=total_memory, percent=(used_memory / total_memory) * 100)) + logger.info((_('memory_usage')).format(used=used_memory, total=total_memory, percent=(used_memory / total_memory) * 100)) if used_memory > total_memory * 0.9: print(f"{YELLOW}{(_('memory_above_90')).format(percent=(used_memory / total_memory) * 100)}{RESET}") - print((_('total_memory')).format(total=total_memory)) - print((_('used_memory')).format(used=used_memory)) + logger.info((_('total_memory')).format(total=total_memory)) + logger.info((_('used_memory')).format(used=used_memory)) if free_memory < 1: - print(f"{RED}{(_('low_free_memory')).format(free=free_memory)}{RESET}") + logger.warning(f"{(_('low_free_memory')).format(free=free_memory)}") sys.exit(1) except ImportError: - print(_('psutil_not_installed')) # todo: translate this into italian and put it in the translations "psutil is not installed. Memory check skipped." + logger.error(_('psutil_not_installed')) # todo: translate this into italian and put it in the translations "psutil is not installed. Memory check skipped." def check_cpu(): if psutilavaliable == False: return - print((_('measuring_cpu'))) + logger.info((_('measuring_cpu'))) cpu_per_core = psutil.cpu_percent(interval=1, percpu=True) # type: ignore for idx, core_usage in enumerate(cpu_per_core): bar_length = int(core_usage / 5) @@ -203,33 +206,33 @@ def check_cpu(): color = YELLOW else: color = GREEN - print((_('core_usage')).format(idx=idx, bar=bar, usage=core_usage)) + logger.info((_('core_usage')).format(idx=idx, bar=bar, usage=core_usage)) total_cpu = sum(cpu_per_core) / len(cpu_per_core) - print((_('total_cpu_usage')).format(usage=total_cpu)) + logger.info((_('total_cpu_usage')).format(usage=total_cpu)) if total_cpu > 85: - print(f"{YELLOW}{(_('high_avg_cpu')).format(usage=total_cpu)}{RESET}") + logger.warning(f"{(_('high_avg_cpu')).format(usage=total_cpu)}") if total_cpu > 95: - print(f"{RED}{(_('really_high_cpu'))}{RESET}") + logger.error(_('really_high_cpu')) sys.exit(1) def check_memoryjson(): try: - print((_('memory_file')).format(size=os.path.getsize(MEMORY_FILE) / (1024 ** 2))) + logger.info((_('memory_file')).format(size=os.path.getsize(MEMORY_FILE) / (1024 ** 2))) if os.path.getsize(MEMORY_FILE) > 1_073_741_824: - print(f"{YELLOW}{(_('memory_file_large'))}{RESET}") + logger.warning(f"{(_('memory_file_large'))}") try: with open(MEMORY_FILE, 'r', encoding='utf-8') as f: json.load(f) except json.JSONDecodeError as e: - print(f"{RED}{(_('memory_file_corrupted')).format(error=e)}{RESET}") - print(f"{YELLOW}{(_('consider_backup_memory'))}{RESET}") + logger.error(f"{(_('memory_file_corrupted')).format(error=e)}") + logger.warning(f"{(_('consider_backup_memory'))}") except UnicodeDecodeError as e: - print(f"{RED}{(_('memory_file_encoding')).format(error=e)}{RESET}") - print(f"{YELLOW}{(_('consider_backup_memory'))}{RESET}") + logger.error(f"{(_('memory_file_encoding')).format(error=e)}") + logger.warning(f"{(_('consider_backup_memory'))}") except Exception as e: - print(f"{RED}{(_('error_reading_memory')).format(error=e)}{RESET}") + logger.error(f"{(_('error_reading_memory')).format(error=e)}") except FileNotFoundError: - print(f"{YELLOW}{(_('memory_file_not_found'))}{RESET}") + logger(f"{(_('memory_file_not_found'))}") def presskey2skip(timeout): if os.name == 'nt': @@ -265,9 +268,9 @@ def presskey2skip(timeout): beta = beta def start_checks(): if CHECKS_DISABLED == "True": - print(f"{YELLOW}{(_('checks_disabled'))}{RESET}") + logger.warning(f"{(_('checks_disabled'))}") return - print(_('running_prestart_checks')) + logger.info(_('running_prestart_checks')) check_for_model() iscloned() check_missing_translations() @@ -279,13 +282,13 @@ def start_checks(): if os.path.exists(".env"): pass else: - print(f"{YELLOW}{(_('env_file_not_found'))}{RESET}") + logger.warning(f"{(_('env_file_not_found'))}") sys.exit(1) if beta == True: - print(f"{YELLOW}this build isnt finished yet, some things might not work as expected{RESET}") + logger.warning(f"this build isnt finished yet, some things might not work as expected") else: pass - print(_('continuing_in_seconds').format(seconds=5)) + 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 From 2d91a262ddd8b4ca86176f143dfde192e6570edf Mon Sep 17 00:00:00 2001 From: ctih1 Date: Tue, 8 Jul 2025 17:05:32 +0300 Subject: [PATCH 21/84] excluded log.txt --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2c32b44..704d466 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ received_memory.json translation_report.txt translationcompleteness.py modules/volta +log.txt \ No newline at end of file From cd7e38646b7f8fb123073f39d4f8e462da8eca79 Mon Sep 17 00:00:00 2001 From: Charlie Date: Tue, 8 Jul 2025 10:58:44 -0400 Subject: [PATCH 22/84] Update globalvars.py --- modules/globalvars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index 998267b..2356a2e 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -37,7 +37,7 @@ 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 = "2.1.2" +local_version = "2.1.3" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks From ee4f294b3684f155b18b2f00182e83806f9813ac Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:05:06 +0200 Subject: [PATCH 23/84] fuckass git --- bot.py | 27 ++++++++++--------------- log.txt | 37 ----------------------------------- modules/central.py | 24 ++++++++++++----------- modules/globalvars.py | 2 +- modules/markovmemory.py | 9 +++++---- modules/sentenceprocessing.py | 11 +++++++---- modules/version.py | 31 +++++++++++++++-------------- 7 files changed, 52 insertions(+), 89 deletions(-) delete mode 100644 log.txt diff --git a/bot.py b/bot.py index 5beb003..8cb283d 100644 --- a/bot.py +++ b/bot.py @@ -31,13 +31,6 @@ file_handler.setFormatter(GooberFormatter(colors=False)) logger.addHandler(console_handler) logger.addHandler(file_handler) -logger.debug("Testing logging") -logger.info("Testing logging") -logger.warning("Testing logging") -logger.error("Testing logging") -logger.critical("Testing logging") - - # Print splash text and check for updates print(splashtext) # Print splash text (from modules/globalvars.py) start_checks() @@ -204,13 +197,13 @@ async def retrain(ctx: commands.Context) -> None: for i, data in enumerate(memory): processed_data += 1 if processed_data % 1000 == 0 or processed_data == data_size: - await send_message(ctx, f"{(_('command_markov_retraining')).format(processed_data=processed_data, data_size=data_size)}", edit=True, message_reference=processing_message_ref) + await send_message(ctx, f"{_('command_markov_retraining').format(processed_data=processed_data, data_size=data_size)}", edit=True, message_reference=processing_message_ref) global markov_model markov_model = train_markov_model(memory) save_markov_model(markov_model) - await send_message(ctx, f"{(_('command_markov_retrain_successful')).format(data_size=data_size)}", edit=True, message_reference=processing_message_ref) + 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'))}") @@ -270,7 +263,7 @@ async def impact(ctx: commands.Context) -> None: else: fallback_image: Optional[str] = get_random_asset_image() if fallback_image is None: - await ctx.reply((_('no_image_available'))) + await ctx.reply(_('no_image_available')) return temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) shutil.copy(fallback_image, temp_input) @@ -278,7 +271,7 @@ async def impact(ctx: commands.Context) -> None: else: fallback_image = get_random_asset_image() if fallback_image is None: - await ctx.reply((_('no_image_available'))) + await ctx.reply(_('no_image_available')) return temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) shutil.copy(fallback_image, temp_input) @@ -289,7 +282,7 @@ async def impact(ctx: commands.Context) -> None: 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'))) + await ctx.reply(_('failed_generate_image')) return await ctx.send(file=discord.File(output_path)) @@ -319,7 +312,7 @@ async def demotivator(ctx: commands.Context) -> None: else: fallback_image: Optional[str] = get_random_asset_image() if fallback_image is None: - await ctx.reply((_('no_image_available'))) + await ctx.reply(_('no_image_available')) return temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) shutil.copy(fallback_image, temp_input) @@ -327,7 +320,7 @@ async def demotivator(ctx: commands.Context) -> None: else: fallback_image = get_random_asset_image() if fallback_image is None: - await ctx.reply((_('no_image_available'))) + await ctx.reply(_('no_image_available')) return temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) shutil.copy(fallback_image, temp_input) @@ -438,11 +431,11 @@ async def block_blacklisted(ctx: commands.Context) -> bool: try: if isinstance(ctx, discord.Interaction): if not ctx.response.is_done(): - await ctx.response.send_message((_('blacklisted')), ephemeral=True) + await ctx.response.send_message(_('blacklisted'), ephemeral=True) else: - await ctx.followup.send((_('blacklisted')), ephemeral=True) + await ctx.followup.send(_('blacklisted'), ephemeral=True) else: - await ctx.send((_('blacklisted_user')), ephemeral=True) + await ctx.send(_('blacklisted_user'), ephemeral=True) except: pass return False diff --git a/log.txt b/log.txt deleted file mode 100644 index f0b1bba..0000000 --- a/log.txt +++ /dev/null @@ -1,37 +0,0 @@ -[ DEBUG ]: Testing logging  [07/08/25 17:01:45.827] (bot.py:)  -[ INFO ]: Testing logging  [07/08/25 17:01:45.828] (bot.py:)  -[ WARNING ]: Testing logging  [07/08/25 17:01:45.828] (bot.py:)  -[ ERROR ]: Testing logging  [07/08/25 17:01:45.829] (bot.py:)  -[ CRITICAL ]: Testing logging  [07/08/25 17:01:45.829] (bot.py:)  -[ INFO ]: Suoritetaan esikäynnistystarkistuksia...  [07/08/25 17:01:45.830] (prestartchecks.py:start_checks)  -[ INFO ]: Model is installed.  [07/08/25 17:01:45.832] (prestartchecks.py:check_for_model)  -[ INFO ]: OK aiohttp  [07/08/25 17:01:48.091] (prestartchecks.py:check_requirements)  -[ INFO ]: OK better-profanity  [07/08/25 17:01:48.091] (prestartchecks.py:check_requirements)  -[ INFO ]: OK discord.py  [07/08/25 17:01:48.092] (prestartchecks.py:check_requirements)  -[ INFO ]: OK discord.py  [07/08/25 17:01:48.092] (prestartchecks.py:check_requirements)  -[ INFO ]: OK python-dotenv  [07/08/25 17:01:48.093] (prestartchecks.py:check_requirements)  -[ INFO ]: OK markovify  [07/08/25 17:01:48.093] (prestartchecks.py:check_requirements)  -[ INFO ]: OK pillow  [07/08/25 17:01:48.093] (prestartchecks.py:check_requirements)  -[ INFO ]: OK psutil  [07/08/25 17:01:48.094] (prestartchecks.py:check_requirements)  -[ INFO ]: OK python-dotenv  [07/08/25 17:01:48.094] (prestartchecks.py:check_requirements)  -[ INFO ]: OK requests  [07/08/25 17:01:48.095] (prestartchecks.py:check_requirements)  -[ INFO ]: OK spacy  [07/08/25 17:01:48.095] (prestartchecks.py:check_requirements)  -[ INFO ]: OK spacytextblob  [07/08/25 17:01:48.095] (prestartchecks.py:check_requirements)  -[ INFO ]: Kaikki vaatimukset täyttyvät.  [07/08/25 17:01:48.096] (prestartchecks.py:check_requirements)  -[ INFO ]: Ping osoitteeseen 1.1.1.1: 117.0 ms  [07/08/25 17:01:48.240] (prestartchecks.py:check_latency)  -[ INFO ]: Muistin käyttö: 12.796562194824219 Gt / 15.846412658691406 Gt (80.753685205879%)  [07/08/25 17:01:48.248] (prestartchecks.py:check_memory)  -[ INFO ]: Kokonaismuisti: 15.846412658691406 Gt  [07/08/25 17:01:48.249] (prestartchecks.py:check_memory)  -[ INFO ]: Käytetty muisti: 12.796562194824219 Gt  [07/08/25 17:01:48.249] (prestartchecks.py:check_memory)  -[ INFO ]: Muistitiedosto: 0.00022029876708984375 Mt  [07/08/25 17:01:48.250] (prestartchecks.py:check_memoryjson)  -[ INFO ]: Mitataan suorittimen käyttöä ytimittäin...  [07/08/25 17:01:48.250] (prestartchecks.py:check_cpu)  -[ INFO ]: Ydin 0: [██------------------] 14.1%  [07/08/25 17:01:49.251] (prestartchecks.py:check_cpu)  -[ INFO ]: Ydin 1: [█-------------------] 9.4%  [07/08/25 17:01:49.252] (prestartchecks.py:check_cpu)  -[ INFO ]: Ydin 2: [███-----------------] 17.2%  [07/08/25 17:01:49.252] (prestartchecks.py:check_cpu)  -[ INFO ]: Ydin 3: [███-----------------] 18.8%  [07/08/25 17:01:49.253] (prestartchecks.py:check_cpu)  -[ INFO ]: Ydin 4: [███████-------------] 35.4%  [07/08/25 17:01:49.253] (prestartchecks.py:check_cpu)  -[ INFO ]: Ydin 5: [██████--------------] 31.8%  [07/08/25 17:01:49.253] (prestartchecks.py:check_cpu)  -[ INFO ]: Ydin 6: [██████████----------] 54.4%  [07/08/25 17:01:49.254] (prestartchecks.py:check_cpu)  -[ INFO ]: Ydin 7: [███████-------------] 39.1%  [07/08/25 17:01:49.254] (prestartchecks.py:check_cpu)  -[ INFO ]: Kokonaisprosessorin käyttö: 27.525%  [07/08/25 17:01:49.255] (prestartchecks.py:check_cpu)  -[ INFO ]: Jatketaan 5 sekunnin kuluttua... Paina mitä tahansa näppäintä ohittaaksesi.  [07/08/25 17:01:49.255] (prestartchecks.py:start_checks)  -[ ERROR ]: [VOLTA] Missing key: 'markov_model_not_found' in en.json!  [07/08/25 17:01:59.630] (bot.py:)  diff --git a/modules/central.py b/modules/central.py index 335e6ef..6293bbb 100644 --- a/modules/central.py +++ b/modules/central.py @@ -3,17 +3,19 @@ import os import modules.globalvars as gv from modules.volta.main import _ from modules.markovmemory import get_file_info +import logging +logger = logging.getLogger("goober") # Ping the server to check if it's alive and send some info def ping_server(): if gv.ALIVEPING == "false": # If pinging is disabled, print message and set environment variable - print(f"{gv.YELLOW}{(_('pinging_disabled'))}{gv.RESET}") + print(f"{gv.YELLOW}{(_('pinging_disabled'))}") os.environ['gooberauthenticated'] = 'No' return # Get server alert message goobres = requests.get(f"{gv.VERSION_URL}/alert") - print(f"{(_('goober_server_alert'))}{goobres.text}") + logger.info(f"{(_('goober_server_alert'))}{goobres.text}") # Gather file info for payload file_info = get_file_info(gv.MEMORY_FILE) payload = { @@ -28,15 +30,15 @@ def ping_server(): response = requests.post(gv.VERSION_URL+"/ping", json=payload) if response.status_code == 200: # Success: print message and set environment variable - print(f"{gv.GREEN}{(_('goober_ping_success')).format(NAME=gv.NAME)}{gv.RESET}") + logger.info(f"{(_('goober_ping_success')).format(NAME=gv.NAME)}") os.environ['gooberauthenticated'] = 'Yes' else: # Failure: print error and set environment variable - print(f"{gv.RED}{(_('goober_ping_fail'))} {response.status_code}{gv.RESET}") + logger.error(f"{(_('goober_ping_fail'))} {response.status_code}") os.environ['gooberauthenticated'] = 'No' except Exception as e: # Exception: print error and set environment variable - print(f"{gv.RED}{(_('goober_ping_fail2'))} {str(e)}{gv.RESET}") + logger.error(f"{(_('goober_ping_fail2'))} {str(e)}") os.environ['gooberauthenticated'] = 'No' # Check if a given name is available for registration @@ -52,11 +54,11 @@ def is_name_available(NAME): return data.get("available", False) else: # Print error if request failed - print(f"{(_('name_check'))}", response.json()) + logger.e(f"{(_('name_check'))}", response.json()) return False except Exception as e: # Print exception if request failed - print(f"{(_('name_check2'))}", e) + logger.error(f"{(_('name_check2'))}", e) return False # Register a new name with the server @@ -70,7 +72,7 @@ def register_name(NAME): if os.getenv("gooberTOKEN"): return # Name taken: print error and exit - print(f"{gv.RED}{(_('name_taken'))}{gv.RESET}") + logger.critical(f"{(_('name_taken'))}") quit() # Register the name response = requests.post(f"{gv.VERSION_URL}/register", json={"name": NAME}, headers={"Content-Type": "application/json"}) @@ -79,18 +81,18 @@ def register_name(NAME): token = data.get("token") if not os.getenv("gooberTOKEN"): # Print instructions to add token and exit - print(f"{gv.GREEN}{(_('add_token')).format(token=token)} gooberTOKEN=.{gv.gv.RESET}") + logger.info(f"{(_('add_token')).format(token=token)} gooberTOKEN=.") quit() else: print(f"{gv.GREEN}{gv.gv.RESET}") return token else: # Print error if registration failed - print(f"{gv.RED}{(_('token_exists')).format()}{gv.RESET}", response.json()) + logger.critical(f"{gv.RED}{(_('token_exists')).format()}", response.json()) return None except Exception as e: # Print exception if registration failed - print(f"{gv.RED}{(_('registration_error')).format()}{gv.RESET}", e) + logger.critical(f"{gv.RED}{(_('registration_error')).format()}", e) return None # Attempt to register the name at module load diff --git a/modules/globalvars.py b/modules/globalvars.py index 3c76bc7..41ba19d 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -41,5 +41,5 @@ latest_version = "0.0.0" local_version = "2.1.3" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") -beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks +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 \ No newline at end of file diff --git a/modules/markovmemory.py b/modules/markovmemory.py index eae003e..f903f59 100644 --- a/modules/markovmemory.py +++ b/modules/markovmemory.py @@ -4,7 +4,8 @@ import markovify import pickle from modules.globalvars import * from modules.volta.main import _ - +import logging +logger = logging.getLogger("goober") # Get file size and line count for a given file path def get_file_info(file_path): try: @@ -47,15 +48,15 @@ def train_markov_model(memory, additional_data=None): def save_markov_model(model, filename='markov_model.pkl'): with open(filename, 'wb') as f: pickle.dump(model, f) - print(f"Markov model saved to {filename}.") + logger.info(f"Markov model saved to {filename}.") # Load the Markov model from a pickle file def load_markov_model(filename='markov_model.pkl'): try: with open(filename, 'rb') as f: model = pickle.load(f) - print(f"{GREEN}{_('model_loaded')} {filename}.{RESET}") + logger.info(f"{_('model_loaded')} {filename}.{RESET}") return model except FileNotFoundError: - print(f"{RED}{filename} {_('not_found')}{RESET}") + logger.error(f"{filename} {_('not_found')}{RESET}") return None \ No newline at end of file diff --git a/modules/sentenceprocessing.py b/modules/sentenceprocessing.py index c470aef..993ba90 100644 --- a/modules/sentenceprocessing.py +++ b/modules/sentenceprocessing.py @@ -6,17 +6,20 @@ import spacy from spacy.tokens import Doc from spacytextblob.spacytextblob import SpacyTextBlob +import logging +logger = logging.getLogger("goober") + def check_resources(): try: nlp = spacy.load("en_core_web_sm") except OSError: - print((_('spacy_model_not_found'))) + logging.critical((_('spacy_model_not_found'))) spacy.cli.download("en_core_web_sm") nlp = spacy.load("en_core_web_sm") if "spacytextblob" not in nlp.pipe_names: nlp.add_pipe("spacytextblob") - print((_('spacy_initialized'))) + logger.info((_('spacy_initialized'))) check_resources() @@ -28,8 +31,8 @@ def is_positive(sentence): doc = nlp(sentence) sentiment_score = doc._.polarity # from spacytextblob - debug_message = f"{DEBUG}{(_('sentence_positivity'))} {sentiment_score}{RESET}" - print(debug_message) + debug_message = f"{(_('sentence_positivity'))} {sentiment_score}{RESET}" + logger.debug(debug_message) return sentiment_score > 0.6 # had to raise the bar because it kept saying "death to jews" was fine and it kept reacting to them diff --git a/modules/version.py b/modules/version.py index f876123..d1493ab 100644 --- a/modules/version.py +++ b/modules/version.py @@ -3,7 +3,8 @@ from modules.globalvars import * import requests import subprocess import sys - +import logging +logger = logging.getLogger("goober") launched = False # Run a shell command and return its output @@ -27,11 +28,11 @@ def auto_update(branch='main', remote='origin'): if is_remote_ahead(branch, remote): print(_( "remote_ahead").format(remote=remote, branch=branch)) pull_result = run_cmd(f'git pull {remote} {branch}') - print(pull_result) - print(_( "please_restart")) + logger.info(pull_result) + logger.info(_( "please_restart")) sys.exit(0) else: - print(_( "local_ahead").format(remote=remote, branch=branch)) + logger.info(_( "local_ahead").format(remote=remote, branch=branch)) # Fetch the latest version info from the update server def get_latest_version_info(): @@ -40,10 +41,10 @@ def get_latest_version_info(): if response.status_code == 200: return response.json() else: - print(f"{RED}{_( 'version_error')} {response.status_code}{RESET}") + logger.error(f"{RED}{_( 'version_error')} {response.status_code}{RESET}") return None except requests.RequestException as e: - print(f"{RED}{_( 'version_error')} {e}{RESET}") + logger.error(f"{RED}{_( 'version_error')} {e}{RESET}") return None # Check if an update is available and perform update if needed @@ -54,7 +55,7 @@ def check_for_update(): latest_version_info = get_latest_version_info() if not latest_version_info: - print(f"{_('fetch_update_fail')}") + logger.error(f"{_('fetch_update_fail')}") return None, None latest_version = latest_version_info.get("version") @@ -62,25 +63,25 @@ def check_for_update(): download_url = latest_version_info.get("download_url") if not latest_version or not download_url: - print(f"{RED}{_(LOCALE, 'invalid_server')}{RESET}") + logger.error(f"{RED}{_(LOCALE, 'invalid_server')}{RESET}") return None, None # Check if local_version is valid if local_version == "0.0.0" or None: - print(f"{RED}{_('cant_find_local_version')}{RESET}") + logger.error(f"{RED}{_('cant_find_local_version')}{RESET}") return # Compare local and latest versions if local_version < latest_version: - print(f"{YELLOW}{_('new_version').format(latest_version=latest_version, local_version=local_version)}{RESET}") - print(f"{YELLOW}{_('changelog').format(VERSION_URL=VERSION_URL)}{RESET}") + logger.info(f"{YELLOW}{_('new_version').format(latest_version=latest_version, local_version=local_version)}{RESET}") + logger.info(f"{YELLOW}{_('changelog').format(VERSION_URL=VERSION_URL)}{RESET}") auto_update() elif beta == True: - print(f"{YELLOW}You are running an \"unstable\" version of Goober, do not expect it to work properly.\nVersion {local_version}{RESET}") + logger.warning(f"You are running an \"unstable\" version of Goober, do not expect it to work properly.\nVersion {local_version}{RESET}") elif local_version > latest_version: - print(f"{YELLOW}{_('modification_warning')}{RESET}") + logger.warning(f"{_('modification_warning')}") elif local_version == latest_version: - print(f"{GREEN}{_('latest_version')} {local_version}{RESET}") - print(f"{_('latest_version2').format(VERSION_URL=VERSION_URL)}\n\n") + logger.info(f"{_('latest_version')} {local_version}") + logger.info(f"{_('latest_version2').format(VERSION_URL=VERSION_URL)}\n\n") launched = True return latest_version \ No newline at end of file From ffeefaa1a436d0fcec40de780202ca20737aa643 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:07:43 +0200 Subject: [PATCH 24/84] fucking idoit --- modules/globalvars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index 41ba19d..3c76bc7 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -41,5 +41,5 @@ latest_version = "0.0.0" local_version = "2.1.3" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") -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 +beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks \ No newline at end of file From 24beb9a42e92cd74f3584ead646d307869459271 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 9 Jul 2025 22:57:59 +0200 Subject: [PATCH 25/84] cleaned up the .env n shit --- bot.py | 3 ++- example.env | 33 ++++++++++++++++++--------------- modules/central.py | 4 ++-- modules/globalvars.py | 24 ++++++++++++------------ modules/volta/main.py | 2 +- 5 files changed, 35 insertions(+), 31 deletions(-) diff --git a/bot.py b/bot.py index 8cb283d..b11da34 100644 --- a/bot.py +++ b/bot.py @@ -468,7 +468,8 @@ async def about(ctx: commands.Context) -> None: 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"{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"Github", value=f"https://github.com/gooberinc/goober") + await send_message(ctx, embed=embed) # Command: Show bot statistics (admin only) diff --git a/example.env b/example.env index 50d672d..03c30c6 100644 --- a/example.env +++ b/example.env @@ -1,18 +1,21 @@ -DISCORD_BOT_TOKEN=token -BOT_PREFIX="g." -PING_LINE="The Beretta fires fast and won't make you feel any better!" -BLACKLISTED_USERS= -USERTRAIN_ENABLED="true" -showmemenabled="true" -NAME="an instance of goober" -locale=fi -ALIVEPING="true" +DISCORDBOTTOKEN= +BOTPREFIX="g." +PINGLINE="The Beretta fires fast and won't make you feel any better!" +BLACKLISTEDUSERS= +OWNERID= +USERTRAINENABLED="true" +SHOWMEMENABLED="true" +NAME="gooberino goobs" +LOCALE=fi +ALIVEPING="True" AUTOUPDATE="True" -gooberTOKEN= -song="A Heart of Cold - Heaven Pierce Her" +GOOBERTOKEN= +SONG="Basket Case - Green Day" +CHECKSDISABLED="Frue" REACT="True" -POSITIVE_GIFS="https://tenor.com/view/chill-guy-my-new-character-gif-2777893510283028272, https://tenor.com/view/goodnight-goodnight-friends-weezer-weezer-goodnight-gif-7322052181075806988" -splashtext=" +POSITIVEGIFS="https://media.discordapp.net/attachments/821047460151427135/1181371808566493184/jjpQGeno.gif, https://tenor.com/view/chill-guy-my-new-character-gif-2777893510283028272,https://tenor.com/view/goodnight-goodnight-friends-weezer-weezer-goodnight-gif-7322052181075806988" +SPLASHTEXT=" + SS\ SS | SSSSSS\ SSSSSS\ SSSSSS\ SSSSSSS\ SSSSSS\ SSSSSS\ @@ -23,5 +26,5 @@ SS | SS |SS | SS |SS | SS |SS | SS |SS ____|SS | \____SS | \______/ \______/ \_______/ \_______|\__| SS\ SS | \SSSSSS | - \______/ -" + \______/ +" \ No newline at end of file diff --git a/modules/central.py b/modules/central.py index 6293bbb..f13b23f 100644 --- a/modules/central.py +++ b/modules/central.py @@ -54,7 +54,7 @@ def is_name_available(NAME): return data.get("available", False) else: # Print error if request failed - logger.e(f"{(_('name_check'))}", response.json()) + logger.error(f"{(_('name_check'))}", response.json()) return False except Exception as e: # Print exception if request failed @@ -69,7 +69,7 @@ def register_name(NAME): return # Check if the name is available if not is_name_available(NAME): - if os.getenv("gooberTOKEN"): + if os.getenv("GOOBERTOKEN"): return # Name taken: print error and exit logger.critical(f"{(_('name_taken'))}") diff --git a/modules/globalvars.py b/modules/globalvars.py index 3c76bc7..3469db3 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -16,17 +16,17 @@ RESET = f"{ANSI}0m" VERSION_URL = "https://goober.expect.ovh" UPDATE_URL = VERSION_URL+"/latest_version.json" LOCAL_VERSION_FILE = "current_version.txt" -TOKEN = os.getenv("DISCORD_BOT_TOKEN", "0") -PREFIX = os.getenv("BOT_PREFIX", "g.") -PING_LINE = os.getenv("PING_LINE") -CHECKS_DISABLED = os.getenv("CHECKS_DISABLED") -LOCALE = os.getenv("locale", "en") -gooberTOKEN = os.getenv("gooberTOKEN") -splashtext = os.getenv("splashtext") -ownerid = int(os.getenv("ownerid", "0")) -showmemenabled = os.getenv("showmemenabled") -BLACKLISTED_USERS = os.getenv("BLACKLISTED_USERS", "").split(",") -USERTRAIN_ENABLED = os.getenv("USERTRAIN_ENABLED", "true").lower() == "true" +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")) +showmemenabled = os.getenv("SHOWMEMENABLED") +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 @@ -41,5 +41,5 @@ latest_version = "0.0.0" local_version = "2.1.3" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") -beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks +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 \ No newline at end of file diff --git a/modules/volta/main.py b/modules/volta/main.py index b8e88ea..03f5d4f 100644 --- a/modules/volta/main.py +++ b/modules/volta/main.py @@ -18,7 +18,7 @@ RESET = f"{ANSI}0m" load_dotenv() -LOCALE = os.getenv("locale") +LOCALE = os.getenv("LOCALE") module_dir = pathlib.Path(__file__).parent.parent working_dir = pathlib.Path.cwd() EXCLUDE_DIRS = {'.git', '__pycache__'} From 27d5d056fe64a711c5875a53d005538a2d1f5ab1 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Thu, 10 Jul 2025 20:37:57 +0200 Subject: [PATCH 26/84] pinging is fucking stupid --- bot.py | 2 - modules/central.py | 99 ---------------------------------------------- 2 files changed, 101 deletions(-) delete mode 100644 modules/central.py diff --git a/bot.py b/bot.py index b11da34..437d777 100644 --- a/bot.py +++ b/bot.py @@ -45,7 +45,6 @@ from discord.abc import Messageable from better_profanity import profanity from discord.ext import commands -from modules.central import ping_server from modules.volta.main import _, set_language from modules.markovmemory import * from modules.version import * @@ -135,7 +134,6 @@ async def on_ready() -> None: synced: List[discord.app_commands.AppCommand] = await bot.tree.sync() logger.info(f"{_('synced_commands')} {len(synced)} {(_('synced_commands2'))}") slash_commands_enabled = True - ping_server() # ping_server from modules/central.py active_users: str = await fetch_active_users() logger.info(f"{(_('active_users:'))} {active_users}") diff --git a/modules/central.py b/modules/central.py deleted file mode 100644 index f13b23f..0000000 --- a/modules/central.py +++ /dev/null @@ -1,99 +0,0 @@ -import requests -import os -import modules.globalvars as gv -from modules.volta.main import _ -from modules.markovmemory import get_file_info -import logging -logger = logging.getLogger("goober") - -# Ping the server to check if it's alive and send some info -def ping_server(): - if gv.ALIVEPING == "false": - # If pinging is disabled, print message and set environment variable - print(f"{gv.YELLOW}{(_('pinging_disabled'))}") - os.environ['gooberauthenticated'] = 'No' - return - # Get server alert message - goobres = requests.get(f"{gv.VERSION_URL}/alert") - logger.info(f"{(_('goober_server_alert'))}{goobres.text}") - # Gather file info for payload - file_info = get_file_info(gv.MEMORY_FILE) - payload = { - "name": gv.NAME, - "memory_file_info": file_info, - "version": gv.local_version, - "slash_commands": gv.slash_commands_enabled, - "token": gv.gooberTOKEN - } - try: - # Send ping to server - response = requests.post(gv.VERSION_URL+"/ping", json=payload) - if response.status_code == 200: - # Success: print message and set environment variable - logger.info(f"{(_('goober_ping_success')).format(NAME=gv.NAME)}") - os.environ['gooberauthenticated'] = 'Yes' - else: - # Failure: print error and set environment variable - logger.error(f"{(_('goober_ping_fail'))} {response.status_code}") - os.environ['gooberauthenticated'] = 'No' - except Exception as e: - # Exception: print error and set environment variable - logger.error(f"{(_('goober_ping_fail2'))} {str(e)}") - os.environ['gooberauthenticated'] = 'No' - -# Check if a given name is available for registration -def is_name_available(NAME): - if os.getenv("gooberTOKEN"): - # If token is already set, skip check - return - try: - # Send request to check name availability - response = requests.post(f"{gv.VERSION_URL}/check-if-available", json={"name": NAME}, headers={"Content-Type": "application/json"}) - if response.status_code == 200: - data = response.json() - return data.get("available", False) - else: - # Print error if request failed - logger.error(f"{(_('name_check'))}", response.json()) - return False - except Exception as e: - # Print exception if request failed - logger.error(f"{(_('name_check2'))}", e) - return False - -# Register a new name with the server -def register_name(NAME): - try: - if gv.ALIVEPING == False: - # If pinging is disabled, do nothing - return - # Check if the name is available - if not is_name_available(NAME): - if os.getenv("GOOBERTOKEN"): - return - # Name taken: print error and exit - logger.critical(f"{(_('name_taken'))}") - quit() - # Register the name - response = requests.post(f"{gv.VERSION_URL}/register", json={"name": NAME}, headers={"Content-Type": "application/json"}) - if response.status_code == 200: - data = response.json() - token = data.get("token") - if not os.getenv("gooberTOKEN"): - # Print instructions to add token and exit - logger.info(f"{(_('add_token')).format(token=token)} gooberTOKEN=.") - quit() - else: - print(f"{gv.GREEN}{gv.gv.RESET}") - return token - else: - # Print error if registration failed - logger.critical(f"{gv.RED}{(_('token_exists')).format()}", response.json()) - return None - except Exception as e: - # Print exception if registration failed - logger.critical(f"{gv.RED}{(_('registration_error')).format()}", e) - return None - -# Attempt to register the name at module load -register_name(gv.NAME) \ No newline at end of file From 70ad6ddc1fdceaba2e1f5a653be703656f180b99 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 11 Jul 2025 15:38:21 +0200 Subject: [PATCH 27/84] push --- modules/globalvars.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index 3469db3..5c2e2e3 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -38,8 +38,8 @@ 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 = "2.1.3" +local_version = "2.1.4" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") -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 +beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks \ No newline at end of file From 62afd3bbb6a789c1cc11c39d866264d165ef8684 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 11 Jul 2025 15:41:36 +0200 Subject: [PATCH 28/84] wrong version --- modules/globalvars.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index 5c2e2e3..571ec11 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -38,8 +38,8 @@ 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 = "2.1.4" +local_version = "2.2.0" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks - \ No newline at end of file + From ffcab75274465e17a83b5611a1f143c594c08ae8 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 14 Jul 2025 21:43:54 +0200 Subject: [PATCH 29/84] =?UTF-8?q?You=E2=80=99re=20the=20star=20of=20the=20?= =?UTF-8?q?show=20now=20baby!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/locales/en.json b/assets/locales/en.json index 7e1815a..5cada27 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -66,7 +66,7 @@ "synced_commands": "Synced", "synced_commands2": "commands!", "fail_commands_sync": "Failed to sync commands:", - "started": "{name} has started!", + "started": "{name} has started!/n You're the star of the show now baby!", "name_check": "Error checking name availability:", "name_taken": "Name is already taken. Please choose a different name.", "name_check2": "Error during name availability check:", From 1cb1ed3e19e2b5a55bff44f83fdfde0083f524a0 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Tue, 15 Jul 2025 23:15:58 +0200 Subject: [PATCH 30/84] Update en.json --- assets/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/locales/en.json b/assets/locales/en.json index 5cada27..affadea 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -66,7 +66,7 @@ "synced_commands": "Synced", "synced_commands2": "commands!", "fail_commands_sync": "Failed to sync commands:", - "started": "{name} has started!/n You're the star of the show now baby!", + "started": "{name} has started!\n You're the star of the show now baby!", "name_check": "Error checking name availability:", "name_taken": "Name is already taken. Please choose a different name.", "name_check2": "Error during name availability check:", From 43d9dfc79010498980e6962956a4237d1d66ac39 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Tue, 15 Jul 2025 23:17:44 +0200 Subject: [PATCH 31/84] why the fuck was there a space --- assets/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/locales/en.json b/assets/locales/en.json index affadea..29a07cc 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -66,7 +66,7 @@ "synced_commands": "Synced", "synced_commands2": "commands!", "fail_commands_sync": "Failed to sync commands:", - "started": "{name} has started!\n You're the star of the show now baby!", + "started": "{name} has started!\nYou're the star of the show now baby!", "name_check": "Error checking name availability:", "name_taken": "Name is already taken. Please choose a different name.", "name_check2": "Error during name availability check:", From ebd8a1746e7634a857693339b7bec86b2229bc2e Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Tue, 15 Jul 2025 23:20:59 +0200 Subject: [PATCH 32/84] Update it.json --- assets/locales/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/locales/it.json b/assets/locales/it.json index d59e352..c539e24 100644 --- a/assets/locales/it.json +++ b/assets/locales/it.json @@ -67,7 +67,7 @@ "synced_commands": "Sincronizzati", "synced_commands2": "comandi!", "fail_commands_sync": "Impossibile sincronizzare i comandi:", - "started": "{name} è stato avviato!", + "started": "{name} è stato avviato!\nIl palco è tuo", "name_check": "Errore nel controllo disponibilità del nome:", "name_taken": "Il nome è già preso. Scegli un nome diverso.", "name_check2": "Errore durante il controllo della disponibilità del nome:", From 02e716e44b4895e3dc4f377fa5fe240f0cfe90cb Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Tue, 15 Jul 2025 23:21:23 +0200 Subject: [PATCH 33/84] Update it.json --- assets/locales/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/locales/it.json b/assets/locales/it.json index c539e24..734e0d2 100644 --- a/assets/locales/it.json +++ b/assets/locales/it.json @@ -67,7 +67,7 @@ "synced_commands": "Sincronizzati", "synced_commands2": "comandi!", "fail_commands_sync": "Impossibile sincronizzare i comandi:", - "started": "{name} è stato avviato!\nIl palco è tuo", + "started": "{name} è stato avviato!\nIl palco è tuo!", "name_check": "Errore nel controllo disponibilità del nome:", "name_taken": "Il nome è già preso. Scegli un nome diverso.", "name_check2": "Errore durante il controllo della disponibilità del nome:", From 58542093b444dcb737dde394dfa4d990a1cbebb8 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Tue, 15 Jul 2025 23:24:30 +0200 Subject: [PATCH 34/84] Update fi.json --- assets/locales/fi.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/locales/fi.json b/assets/locales/fi.json index ea6adf0..68e2c83 100644 --- a/assets/locales/fi.json +++ b/assets/locales/fi.json @@ -68,7 +68,7 @@ "synced_commands": "Synkronoitiin", "synced_commands2": "komennot!", "fail_commands_sync": "Komentojen synkronointi epäonnistui:", - "started": "{name} on käynnistynyt!", + "started": "{name} on käynnistynyt!\nOlet nyt sarjan tähti, beibi!", "name_check": "Nimen saatavuuden tarkistus epäonnistui:", "name_taken": "Nimi on jo käytössä. Valitse toinen nimi.", "name_check2": "Virhe tapahtui nimen saatavuuden tarkistamisessa:", @@ -130,4 +130,4 @@ "command_stats_embed_field3name": "Muuttajainformaatio", "command_stats_embed_field3value": "Nimi: {NAME} \nEtuliite: {PREFIX} \nOmistajan ID: {ownerid}\nPing-linja: {PING_LINE} \nMuistin jako päällä: {showmemenabled} \nOppiminen käyttäjistä: {USERTRAIN_ENABLED}\nLaulu: {song} \nRoisketeksti: ```{splashtext}```" } - \ No newline at end of file + From 0bdd74b4feb621ec2822e92bfc4db15f210ee569 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Tue, 15 Jul 2025 23:27:17 +0200 Subject: [PATCH 35/84] eight balls --- assets/cogs/eightball.py | 57 ++++++++++++++++++++++++++++++++++++++++ modules/globalvars.py | 4 +-- 2 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 assets/cogs/eightball.py diff --git a/assets/cogs/eightball.py b/assets/cogs/eightball.py new file mode 100644 index 0000000..ec6c955 --- /dev/null +++ b/assets/cogs/eightball.py @@ -0,0 +1,57 @@ +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.randint(1, 20) + text = "Nothing" + if answer==1: + text = "It is certain." + elif answer==2: + text = "It is decidedly so." + elif answer==3: + text = "Without a doubt." + elif answer==4: + text = "Yes definitely." + elif answer==5: + text = "You may rely on it." + elif answer==6: + text = "As I see it, yes." + elif answer==7: + text = "Most likely." + elif answer==8: + text = "Outlook good." + elif answer==9: + text = "Yes." + elif answer==10: + text = "Signs point to yes." + elif answer==11: + text = "Reply hazy, try again." + elif answer==12: + text = "Ask again later." + elif answer==13: + text = "Better not tell you now." + elif answer==14: + text = "Cannot predict now." + elif answer==15: + text = "Concentrate and ask again." + elif answer==16: + text = "Don't count on it." + elif answer==17: + text = "My reply is no." + elif answer==18: + text = "My sources say no." + elif answer==19: + text = "Outlook not so good." + elif answer==20: + text = "Very doubtful." + + await ctx.send(text) + +async def setup(bot): + await bot.add_cog(eightball(bot)) diff --git a/modules/globalvars.py b/modules/globalvars.py index 571ec11..72f6af4 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -41,5 +41,5 @@ latest_version = "0.0.0" local_version = "2.2.0" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") -beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks - +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 + \ No newline at end of file From e7f18eecd20a3ce680ef5af91bb1515ac6e88b43 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Tue, 15 Jul 2025 23:56:44 +0200 Subject: [PATCH 36/84] goodbye old friend --- modules/globalvars.py | 5 +++-- modules/version.py | 15 ++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index 72f6af4..ddea186 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -13,8 +13,9 @@ YELLOW = f"{ANSI}33m" PURPLE = f"{ANSI}35m" DEBUG = f"{ANSI}1;30m" RESET = f"{ANSI}0m" -VERSION_URL = "https://goober.expect.ovh" +VERSION_URL = "https://raw.githubusercontent.com/gooberinc/version/main" UPDATE_URL = VERSION_URL+"/latest_version.json" +print(UPDATE_URL) LOCAL_VERSION_FILE = "current_version.txt" TOKEN = os.getenv("DISCORDBOTTOKEN", "0") PREFIX = os.getenv("BOTPREFIX", "g.") @@ -38,7 +39,7 @@ 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 = "2.2.0" +local_version = "2.3.0" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") 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 diff --git a/modules/version.py b/modules/version.py index d1493ab..a925217 100644 --- a/modules/version.py +++ b/modules/version.py @@ -4,6 +4,7 @@ import requests import subprocess import sys import logging +import json logger = logging.getLogger("goober") launched = False @@ -34,17 +35,21 @@ def auto_update(branch='main', remote='origin'): else: logger.info(_( "local_ahead").format(remote=remote, branch=branch)) -# Fetch the latest version info from the update server def get_latest_version_info(): try: response = requests.get(UPDATE_URL, timeout=5) if response.status_code == 200: - return response.json() + # Try parsing JSON manually, so i dont have to maintain goober central anymore + try: + return response.json() + except json.JSONDecodeError: + logger.error(f"{RED}{_('version_error')} JSON decode failed{RESET}") + return None else: - logger.error(f"{RED}{_( 'version_error')} {response.status_code}{RESET}") + logger.error(f"{RED}{_('version_error')} {response.status_code}{RESET}") return None except requests.RequestException as e: - logger.error(f"{RED}{_( 'version_error')} {e}{RESET}") + logger.error(f"{RED}{_('version_error')} {e}{RESET}") return None # Check if an update is available and perform update if needed @@ -63,7 +68,7 @@ def check_for_update(): download_url = latest_version_info.get("download_url") if not latest_version or not download_url: - logger.error(f"{RED}{_(LOCALE, 'invalid_server')}{RESET}") + logger.error(f"{RED}{_('invalid_server')}{RESET}") return None, None # Check if local_version is valid From 73185f8708c209a5a5d254a1f1c988da6a33d0d9 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Tue, 15 Jul 2025 23:58:25 +0200 Subject: [PATCH 37/84] IDIOT --- modules/globalvars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index ddea186..00cb139 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -42,5 +42,5 @@ latest_version = "0.0.0" local_version = "2.3.0" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") -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 +beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks \ No newline at end of file From 48bb558549b426b5d3c3f777ac87aae453d84bcd Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 00:01:27 +0200 Subject: [PATCH 38/84] automatic brah --- modules/globalvars.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index 00cb139..f6d572b 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -2,6 +2,16 @@ import os import platform from dotenv import load_dotenv import pathlib +import subprocess +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) @@ -42,5 +52,7 @@ latest_version = "0.0.0" local_version = "2.3.0" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") -beta = False # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks +if get_git_branch() == "dev": + 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 \ No newline at end of file From b7af9c068ee9b68e9d91091c61d2399f4aac5d13 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 00:04:10 +0200 Subject: [PATCH 39/84] throwing shit at the wall --- modules/globalvars.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index f6d572b..eed4b14 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -55,4 +55,5 @@ REACT = os.getenv("REACT") if get_git_branch() == "dev": 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 - \ No newline at end of file +else: + beta = False \ No newline at end of file From b54419702e94bb82b9a9f03ab374fa61f59c5df0 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 00:32:59 +0200 Subject: [PATCH 40/84] no cache --- modules/globalvars.py | 2 +- modules/version.py | 43 ++++++++++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index eed4b14..9f6e2e7 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -49,7 +49,7 @@ 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 = "2.3.0" +local_version = "2.3.1" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") if get_git_branch() == "dev": diff --git a/modules/version.py b/modules/version.py index a925217..b8e8013 100644 --- a/modules/version.py +++ b/modules/version.py @@ -5,6 +5,8 @@ import subprocess import sys import logging import json +import time +import random logger = logging.getLogger("goober") launched = False @@ -37,19 +39,34 @@ def auto_update(branch='main', remote='origin'): def get_latest_version_info(): try: - response = requests.get(UPDATE_URL, timeout=5) - if response.status_code == 200: - # Try parsing JSON manually, so i dont have to maintain goober central anymore - try: - return response.json() - except json.JSONDecodeError: - logger.error(f"{RED}{_('version_error')} JSON decode failed{RESET}") - return None - else: - logger.error(f"{RED}{_('version_error')} {response.status_code}{RESET}") + unique_suffix = f"{int(time.time())}_{random.randint(0, 9999)}" + url = f"{UPDATE_URL}?_={unique_suffix}" + + curl_cmd = [ + "curl", + "-s", + "-H", "Cache-Control: no-cache", + "-H", "Pragma: no-cache", + url + ] + + result = subprocess.run(curl_cmd, capture_output=True, text=True, timeout=5) + content = result.stdout + + if result.returncode != 0: + logger.error(f"curl failed with return code {result.returncode}") return None - except requests.RequestException as e: - logger.error(f"{RED}{_('version_error')} {e}{RESET}") + + try: + data = json.loads(content) + return data + except json.JSONDecodeError: + logger.error("JSON decode failed") + logger.error(content[:500]") + return None + + except Exception as e: + logger.error(f"Exception in get_latest_version_info: {e}") return None # Check if an update is available and perform update if needed @@ -82,7 +99,7 @@ def check_for_update(): logger.info(f"{YELLOW}{_('changelog').format(VERSION_URL=VERSION_URL)}{RESET}") auto_update() elif beta == True: - logger.warning(f"You are running an \"unstable\" version of Goober, do not expect it to work properly.\nVersion {local_version}{RESET}") + logger.warning(f"You are running an \"unstable\" version of Goober, do not expect it to work properly.\nVersion {local_version}\nServer: {latest_version}{RESET}") elif local_version > latest_version: logger.warning(f"{_('modification_warning')}") elif local_version == latest_version: From f16a9e732d1909ae13fd342dfd13f96f2cfa4157 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 00:33:14 +0200 Subject: [PATCH 41/84] idiot.. --- modules/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/version.py b/modules/version.py index b8e8013..8ca5bc9 100644 --- a/modules/version.py +++ b/modules/version.py @@ -62,7 +62,7 @@ def get_latest_version_info(): return data except json.JSONDecodeError: logger.error("JSON decode failed") - logger.error(content[:500]") + logger.error(content[:500]) return None except Exception as e: From 9fa18247e2311e0cb370b936175effa4818788fd Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 00:43:45 +0200 Subject: [PATCH 42/84] forgot to fix up the .env --- example.env | 2 -- 1 file changed, 2 deletions(-) diff --git a/example.env b/example.env index 03c30c6..6792699 100644 --- a/example.env +++ b/example.env @@ -5,11 +5,9 @@ BLACKLISTEDUSERS= OWNERID= USERTRAINENABLED="true" SHOWMEMENABLED="true" -NAME="gooberino goobs" LOCALE=fi ALIVEPING="True" AUTOUPDATE="True" -GOOBERTOKEN= SONG="Basket Case - Green Day" CHECKSDISABLED="Frue" REACT="True" From ed5f19ccf922c96b2534f62f5969b2f21dd16b87 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 00:47:57 +0200 Subject: [PATCH 43/84] goodbye active users --- bot.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/bot.py b/bot.py index 437d777..4e53488 100644 --- a/bot.py +++ b/bot.py @@ -99,17 +99,6 @@ async def load_cogs_from_folder(bot, folder_name="assets/cogs"): logger.error(f"{(_('cog_fail'))} {cog_name} {e}") traceback.print_exc() -async def fetch_active_users() -> str: - try: - response: requests.Response = requests.get(f"{VERSION_URL}/active-users") - if response.status_code == 200: - return response.text.strip() - else: - return "?" - except Exception as e: - logger.e(f"{_('error_fetching_active_users')} {RESET} {e}") - return "?" - async def send_alive_ping_periodically() -> None: while True: try: @@ -134,9 +123,6 @@ async def on_ready() -> None: synced: List[discord.app_commands.AppCommand] = await bot.tree.sync() logger.info(f"{_('synced_commands')} {len(synced)} {(_('synced_commands2'))}") slash_commands_enabled = True - - active_users: str = await fetch_active_users() - logger.info(f"{(_('active_users:'))} {active_users}") logger.info(f"{(_('started')).format(name=NAME)}") bot.loop.create_task(send_alive_ping_periodically()) From 260b7e3360b400e234e0a1fe11bffa04c488da86 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 00:48:15 +0200 Subject: [PATCH 44/84] dead --- assets/cogs/grabtemplate.py | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 assets/cogs/grabtemplate.py diff --git a/assets/cogs/grabtemplate.py b/assets/cogs/grabtemplate.py deleted file mode 100644 index 5a9d8a0..0000000 --- a/assets/cogs/grabtemplate.py +++ /dev/null @@ -1,29 +0,0 @@ -import discord -from discord.ext import commands -import os -import requests -import ast -from modules.globalvars import VERSION_URL - - -class grabTemplate(commands.Cog): - def __init__(self, bot): - self.bot = bot - - def download_json(): - response = requests.get(f"{VERSION_URL}/goob/template.json") - if response.status_code == 200: - if os.path.exists("memory.json"): - return - else: - userinput = input("Do you want to download the template json instead of starting from scratch?\n(Y/N)\n") - if userinput.lower() == "y": - with open("memory.json", "w", encoding="utf-8") as file: - file.write(response.text) - else: - print("Starting from scratch...") - elif response.status_code == 404: - print("File not found on goober central!!") - download_json() -async def setup(bot): - await bot.add_cog(grabTemplate(bot)) From 81b3abf287327773bd704e3cd9ace0de2bd2a530 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 01:32:38 +0200 Subject: [PATCH 45/84] duct tape fix for cogmanager and tested using the image module inside of a cog --- assets/cogs/cogmanager.py | 11 +++-- assets/cogs/fuckup.py | 97 +++++++++++++++++++++++++++++++++++++++ modules/coghooks.py | 51 ++++++++++++++++++++ 3 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 assets/cogs/fuckup.py create mode 100644 modules/coghooks.py diff --git a/assets/cogs/cogmanager.py b/assets/cogs/cogmanager.py index 3bddc5f..017f021 100644 --- a/assets/cogs/cogmanager.py +++ b/assets/cogs/cogmanager.py @@ -1,8 +1,9 @@ import discord from discord.ext import commands -import os from modules.globalvars import ownerid +COG_PREFIX = "assets.cogs." + class CogManager(commands.Cog): def __init__(self, bot): self.bot = bot @@ -16,7 +17,7 @@ class CogManager(commands.Cog): await ctx.send("Please provide the cog name to load.") return try: - await self.bot.load_extension(f"cogs.{cog_name}") + await self.bot.load_extension(COG_PREFIX + cog_name) await ctx.send(f"Loaded cog `{cog_name}` successfully.") except Exception as e: await ctx.send(f"Error loading cog `{cog_name}`: {e}") @@ -30,7 +31,7 @@ class CogManager(commands.Cog): await ctx.send("Please provide the cog name to unload.") return try: - await self.bot.unload_extension(f"cogs.{cog_name}") + await self.bot.unload_extension(COG_PREFIX + cog_name) await ctx.send(f"Unloaded cog `{cog_name}` successfully.") except Exception as e: await ctx.send(f"Error unloading cog `{cog_name}`: {e}") @@ -44,8 +45,8 @@ class CogManager(commands.Cog): await ctx.send("Please provide the cog name to reload.") return try: - await self.bot.unload_extension(f"cogs.{cog_name}") - await self.bot.load_extension(f"cogs.{cog_name}") + await self.bot.unload_extension(COG_PREFIX + cog_name) + await self.bot.load_extension(COG_PREFIX + cog_name) await ctx.send(f"Reloaded cog `{cog_name}` successfully.") except Exception as e: await ctx.send(f"Error reloading cog `{cog_name}`: {e}") diff --git a/assets/cogs/fuckup.py b/assets/cogs/fuckup.py new file mode 100644 index 0000000..0540f09 --- /dev/null +++ b/assets/cogs/fuckup.py @@ -0,0 +1,97 @@ +import discord +from discord.ext import commands +from modules.image import * +from PIL import Image, ImageEnhance, ImageFilter, ImageOps, ImageChops, ImageColor +import os, random, shutil, tempfile + +async def deepfryimage(path): + with Image.open(path).convert("RGB") as im: + # make it burn + for _ in range(3): + im = im.resize((int(im.width * 0.7), int(im.height * 0.7))) + im = im.resize((int(im.width * 1.5), int(im.height * 1.5))) + im = ImageEnhance.Contrast(im).enhance(random.uniform(5, 10)) + im = ImageEnhance.Sharpness(im).enhance(random.uniform(10, 50)) + im = ImageEnhance.Brightness(im).enhance(random.uniform(1.5, 3)) + r, g, b = im.split() + r = r.point(lambda i: min(255, i * random.uniform(1.2, 2.0))) + g = g.point(lambda i: min(255, i * random.uniform(0.5, 1.5))) + b = b.point(lambda i: min(255, i * random.uniform(0.5, 2.0))) + channels = [r, g, b] + random.shuffle(channels) + im = Image.merge("RGB", tuple(channels)) + overlay_color = tuple(random.randint(0, 255) for _ in range(3)) + overlay = Image.new("RGB", im.size, overlay_color) + im = ImageChops.add(im, overlay, scale=2.0, offset=random.randint(-64, 64)) + + im = im.filter(ImageFilter.EDGE_ENHANCE_MORE) + im = im.filter(ImageFilter.GaussianBlur(radius=random.uniform(0.5, 2))) + for _ in range(3): + tmp_path = tempfile.mktemp(suffix=".jpg") + im.save(tmp_path, format="JPEG", quality=random.randint(5, 15)) + im = Image.open(tmp_path) + if random.random() < 0.3: + im = ImageOps.posterize(im, bits=random.choice([2, 3, 4])) + if random.random() < 0.2: + im = ImageOps.invert(im) + out_path = tempfile.mktemp(suffix=".jpg") + im.save(out_path, format="JPEG", quality=5) + return out_path + + +class whami(commands.Cog): + def __init__(self, bot): + self.bot = bot + + + @commands.command() + async def fuckup(self, ctx): + assets_folder = "assets/images" + temp_input = None + + def get_random_asset_image(): + files = [f for f in os.listdir(assets_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp'))] + if not files: + return None + return os.path.join(assets_folder, random.choice(files)) + + if ctx.message.attachments: + attachment = ctx.message.attachments[0] + if attachment.content_type and attachment.content_type.startswith("image/"): + ext = os.path.splitext(attachment.filename)[1] + temp_input = f"tempy{ext}" + await attachment.save(temp_input) + input_path = temp_input + else: + fallback_image = get_random_asset_image() + if fallback_image is None: + await ctx.reply(_('no_image_available')) + return + temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) + shutil.copy(fallback_image, temp_input) + input_path = temp_input + else: + fallback_image = get_random_asset_image() + if fallback_image is None: + await ctx.reply(_('no_image_available')) + return + temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) + shutil.copy(fallback_image, temp_input) + input_path = temp_input + + output_path = await gen_meme(input_path) + + if output_path is None or not os.path.isfile(output_path): + if temp_input and os.path.exists(temp_input): + os.remove(temp_input) + await ctx.reply(_('failed_generate_image')) + return + + deepfried_path = await deepfryimage(output_path) + await ctx.send(file=discord.File(deepfried_path)) + + if temp_input and os.path.exists(temp_input): + os.remove(temp_input) + +async def setup(bot): + await bot.add_cog(whami(bot)) diff --git a/modules/coghooks.py b/modules/coghooks.py new file mode 100644 index 0000000..ab56339 --- /dev/null +++ b/modules/coghooks.py @@ -0,0 +1,51 @@ +import os +import importlib +import inspect +import sys + +_hooks = {} + +def register_hook(name, func): + if name not in _hooks: + _hooks[name] = [] + _hooks[name].append(func) + +async def call_hook(name, *args, **kwargs): + if name not in _hooks: + return + for func in _hooks[name]: + if callable(func): + if inspect.iscoroutinefunction(func): + await func(*args, **kwargs) + else: + func(*args, **kwargs) + +def register_all_functions_as_hooks(module): + """Register every function/coroutine function in the given module as a hook under its function name.""" + for name, obj in inspect.getmembers(module): + if inspect.isfunction(obj) or inspect.iscoroutinefunction(obj): + register_hook(name, obj) + +def _register_all_modules_functions(): + """Scan all python modules in this 'modules' folder (excluding this file) and register their functions.""" + # Calculate the path to the modules directory relative to this file + modules_dir = os.path.dirname(os.path.abspath(__file__)) + + # Current file name so we skip it + current_file = os.path.basename(__file__) + + # Ensure 'modules' is in sys.path for importlib to work + if modules_dir not in sys.path: + sys.path.insert(0, modules_dir) + + # List all python files except dunder files and this file + for filename in os.listdir(modules_dir): + if filename.endswith(".py") and not filename.startswith("__") and filename != current_file: + module_name = filename[:-3] # strip .py extension + try: + module = importlib.import_module(module_name) + register_all_functions_as_hooks(module) + except Exception as e: + print(f"[hooks] Failed to import {module_name}: {e}") + + From c19ee38c9af71465334cb6a8ef0347be110d3081 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 11:02:25 +0200 Subject: [PATCH 46/84] cleaned some crap up and updated volta --- assets/cogs/fuckup.py | 1 + example.env | 1 - modules/volta/main.py | 58 ++++++++++++++++++++++++++++++++++++------- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/assets/cogs/fuckup.py b/assets/cogs/fuckup.py index 0540f09..1dbc4d0 100644 --- a/assets/cogs/fuckup.py +++ b/assets/cogs/fuckup.py @@ -1,6 +1,7 @@ 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 diff --git a/example.env b/example.env index 6792699..8913dff 100644 --- a/example.env +++ b/example.env @@ -6,7 +6,6 @@ OWNERID= USERTRAINENABLED="true" SHOWMEMENABLED="true" LOCALE=fi -ALIVEPING="True" AUTOUPDATE="True" SONG="Basket Case - Green Day" CHECKSDISABLED="Frue" diff --git a/modules/volta/main.py b/modules/volta/main.py index 03f5d4f..fb6a080 100644 --- a/modules/volta/main.py +++ b/modules/volta/main.py @@ -3,6 +3,7 @@ # For updates or contributions, visit: https://github.com/gooberinc/volta # Also, Note to self: Add more comments it needs more love import os +import locale import json import pathlib import threading @@ -16,8 +17,6 @@ YELLOW = f"{ANSI}33m" DEBUG = f"{ANSI}1;30m" RESET = f"{ANSI}0m" -load_dotenv() - LOCALE = os.getenv("LOCALE") module_dir = pathlib.Path(__file__).parent.parent working_dir = pathlib.Path.cwd() @@ -62,6 +61,37 @@ if working_dir != module_dir: translations = {} _file_mod_times = {} +import locale +import platform +import os +import sys + +def get_system_locale(): + system = platform.system() # fallback incase locale isnt set + if system == "Windows": + lang, _ = locale.getdefaultlocale() + return lang or os.getenv("LANG") + elif system == "Darwin": + try: + import subprocess + result = subprocess.run( + ["defaults", "read", "-g", "AppleLocale"], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + text=True + ) + return result.stdout.strip() or locale.getdefaultlocale()[0] + except Exception: + return locale.getdefaultlocale()[0] + elif system == "Linux": + return ( + os.getenv("LC_ALL") or + os.getenv("LANG") or + locale.getdefaultlocale()[0] + ) + return locale.getdefaultlocale()[0] + + def load_translations(): global translations, _file_mod_times translations.clear() @@ -99,7 +129,9 @@ def reload_if_changed(): def set_language(lang: str): global LOCALE, ENGLISH_MISSING - if lang in translations: + if not LOCALE: + LOCALE = get_system_locale() + elif lang in translations: LOCALE = lang else: print(f"[VOLTA] {RED}Language '{lang}' not found, defaulting to 'en'{RESET}") @@ -141,19 +173,27 @@ def check_missing_translations(): else: print(f"[VOLTA] All translation keys present for locale: {LOCALE}") +printedsystemfallback = False def get_translation(lang: str, key: str): + global printedsystemfallback if ENGLISH_MISSING: return f"[VOLTA] {RED}No fallback available!{RESET}" + fallback_translations = translations.get(FALLBACK_LOCALE, {}) + sys_lang = get_system_locale().split("_")[0] if get_system_locale() else None + sys_translations = translations.get(sys_lang, {}) if sys_lang else {} lang_translations = translations.get(lang, {}) if key in lang_translations: return lang_translations[key] - else: - if key not in translations.get(FALLBACK_LOCALE, {}): - return f"[VOLTA] {YELLOW}Missing key: '{key}' in {FALLBACK_LOCALE}.json!{RESET}" - fallback = translations.get(FALLBACK_LOCALE, {}).get(key, key) - print(f"[VOLTA] {YELLOW}Missing key: '{key}' in language '{lang}', falling back to: '{fallback}' using {FALLBACK_LOCALE}.json{RESET}") # yeah probably print this - return fallback + if sys_lang and sys_lang != lang and key in sys_translations: + if not printedsystemfallback: + print(f"[VOLTA] {YELLOW}Falling back to system language {sys_lang}!{RESET}") + printedsystemfallback = True + return sys_translations[key] + if key in fallback_translations: + print(f"[VOLTA] {YELLOW}Missing key: '{key}' in '{lang}', falling back to fallback locale '{FALLBACK_LOCALE}'{RESET}") + return fallback_translations[key] + return f"[VOLTA] {YELLOW}Missing key: '{key}' in all locales!{RESET}" def _(key: str) -> str: return get_translation(LOCALE, key) From e333e9bba2009fe9a806a3d28a83d4931ff83928 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 11:06:07 +0200 Subject: [PATCH 47/84] almost forgot to bump up the version by 1 --- modules/globalvars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index 9f6e2e7..23e7e79 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -49,7 +49,7 @@ 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 = "2.3.1" +local_version = "2.3.2" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") if get_git_branch() == "dev": From 5c81bcca68e0dcfdbdf4ac4d8657e81ca25ac6a3 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 12:15:17 +0200 Subject: [PATCH 48/84] probably wasnt a good idea tbh --- assets/locales/en.json | 2 +- assets/locales/it.json | 2 +- bot.py | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/assets/locales/en.json b/assets/locales/en.json index 29a07cc..eb724eb 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -99,7 +99,7 @@ "command_markov_retrain": "Retraining the Markov model... Please wait.", "command_markov_memory_not_found": "Error: memory file not found!", "command_markov_memory_is_corrupt": "Error: memory file is corrupt!", - "command_markov_retraining": "Processing {processed_data}/{data_size} data points...", + "command_markov_retraining": "Processing {data_size} data points...", "command_markov_retrain_successful": "Markov model retrained successfully using {data_size} data points!", "command_desc_talk":"talks n like stuf", "command_talk_insufficent_text": "I need to learn more from messages before I can talk.", diff --git a/assets/locales/it.json b/assets/locales/it.json index 734e0d2..efb5a60 100644 --- a/assets/locales/it.json +++ b/assets/locales/it.json @@ -100,7 +100,7 @@ "command_markov_retrain": "Rafforzamento del modello Markov in corso... Attendere.", "command_markov_memory_not_found": "Errore: file di memoria non trovato!", "command_markov_memory_is_corrupt": "Errore: file di memoria corrotto!", - "command_markov_retraining": "Elaborazione di {processed_data}/{data_size} punti dati...", + "command_markov_retraining": "Elaborazione di {data_size} punti dati...", "command_markov_retrain_successful": "Modello Markov rafforzato con successo utilizzando {data_size} punti dati!", "command_desc_talk": "parla n come stuf", "command_talk_insufficent_text": "Ho bisogno di imparare di più dai messaggi prima di poter parlare.", diff --git a/bot.py b/bot.py index 4e53488..358ef07 100644 --- a/bot.py +++ b/bot.py @@ -180,8 +180,6 @@ async def retrain(ctx: commands.Context) -> None: for i, data in enumerate(memory): processed_data += 1 - if processed_data % 1000 == 0 or processed_data == data_size: - await send_message(ctx, f"{_('command_markov_retraining').format(processed_data=processed_data, data_size=data_size)}", edit=True, message_reference=processing_message_ref) global markov_model markov_model = train_markov_model(memory) From 9a4ac706b91d1d2a221068f2182f17eedc59d414 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 12:28:36 +0200 Subject: [PATCH 49/84] yeahhh lets be real its europe they probably know one of these languages anyway --- assets/locales/README.md | 2 +- assets/locales/et.json | 131 --------------------------------------- assets/locales/fy.json | 125 ------------------------------------- assets/locales/mt.json | 125 ------------------------------------- 4 files changed, 1 insertion(+), 382 deletions(-) delete mode 100644 assets/locales/et.json delete mode 100644 assets/locales/fy.json delete mode 100644 assets/locales/mt.json diff --git a/assets/locales/README.md b/assets/locales/README.md index e2dff6a..5fd0e77 100644 --- a/assets/locales/README.md +++ b/assets/locales/README.md @@ -3,6 +3,6 @@ This folder contains localization files for the project. **Notice:** -The Maltese, Frisian, Estonian, Spanish, and French locales currently do not have maintainers. If you are interested in helping maintain or improve these translations, please consider contributing! +The Spanish and French locales currently do not have maintainers. If you are interested in helping maintain or improve these translations, please consider contributing! Thank you for supporting localization efforts! \ No newline at end of file diff --git a/assets/locales/et.json b/assets/locales/et.json deleted file mode 100644 index a169d05..0000000 --- a/assets/locales/et.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "checks_disabled": "Tarkistukset on poistettu käytöstä!", - "active_users:": "Aktiiviset käyttäjät:", - "spacy_initialized": "spaCy ja spacytextblob ovat valmiita.", - "spacy_model_not_found": "spaCy mallia ei löytynyt! Ladataan se....`", - "unhandled_exception": "Käsittelemätön virhe tapahtui. Ilmoita tästä GitHubiin.", - "env_file_not_found": ".env faili ei leitud! Palun loo see vajalike muutujatega.", - "error_fetching_active_users": "Aktiivsete kasutajate hankimisel tekkis viga: {error}", - "error_sending_alive_ping": "Elusoleku ping'i saatmisel tekkis viga: {error}", - "already_started": "Olen juba käivitatud! Ei uuenda...", - "please_restart": "Palun taaskäivita goober!", - "local_ahead": "Kohalik {remote}/{branch} on ees või ajakohane. Ei uuenda...", - "remote_ahead": "Kaug-{remote}/{branch} on ees. Uuendan...", - "cant_find_local_version": "Ma ei leia local_version muutujat! Või on seda muudetud ja see pole täisarv!", - "running_prestart_checks": "Käivitamiseelsed kontrollid käivad...", - "continuing_in_seconds": "Jätkan {seconds} sekundi pärast... Vajuta suvalist klahvi, et vahele jätta.", - "missing_requests_psutil": "Puuduvad requests ja psutil! Palun paigalda need käsuga: `pip install requests psutil`", - "requirements_not_found": "requirements.txt faili ei leitud asukohast {path}, kas seda on muudetud?", - "warning_failed_parse_imports": "Hoiatus: Importide parsimine ebaõnnestus failist {filename}: {error}", - "cogs_dir_not_found": "Cogs kataloogi ei leitud asukohast {path}, jätan vahele.", - "std_lib_local_skipped": "STD LIB / KOHALIK {package} (kontroll vahele jäetud)", - "ok_installed": "OK", - "missing_package": "PUUDUB", - "missing_package2": "ei ole paigaldatud", - "missing_packages_detected": "Tuvastati puuduvad paketid:", - "telling_goober_central": "Teavitan goober central aadressil {url}", - "failed_to_contact": "Ei õnnestunud ühendust saada {url}: {error}", - "all_requirements_satisfied": "Kõik nõuded on täidetud.", - "ping_to": "Ping aadressile {host}: {latency} ms", - "high_latency": "Tuvastatud kõrge latentsus! Võid kogeda viivitusi vastustes.", - "could_not_parse_latency": "Latentsust ei õnnestunud parsida.", - "ping_failed": "Ping aadressile {host} ebaõnnestus.", - "error_running_ping": "Ping'i käivitamisel tekkis viga: {error}", - "memory_usage": "Mälu kasutus: {used} GB / {total} GB ({percent}%)", - "memory_above_90": "Mälu kasutus on üle 90% ({percent}%). Kaalu mälu vabastamist.", - "total_memory": "Kogu mälu: {total} GB", - "used_memory": "Kasutatud mälu: {used} GB", - "low_free_memory": "Tuvastatud vähe vaba mälu! Ainult {free} GB saadaval.", - "measuring_cpu": "Mõõdan protsessori kasutust tuuma kohta...", - "core_usage": "Tuuma {idx}: [{bar}] {usage}%", - "total_cpu_usage": "Kogu protsessori kasutus: {usage}%", - "high_avg_cpu": "Kõrge keskmine protsessori kasutus: {usage}%", - "really_high_cpu": "Väga kõrge protsessori koormus! Süsteem võib hakata tõrkuma.", - "memory_file": "Mälufail: {size} MB", - "memory_file_large": "Mälufail on 1GB või suurem, kaalu selle kustutamist ruumi vabastamiseks.", - "memory_file_corrupted": "Mälufail on rikutud! JSON dekodeerimise viga: {error}", - "consider_backup_memory": "Kaalu mälufaili varundamist ja uuesti loomist.", - "memory_file_encoding": "Mälufailil on kodeeringu probleemid: {error}", - "error_reading_memory": "Viga mälufaili lugemisel: {error}", - "memory_file_not_found": "Mälufaili ei leitud.", - "modification_warning": "Gooberit on muudetud! Kõik muudatused lähevad uuendamisel kaotsi!", - "reported_version": "Teatatud versioon:", - "current_hash": "Praegune räsi:", - "not_found": "ei leitud!", - "version_error": "Versiooni infot ei õnnestunud hankida. Staatuskood", - "loaded_cog": "Laaditud cog:", - "loaded_cog2": "Laaditud moodul:", - "cog_fail": "Cog'i laadimine ebaõnnestus:", - "cog_fail2": "Mooduli laadimine ebaõnnestus:", - "no_model": "Salvestatud Markovi mudelit ei leitud. Alustan nullist.", - "folder_created": "Kaust '{folder_name}' loodud.", - "folder_exists": "Kaust '{folder_name}' on juba olemas. Jätan vahele...", - "logged_in": "Sisse logitud kui", - "synced_commands": "Sünkroonitud", - "synced_commands2": "käsku!", - "fail_commands_sync": "Käskude sünkroonimine ebaõnnestus:", - "started": "{name} on käivitatud!", - "name_check": "Viga nime saadavuse kontrollimisel:", - "name_taken": "Nimi on juba võetud. Palun vali teine nimi.", - "name_check2": "Viga nime saadavuse kontrolli ajal:", - "add_token": "Token: {token}\nPalun lisa see token oma .env faili kui", - "token_exists": "Token on juba .env failis olemas. Kasutan olemasolevat tokenit.", - "registration_error": "Registreerimisel tekkis viga:", - "version_backup": "Varukoopia loodud:", - "backup_error": "Viga: {LOCAL_VERSION_FILE} ei leitud varundamiseks.", - "model_loaded": "Markovi mudel laaditud asukohast", - "fetch_update_fail": "Uuenduse infot ei õnnestunud hankida.", - "invalid_server": "Viga: Serverilt saadud versiooni info on vigane.", - "goober_server_alert": "Hoiatus goober centralilt!\n", - "new_version": "Uus versioon saadaval: {latest_version} (Praegune: {local_version})", - "changelog": "Vaata {VERSION_URL}/goob/changes.txt, et näha muudatuste logi\n\n", - "invalid_version": "Versioon: {local_version} ei ole kehtiv!", - "invalid_version2": "Kui see on tahtlik, siis ignoreeri seda teadet, muidu vajuta Y, et tõmmata serverist kehtiv versioon sõltumata praegu töötavast gooberi versioonist", - "invalid_version3": "Praegune versioon varundatakse faili current_version.bak..", - "input": "(Y või mõni muu klahv, et ignoreerida....)", - "modification_ignored": "Oled muutnud", - "modification_ignored2": "IGNOREWARNING on seatud väärtusele false..", - "latest_version": "Kasutad uusimat versiooni:", - "latest_version2": "Vaata {VERSION_URL}/goob/changes.txt, et näha muudatuste logi", - "pinging_disabled": "Pingimine on keelatud! Ei teavita serverit...", - "goober_ping_success": "Sisse logitud goober centralisse kui {NAME}", - "goober_ping_fail": "Andmete saatmine ebaõnnestus. Server tagastas staatuse:", - "goober_ping_fail2": "Andmete saatmisel tekkis viga:", - "sentence_positivity": "Lause positiivsus on:", - "command_edit_fail": "Sõnumi muutmine ebaõnnestus:", - "command_desc_retrain": "Treeni Markovi mudelit käsitsi uuesti.", - "command_markov_retrain": "Markovi mudelit treenitakse uuesti... Palun oota.", - "command_markov_memory_not_found": "Viga: mälufaili ei leitud!", - "command_markov_memory_is_corrupt": "Viga: mälufail on rikutud!", - "command_markov_retraining": "Töötlen {processed_data}/{data_size} andmepunkti...", - "command_markov_retrain_successful": "Markovi mudel treeniti edukalt uuesti, kasutades {data_size} andmepunkti!", - "command_desc_talk": "räägib ja muud sellist", - "command_talk_insufficent_text": "Mul on vaja rohkem sõnumeid õppimiseks, enne kui saan rääkida.", - "command_talk_generation_fail": "Mul pole hetkel midagi öelda!", - "command_desc_help": "abi", - "command_help_embed_title": "Boti abi", - "command_help_embed_desc": "Käskude loetelu kategooriate kaupa.", - "command_help_categories_general": "Üldine", - "command_help_categories_admin": "Administreerimine", - "command_help_categories_custom": "Kohandatud käsud", - "command_ran": "Info: {message.author.name} käivitas {message.content}", - "command_ran_s": "Info: {interaction.user} käivitas ", - "command_desc_ping": "ping", - "command_ping_embed_desc": "Boti latentsus:", - "command_ping_footer": "Soovis", - "command_about_desc": "teave", - "command_about_embed_title": "Minust", - "command_about_embed_field1": "Nimi", - "command_about_embed_field2name": "Versioon", - "command_about_embed_field2value": "Kohalik: {local_version} \nViimane: {latest_version}", - "command_desc_stats": "statistika", - "command_stats_embed_title": "Boti statistika", - "command_stats_embed_desc": "Andmed boti mälu kohta.", - "command_stats_embed_field1name": "Faili statistika", - "command_stats_embed_field1value": "Suurus: {file_size} baiti\nRead: {line_count}", - "command_stats_embed_field2name": "Versioon", - "command_stats_embed_field2value": "Kohalik: {local_version} \nViimane: {latest_version}", - "command_stats_embed_field3name": "Muutuja info", - "command_stats_embed_field3value": "Nimi: {NAME} \nPrefiks: {PREFIX} \nOmaniku ID: {ownerid}\nPingirida: {PING_LINE} \nMälu jagamine lubatud: {showmemenabled} \nKasutajaõpe lubatud: {USERTRAIN_ENABLED}\nLaul: {song} \nSplashtekst: ```{splashtext}```" -} - diff --git a/assets/locales/fy.json b/assets/locales/fy.json deleted file mode 100644 index 5881b5b..0000000 --- a/assets/locales/fy.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "env_file_not_found": "It .env-bestân is net fûn! Meitsje ien mei de fereaske fariabelen.", - "error_fetching_active_users": "Flater by it opheljen fan aktive brûkers: {error}", - "error_sending_alive_ping": "Flater by it ferstjoeren fan alive ping: {error}", - "already_started": "Ik bin al start! Gjin update...", - "please_restart": "Start opnij, goober!", - "local_ahead": "Lokaal {remote}/{branch} is foarút of by de tiid. Gjin update...", - "remote_ahead": "Remote {remote}/{branch} is foarút. Update wurdt útfierd...", - "cant_find_local_version": "Kin de local_version-fariabele net fine! Of it is ferboud en gjin integer mear!", - "running_prestart_checks": "Pre-start kontrôles wurde útfierd...", - "continuing_in_seconds": "Trochgean oer {seconds} sekonden... Druk op in toets om oer te slaan.", - "missing_requests_psutil": "Requests en psutil ûntbrekke! Ynstallearje se mei pip: `pip install requests psutil`", - "requirements_not_found": "requirements.txt net fûn op {path}, is it ferboud?", - "warning_failed_parse_imports": "Warskôging: Ynlêzen fan imports út {filename} mislearre: {error}", - "cogs_dir_not_found": "Cogs-map net fûn op {path}, oerslein.", - "std_lib_local_skipped": "STD LIB / LOKAAL {package} (kontrôle oerslein)", - "ok_installed": "OK", - "missing_package": "NET FÛN", - "missing_package2": "is net ynstallearre", - "missing_packages_detected": "Untbrekkende pakketten fûn:", - "telling_goober_central": "Ynformaasje ferstjoerd nei goober central op {url}", - "failed_to_contact": "Kontakt mei {url} mislearre: {error}", - "all_requirements_satisfied": "Alle easken binne foldien.", - "ping_to": "Ping nei {host}: {latency} ms", - "high_latency": "Hege latency ûntdutsen! Jo kinne fertraging ûnderfine.", - "could_not_parse_latency": "Latency koe net lêzen wurde.", - "ping_failed": "Ping nei {host} mislearre.", - "error_running_ping": "Flater by it útfieren fan ping: {error}", - "memory_usage": "Unthâldbrûk: {used} GB / {total} GB ({percent}%)", - "memory_above_90": "Unthâldbrûk is boppe 90% ({percent}%). Frij wat ûnthâld op.", - "total_memory": "Totaal Unthâld: {total} GB", - "used_memory": "Brûkt Unthâld: {used} GB", - "low_free_memory": "Leech frij ûnthâld ûntdutsen! Mar {free} GB beskikber.", - "measuring_cpu": "CPU-brûk per kearn wurdt metten...", - "core_usage": "Kearn {idx}: [{bar}] {usage}%", - "total_cpu_usage": "Totale CPU-brûk: {usage}%", - "high_avg_cpu": "Heech gemiddeld CPU-brûk: {usage}%", - "really_high_cpu": "Tige hege CPU-lêst! It systeem kin traach wurde of hingje.", - "memory_file": "Unthâld-bestân: {size} MB", - "memory_file_large": "Unthâld-bestân is 1GB of mear, wiskje om romte frij te meitsjen.", - "memory_file_corrupted": "Unthâld-bestân is skansearre! JSON decode-flater: {error}", - "consider_backup_memory": "Tink derom om in reservekopy fan it ûnthâld-bestân te meitsjen en opnij oan te meitsjen.", - "memory_file_encoding": "Unthâld-bestân hat enkodearingsproblemen: {error}", - "error_reading_memory": "Flater by it lêzen fan ûnthâld-bestân: {error}", - "memory_file_not_found": "Unthâld-bestân net fûn.", - "modification_warning": "Goober is oanpast! Wizigingen sille ferlern gean by in update!", - "reported_version": "Melde ferzje:", - "current_hash": "Aktuele Hash:", - "not_found": "net fûn!", - "version_error": "Koe ferzje-ynfo net ophelje. Statuskoade", - "loaded_cog": "Cog laden:", - "loaded_cog2": "Module laden:", - "cog_fail": "Cog laden mislearre:", - "cog_fail2": "Module laden mislearre:", - "no_model": "Gjin bewarre Markov-model fûn. Fanôf nij begjinne.", - "folder_created": "Map '{folder_name}' oanmakke.", - "folder_exists": "Map '{folder_name}' bestiet al. oerslein...", - "logged_in": "Ynlogd as", - "synced_commands": "Kommando's syngronisearre", - "synced_commands2": "kommando's!", - "fail_commands_sync": "Syngronisaasje fan kommando's mislearre:", - "started": "{name} is begûn!", - "name_check": "Flater by it kontrolearjen fan namme beskikberens:", - "name_taken": "Namme is al yn gebrûk. Kies in oare.", - "name_check2": "Flater by nammekontrôle:", - "add_token": "Token: {token}\nFoegje dizze token ta oan jo .env-bestân as", - "token_exists": "Token bestiet al yn .env. Fierder mei besteande token.", - "registration_error": "Flater by registraasje:", - "version_backup": "Reservekopy makke:", - "backup_error": "Flater: {LOCAL_VERSION_FILE} net fûn foar reservekopy.", - "model_loaded": "Markov-model laden fan", - "fetch_update_fail": "Koe update-ynformaasje net ophelje.", - "invalid_server": "Flater: Unjildige ferzje ynformaasje ûntfongen fan server.", - "goober_server_alert": "Warskôging fan goober central!\n", - "new_version": "Nije ferzje beskikber: {latest_version} (Aktueel: {local_version})", - "changelog": "Sjoch {VERSION_URL}/goob/changes.txt foar de wizigingslog\n\n", - "invalid_version": "De ferzje: {local_version} is net jildich!", - "invalid_version2": "As dit bewust is, negearje dit dan. Oars druk op Y om in jildige ferzje fan 'e server te heljen.", - "invalid_version3": "De aktuele ferzje wurdt bewarre as current_version.bak..", - "input": "(Y of in oare toets om te negearjen....)", - "modification_ignored": "Jo hawwe oanpassingen dien oan", - "modification_ignored2": "IGNOREWARNING is ynsteld op false..", - "latest_version": "Jo brûke de lêste ferzje:", - "latest_version2": "Sjoch {VERSION_URL}/goob/changes.txt foar de wizigingslog", - "pinging_disabled": "Pingjen is útskeakele! Gjin melding oan de server...", - "goober_ping_success": "Ynlogd op goober central as {NAME}", - "goober_ping_fail": "Ferstjoeren fan gegevens mislearre. Server joech statuskoade:", - "goober_ping_fail2": "Flater by it ferstjoeren fan gegevens:", - "sentence_positivity": "Positiviteit fan sin is:", - "command_edit_fail": "Flater by it bewurkjen fan berjocht:", - "command_desc_retrain": "Traind it Markov-model opnij.", - "command_markov_retrain": "Markov-model wurdt opnij traind... Wachtsje efkes.", - "command_markov_memory_not_found": "Flater: ûnthâld-bestân net fûn!", - "command_markov_memory_is_corrupt": "Flater: ûnthâld-bestân is skansearre!", - "command_markov_retraining": "Ferwurket {processed_data}/{data_size} gegevenspunten...", - "command_markov_retrain_successful": "Markov-model mei sukses opnij traind mei {data_size} gegevenspunten!", - "command_desc_talk": "praat en sa", - "command_talk_insufficent_text": "Ik moat mear leare fan berjochten foardat ik prate kin.", - "command_talk_generation_fail": "Ik ha no neat te sizzen!", - "command_desc_help": "help", - "command_help_embed_title": "Bot Help", - "command_help_embed_desc": "List mei kommando's groepearre op kategory.", - "command_help_categories_general": "Algemien", - "command_help_categories_admin": "Behear", - "command_help_categories_custom": "Oanpaste Kommando's", - "command_ran": "Info: {message.author.name} hat {message.content} útfierd", - "command_ran_s": "Info: {interaction.user} hat útfierd ", - "command_desc_ping": "ping", - "command_ping_embed_desc": "Bot Latency:", - "command_ping_footer": "Frege troch", - "command_about_desc": "oer", - "command_about_embed_title": "Oer my", - "command_about_embed_field1": "Namme", - "command_about_embed_field2name": "Ferzje", - "command_about_embed_field2value": "Lokaal: {local_version} \nLêste: {latest_version}", - "command_desc_stats": "stats", - "command_stats_embed_title": "Bot Statistyk", - "command_stats_embed_desc": "Gegevens oer it ûnthâld fan de bot.", - "command_stats_embed_field1name": "Bestân Statistyk", - "command_stats_embed_field1value": "Grutte: {file_size} bytes\nRigels: {line_count}", - "command_stats_embed_field2name": "Ferzje", - "command_stats_embed_field2value": "Lokaal: {local_version} \nLêste: {latest_version}", - "command_stats_embed_field3name": "Fariabel Info", - "command_stats_embed_field3value": "Namme: {NAME} \nFoarheaksel: {PREFIX} \nEigner ID: {ownerid}\nPingrigel: {PING_LINE} \nUnthâld Dielen Ynskeakele: {showmemenabled} \nBrûkerstraining Ynskeakele: {USERTRAIN_ENABLED}\nLiet: {song} \nSplashtekst: ```{splashtext}```" -} diff --git a/assets/locales/mt.json b/assets/locales/mt.json deleted file mode 100644 index cb41722..0000000 --- a/assets/locales/mt.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "env_file_not_found": "Ma nstab l‑file .env! Oħloq wieħed bil‑varjabbli meħtieġa.", - "error_fetching_active_users": "Żball waqt il‑ġbir tal‑utenti attivi: {error}", - "error_sending_alive_ping": "Żball biex jintbagħat l‑alive ping: {error}", - "already_started": "Illum diġà bdejt! Ma sejħinx l‑aġġornament…", - "please_restart": "Irristartja jekk jogħġbok, goober!", - "local_ahead": "L‑lokali {remote}/{branch} qiegħed qabel jew parallelament. Ma qed naġġornax…", - "remote_ahead": "Il‑remote {remote}/{branch} hu 'l quddiem. Qed naġġorna…", - "cant_find_local_version": "Ma nistax insib il‑varjabbli local_version! Jew ġie ħassar u mhux intier!", - "running_prestart_checks": "Qed inħadmu ċ‑ċekkjijiet tal‑pre‑start…", - "continuing_in_seconds": "Se nkunu qed inkomplu fi żmien {seconds} sekondi… Agħfas kwalunkwe buttuna biex tiċċekkja.", - "missing_requests_psutil": "M’hemmx requests u psutil! Poġġihom b’pip: `pip install requests psutil`", - "requirements_not_found": "requirements.txt mhux instab f’{path}, ġie modifikat?", - "warning_failed_parse_imports": "Twissija: Parse tal‑imports f’{filename} fallita: {error}", - "cogs_dir_not_found": "Direttorju “cogs” mhux instab f’{path}, qed niskippjaw l‑iskan.", - "std_lib_local_skipped": "STD LIB / LOKALI {package} (ċekk skippjat)", - "ok_installed": "OK", - "missing_package": "NINNEŻER", - "missing_package2": "mhux installat", - "missing_packages_detected": "Parzijiet nieqsa ġew skoperti:", - "telling_goober_central": "Qed ninfurmaw lill‑goober central f’{url}", - "failed_to_contact": "Falliment fil‑kuntatt ma’ {url}: {error}", - "all_requirements_satisfied": "Il‑ħtiġijiet kollha laqgħu.", - "ping_to": "Ping lejn {host}: {latency} ms", - "high_latency": "Latentizza għolja skoperta! Possibbli dewmien fir‑rispons.", - "could_not_parse_latency": "Ma nistax ninterpreta l‑latentizza.", - "ping_failed": "Ping lejn {host} falla.", - "error_running_ping": "Żball fl‑eżekuzzjoni tal‑ping: {error}", - "memory_usage": "Użu ta' memorja: {used} GB / {total} GB ({percent}%)", - "memory_above_90": "Użu ta’ memorja fuq 90% ({percent}%). Ħsieb li tnaddaf xi ħaġa.", - "total_memory": "Memorja totali: {total} GB", - "used_memory": "Memorja użata: {used} GB", - "low_free_memory": "Memorja ħielsa baxxa! Biss {free} GB disponibbli.", - "measuring_cpu": "Qed nkejlu l‑CPU għal kull core…", - "core_usage": "Core {idx}: [{bar}] {usage}%", - "total_cpu_usage": "Użu totali tal‑CPU: {usage}%", - "high_avg_cpu": "Użu medju għoli tal‑CPU: {usage}%", - "really_high_cpu": "Użu eċċessiv tal‑CPU! Is‑sistema tista’ tieħu throttle jew tieqaf.", - "memory_file": "Fajl tal‑memorja: {size} MB", - "memory_file_large": "Il‑fajl tal‑memorja hu 1 GB jew aktar, ħsieb biex tiċċara għandu tagħmel sens.", - "memory_file_corrupted": "Il‑fajl tal‑memorja hu korrott! Żball JSON decode: {error}", - "consider_backup_memory": "Inħeġġuk biex tagħmel backup u terġa’ toħloq il‑fajl tal‑memorja.", - "memory_file_encoding": "Il‑fajl tal‑memorja għandu issues ta’ kodifika: {error}", - "error_reading_memory": "Żball fil‑qari tal‑fajl tal‑memorja: {error}", - "memory_file_not_found": "Fajl tal‑memorja mhux instab.", - "modification_warning": "Il‑Goober ġie mmodifikat! Kull tibdil jintilef fl‑aġġornament!", - "reported_version": "Verżjoni rrappurtata:", - "current_hash": "Hash attwali:", - "not_found": "mhux instab!", - "version_error": "Ma nistax niġbor informazzjoni dwar il‑verżjoni. Kodiċi ta’ stat", - "loaded_cog": "Cog imlaħħaq:", - "loaded_cog2": "Modulu imlaħħaq:", - "cog_fail": "Falliment fit‑tagħbija tal‑cog:", - "cog_fail2": "Falliment fit‑tagħbija tal‑modulu:", - "no_model": "Ma nstab l-ebda Markov model maħżun. Bdejt minn null.", - "folder_created": "Folder ‘{folder_name}’ ġie maħluq.", - "folder_exists": "Folder ‘{folder_name}’ diġà teżisti. qed niskippja…", - "logged_in": "Id‑depożitu bħala", - "synced_commands": "Sync sseħħ", - "synced_commands2": "kmandi!", - "fail_commands_sync": "Falliment tas‑sync tal‑kmandi:", - "started": "{name} beda!", - "name_check": "Żball fil‑ċekk tal‑isem:", - "name_taken": "L‑isem diġà jieħu. Sib ieħor, jekk jogħġbok.", - "name_check2": "Żball waqt iċ‑ċekk tal‑availabbiltà ta’ l‑isem:", - "add_token": "Token: {token}\nŻid dan fil‑.env bħala", - "token_exists": "Token diġà jeżisti fil‑.env. Qed inkomplu bil‑token attwali.", - "registration_error": "Żball fir‑reġistrazzjoni:", - "version_backup": "Backup maħluq:", - "backup_error": "Żball: {LOCAL_VERSION_FILE} mhux instab għaż‑backup.", - "model_loaded": "Markov model imlaħħaq minn", - "fetch_update_fail": "Ma setgħux jiġbdu informazzjoni dwar l‑aġġornament.", - "invalid_server": "Żball: Informazzjoni dwar il‑verżjoni misjuba mis‑server mhix valida.", - "goober_server_alert": "Allarm minn goober central!\n", - "new_version": "Verżjoni ġdida disponibbli: {latest_version} (Attwali: {local_version})", - "changelog": "Eżamina {VERSION_URL}/goob/changes.txt biex tara l‑modifiki\n\n", - "invalid_version": "Il‑verżjoni: {local_version} mhix valida!", - "invalid_version2": "Jekk intenzjonata, ignora; inkella agħfas Y biex tittrasferixxi verżjoni valida mill‑server minkejja l‑verżjoni ta’ goober attwali", - "invalid_version3": "Il‑verżjoni attwali tkun backuppjata għal current_version.bak..", - "input": "(Y jew kwalunkwe buttuna oħra biex tinjoraha…)", - "modification_ignored": "Int immodifikajt", - "modification_ignored2": "IGNOREWARNING hija false..", - "latest_version": "Int qed tuża l‑verżjoni l‑iktar ġdida:", - "latest_version2": "Ara {VERSION_URL}/goob/changes.txt biex tara l‑log tal‑modifiki", - "pinging_disabled": "Pinging imblukkat! Ma qed ninfurmakx li għadek online…", - "goober_ping_success": "Logged fil‑goober central bħala {NAME}", - "goober_ping_fail": "Falliment fl‑invjaġġ tal‑data. Il‑server irrisponda b’kodiċi stat:", - "goober_ping_fail2": "Żball seħħ waqt li bagħta data:", - "sentence_positivity": "Posittività ta’ sentenza hi:", - "command_edit_fail": "Falliment fit‑tbiddel tal‑messaġġ:", - "command_desc_retrain": "Retrain il‑Markov model b’manu.", - "command_markov_retrain": "Qed inretrain il‑Markov model… Awżilja, jekk jogħġbok.", - "command_markov_memory_not_found": "Żball: fajl tal‑memorja maħżun mhux instab!", - "command_markov_memory_is_corrupt": "Żball: fajl tal‑memorja hu korrott!", - "command_markov_retraining": "Qed nipproċessa {processed_data}/{data_size} punti tad‑data…", - "command_markov_retrain_successful": "Markov model retrained b’suċċess bl‑użu ta’ {data_size} punti tad‑data!", - "command_desc_talk": "jitkellem bħal affarijiet", - "command_talk_insufficent_text": "Għandi bżonn nitgħallem iktar mill‑messaġġi qabel nista’ nitkellem.", - "command_talk_generation_fail": "M’għandi xejn x’ngħid bħalissa!", - "command_desc_help": "għin", - "command_help_embed_title": "Għajnuna tal‑Bot", - "command_help_embed_desc": "Lista ta’ kmandi maqsuma b’kategoriji.", - "command_help_categories_general": "Ġenerali", - "command_help_categories_admin": "Amministrazzjoni", - "command_help_categories_custom": "Kmandi Personalizzati", - "command_ran": "Info: {message.author.name} użani {message.content}", - "command_ran_s": "Info: {interaction.user} użani ", - "command_desc_ping": "ping", - "command_ping_embed_desc": "Latentizza tal‑Bot:", - "command_ping_footer": "Mtitlub minn", - "command_about_desc": "dwar", - "command_about_embed_title": "Dwar jien", - "command_about_embed_field1": "Isem", - "command_about_embed_field2name": "Verżjoni", - "command_about_embed_field2value": "Lokali: {local_version} \nĠdida: {latest_version}", - "command_desc_stats": "stats", - "command_stats_embed_title": "Stati tal‑Bot", - "command_stats_embed_desc": "Informazzjoni dwar il‑memorja tal‑bot.", - "command_stats_embed_field1name": "Statistika tal‑Fajl", - "command_stats_embed_field1value": "Daqs: {file_size} bytes\nLinji: {line_count}", - "command_stats_embed_field2name": "Verżjoni", - "command_stats_embed_field2value": "Lokali: {local_version} \nĠdida: {latest_version}", - "command_stats_embed_field3name": "Informazzjoni Varjabbli", - "command_stats_embed_field3value": "Isem: {NAME} \nPrefiss: {PREFIX} \nID ta’ Sid: {ownerid}\nPing line: {PING_LINE} \nImoħħar Memenja: {showmemenabled} \nUser Training Attiva: {USERTRAIN_ENABLED}\nKan ta’: {song} \nSplashtext: ```{splashtext}```" -} From b6449cfa282668414d488cbf2de279d053a2886f Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 14:39:18 +0200 Subject: [PATCH 50/84] sigh --- assets/locales/en.json | 2 ++ assets/locales/fi.json | 2 ++ assets/locales/it.json | 2 ++ modules/globalvars.py | 2 +- modules/prestartchecks.py | 73 +++++++++------------------------------ modules/version.py | 6 +--- 6 files changed, 25 insertions(+), 62 deletions(-) diff --git a/assets/locales/en.json b/assets/locales/en.json index eb724eb..61f4778 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -1,4 +1,6 @@ { + "memory_file_valid": "The memory.json file is valid!", + "file_aint_uft8": "File is not valid UTF-8 text. Might be binary or corrupted.", "psutil_not_installed": "Memory check skipped.", "not_cloned": "Goober is not cloned! Please clone it from GitHub.", "checks_disabled": "Checks are disabled!", diff --git a/assets/locales/fi.json b/assets/locales/fi.json index 68e2c83..93141cc 100644 --- a/assets/locales/fi.json +++ b/assets/locales/fi.json @@ -1,4 +1,6 @@ { + "memory_file_valid": "memory.json on toimiva!", + "file_aint_uft8": "Tiedosto ei ole UTF-8 tekstiä. Saattaa olla binääriä tai korruptoitunut.", "active_users:": "Aktiiviset käyttäjät:", "cog_fail2": "Moduulin lataaminen epäonnistui:", "command_ran_s": "Info: {interaction.user} suoritti", diff --git a/assets/locales/it.json b/assets/locales/it.json index efb5a60..954b0ce 100644 --- a/assets/locales/it.json +++ b/assets/locales/it.json @@ -1,4 +1,6 @@ { + "memory_file_valid": "Il file JSON è valido!", + "file_aint_utf8": "Il file non è un UTF-8 valido. Forse è binario?", "psutil_not_installed": "Controllo memoria saltato.", "not_cloned": "Goober non è stato clonato! Clonalo da GitHub.", "checks_disabled": "I controlli sono disabilitati!", diff --git a/modules/globalvars.py b/modules/globalvars.py index 23e7e79..2281388 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -49,7 +49,7 @@ 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 = "2.3.2" +local_version = "2.3.3" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") if get_git_branch() == "dev": diff --git a/modules/prestartchecks.py b/modules/prestartchecks.py index 60c1d76..bb952f4 100644 --- a/modules/prestartchecks.py +++ b/modules/prestartchecks.py @@ -59,7 +59,8 @@ def check_requirements(): PACKAGE_ALIASES = { "discord": "discord.py", "better_profanity": "better-profanity", - "dotenv": "python-dotenv" + "dotenv": "python-dotenv", + "pil": "pillow" } parent_dir = os.path.dirname(os.path.abspath(__file__)) @@ -71,36 +72,13 @@ def check_requirements(): with open(requirements_path, 'r') as f: lines = f.readlines() - requirements = { - line.strip() for line in lines - if line.strip() and not line.startswith('#') - } - - cogs_dir = os.path.abspath(os.path.join(parent_dir, '..', 'assets', 'cogs')) - if os.path.isdir(cogs_dir): - for filename in os.listdir(cogs_dir): - if filename.endswith('.py'): - filepath = os.path.join(cogs_dir, filename) - with open(filepath, 'r', encoding='utf-8') as f: - try: - tree = ast.parse(f.read(), filename=filename) - for node in ast.walk(tree): - if isinstance(node, ast.Import): - for alias in node.names: - pkg = alias.name.split('.')[0] - if pkg in STD_LIB_MODULES or pkg == 'modules': - continue - requirements.add(pkg) - elif isinstance(node, ast.ImportFrom): - if node.module: - pkg = node.module.split('.')[0] - if pkg in STD_LIB_MODULES or pkg == 'modules': - continue - requirements.add(pkg) - except Exception as e: - logger.warning(f"{(_('warning_failed_parse_imports')).format(filename=filename, error=e)}") - else: - logger.warning(f"{(_('cogs_dir_not_found')).format(path=cogs_dir)}") + requirements = set() + for line in lines: + line = line.strip() + if line and not line.startswith('#'): + base_pkg = line.split('==')[0].lower() + aliased_pkg = PACKAGE_ALIASES.get(base_pkg, base_pkg) + requirements.add(aliased_pkg) installed_packages = {dist.metadata['Name'].lower() for dist in importlib.metadata.distributions()} missing = [] @@ -110,7 +88,7 @@ def check_requirements(): print((_('std_lib_local_skipped')).format(package=req)) continue - check_name = PACKAGE_ALIASES.get(req, req).lower() + check_name = req.lower() if check_name in installed_packages: logger.info(f"{_('ok_installed').format(package=check_name)} {check_name}") @@ -122,31 +100,25 @@ def check_requirements(): logger.error(_('missing_packages_detected')) for pkg in missing: print(f" - {pkg}") - logger.info((_('telling_goober_central')).format(url=VERSION_URL)) - payload = { - "name": NAME, - "version": local_version, - "slash_commands": f"{slash_commands_enabled}\n\n**Error**\nMissing packages have been detected, Failed to start", - "token": gooberTOKEN - } - try: - requests.post(VERSION_URL + "/ping", json=payload) # type: ignore - except Exception as e: - logger.error(f"{(_('failed_to_contact')).format(url=VERSION_URL, error=e)}") sys.exit(1) else: logger.info(_('all_requirements_satisfied')) def check_latency(): host = "1.1.1.1" - system = platform.system() + if system == "Windows": cmd = ["ping", "-n", "1", "-w", "1000", host] latency_pattern = r"Average = (\d+)ms" + + elif system == "Darwin": + cmd = ["ping", "-c", "1", host] + latency_pattern = r"time=([\d\.]+) ms" + else: cmd = ["ping", "-c", "1", "-W", "1", host] - latency_pattern = r"time[=<]\s*([\d\.]+)\s*ms" + latency_pattern = r"time=([\d\.]+) ms" try: result = subprocess.run( @@ -157,7 +129,6 @@ def check_latency(): ) if result.returncode == 0: - print(result.stdout) match = re.search(latency_pattern, result.stdout) if match: latency_ms = float(match.group(1)) @@ -197,16 +168,6 @@ def check_cpu(): return logger.info((_('measuring_cpu'))) cpu_per_core = psutil.cpu_percent(interval=1, percpu=True) # type: ignore - for idx, core_usage in enumerate(cpu_per_core): - bar_length = int(core_usage / 5) - bar = '█' * bar_length + '-' * (20 - bar_length) - if core_usage > 85: - color = RED - elif core_usage > 60: - color = YELLOW - else: - color = GREEN - logger.info((_('core_usage')).format(idx=idx, bar=bar, usage=core_usage)) total_cpu = sum(cpu_per_core) / len(cpu_per_core) logger.info((_('total_cpu_usage')).format(usage=total_cpu)) if total_cpu > 85: diff --git a/modules/version.py b/modules/version.py index 8ca5bc9..a5c1132 100644 --- a/modules/version.py +++ b/modules/version.py @@ -70,9 +70,7 @@ def get_latest_version_info(): return None # Check if an update is available and perform update if needed -def check_for_update(): - if ALIVEPING != "True": - return +def check_for_update(): global latest_version, local_version, launched latest_version_info = get_latest_version_info() @@ -87,12 +85,10 @@ def check_for_update(): if not latest_version or not download_url: logger.error(f"{RED}{_('invalid_server')}{RESET}") return None, None - # Check if local_version is valid if local_version == "0.0.0" or None: logger.error(f"{RED}{_('cant_find_local_version')}{RESET}") return - # Compare local and latest versions if local_version < latest_version: logger.info(f"{YELLOW}{_('new_version').format(latest_version=latest_version, local_version=local_version)}{RESET}") From 6b5035890e2287cf0d1ea0a8e1b52bb62774d9fc Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 18:37:30 +0200 Subject: [PATCH 51/84] scrapped idea --- example.env | 1 + modules/coghooks.py | 51 --------------------------------------------- 2 files changed, 1 insertion(+), 51 deletions(-) delete mode 100644 modules/coghooks.py diff --git a/example.env b/example.env index 8913dff..fe40023 100644 --- a/example.env +++ b/example.env @@ -6,6 +6,7 @@ OWNERID= USERTRAINENABLED="true" SHOWMEMENABLED="true" LOCALE=fi +NAME=goober AUTOUPDATE="True" SONG="Basket Case - Green Day" CHECKSDISABLED="Frue" diff --git a/modules/coghooks.py b/modules/coghooks.py deleted file mode 100644 index ab56339..0000000 --- a/modules/coghooks.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import importlib -import inspect -import sys - -_hooks = {} - -def register_hook(name, func): - if name not in _hooks: - _hooks[name] = [] - _hooks[name].append(func) - -async def call_hook(name, *args, **kwargs): - if name not in _hooks: - return - for func in _hooks[name]: - if callable(func): - if inspect.iscoroutinefunction(func): - await func(*args, **kwargs) - else: - func(*args, **kwargs) - -def register_all_functions_as_hooks(module): - """Register every function/coroutine function in the given module as a hook under its function name.""" - for name, obj in inspect.getmembers(module): - if inspect.isfunction(obj) or inspect.iscoroutinefunction(obj): - register_hook(name, obj) - -def _register_all_modules_functions(): - """Scan all python modules in this 'modules' folder (excluding this file) and register their functions.""" - # Calculate the path to the modules directory relative to this file - modules_dir = os.path.dirname(os.path.abspath(__file__)) - - # Current file name so we skip it - current_file = os.path.basename(__file__) - - # Ensure 'modules' is in sys.path for importlib to work - if modules_dir not in sys.path: - sys.path.insert(0, modules_dir) - - # List all python files except dunder files and this file - for filename in os.listdir(modules_dir): - if filename.endswith(".py") and not filename.startswith("__") and filename != current_file: - module_name = filename[:-3] # strip .py extension - try: - module = importlib.import_module(module_name) - register_all_functions_as_hooks(module) - except Exception as e: - print(f"[hooks] Failed to import {module_name}: {e}") - - From d89a08642030acc6658b1aa21e5417b73aaa8c44 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 19:15:58 +0200 Subject: [PATCH 52/84] webserver sucks ass dont plan on fixing it now and added a new cog --- assets/cogs/pulse.py | 66 +++++++++++++++++++ .../{webserver.py => webserver.py.disabled} | 0 2 files changed, 66 insertions(+) create mode 100644 assets/cogs/pulse.py rename assets/cogs/{webserver.py => webserver.py.disabled} (100%) diff --git a/assets/cogs/pulse.py b/assets/cogs/pulse.py new file mode 100644 index 0000000..f717221 --- /dev/null +++ b/assets/cogs/pulse.py @@ -0,0 +1,66 @@ +from discord.ext import commands +import discord +from collections import defaultdict, Counter +import datetime +from modules.globalvars import ownerid +class StatsCog(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.start_time = datetime.datetime.utcnow() + self.active_users = set() + self.total_messages = 0 + self.command_usage = Counter() + self.user_message_counts = Counter() + self.messages_per_hour = defaultdict(int) + + @commands.Cog.listener() + async def on_message(self, message): + if message.author.bot: + return + self.active_users.add(message.author.id) + self.total_messages += 1 + self.user_message_counts[message.author.id] += 1 + + now = datetime.datetime.utcnow() + hour_key = now.strftime("%Y-%m-%d %H") + self.messages_per_hour[hour_key] += 1 + + @commands.Cog.listener() + async def on_command(self, ctx): + self.command_usage[ctx.command.qualified_name] += 1 + + @commands.command() + async def spyware(self, ctx): + if ctx.author.id != ownerid: + 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]) + 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) + + 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" + 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)) diff --git a/assets/cogs/webserver.py b/assets/cogs/webserver.py.disabled similarity index 100% rename from assets/cogs/webserver.py rename to assets/cogs/webserver.py.disabled From 4ff0630d663f6444af79d24dc29e3b867067f240 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 19:27:12 +0200 Subject: [PATCH 53/84] You're the star of the show now baby! --- bot.py | 18 ++++++++++++++++++ modules/markovmemory.py | 8 +++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/bot.py b/bot.py index 358ef07..74cf0b7 100644 --- a/bot.py +++ b/bot.py @@ -387,6 +387,24 @@ async def on_message(message: discord.Message) -> None: cleaned_message: str = preprocess_message(formatted_message) if cleaned_message: memory.append(cleaned_message) + message_metadata = { + "user_id": str(message.author.id), + "user_name": str(message.author), + "guild_id": str(message.guild.id) if message.guild else "DM", + "guild_name": str(message.guild.name) if message.guild else "DM", + "channel_id": str(message.channel.id), + "channel_name": str(message.channel), + "message": message.content, + "timestamp": time.time() + } + try: + if isinstance(memory, list): + memory.append({"_meta": message_metadata}) + else: + logger.warning("Memory is not a list; can't append metadata") + except Exception as e: + logger.warning(f"Failed to append metadata to memory: {e}") + save_memory(memory) sentiment_score = is_positive(message.content) # doesnt work but im scared to change the logic now please ignore diff --git a/modules/markovmemory.py b/modules/markovmemory.py index f903f59..3235035 100644 --- a/modules/markovmemory.py +++ b/modules/markovmemory.py @@ -34,13 +34,15 @@ def save_memory(memory): with open(MEMORY_FILE, "w") as f: json.dump(memory, f, indent=4) -# Train a Markov model using memory and optional additional data def train_markov_model(memory, additional_data=None): if not memory: return None - text = "\n".join(memory) + filtered_memory = [line for line in memory if isinstance(line, str)] if additional_data: - text += "\n" + "\n".join(additional_data) + filtered_memory.extend(line for line in additional_data if isinstance(line, str)) + if not filtered_memory: + return None + text = "\n".join(filtered_memory) model = markovify.NewlineText(text, state_size=2) return model From c565f962c5c610b33aabe4dd09e4188c050e0681 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Wed, 16 Jul 2025 20:11:08 +0200 Subject: [PATCH 54/84] sigh --- .../{webserver.py.disabled => webserver.py} | 0 bot.py | 5 ++-- modules/image.py | 29 ++++++++++--------- 3 files changed, 19 insertions(+), 15 deletions(-) rename assets/cogs/{webserver.py.disabled => webserver.py} (100%) diff --git a/assets/cogs/webserver.py.disabled b/assets/cogs/webserver.py similarity index 100% rename from assets/cogs/webserver.py.disabled rename to assets/cogs/webserver.py diff --git a/bot.py b/bot.py index 74cf0b7..0c40592 100644 --- a/bot.py +++ b/bot.py @@ -225,7 +225,7 @@ async def talk(ctx: commands.Context, sentence_size: int = 5) -> None: # Command: Generate an image @bot.hybrid_command(description=f"{(_('command_desc_help'))}") -async def impact(ctx: commands.Context) -> None: +async def impact(ctx: commands.Context, text: Optional[str] = None) -> None: assets_folder: str = "assets/images" temp_input: Optional[str] = None @@ -259,7 +259,8 @@ async def impact(ctx: commands.Context) -> None: shutil.copy(fallback_image, temp_input) input_path = temp_input - output_path: Optional[str] = await gen_meme(input_path) + output_path: Optional[str] = await gen_meme(input_path, custom_text=text) + if output_path is None or not os.path.isfile(output_path): if temp_input and os.path.exists(temp_input): diff --git a/modules/image.py b/modules/image.py index 58180d5..d3807df 100644 --- a/modules/image.py +++ b/modules/image.py @@ -37,7 +37,7 @@ def split_text_to_fit(text, font, max_width, draw): midpoint = len(words) // 2 return " ".join(words[:midpoint]), " ".join(words[midpoint:]) -async def gen_meme(input_image_path, sentence_size=5, max_attempts=10): +async def gen_meme(input_image_path, sentence_size=5, max_attempts=10, custom_text=None): markov_model = load_markov_model() if not markov_model or not os.path.isfile(input_image_path): return None @@ -52,19 +52,22 @@ async def gen_meme(input_image_path, sentence_size=5, max_attempts=10): font = load_font(font_size) response = None - for _ in range(20): - if sentence_size == 1: - candidate = markov_model.make_short_sentence(max_chars=100, tries=100) - if candidate: - candidate = candidate.split()[0] - else: - candidate = markov_model.make_sentence(tries=100, max_words=sentence_size) + if custom_text: + response = custom_text + else: + for _ in range(20): + if sentence_size == 1: + candidate = markov_model.make_short_sentence(max_chars=100, tries=100) + if candidate: + candidate = candidate.split()[0] + else: + candidate = markov_model.make_sentence(tries=100, max_words=sentence_size) - if candidate and candidate not in generated_sentences: - if sentence_size > 1: - candidate = improve_sentence_coherence(candidate) - generated_sentences.add(candidate) - response = candidate + if candidate and candidate not in generated_sentences: + if sentence_size > 1: + candidate = improve_sentence_coherence(candidate) + generated_sentences.add(candidate) + response = candidate break if not response: From 7c6be59dc96a1987bb2c74fb25ef474fcc58c089 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Thu, 17 Jul 2025 15:22:39 +0200 Subject: [PATCH 55/84] yeah no lastfm gets the status change so fix later i guess --- assets/cogs/{lastfm.py => lastfm.py.disabled} | 0 bot.py | 14 +++++++++++--- example.env | 1 + modules/globalvars.py | 3 ++- 4 files changed, 14 insertions(+), 4 deletions(-) rename assets/cogs/{lastfm.py => lastfm.py.disabled} (100%) diff --git a/assets/cogs/lastfm.py b/assets/cogs/lastfm.py.disabled similarity index 100% rename from assets/cogs/lastfm.py rename to assets/cogs/lastfm.py.disabled diff --git a/bot.py b/bot.py index 0c40592..4c7f817 100644 --- a/bot.py +++ b/bot.py @@ -113,6 +113,7 @@ async def on_ready() -> None: global launched global slash_commands_enabled global NAME + global status folder_name: str = "cogs" if launched: @@ -136,8 +137,15 @@ async def on_ready() -> None: quit() if not song: - return - await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=f"{song}")) + return + + status = { + "idle": discord.Status.idle, + "dnd": discord.Status.dnd, + "invisible": discord.Status.invisible, + "online": discord.Status.online + }.get(status.lower(), discord.Status.online) + await bot.change_presence(status=status, activity=discord.Activity(type=discord.ActivityType.listening, name=f"{song}")) launched = True @bot.event @@ -372,7 +380,7 @@ async def on_message(message: discord.Message) -> None: if str(message.author.id) in BLACKLISTED_USERS: return - + if message.content.startswith((f"{PREFIX}talk", f"{PREFIX}mem", f"{PREFIX}help", f"{PREFIX}stats", f"{PREFIX}")): logger.info(f"{(_('command_ran')).format(message=message)}") await bot.process_commands(message) diff --git a/example.env b/example.env index fe40023..45cfd9b 100644 --- a/example.env +++ b/example.env @@ -11,6 +11,7 @@ AUTOUPDATE="True" SONG="Basket Case - Green Day" CHECKSDISABLED="Frue" REACT="True" +STATUS="idle" POSITIVEGIFS="https://media.discordapp.net/attachments/821047460151427135/1181371808566493184/jjpQGeno.gif, https://tenor.com/view/chill-guy-my-new-character-gif-2777893510283028272,https://tenor.com/view/goodnight-goodnight-friends-weezer-weezer-goodnight-gif-7322052181075806988" SPLASHTEXT=" diff --git a/modules/globalvars.py b/modules/globalvars.py index 2281388..5241cca 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -35,6 +35,7 @@ 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" @@ -44,7 +45,7 @@ MEMORY_LOADED_FILE = "MEMORY_LOADED" # is this still even used?? okay just check ALIVEPING = os.getenv("ALIVEPING") AUTOUPDATE = os.getenv("AUTOUPDATE") # IGNOREWARNING = False # is this either??? i don't think so? -song = os.getenv("song") +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 From cb45a9fffc94946246220f057df12f3bfa32c5fb Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Thu, 17 Jul 2025 15:33:32 +0200 Subject: [PATCH 56/84] Update todo.txt --- todo.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/todo.txt b/todo.txt index 428c6c8..9a1a0f3 100644 --- a/todo.txt +++ b/todo.txt @@ -1,4 +1,2 @@ -- fix missing translations in some cases - revamp wiki -- clean the rest - alot From c56b2dbc32a191774533c5c36f56075f3910dbd3 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Thu, 17 Jul 2025 16:03:47 +0200 Subject: [PATCH 57/84] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc1f094..3939b86 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,5 @@ Special thanks to [Charlie's Computers](https://github.com/PowerPCFan) for being [Goober Central](https://github.com/whatdidyouexpect/goober-central) -![the goober](https://goober.whatdidyouexpect.eu/imgs/goobs/goobs.png) +[Another mirror](https://forgejo.expect.ovh/WhatDidYouExpect/goober) +no promises that it'll be stable From 016e907d39c2f77b3b8afa76001b2d05b69dab22 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Thu, 17 Jul 2025 17:42:21 +0200 Subject: [PATCH 58/84] probably wont break anything (foreshadowing) --- modules/globalvars.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index 5241cca..8ff634e 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -44,10 +44,8 @@ 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") -# 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 = "2.3.3" From eb5109424bfe090c729457614bb413b6223e1849 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Thu, 17 Jul 2025 18:50:39 +0200 Subject: [PATCH 59/84] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3939b86..c1e9957 100644 --- a/README.md +++ b/README.md @@ -5,5 +5,5 @@ Special thanks to [Charlie's Computers](https://github.com/PowerPCFan) for being [Goober Central](https://github.com/whatdidyouexpect/goober-central) -[Another mirror](https://forgejo.expect.ovh/WhatDidYouExpect/goober) +[Another mirror](https://forgejo.expect.ovh/gooberinc/goober) no promises that it'll be stable From 53e0eb5289bd2102d745150c6d8bf3e1069e9565 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Thu, 17 Jul 2025 23:35:59 +0200 Subject: [PATCH 60/84] bump it to 2.3.4 --- modules/globalvars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index 8ff634e..0a5a57b 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -48,7 +48,7 @@ song = os.getenv("SONG") arch = platform.machine() launched = False latest_version = "0.0.0" -local_version = "2.3.3" +local_version = "2.3.4" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") if get_git_branch() == "dev": From 54b8bf4c5937619e2bfc494347b22aeead76521c Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Thu, 17 Jul 2025 23:48:41 +0200 Subject: [PATCH 61/84] change submodule path --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index fc06f56..ca39f8a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "modules/volta"] path = modules/volta - url = https://github.com/gooberinc/volta + url = https://forgejo.expect.ovh/gooberinc/volta From dec83f1513de220eb0193e02a1f7a92aa986c3ea Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 00:49:04 +0200 Subject: [PATCH 62/84] why did they call the song rule #34 if it has nothing to do with rule34 --- assets/cogs/README.md | 3 - assets/cogs/tf.py.disabled | 155 ---------------------------------- bot.py | 9 +- modules/sentenceprocessing.py | 11 +-- 4 files changed, 5 insertions(+), 173 deletions(-) delete mode 100644 assets/cogs/tf.py.disabled diff --git a/assets/cogs/README.md b/assets/cogs/README.md index 7d2ddee..8680835 100644 --- a/assets/cogs/README.md +++ b/assets/cogs/README.md @@ -8,9 +8,6 @@ by PowerPCFan [Cog Manager](https://github.com/WhatDidYouExpect/goober/blob/main/cogs/cogmanager.py) by expect -[TensorFlow integration](https://github.com/WhatDidYouExpect/goober/blob/main/cogs/tf.py) -by SuperSilly2 (requires Python 3.7 - 3.10, tensorflow-metal/tensorflow-gpu and tensorflow/tensorflow-macos) - [Web Scraper](https://raw.githubusercontent.com/WhatDidYouExpect/goober/refs/heads/main/cogs/webscraper.py) by expect (requires goober version 0.11.7.2 or higher) diff --git a/assets/cogs/tf.py.disabled b/assets/cogs/tf.py.disabled deleted file mode 100644 index 28609e6..0000000 --- a/assets/cogs/tf.py.disabled +++ /dev/null @@ -1,155 +0,0 @@ -import discord -from discord.ext import commands -import os -import numpy as np -import json -import pickle -import functools -import re -import time -import asyncio - -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 - from tensorflow import keras - from tensorflow.keras.preprocessing.text import Tokenizer - from tensorflow.keras.preprocessing.sequence import pad_sequences - from tensorflow.keras.models import Sequential, load_model - from tensorflow.keras.layers import Embedding, LSTM, Dense - from tensorflow.keras.backend import clear_session - - if tf.config.list_physical_devices('GPU'): - print("Using GPU acceleration") - 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("tensorflow>=2.15.0") - print("For macOS (Apple Silicon): tensorflow-metal") - ready = False - - -class TFCallback(keras.callbacks.Callback): - def __init__(self, bot, progress_embed: discord.Embed, message): - self.embed = progress_embed - 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" - {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.")) - - 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)) - - 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)}")) - - -class Ai: - def __init__(self): - model_path = settings.get("model_path") - if model_path: - self.__load_model(model_path) - self.is_loaded = model_path is not None - self.batch_size = 64 - - def generate_model_name(self): - 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")) - model_name = os.path.basename(model_path) - try: - with open(os.path.join(model_path, "tokenizer.pkl"), "rb") as f: - self.tokenizer = pickle.load(f) - except FileNotFoundError: - print("Failed to load tokenizer, using default.") - self.tokenizer = Tokenizer() - 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") - if model_path: - self.__load_model(model_path) - 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)) - - -class Learning(Ai): - def create_model(self, memory, epochs=2): - memory = memory[:2000] - tokenizer = Tokenizer() - tokenizer.fit_on_texts(memory) - sequences = tokenizer.texts_to_sequences(memory) - X, y = [], [] - for seq in sequences: - for i in range(1, len(seq)): - X.append(seq[:i]) - y.append(seq[i]) - 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"]) - 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), "") - seed += " " + output_word - return seed - - -VOCAB_SIZE = 100_000 -settings = {} -learning = Learning() -generation = Generation() - -tf_callback = None - - -async def setup(bot): - await bot.add_cog(Tf(bot)) \ No newline at end of file diff --git a/bot.py b/bot.py index 4c7f817..8af816b 100644 --- a/bot.py +++ b/bot.py @@ -41,8 +41,6 @@ from discord.ext import commands from discord import app_commands from discord import Colour, Embed, File, Interaction, Message from discord.abc import Messageable - -from better_profanity import profanity from discord.ext import commands from modules.volta.main import _, set_language @@ -386,14 +384,11 @@ async def on_message(message: discord.Message) -> None: await bot.process_commands(message) return - if profanity.contains_profanity(message.content): - return - if message.content: if not USERTRAIN_ENABLED: return - formatted_message: str = append_mentions_to_18digit_integer(message.content) - cleaned_message: str = preprocess_message(formatted_message) + formatted_message: str = message.content + cleaned_message: str = formatted_message if cleaned_message: memory.append(cleaned_message) message_metadata = { diff --git a/modules/sentenceprocessing.py b/modules/sentenceprocessing.py index 993ba90..95a703b 100644 --- a/modules/sentenceprocessing.py +++ b/modules/sentenceprocessing.py @@ -61,20 +61,15 @@ async def send_message(ctx, message=None, embed=None, file=None, edit=False, mes sent_message = await ctx.send(file=file) return sent_message -def append_mentions_to_18digit_integer(message): - pattern = r'\b\d{18}\b' - return re.sub(pattern, lambda match: "", message) - def preprocess_message(message): - message = append_mentions_to_18digit_integer(message) + message = message doc = nlp(message) tokens = [token.text for token in doc if token.is_alpha or token.is_digit] return " ".join(tokens) def improve_sentence_coherence(sentence): - return re.sub(r'\bi\b', 'I', sentence) + return "" def rephrase_for_coherence(sentence): - words = sentence.split() - coherent_sentence = " ".join(words) + coherent_sentence = sentence return coherent_sentence From 280df4f5e012a94d7bf656c3bb2c242a10dbd477 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 01:26:55 +0200 Subject: [PATCH 63/84] fuck it one liner --- bot.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bot.py b/bot.py index 8af816b..6833874 100644 --- a/bot.py +++ b/bot.py @@ -68,11 +68,7 @@ slash_commands_enabled: bool = False intents: discord.Intents = discord.Intents.default() intents.messages = True intents.message_content = True -bot: commands.Bot = commands.Bot( - command_prefix=PREFIX, - intents=intents, - allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False, replied_user=True) -) +bot: commands.Bot = commands.Bot(command_prefix=PREFIX, intents=intents, allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False, replied_user=True)) # Load memory and Markov model for text generation memory: List[str] = load_memory() From ae2d565004b7f32314f1af605be77ebd0300d1d1 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 01:29:31 +0200 Subject: [PATCH 64/84] dynamic git URL --- bot.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 6833874..9315b3a 100644 --- a/bot.py +++ b/bot.py @@ -459,6 +459,17 @@ async def ping(ctx: commands.Context) -> None: await ctx.send(embed=LOLembed) +def get_git_remote_url(): + try: + url = subprocess.check_output( + ["git", "config", "--get", "remote.origin.url"], + text=True, + stderr=subprocess.DEVNULL, + ).strip() + return url + except subprocess.CalledProcessError: + return "Unknown" + # Command: Show about information @bot.hybrid_command(description=f"{(_('command_about_desc'))}") async def about(ctx: commands.Context) -> None: @@ -468,7 +479,7 @@ async def about(ctx: commands.Context) -> None: 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"{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"Github", value=f"https://github.com/gooberinc/goober") + embed.add_field(name=f"Github", value=get_git_remote_url()) await send_message(ctx, embed=embed) From 4a695a7baca6c4c5d99d743a68032ef2a1371108 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 01:30:57 +0200 Subject: [PATCH 65/84] added os why not fuck it amirite --- bot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 9315b3a..45da38d 100644 --- a/bot.py +++ b/bot.py @@ -7,8 +7,8 @@ import traceback import subprocess import tempfile import shutil -import uuid import asyncio +import platform import sys from typing import List, Dict, Set, Optional, Tuple, Any, Union, Callable, Coroutine, TypeVar, Type import logging @@ -480,6 +480,7 @@ async def about(ctx: commands.Context) -> None: embed.add_field(name=f"{(_('command_about_embed_field1'))}", value=f"{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"Github", value=get_git_remote_url()) + embed.add_field(name=f"OS", value=platform.platform) await send_message(ctx, embed=embed) From cbe7fe201fb4f18e3826cbf65e807e6f11a9bd60 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 01:31:46 +0200 Subject: [PATCH 66/84] dumbass forgot to add () --- bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 45da38d..4178468 100644 --- a/bot.py +++ b/bot.py @@ -480,7 +480,7 @@ async def about(ctx: commands.Context) -> None: embed.add_field(name=f"{(_('command_about_embed_field1'))}", value=f"{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"Github", value=get_git_remote_url()) - embed.add_field(name=f"OS", value=platform.platform) + embed.add_field(name=f"OS", value=platform.platform()) await send_message(ctx, embed=embed) From 01b40f8b58091507f50ecea9b4ab42b8f6edd41e Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 01:33:01 +0200 Subject: [PATCH 67/84] added python version jst incase --- bot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 4178468..5d11e6b 100644 --- a/bot.py +++ b/bot.py @@ -502,7 +502,8 @@ async def stats(ctx: commands.Context) -> None: embed.add_field(name=f"{(_('command_stats_embed_field1name'))}", value=f"{(_('command_stats_embed_field1value')).format(file_size=file_size, line_count=line_count)}", inline=False) embed.add_field(name=f"{(_('command_stats_embed_field2name'))}", value=f"{(_('command_stats_embed_field2value')).format(local_version=local_version, latest_version=latest_version)}", inline=False) embed.add_field(name=f"{(_('command_stats_embed_field3name'))}", value=f"{(_('command_stats_embed_field3value')).format(NAME=NAME, PREFIX=PREFIX, ownerid=ownerid, PING_LINE=PING_LINE, showmemenabled=showmemenabled, USERTRAIN_ENABLED=USERTRAIN_ENABLED, song=song, splashtext=splashtext)}", inline=False) - + embed.add_field(name=f"OS", value=platform.platform()) + embed.add_field(name="Python Version", value=platform.python_version()) await send_message(ctx, embed=embed) # Command: Upload memory.json to litterbox.catbox.moe and return the link From f5d7121dfb315cc3ebf902a15a149a80109284bf Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 01:36:16 +0200 Subject: [PATCH 68/84] changed from github to git cause i self host it now (i use arch btw) --- bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 5d11e6b..38a01b4 100644 --- a/bot.py +++ b/bot.py @@ -479,7 +479,7 @@ async def about(ctx: commands.Context) -> None: 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"{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"Github", value=get_git_remote_url()) + embed.add_field(name=f"Git", value=get_git_remote_url()) embed.add_field(name=f"OS", value=platform.platform()) await send_message(ctx, embed=embed) From 9a4d486e2587170f9d7a1e3a8b690ae569d3c4aa Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 01:40:40 +0200 Subject: [PATCH 69/84] Please enter the commit message for your changes. Lines starting --- modules/globalvars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/globalvars.py b/modules/globalvars.py index 0a5a57b..37b22e9 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -48,7 +48,7 @@ song = os.getenv("SONG") arch = platform.machine() launched = False latest_version = "0.0.0" -local_version = "2.3.4" +local_version = "2.3.5" os.environ['gooberlocal_version'] = local_version REACT = os.getenv("REACT") if get_git_branch() == "dev": From 8666c835656b92703a866c7e53e5523c20849842 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 13:57:58 +0200 Subject: [PATCH 70/84] prolly gonna move command to their own modules soon soo --- bot.py | 16 +++++----- modules/globalvars.py | 16 ++++++++-- modules/minigames.py | 70 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 modules/minigames.py diff --git a/bot.py b/bot.py index 38a01b4..3af7e5e 100644 --- a/bot.py +++ b/bot.py @@ -49,7 +49,7 @@ from modules.version import * from modules.sentenceprocessing import * from modules.unhandledexception import handle_exception from modules.image import gen_meme, gen_demotivator - +from modules.minigames import guessthenumber, hangman sys.excepthook = handle_exception check_for_update() # Check for updates (from modules/version.py) @@ -64,12 +64,6 @@ 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.message_content = True -bot: commands.Bot = commands.Bot(command_prefix=PREFIX, intents=intents, allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False, replied_user=True)) - # Load memory and Markov model for text generation memory: List[str] = load_memory() markov_model: Optional[markovify.Text] = load_markov_model() @@ -419,10 +413,14 @@ async def on_message(message: discord.Message) -> None: await bot.process_commands(message) -# Event: Called on every interaction (slash command, etc.) @bot.event async def on_interaction(interaction: discord.Interaction) -> None: - logger.info(f"{(_('command_ran_s')).format(interaction=interaction)}{interaction.data['name']}") + name = None + if interaction.data.get('name') is None: + name = "Unknown" + else: + name = interaction.data['name'] + logger.info(f"{(_('command_ran_s')).format(interaction=interaction)}{name}") # Global check: Block blacklisted users from running commands @bot.check diff --git a/modules/globalvars.py b/modules/globalvars.py index 37b22e9..5c20f1d 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -2,6 +2,12 @@ import os import platform from dotenv import load_dotenv import pathlib +import discord +from discord.ext import commands +from discord import app_commands +from discord import Colour, Embed, File, Interaction, Message +from discord.abc import Messageable +from discord.ext import commands import subprocess def get_git_branch(): try: @@ -15,7 +21,6 @@ def get_git_branch(): env_path = pathlib.Path(__file__).parent.parent / '.env' load_dotenv(dotenv_path=env_path) - ANSI = "\033[" RED = f"{ANSI}31m" GREEN = f"{ANSI}32m" @@ -55,4 +60,11 @@ if get_git_branch() == "dev": beta = True # this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks else: - beta = False \ No newline at end of file + beta = False + + +# Set up Discord bot intents and create bot instance +intents: discord.Intents = discord.Intents.default() +intents.messages = True +intents.message_content = True +bot: commands.Bot = commands.Bot(command_prefix=PREFIX, intents=intents, allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False, replied_user=True)) \ No newline at end of file diff --git a/modules/minigames.py b/modules/minigames.py new file mode 100644 index 0000000..d7ef821 --- /dev/null +++ b/modules/minigames.py @@ -0,0 +1,70 @@ +import random +import discord +from discord import ui, Interaction, TextStyle +from discord.ext import commands +import aiohttp +import asyncio +from modules.globalvars import bot + +@bot.hybrid_command(description="Guess the number game") +async def guessthenumber(ctx: commands.Context): + number = random.randint(1, 10) + class GuessModal(ui.Modal, title="Guess the Number"): + guess = ui.TextInput(label="Your guess (1-10)", style=TextStyle.short) + async def on_submit(self, interaction: Interaction): + try: + user_guess = int(self.guess.value) + except: + await interaction.response.send_message("Invalid number!", ephemeral=True) + return + if user_guess == number: + await interaction.response.send_message("Correct!", ephemeral=True) + else: + await interaction.response.send_message(f"Wrong! The number was {number}.", ephemeral=True) + async def button_callback(interaction: Interaction): + await interaction.response.send_modal(GuessModal()) + button = ui.Button(label="Guess", style=discord.ButtonStyle.primary) + button.callback = button_callback + view = ui.View() + view.add_item(button) + await ctx.send("Click to guess a number from 1 to 10", view=view) + +@bot.hybrid_command(description="Play Hangman with a random word") +async def hangman(ctx: commands.Context): + async with aiohttp.ClientSession() as session: + async with session.get("https://random-word-api.herokuapp.com/word?number=1") as resp: + if resp.status != 200: + await ctx.send("Failed to get a random word.") + return + data = await resp.json() + word = data[0].lower() + print(word) + guessed_letters = set() + wrong_guesses = 0 + max_wrong = 6 + def display_word(): + return " ".join([c if c in guessed_letters else "_" for c in word]) + class GuessModal(ui.Modal, title="Guess a Letter"): + letter = ui.TextInput(label="Your letter guess", style=TextStyle.short, max_length=1) + async def on_submit(self, interaction: Interaction): + nonlocal guessed_letters, wrong_guesses + guess = self.letter.value.lower() + if guess in guessed_letters: + await interaction.response.send_message(f"You already guessed '{guess}'!", ephemeral=True) + return + guessed_letters.add(guess) + if guess not in word: + wrong_guesses += 1 + if all(c in guessed_letters for c in word): + await interaction.response.edit_message(content=f"You won! The word was: **{word}**", view=None) + elif wrong_guesses >= max_wrong: + await interaction.response.edit_message(content=f"You lost! The word was: **{word}**", view=None) + else: + await interaction.response.edit_message(content=f"Word: {display_word()}\nWrong guesses: {wrong_guesses}/{max_wrong}", view=view) + async def button_callback(interaction: Interaction): + await interaction.response.send_modal(GuessModal()) + button = ui.Button(label="Guess a letter", style=discord.ButtonStyle.primary) + button.callback = button_callback + view = ui.View() + view.add_item(button) + await ctx.send(f"Word: {display_word()}\nWrong guesses: {wrong_guesses}/{max_wrong}\nClick the button to guess a letter.", view=view) \ No newline at end of file From 52d9b058cf4437059887d7c1b2cc1324f41d6c2c Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:02:57 +0200 Subject: [PATCH 71/84] half assed translations not doing this today --- assets/locales/en.json | 4 +- modules/commands.py | 207 +++++++++++++++++++++++++++++++++++++++++ modules/minigames.py | 7 +- 3 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 modules/commands.py diff --git a/assets/locales/en.json b/assets/locales/en.json index 61f4778..4465658 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -1,5 +1,7 @@ { - "memory_file_valid": "The memory.json file is valid!", + "guess_the_number": "Guess the number", + "your_guess": "Your guess (1-10)", + "memosry_file_valid": "The memory.json file is valid!", "file_aint_uft8": "File is not valid UTF-8 text. Might be binary or corrupted.", "psutil_not_installed": "Memory check skipped.", "not_cloned": "Goober is not cloned! Please clone it from GitHub.", diff --git a/modules/commands.py b/modules/commands.py new file mode 100644 index 0000000..573e27f --- /dev/null +++ b/modules/commands.py @@ -0,0 +1,207 @@ + +from modules.globalvars import * +# Command: Retrain the Markov model from memory +@bot.hybrid_command(description=f"{(_('command_desc_retrain'))}") +async def retrain(ctx: commands.Context) -> None: + if ctx.author.id != ownerid: + return + + message_ref: MessageReference = await send_message(ctx, f"{(_('command_markov_retrain'))}") + try: + with open(MEMORY_FILE, 'r') as f: + memory: List[str] = json.load(f) + except FileNotFoundError: + await send_message(ctx, f"{(_('command_markov_memory_not_found'))}") + return + except json.JSONDecodeError: + await send_message(ctx, f"{(_('command_markov_memory_is_corrupt'))}") + return + + data_size: int = len(memory) + processed_data: int = 0 + processing_message_ref: MessageReference = await send_message(ctx, f"{(_('command_markov_retraining')).format(processed_data=processed_data, data_size=data_size)}") + start_time: float = time.time() + + for i, data in enumerate(memory): + processed_data += 1 + + global markov_model + markov_model = train_markov_model(memory) + save_markov_model(markov_model) + + await send_message(ctx, f"{_('command_markov_retrain_successful').format(data_size=data_size)}", edit=True, message_reference=processing_message_ref) + +# Command: Generate a sentence using the Markov model +@bot.hybrid_command(description=f"{(_('command_desc_talk'))}") +async def talk(ctx: commands.Context, sentence_size: int = 5) -> None: + if not markov_model: + await send_message(ctx, f"{(_('command_talk_insufficent_text'))}") + return + + response: Optional[str] = None + for _ in range(20): + if sentence_size == 1: + response = markov_model.make_short_sentence(max_chars=100, tries=100) + if response: + response = response.split()[0] + else: + response = markov_model.make_sentence(tries=100, max_words=sentence_size) + + if response and response not in generated_sentences: + if sentence_size > 1: + response = improve_sentence_coherence(response) + generated_sentences.add(response) + break + + if response: + cleaned_response: str = re.sub(r'[^\w\s]', '', response).lower() + coherent_response: str = rephrase_for_coherence(cleaned_response) + if random.random() < 0.9 and is_positive(coherent_response): + gif_url: str = random.choice(positive_gifs) + combined_message: str = f"{coherent_response}\n[jif]({gif_url})" + else: + combined_message: str = coherent_response + logger.info(combined_message) + os.environ['gooberlatestgen'] = combined_message + await send_message(ctx, combined_message) + else: + await send_message(ctx, f"{(_('command_talk_generation_fail'))}") + +# Command: Generate an image +@bot.hybrid_command(description=f"{(_('command_desc_help'))}") +async def impact(ctx: commands.Context, text: Optional[str] = None) -> None: + assets_folder: str = "assets/images" + temp_input: Optional[str] = None + + def get_random_asset_image() -> Optional[str]: + files: List[str] = [f for f in os.listdir(assets_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp'))] + if not files: + return None + return os.path.join(assets_folder, random.choice(files)) + + if ctx.message.attachments: + attachment: discord.Attachment = ctx.message.attachments[0] + if attachment.content_type and attachment.content_type.startswith("image/"): + ext: str = os.path.splitext(attachment.filename)[1] + temp_input = f"tempy{ext}" + await attachment.save(temp_input) + input_path: str = temp_input + else: + fallback_image: Optional[str] = get_random_asset_image() + if fallback_image is None: + await ctx.reply(_('no_image_available')) + return + temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) + shutil.copy(fallback_image, temp_input) + input_path = temp_input + else: + fallback_image = get_random_asset_image() + if fallback_image is None: + await ctx.reply(_('no_image_available')) + return + temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) + shutil.copy(fallback_image, temp_input) + input_path = temp_input + + output_path: Optional[str] = await gen_meme(input_path, custom_text=text) + + + if output_path is None or not os.path.isfile(output_path): + if temp_input and os.path.exists(temp_input): + os.remove(temp_input) + await ctx.reply(_('failed_generate_image')) + return + + await ctx.send(file=discord.File(output_path)) + + if temp_input and os.path.exists(temp_input): + os.remove(temp_input) + +# New demotivator command +@bot.hybrid_command(description="Generate a demotivator poster with two lines of text") +async def demotivator(ctx: commands.Context) -> None: + assets_folder: str = "assets/images" + temp_input: Optional[str] = None + + def get_random_asset_image() -> Optional[str]: + files: List[str] = [f for f in os.listdir(assets_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp'))] + if not files: + return None + return os.path.join(assets_folder, random.choice(files)) + + if ctx.message.attachments: + attachment: discord.Attachment = ctx.message.attachments[0] + if attachment.content_type and attachment.content_type.startswith("image/"): + ext: str = os.path.splitext(attachment.filename)[1] + temp_input = f"tempy{ext}" + await attachment.save(temp_input) + input_path: str = temp_input + else: + fallback_image: Optional[str] = get_random_asset_image() + if fallback_image is None: + await ctx.reply(_('no_image_available')) + return + temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) + shutil.copy(fallback_image, temp_input) + input_path = temp_input + else: + fallback_image = get_random_asset_image() + if fallback_image is None: + await ctx.reply(_('no_image_available')) + return + temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) + shutil.copy(fallback_image, temp_input) + input_path = temp_input + + output_path: Optional[str] = await gen_demotivator(input_path) + + if output_path is None or not os.path.isfile(output_path): + if temp_input and os.path.exists(temp_input): + os.remove(temp_input) + await ctx.reply("Failed to generate demotivator.") + return + + await ctx.send(file=discord.File(output_path)) + + if temp_input and os.path.exists(temp_input): + os.remove(temp_input) + +bot.remove_command('help') +# Command: Show help information +@bot.hybrid_command(description=f"{(_('command_desc_help'))}") +async def help(ctx: commands.Context) -> None: + embed: discord.Embed = discord.Embed( + title=f"{(_('command_help_embed_title'))}", + description=f"{(_('command_help_embed_desc'))}", + color=Colour(0x000000) + ) + + command_categories: Dict[str, List[str]] = { + f"{(_('command_help_categories_general'))}": ["mem", "talk", "about", "ping", "impact", "demotivator", "help"], + f"{(_('command_help_categories_admin'))}": ["stats", "retrain", "setlanguage"] + } + + custom_commands: List[str] = [] + for cog_name, cog in bot.cogs.items(): + for command in cog.get_commands(): + if command.name not in command_categories[f"{(_('command_help_categories_general'))}"] and command.name not in command_categories[f"{(_('command_help_categories_admin'))}"]: + custom_commands.append(command.name) + + if custom_commands: + embed.add_field(name=f"{(_('command_help_categories_custom'))}", value="\n".join([f"{PREFIX}{command}" for command in custom_commands]), inline=False) + + for category, commands_list in command_categories.items(): + commands_in_category: str = "\n".join([f"{PREFIX}{command}" for command in commands_list]) + embed.add_field(name=category, value=commands_in_category, inline=False) + + await send_message(ctx, embed=embed) + +@bot.hybrid_command(description=f"{(_('command_desc_setlang'))}") +@app_commands.describe(locale="Choose your language") +async def setlanguage(ctx: commands.Context, locale: str) -> None: + if ctx.author.id != ownerid: + await ctx.send(":thumbsdown:") + return + await ctx.defer() + set_language(locale) + await ctx.send(":thumbsup:") \ No newline at end of file diff --git a/modules/minigames.py b/modules/minigames.py index d7ef821..9cf8b17 100644 --- a/modules/minigames.py +++ b/modules/minigames.py @@ -5,12 +5,13 @@ from discord.ext import commands import aiohttp import asyncio from modules.globalvars import bot +from modules.volta.main import _ -@bot.hybrid_command(description="Guess the number game") +@bot.hybrid_command(description=_('guess_the_number')) async def guessthenumber(ctx: commands.Context): number = random.randint(1, 10) - class GuessModal(ui.Modal, title="Guess the Number"): - guess = ui.TextInput(label="Your guess (1-10)", style=TextStyle.short) + class GuessModal(ui.Modal, title=_('guess_the_number')): + guess = ui.TextInput(label=_('your_guess'), style=TextStyle.short) async def on_submit(self, interaction: Interaction): try: user_guess = int(self.guess.value) From 954014d1a4513b2da399213f20225e37ae3f96bc Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:03:35 +0200 Subject: [PATCH 72/84] fuck --- modules/commands.py | 207 -------------------------------------------- 1 file changed, 207 deletions(-) delete mode 100644 modules/commands.py diff --git a/modules/commands.py b/modules/commands.py deleted file mode 100644 index 573e27f..0000000 --- a/modules/commands.py +++ /dev/null @@ -1,207 +0,0 @@ - -from modules.globalvars import * -# Command: Retrain the Markov model from memory -@bot.hybrid_command(description=f"{(_('command_desc_retrain'))}") -async def retrain(ctx: commands.Context) -> None: - if ctx.author.id != ownerid: - return - - message_ref: MessageReference = await send_message(ctx, f"{(_('command_markov_retrain'))}") - try: - with open(MEMORY_FILE, 'r') as f: - memory: List[str] = json.load(f) - except FileNotFoundError: - await send_message(ctx, f"{(_('command_markov_memory_not_found'))}") - return - except json.JSONDecodeError: - await send_message(ctx, f"{(_('command_markov_memory_is_corrupt'))}") - return - - data_size: int = len(memory) - processed_data: int = 0 - processing_message_ref: MessageReference = await send_message(ctx, f"{(_('command_markov_retraining')).format(processed_data=processed_data, data_size=data_size)}") - start_time: float = time.time() - - for i, data in enumerate(memory): - processed_data += 1 - - global markov_model - markov_model = train_markov_model(memory) - save_markov_model(markov_model) - - await send_message(ctx, f"{_('command_markov_retrain_successful').format(data_size=data_size)}", edit=True, message_reference=processing_message_ref) - -# Command: Generate a sentence using the Markov model -@bot.hybrid_command(description=f"{(_('command_desc_talk'))}") -async def talk(ctx: commands.Context, sentence_size: int = 5) -> None: - if not markov_model: - await send_message(ctx, f"{(_('command_talk_insufficent_text'))}") - return - - response: Optional[str] = None - for _ in range(20): - if sentence_size == 1: - response = markov_model.make_short_sentence(max_chars=100, tries=100) - if response: - response = response.split()[0] - else: - response = markov_model.make_sentence(tries=100, max_words=sentence_size) - - if response and response not in generated_sentences: - if sentence_size > 1: - response = improve_sentence_coherence(response) - generated_sentences.add(response) - break - - if response: - cleaned_response: str = re.sub(r'[^\w\s]', '', response).lower() - coherent_response: str = rephrase_for_coherence(cleaned_response) - if random.random() < 0.9 and is_positive(coherent_response): - gif_url: str = random.choice(positive_gifs) - combined_message: str = f"{coherent_response}\n[jif]({gif_url})" - else: - combined_message: str = coherent_response - logger.info(combined_message) - os.environ['gooberlatestgen'] = combined_message - await send_message(ctx, combined_message) - else: - await send_message(ctx, f"{(_('command_talk_generation_fail'))}") - -# Command: Generate an image -@bot.hybrid_command(description=f"{(_('command_desc_help'))}") -async def impact(ctx: commands.Context, text: Optional[str] = None) -> None: - assets_folder: str = "assets/images" - temp_input: Optional[str] = None - - def get_random_asset_image() -> Optional[str]: - files: List[str] = [f for f in os.listdir(assets_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp'))] - if not files: - return None - return os.path.join(assets_folder, random.choice(files)) - - if ctx.message.attachments: - attachment: discord.Attachment = ctx.message.attachments[0] - if attachment.content_type and attachment.content_type.startswith("image/"): - ext: str = os.path.splitext(attachment.filename)[1] - temp_input = f"tempy{ext}" - await attachment.save(temp_input) - input_path: str = temp_input - else: - fallback_image: Optional[str] = get_random_asset_image() - if fallback_image is None: - await ctx.reply(_('no_image_available')) - return - temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) - shutil.copy(fallback_image, temp_input) - input_path = temp_input - else: - fallback_image = get_random_asset_image() - if fallback_image is None: - await ctx.reply(_('no_image_available')) - return - temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) - shutil.copy(fallback_image, temp_input) - input_path = temp_input - - output_path: Optional[str] = await gen_meme(input_path, custom_text=text) - - - if output_path is None or not os.path.isfile(output_path): - if temp_input and os.path.exists(temp_input): - os.remove(temp_input) - await ctx.reply(_('failed_generate_image')) - return - - await ctx.send(file=discord.File(output_path)) - - if temp_input and os.path.exists(temp_input): - os.remove(temp_input) - -# New demotivator command -@bot.hybrid_command(description="Generate a demotivator poster with two lines of text") -async def demotivator(ctx: commands.Context) -> None: - assets_folder: str = "assets/images" - temp_input: Optional[str] = None - - def get_random_asset_image() -> Optional[str]: - files: List[str] = [f for f in os.listdir(assets_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp'))] - if not files: - return None - return os.path.join(assets_folder, random.choice(files)) - - if ctx.message.attachments: - attachment: discord.Attachment = ctx.message.attachments[0] - if attachment.content_type and attachment.content_type.startswith("image/"): - ext: str = os.path.splitext(attachment.filename)[1] - temp_input = f"tempy{ext}" - await attachment.save(temp_input) - input_path: str = temp_input - else: - fallback_image: Optional[str] = get_random_asset_image() - if fallback_image is None: - await ctx.reply(_('no_image_available')) - return - temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) - shutil.copy(fallback_image, temp_input) - input_path = temp_input - else: - fallback_image = get_random_asset_image() - if fallback_image is None: - await ctx.reply(_('no_image_available')) - return - temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1]) - shutil.copy(fallback_image, temp_input) - input_path = temp_input - - output_path: Optional[str] = await gen_demotivator(input_path) - - if output_path is None or not os.path.isfile(output_path): - if temp_input and os.path.exists(temp_input): - os.remove(temp_input) - await ctx.reply("Failed to generate demotivator.") - return - - await ctx.send(file=discord.File(output_path)) - - if temp_input and os.path.exists(temp_input): - os.remove(temp_input) - -bot.remove_command('help') -# Command: Show help information -@bot.hybrid_command(description=f"{(_('command_desc_help'))}") -async def help(ctx: commands.Context) -> None: - embed: discord.Embed = discord.Embed( - title=f"{(_('command_help_embed_title'))}", - description=f"{(_('command_help_embed_desc'))}", - color=Colour(0x000000) - ) - - command_categories: Dict[str, List[str]] = { - f"{(_('command_help_categories_general'))}": ["mem", "talk", "about", "ping", "impact", "demotivator", "help"], - f"{(_('command_help_categories_admin'))}": ["stats", "retrain", "setlanguage"] - } - - custom_commands: List[str] = [] - for cog_name, cog in bot.cogs.items(): - for command in cog.get_commands(): - if command.name not in command_categories[f"{(_('command_help_categories_general'))}"] and command.name not in command_categories[f"{(_('command_help_categories_admin'))}"]: - custom_commands.append(command.name) - - if custom_commands: - embed.add_field(name=f"{(_('command_help_categories_custom'))}", value="\n".join([f"{PREFIX}{command}" for command in custom_commands]), inline=False) - - for category, commands_list in command_categories.items(): - commands_in_category: str = "\n".join([f"{PREFIX}{command}" for command in commands_list]) - embed.add_field(name=category, value=commands_in_category, inline=False) - - await send_message(ctx, embed=embed) - -@bot.hybrid_command(description=f"{(_('command_desc_setlang'))}") -@app_commands.describe(locale="Choose your language") -async def setlanguage(ctx: commands.Context, locale: str) -> None: - if ctx.author.id != ownerid: - await ctx.send(":thumbsdown:") - return - await ctx.defer() - set_language(locale) - await ctx.send(":thumbsup:") \ No newline at end of file From 39f2c26fecce88e47600520ac479e873be43a903 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:08:36 +0200 Subject: [PATCH 73/84] updated volta --- modules/volta/main.py | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/modules/volta/main.py b/modules/volta/main.py index fb6a080..1f05473 100644 --- a/modules/volta/main.py +++ b/modules/volta/main.py @@ -9,6 +9,7 @@ import pathlib import threading import time from dotenv import load_dotenv +from functools import lru_cache ANSI = "\033[" RED = f"{ANSI}31m" @@ -140,6 +141,7 @@ def set_language(lang: str): else: print(f"[VOLTA] {RED}The fallback translations cannot be found! No fallback available.{RESET}") ENGLISH_MISSING = True + _lookup_translation.cache_clear() def check_missing_translations(): global LOCALE, ENGLISH_MISSING @@ -175,26 +177,33 @@ def check_missing_translations(): printedsystemfallback = False +@lru_cache(maxsize=600) +def _lookup_translation(lang: str, key: str): + return translations.get(lang, {}).get(key) + def get_translation(lang: str, key: str): global printedsystemfallback if ENGLISH_MISSING: return f"[VOLTA] {RED}No fallback available!{RESET}" - fallback_translations = translations.get(FALLBACK_LOCALE, {}) - sys_lang = get_system_locale().split("_")[0] if get_system_locale() else None - sys_translations = translations.get(sys_lang, {}) if sys_lang else {} - lang_translations = translations.get(lang, {}) - if key in lang_translations: - return lang_translations[key] - if sys_lang and sys_lang != lang and key in sys_translations: - if not printedsystemfallback: - print(f"[VOLTA] {YELLOW}Falling back to system language {sys_lang}!{RESET}") - printedsystemfallback = True - return sys_translations[key] - if key in fallback_translations: - print(f"[VOLTA] {YELLOW}Missing key: '{key}' in '{lang}', falling back to fallback locale '{FALLBACK_LOCALE}'{RESET}") - return fallback_translations[key] - return f"[VOLTA] {YELLOW}Missing key: '{key}' in all locales!{RESET}" + val = _lookup_translation(lang, key) + if val: + return val + sys_lang = get_system_locale().split("_")[0] if get_system_locale() else None + if sys_lang and sys_lang != lang: + sys_val = _lookup_translation(sys_lang, key) + if sys_val: + if not printedsystemfallback: + print(f"[VOLTA] {YELLOW}Falling back to system language {sys_lang}!{RESET}") + printedsystemfallback = True + return sys_val + fallback_val = _lookup_translation(FALLBACK_LOCALE, key) + if fallback_val: + print(f"[VOLTA] {YELLOW}Missing key: '{key}' in '{lang}', falling back to fallback locale '{FALLBACK_LOCALE}'{RESET}") + return fallback_val + + return f"[VOLTA] {YELLOW}Missing key: '{key}' in all locales!{RESET}" + def _(key: str) -> str: return get_translation(LOCALE, key) From a92439b3522b0c0df331de84dfb25693a5104d3d Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:20:24 +0200 Subject: [PATCH 74/84] up 2 date w/volta --- modules/volta/main.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/volta/main.py b/modules/volta/main.py index 1f05473..aae76fa 100644 --- a/modules/volta/main.py +++ b/modules/volta/main.py @@ -120,6 +120,7 @@ def reload_if_changed(): current_mtime = file_path.stat().st_mtime if current_mtime != last_mtime: print(f"[VOLTA] {RED}Translation file changed: {file_path}, reloading...{RESET}") + _lookup_translation.cache_clear() load_translations() break except FileNotFoundError: @@ -143,8 +144,8 @@ def set_language(lang: str): ENGLISH_MISSING = True _lookup_translation.cache_clear() -def check_missing_translations(): - global LOCALE, ENGLISH_MISSING +def check_missing_translations(LOCALE=LOCALE): + global ENGLISH_MISSING load_translations() if FALLBACK_LOCALE not in translations: print(f"[VOLTA] {RED}Fallback translations ({FALLBACK_LOCALE}.json) missing from assets/locales.{RESET}") @@ -213,4 +214,9 @@ watchdog_thread = threading.Thread(target=reload_if_changed, daemon=True) watchdog_thread.start() if __name__ == '__main__': - print("Volta should not be run directly! Please use it as a module..") + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("LOCALE", help="Locale to validate") + args = parser.parse_args() + print("[VOLTA] Validating all locales....") + check_missing_translations(LOCALE=f"{args.LOCALE}") From 2e4576ba4b772afd78398a30128ad5683e593d6e Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 16:09:14 +0200 Subject: [PATCH 75/84] =?UTF-8?q?do=20NOT=20clone=20this=20=E2=9C=8C?= =?UTF-8?q?=EF=B8=8F=F0=9F=98=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/locales/en.json | 19 ++++++++++++++++--- assets/locales/it.json | 15 +++++++++++++++ modules/minigames.py | 30 +++++++++++++++--------------- modules/volta/main.py | 6 ++---- 4 files changed, 48 insertions(+), 22 deletions(-) diff --git a/assets/locales/en.json b/assets/locales/en.json index 4465658..a91a1e1 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -1,7 +1,20 @@ { - "guess_the_number": "Guess the number", - "your_guess": "Your guess (1-10)", - "memosry_file_valid": "The memory.json file is valid!", + "minigames_hangman_game": "Word: {display_word()}\nWrong guesses: {wrong_guesses}/{max_wrong}", + "minigames_hangman_lost": "You lost! The word was:", + "minigames_hangman_won": "You won! The word was:", + "minigames_hangman_already_guessed": "You already guessed", + "minigames_hangman_user_letter_guess": "Your letter guess", + "minigames_hangman_guess": "Guess a Letter", + "minigames_hangman_api_failed": "Failed to get a random word.", + "minigames_hangman": "Play Hangman with a random word", + "minigames_click_to_guess": "Click to guess a number from 1 to 10", + "minigames_guess_button": "Guess", + "minigames_wrong_number": "Wrong! The number was", + "minigames_correct": "Correct!", + "minigames_invalid_number": "Invalid number!", + "minigames_guess_the_number": "Guess the number", + "minigames_your_guess": "Your guess (1-10)", + "memory_file_valid": "The memory.json file is valid!", "file_aint_uft8": "File is not valid UTF-8 text. Might be binary or corrupted.", "psutil_not_installed": "Memory check skipped.", "not_cloned": "Goober is not cloned! Please clone it from GitHub.", diff --git a/assets/locales/it.json b/assets/locales/it.json index 954b0ce..45cfa41 100644 --- a/assets/locales/it.json +++ b/assets/locales/it.json @@ -1,4 +1,19 @@ { + "minigames_hangman_game": "Parola: {display_word()}\nErrori: {wrong_guesses}/{max_wrong}", + "minigames_hangman_lost": "Hai perso! La parola era:", + "minigames_hangman_won": "Hai vinto! La parola era:", + "minigames_hangman_already_guessed": "Hai già indovinato", + "minigames_hangman_user_letter_guess": "La tua lettera", + "minigames_hangman_guess": "Indovina una lettera", + "minigames_hangman_api_failed": "Impossibile ottenere una parola casuale.", + "minigames_hangman": "Gioca all'impiccato con una parola casuale", + "minigames_click_to_guess": "Clicca per indovinare un numero da 1 a 10", + "minigames_guess_button": "Indovina", + "minigames_wrong_number": "Sbagliato! Il numero era", + "minigames_correct": "Corretto!", + "minigames_invalid_number": "Numero non valido!", + "minigames_guess_the_number": "Indovina il numero", + "minigames_your_guess": "Il tuo numero (1-10)", "memory_file_valid": "Il file JSON è valido!", "file_aint_utf8": "Il file non è un UTF-8 valido. Forse è binario?", "psutil_not_installed": "Controllo memoria saltato.", diff --git a/modules/minigames.py b/modules/minigames.py index 9cf8b17..363c1e9 100644 --- a/modules/minigames.py +++ b/modules/minigames.py @@ -7,30 +7,30 @@ import asyncio from modules.globalvars import bot from modules.volta.main import _ -@bot.hybrid_command(description=_('guess_the_number')) +@bot.hybrid_command(description=_('minigames_guess_the_number')) async def guessthenumber(ctx: commands.Context): number = random.randint(1, 10) - class GuessModal(ui.Modal, title=_('guess_the_number')): - guess = ui.TextInput(label=_('your_guess'), style=TextStyle.short) + 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("Invalid number!", ephemeral=True) + await interaction.response.send_message(_('minigames_invalid_number'), ephemeral=True) return if user_guess == number: - await interaction.response.send_message("Correct!", ephemeral=True) + await interaction.response.send_message(_('minigames_correct'), ephemeral=True) else: - await interaction.response.send_message(f"Wrong! The number was {number}.", ephemeral=True) + 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="Guess", style=discord.ButtonStyle.primary) + 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("Click to guess a number from 1 to 10", view=view) + await ctx.send(_('minigames_click_to_guess'), view=view) -@bot.hybrid_command(description="Play Hangman with a random word") +@bot.hybrid_command(description=_('minigames_hangman')) 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: @@ -45,23 +45,23 @@ async def hangman(ctx: commands.Context): max_wrong = 6 def display_word(): return " ".join([c if c in guessed_letters else "_" for c in word]) - class GuessModal(ui.Modal, title="Guess a Letter"): - letter = ui.TextInput(label="Your letter guess", style=TextStyle.short, max_length=1) + 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"You already guessed '{guess}'!", ephemeral=True) + 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"You won! The word was: **{word}**", view=None) + 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"You lost! The word was: **{word}**", view=None) + await interaction.response.edit_message(content=f"{_('minigames_hangman_lost')} **{word}**", view=None) else: - await interaction.response.edit_message(content=f"Word: {display_word()}\nWrong guesses: {wrong_guesses}/{max_wrong}", view=view) + 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="Guess a letter", style=discord.ButtonStyle.primary) diff --git a/modules/volta/main.py b/modules/volta/main.py index aae76fa..4b62144 100644 --- a/modules/volta/main.py +++ b/modules/volta/main.py @@ -7,6 +7,8 @@ import locale import json import pathlib import threading +import platform +import sys import time from dotenv import load_dotenv from functools import lru_cache @@ -62,10 +64,6 @@ if working_dir != module_dir: translations = {} _file_mod_times = {} -import locale -import platform -import os -import sys def get_system_locale(): system = platform.system() # fallback incase locale isnt set From 59ce219183d0a1019bbf5afc4039c17f3456d5bb Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Fri, 18 Jul 2025 16:15:34 +0200 Subject: [PATCH 76/84] fuckass interactions.... --- assets/locales/it.json | 2 +- bot.py | 2 +- modules/minigames.py | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/assets/locales/it.json b/assets/locales/it.json index 45cfa41..0ae6413 100644 --- a/assets/locales/it.json +++ b/assets/locales/it.json @@ -1,5 +1,5 @@ { - "minigames_hangman_game": "Parola: {display_word()}\nErrori: {wrong_guesses}/{max_wrong}", + "minigames_hangman_game": "Parola: {display_word}\nErrori: {wrong_guesses}/{max_wrong}", "minigames_hangman_lost": "Hai perso! La parola era:", "minigames_hangman_won": "Hai vinto! La parola era:", "minigames_hangman_already_guessed": "Hai già indovinato", diff --git a/bot.py b/bot.py index 3af7e5e..dcd777f 100644 --- a/bot.py +++ b/bot.py @@ -49,7 +49,7 @@ from modules.version import * from modules.sentenceprocessing import * from modules.unhandledexception import handle_exception from modules.image import gen_meme, gen_demotivator -from modules.minigames import guessthenumber, hangman +# from modules.minigames import guessthenumber, hangman sys.excepthook = handle_exception check_for_update() # Check for updates (from modules/version.py) diff --git a/modules/minigames.py b/modules/minigames.py index 363c1e9..0a74a82 100644 --- a/modules/minigames.py +++ b/modules/minigames.py @@ -7,7 +7,7 @@ import asyncio from modules.globalvars import bot from modules.volta.main import _ -@bot.hybrid_command(description=_('minigames_guess_the_number')) +# @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')): @@ -30,7 +30,7 @@ async def guessthenumber(ctx: commands.Context): view.add_item(button) await ctx.send(_('minigames_click_to_guess'), view=view) -@bot.hybrid_command(description=_('minigames_hangman')) +# @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: @@ -61,11 +61,11 @@ async def hangman(ctx: commands.Context): 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) + 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="Guess a letter", style=discord.ButtonStyle.primary) + 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(f"Word: {display_word()}\nWrong guesses: {wrong_guesses}/{max_wrong}\nClick the button to guess a letter.", view=view) \ No newline at end of file + await ctx.send(_('minigames_hangman_game').format(display_word=display_word,wrong_guesses=wrong_guesses,max_wrong=max_wrong), view=view) \ No newline at end of file From fcb1a9782ad2a4e415bcf866b5288875ed861d69 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Sat, 19 Jul 2025 00:30:57 +0200 Subject: [PATCH 77/84] Linux openmediavault 6.12.32+bpo-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.32-1~bpo12+1 (2025-06-21) x86_64 GNU/Linux --- bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot.py b/bot.py index dcd777f..a774aa0 100644 --- a/bot.py +++ b/bot.py @@ -352,7 +352,7 @@ async def help(ctx: commands.Context) -> None: @app_commands.describe(locale="Choose your language") async def setlanguage(ctx: commands.Context, locale: str) -> None: if ctx.author.id != ownerid: - await ctx.send(":thumbsdown:") + await ctx.send(":thumbsdown:") return await ctx.defer() set_language(locale) From bf421e4fd0a79a6fbba46d38f880499c8b457bdc Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Sat, 19 Jul 2025 00:34:38 +0200 Subject: [PATCH 78/84] swear to your hearts content --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c53d758..7a9e5af 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ spacy spacytextblob requests psutil -better_profanity python-dotenv dotenv pillow \ No newline at end of file From 3c71e9f54e53bcfc12d54690b5c86bba02300935 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 21 Jul 2025 16:58:35 +0200 Subject: [PATCH 79/84] bundled goober 0.0.5 in with default goober incase i dunno they want a small version of it --- botminimal.py | 245 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 botminimal.py diff --git a/botminimal.py b/botminimal.py new file mode 100644 index 0000000..859662f --- /dev/null +++ b/botminimal.py @@ -0,0 +1,245 @@ +import discord +from discord.ext import commands, tasks +import json +import markovify +import nltk +from nltk.tokenize import word_tokenize +import random +import os +import time +import re +from dotenv import load_dotenv +load_dotenv() +# download NLTK data files +nltk.download('punkt') +MEMORY_FILE = "memory.json" +MEMORY_LOADED_FILE = "MEMORY_LOADED" + +def load_memory(): + data = [] + + # Try to load data from MEMORY_FILE + try: + with open(MEMORY_FILE, "r") as f: + data = json.load(f) + except FileNotFoundError: + pass + + return data + +# Save memory data to MEMORY_FILE +def save_memory(memory): + with open(MEMORY_FILE, "w") as f: + json.dump(memory, f, indent=4) + +def train_markov_model(memory, additional_data=None): + if not memory: + return None + 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)) + if not filtered_memory: + return None + text = "\n".join(filtered_memory) + model = markovify.NewlineText(text, state_size=2) + return model + +#this doesnt work and im extremely pissed and mad +def append_mentions_to_18digit_integer(message): + pattern = r'\b\d{18}\b' + return re.sub(pattern, lambda match: f"<@{match.group(0)}>", message) + +def preprocess_message(message): + message = append_mentions_to_18digit_integer(message) + tokens = word_tokenize(message) + tokens = [token for token in tokens if token.isalnum()] + return " ".join(tokens) + + +intents = discord.Intents.default() +intents.messages = True +intents.message_content = True +bot = commands.Bot(command_prefix="g!", intents=intents) +memory = load_memory() +markov_model = train_markov_model(memory) + +generated_sentences = set() +used_words = set() + +@bot.event +async def on_ready(): + print(f"Logged in as {bot.user}") + post_message.start() + +positive_keywords = ["happy", "good", "great", "amazing", "awesome", "joy", "love", "fantastic", "positive", "cheerful", "victory", "favorite", "lmao", "lol", "xd", "XD", "xD", "Xd"] + +positive_gifs = [ + "https://tenor.com/view/chill-guy-my-new-character-gif-2777893510283028272", + "https://tenor.com/view/goodnight-goodnight-friends-weezer-weezer-goodnight-gif-7322052181075806988" +] + +def is_positive(sentence): + sentence_lower = sentence.lower() + return any(keyword in sentence_lower for keyword in positive_keywords) + +@bot.command() +async def ask(ctx): + await ctx.send("Command undergoing fixes!") + #not really lol + +@bot.command() +async def talk(ctx): + if markov_model: + response = None + for _ in range(10): # im going to shit my pants 10 times to get a coherent sentence + response = markov_model.make_sentence(tries=100) + if response and response not in generated_sentences: + # preprocess shit for grammer + response = improve_sentence_coherence(response) + generated_sentences.add(response) + break + + if response: + async with ctx.typing(): + cleaned_response = re.sub(r'[^\w\s]', '', response) + cleaned_response = cleaned_response.lower() + coherent_response = rephrase_for_coherence(cleaned_response) + if random.random() < 0.9: + if is_positive(coherent_response): + gif_url = random.choice(positive_gifs) + combined_message = f"{coherent_response}\n[jif]({gif_url})" + await ctx.send(combined_message) + else: + await ctx.send(coherent_response) + else: + await ctx.send(coherent_response) + else: + await ctx.send("I have nothing to say right now!") + else: + await ctx.send("I need to learn more from messages before I can talk.") + +def improve_sentence_coherence(sentence): + + sentence = sentence.replace(" i ", " I ") + return sentence + +def rephrase_for_coherence(sentence): + + words = sentence.split() + + coherent_sentence = " ".join(words) + return coherent_sentence + +bot.help_command = None + + +@bot.command() +async def help(ctx, *args): + + if args: + command_name = args[0] + command = bot.get_command(command_name) + + if command: + embed = discord.Embed( + title=f"Help: g!{command_name}", + description=f"**Description:** {command.help}", + color=discord.Color.blue() + ) + await ctx.send(embed=embed) + else: + await ctx.send(f"Command `{command_name}` not found.") + else: + + embed = discord.Embed( + title="Bot Help", + description="List of commands grouped by category.", + color=discord.Color.blue() + ) + + command_categories = { + "General": ["show_memory", "talk", "ask", "ping"], + "Debug": ["word_usage"] + } + + for category, commands_list in command_categories.items(): + commands_in_category = "\n".join([f"g!{command}" for command in commands_list]) + embed.add_field(name=category, value=commands_in_category, inline=False) + + await ctx.send(embed=embed) + +@bot.event +async def on_message(message): + global memory, markov_model, last_random_talk_time + + if message.author.bot: + return + + + if message.content.startswith(("g!talk", "g!show_memory", "g!help", "g!")): + await bot.process_commands(message) + return + + if message.content: + formatted_message = append_mentions_to_18digit_integer(message.content) + cleaned_message = preprocess_message(formatted_message) + if cleaned_message: + memory.append(cleaned_message) + save_memory(memory) + markov_model = train_markov_model(memory) + + # process any commands in the message + await bot.process_commands(message) + +@bot.command() +async def ping(ctx): + await ctx.defer() + #stolen from my expect bot very proud + latency = round(bot.latency * 1000) + + LOLembed = discord.Embed( + title="Pong!!", + description=( + f"The Beretta fires fast and won't make you feel any better!\n" + f"`Bot Latency: {latency}ms`\n" + ), + color=discord.Color.blue() + ) + LOLembed.set_footer(text=f"Requested by {ctx.author.name}", icon_url=ctx.author.avatar.url) + + await ctx.send(embed=LOLembed) # use ctx.send instead of respond because it has nothing to respond to and its not a slash command + +@bot.command() +async def show_memory(ctx): + memory = load_memory() + memory_text = json.dumps(memory, indent=4) + if len(memory_text) > 1024: + with open(MEMORY_FILE, "r") as f: + await ctx.send(" ", file=discord.File(f, MEMORY_FILE)) + else: + embed = discord.Embed(title="Memory Contents", description="The bot's memory.", color=discord.Color.blue()) + embed.add_field(name="Memory Data", value=f"```json\n{memory_text}\n```", inline=False) + await ctx.send(embed=embed) + +def improve_sentence_coherence(sentence): + sentence = sentence.replace(" i ", " I ") + return sentence + +@tasks.loop(minutes=60) +async def post_message(): + channel_id = 1296141985253691433 + channel = bot.get_channel(channel_id) + if channel and markov_model: + response = None + for _ in range(10): + response = markov_model.make_sentence(tries=100) + if response and response not in generated_sentences: + generated_sentences.add(response) + break + + if response: + await channel.send(response) + +# run the bot +TOKEN = os.getenv("DISCORDBOTTOKEN", "0") +bot.run(TOKEN) From 221f2370e42c56a7809407cbb660dd9d753cce6a Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 21 Jul 2025 21:12:31 +0200 Subject: [PATCH 80/84] oh yeah --- assets/locales/en.json | 2 +- assets/locales/fr.json | 130 ----------------------------------------- assets/locales/it.json | 2 +- bot.py | 11 ++++ modules/logger.py | 7 ++- 5 files changed, 19 insertions(+), 133 deletions(-) delete mode 100644 assets/locales/fr.json diff --git a/assets/locales/en.json b/assets/locales/en.json index a91a1e1..53e9dd5 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -17,7 +17,7 @@ "memory_file_valid": "The memory.json file is valid!", "file_aint_uft8": "File is not valid UTF-8 text. Might be binary or corrupted.", "psutil_not_installed": "Memory check skipped.", - "not_cloned": "Goober is not cloned! Please clone it from GitHub.", + "not_cloned": "Goober is not cloned! Please clone it from Git.", "checks_disabled": "Checks are disabled!", "unhandled_exception": "An unhandled exception occurred. Please report this issue on GitHub.", "active_users:": "Active users:", diff --git a/assets/locales/fr.json b/assets/locales/fr.json deleted file mode 100644 index 572d2f4..0000000 --- a/assets/locales/fr.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "checks_disabled": "Les vérifications sont désactivées !", - "unhandled_exception": "Une exception non gérée est survenue. Merci de rapporter ce problème sur GitHub.", - "active_users:": "Utilisateurs actifs :", - "spacy_initialized": "spaCy et spacytextblob sont prêts.", - "spacy_model_not_found": "Le modèle spaCy est introuvable ! Téléchargement en cours...", - "env_file_not_found": "Le fichier .env est introuvable ! Créez-en un avec les variables nécessaires.", - "error_fetching_active_users": "Erreur lors de la récupération des utilisateurs actifs : {error}", - "error_sending_alive_ping": "Erreur lors de l’envoi du ping actif : {error}", - "already_started": "J’ai déjà démarré ! Je ne me mets pas à jour...", - "please_restart": "Redémarre, stp !", - "local_ahead": "Local {remote}/{branch} est en avance ou à jour. Pas de mise à jour...", - "remote_ahead": "Remote {remote}/{branch} est en avance. Mise à jour en cours...", - "cant_find_local_version": "Je ne trouve pas la variable local_version ! Ou elle a été modifiée et ce n’est pas un entier !", - "running_prestart_checks": "Exécution des vérifications préalables au démarrage...", - "continuing_in_seconds": "Reprise dans {seconds} secondes... Appuie sur une touche pour passer.", - "missing_requests_psutil": "requests et psutil manquants ! Installe-les avec pip : `pip install requests psutil`", - "requirements_not_found": "requirements.txt introuvable à {path}, a-t-il été modifié ?", - "warning_failed_parse_imports": "Avertissement : Échec du parsing des imports depuis {filename} : {error}", - "cogs_dir_not_found": "Répertoire des cogs introuvable à {path}, scan ignoré.", - "std_lib_local_skipped": "LIB STD / LOCAL {package} (vérification sautée)", - "ok_installed": "OK", - "missing_package": "MANQUANT", - "missing_package2": "n’est pas installé", - "missing_packages_detected": "Packages manquants détectés :", - "telling_goober_central": "Envoi à goober central à {url}", - "failed_to_contact": "Impossible de contacter {url} : {error}", - "all_requirements_satisfied": "Toutes les dépendances sont satisfaites.", - "ping_to": "Ping vers {host} : {latency} ms", - "high_latency": "Latence élevée détectée ! Tu pourrais avoir des délais de réponse.", - "could_not_parse_latency": "Impossible d’analyser la latence.", - "ping_failed": "Ping vers {host} échoué.", - "error_running_ping": "Erreur lors du ping : {error}", - "memory_usage": "Utilisation mémoire : {used} Go / {total} Go ({percent}%)", - "memory_above_90": "Usage mémoire au-dessus de 90% ({percent}%). Pense à libérer de la mémoire.", - "total_memory": "Mémoire totale : {total} Go", - "used_memory": "Mémoire utilisée : {used} Go", - "low_free_memory": "Mémoire libre faible détectée ! Seulement {free} Go disponibles.", - "measuring_cpu": "Mesure de l’usage CPU par cœur...", - "core_usage": "Cœur {idx} : [{bar}] {usage}%", - "total_cpu_usage": "Usage total CPU : {usage}%", - "high_avg_cpu": "Moyenne CPU élevée : {usage}%", - "really_high_cpu": "Charge CPU vraiment élevée ! Le système pourrait ralentir ou planter.", - "memory_file": "Fichier mémoire : {size} Mo", - "memory_file_large": "Fichier mémoire de 1 Go ou plus, pense à le nettoyer pour libérer de l’espace.", - "memory_file_corrupted": "Fichier mémoire corrompu ! Erreur JSON : {error}", - "consider_backup_memory": "Pense à sauvegarder et recréer le fichier mémoire.", - "memory_file_encoding": "Problèmes d’encodage du fichier mémoire : {error}", - "error_reading_memory": "Erreur lecture fichier mémoire : {error}", - "memory_file_not_found": "Fichier mémoire introuvable.", - "modification_warning": "Goober a été modifié ! Toutes les modifications seront perdues lors d'une mise à jour !", - "reported_version": "Version rapportée :", - "current_hash": "Hachage actuel :", - "not_found": "n'est pas trouvé !", - "version_error": "Impossible de récupérer les informations de version. Code d'état", - "loaded_cog": "Cog chargé :", - "loaded_cog2": "Module chargé :", - "cog_fail": "Échec du chargement du cog :", - "cog_fail2": "Échec du chargement du module :", - "no_model": "Aucun modèle Markov sauvegardé trouvé. Démarrage à partir de zéro.", - "folder_created": "Dossier '{folder_name}' créé.", - "folder_exists": "Le dossier '{folder_name}' existe déjà. Ignorons...", - "logged_in": "Connecté en tant que", - "synced_commands": "Synchronisé", - "synced_commands2": "commandes !", - "fail_commands_sync": "Échec de la synchronisation des commandes :", - "started": "{name} a démarré !", - "name_check": "Erreur lors de la vérification de la disponibilité du nom :", - "name_taken": "Le nom est déjà pris. Veuillez choisir un autre nom.", - "name_check2": "Erreur lors de la vérification de la disponibilité du nom :", - "add_token": "Token : {token}\nVeuillez ajouter ce token à votre fichier .env comme", - "token_exists": "Le token existe déjà dans .env. Utilisation du token existant.", - "registration_error": "Erreur lors de l'enregistrement :", - "version_backup": "Sauvegarde créée :", - "backup_error": "Erreur : {LOCAL_VERSION_FILE} introuvable pour la sauvegarde.", - "model_loaded": "Modèle Markov chargé depuis", - "fetch_update_fail": "Impossible de récupérer les informations de mise à jour.", - "invalid_server": "Erreur : Informations de version invalides reçues du serveur.", - "goober_server_alert": "Alerte du serveur Goober central !\n", - "new_version": "Nouvelle version disponible : {latest_version} (Actuelle : {local_version})", - "changelog": "Consultez {VERSION_URL}/goob/changes.txt pour voir les modifications\n\n", - "invalid_version": "La version : {local_version} n'est pas valide !", - "invalid_version2": "Si c'est intentionnel, ignorez ce message. Sinon, appuyez sur Y pour récupérer une version valide depuis le serveur, quelle que soit la version actuelle de Goober.", - "invalid_version3": "La version actuelle sera sauvegardée dans current_version.bak..", - "input": "(Y ou toute autre touche pour ignorer...)", - "modification_ignored": "Vous avez modifié", - "modification_ignored2": "IGNOREWARNING est désactivé..", - "latest_version": "Vous utilisez la dernière version :", - "latest_version2": "Consultez {VERSION_URL}/goob/changes.txt pour voir les modifications", - "pinging_disabled": "Le ping est désactivé ! Je ne préviens pas le serveur que je suis en ligne...", - "goober_ping_success": "Connecté à Goober central en tant que {NAME}", - "goober_ping_fail": "Échec de l'envoi des données. Le serveur a retourné le code d'état :", - "goober_ping_fail2": "Une erreur est survenue lors de l'envoi des données :", - "sentence_positivity": "La positivité de la phrase est :", - "command_edit_fail": "Échec de la modification du message :", - "command_desc_retrain": "Réentraîne manuellement le modèle Markov.", - "command_markov_retrain": "Réentraînement du modèle Markov... Veuillez patienter.", - "command_markov_memory_not_found": "Erreur : fichier de mémoire introuvable !", - "command_markov_memory_is_corrupt": "Erreur : le fichier de mémoire est corrompu !", - "command_markov_retraining": "Traitement de {processed_data}/{data_size} points de données...", - "command_markov_retrain_successful": "Modèle Markov réentraîné avec succès en utilisant {data_size} points de données !", - "command_desc_talk": "parle et tout ça", - "command_talk_insufficent_text": "Je dois apprendre plus de messages avant de pouvoir parler.", - "command_talk_generation_fail": "Je n'ai rien à dire pour le moment !", - "command_desc_help": "aide", - "command_help_embed_title": "Aide du bot", - "command_help_embed_desc": "Liste des commandes regroupées par catégorie.", - "command_help_categories_general": "Général", - "command_help_categories_admin": "Administration", - "command_help_categories_custom": "Commandes personnalisées", - "command_ran": "Info : {message.author.name} a exécuté {message.content}", - "command_ran_s": "Info : {interaction.user} a exécuté ", - "command_desc_ping": "ping", - "command_ping_embed_desc": "Latence du bot :", - "command_ping_footer": "Demandé par", - "command_about_desc": "à propos", - "command_about_embed_title": "À propos de moi", - "command_about_embed_field1": "Nom", - "command_about_embed_field2name": "Version", - "command_about_embed_field2value": "Locale : {local_version} \nDernière : {latest_version}", - "command_desc_stats": "statistiques", - "command_stats_embed_title": "Statistiques du bot", - "command_stats_embed_desc": "Données sur la mémoire du bot.", - "command_stats_embed_field1name": "Statistiques du fichier", - "command_stats_embed_field1value": "Taille : {file_size} octets\nLignes : {line_count}", - "command_stats_embed_field2name": "Version", - "command_stats_embed_field2value": "Locale : {local_version} \nDernière : {latest_version}", - "command_stats_embed_field3name": "Informations variables", - "command_stats_embed_field3value": "Nom : {NAME} \nPréfixe : {PREFIX} \nID du propriétaire : {ownerid}\nLigne de ping : {PING_LINE} \nPartage de mémoire activé : {showmemenabled} \nEntraînement utilisateur activé : {USERTRAIN_ENABLED} \nChanson : {song} \nTexte de démarrage : ```{splashtext}```" -} \ No newline at end of file diff --git a/assets/locales/it.json b/assets/locales/it.json index 0ae6413..972088d 100644 --- a/assets/locales/it.json +++ b/assets/locales/it.json @@ -17,7 +17,7 @@ "memory_file_valid": "Il file JSON è valido!", "file_aint_utf8": "Il file non è un UTF-8 valido. Forse è binario?", "psutil_not_installed": "Controllo memoria saltato.", - "not_cloned": "Goober non è stato clonato! Clonalo da GitHub.", + "not_cloned": "Goober non è stato clonato! Clonalo da Git.", "checks_disabled": "I controlli sono disabilitati!", "unhandled_exception": "Si è verificata un'eccezione non gestita. Segnala questo problema su GitHub, per favore.", "active_users:": "Utenti attivi:", diff --git a/bot.py b/bot.py index a774aa0..ac5897d 100644 --- a/bot.py +++ b/bot.py @@ -481,6 +481,17 @@ async def about(ctx: commands.Context) -> None: embed.add_field(name=f"OS", value=platform.platform()) await send_message(ctx, embed=embed) +@bot.hybrid_command(description="balls") +async def countUser(ctx, user: discord.User = None): + user = user or ctx.author + user_id = str(user.id) + + with open(MEMORY_FILE, 'r') as f: + data = json.load(f) + + count = sum(1 for entry in data if '_meta' in entry and entry['_meta'].get('user_id') == user_id) + + await ctx.send(f"User {user.display_name}'s ID appears {count} times in the memory file.") # Command: Show bot statistics (admin only) @bot.hybrid_command(description="stats") diff --git a/modules/logger.py b/modules/logger.py index 76f5f10..79d9029 100644 --- a/modules/logger.py +++ b/modules/logger.py @@ -1,4 +1,5 @@ import logging +import re from modules.globalvars import * class GooberFormatter(logging.Formatter): @@ -16,10 +17,14 @@ class GooberFormatter(logging.Formatter): } def format(self, record: logging.LogRecord): + ansiescape = re.compile(r'\x1B[@-_][0-?]*[ -/]*[@-~]') if self.colors: log_fmt = self.FORMATS.get(record.levelno) # Add colors else: log_fmt = self._format # Just use the default format formatter = logging.Formatter(log_fmt, datefmt="%m/%d/%y %H:%M:%S") - return formatter.format(record) + formatted = formatter.format(record) + if not self.colors: + formatted = ansiescape.sub('', formatted) + return formatted From ed53089d4a1c30db96ecd1abee6a7b08d5354950 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 21 Jul 2025 22:10:00 +0200 Subject: [PATCH 81/84] :p --- README.md | 5 ++--- bot.py | 18 +++++++----------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index dc1f094..ae744a6 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ knockoff of genai basically :p - +not that hard to setup just modify the .env and install packages and youre rollin +maybe also create custom commands (you put them in the cog folder its just drap and drop) Special thanks to [Charlie's Computers](https://github.com/PowerPCFan) for being the only one I know of that's hosting Goober 24/7 -[Goober Central](https://github.com/whatdidyouexpect/goober-central) - ![the goober](https://goober.whatdidyouexpect.eu/imgs/goobs/goobs.png) diff --git a/bot.py b/bot.py index ac5897d..12d3d87 100644 --- a/bot.py +++ b/bot.py @@ -7,6 +7,7 @@ import traceback import subprocess import tempfile import shutil +import psutil import asyncio import platform import sys @@ -219,6 +220,12 @@ async def talk(ctx: commands.Context, sentence_size: int = 5) -> None: else: await send_message(ctx, f"{(_('command_talk_generation_fail'))}") +@bot.hybrid_command(description=f"RAM") +async def ramusage(ctx): + process = psutil.Process(os.getpid()) + mem = process.memory_info().rss + await send_message(ctx, f"Total memory used: {mem / 1024 / 1024:.2f} MB") + # Command: Generate an image @bot.hybrid_command(description=f"{(_('command_desc_help'))}") async def impact(ctx: commands.Context, text: Optional[str] = None) -> None: @@ -481,17 +488,6 @@ async def about(ctx: commands.Context) -> None: embed.add_field(name=f"OS", value=platform.platform()) await send_message(ctx, embed=embed) -@bot.hybrid_command(description="balls") -async def countUser(ctx, user: discord.User = None): - user = user or ctx.author - user_id = str(user.id) - - with open(MEMORY_FILE, 'r') as f: - data = json.load(f) - - count = sum(1 for entry in data if '_meta' in entry and entry['_meta'].get('user_id') == user_id) - - await ctx.send(f"User {user.display_name}'s ID appears {count} times in the memory file.") # Command: Show bot statistics (admin only) @bot.hybrid_command(description="stats") From f17db0d22c74efa10e019916eafa2a69c4f55906 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Mon, 21 Jul 2025 22:13:42 +0200 Subject: [PATCH 82/84] canadian french added i dont know any normal french and the guy i know is canadian --- assets/locales/fr_ca.json | 149 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 assets/locales/fr_ca.json diff --git a/assets/locales/fr_ca.json b/assets/locales/fr_ca.json new file mode 100644 index 0000000..782b255 --- /dev/null +++ b/assets/locales/fr_ca.json @@ -0,0 +1,149 @@ +{ + "minigames_hangman_game": "Mot à deviner : {display_word()}\nMauvaises guesses : {wrong_guesses}/{max_wrong}", + "minigames_hangman_lost": "T'es échoué solide! Le mot était :", + "minigames_hangman_won": "T'as gagné en masse! Le mot était :", + "minigames_hangman_already_guessed": "T'as déjà essayé ça mon chum", + "minigames_hangman_user_letter_guess": "Ta guess de lettre", + "minigames_hangman_guess": "Devine une lettre", + "minigames_hangman_api_failed": "Ça a chié en essayant d'avoir un mot aléatoire.", + "minigames_hangman": "Jouer au Pendu avec un mot pogné au hasard", + "minigames_click_to_guess": "Clique pour deviner un chiffre entre 1 pis 10", + "minigames_guess_button": "Devine", + "minigames_wrong_number": "Nope! C'était", + "minigames_correct": "Bonne guess!", + "minigames_invalid_number": "Chiffre pas valide!", + "minigames_guess_the_number": "Devine le chiffre", + "minigames_your_guess": "Ta guess (1-10)", + "memory_file_valid": "Le fichier memory.json est correct!", + "file_aint_uft8": "Le fichier est pas du bon UTF-8. Ça doit être binaire ou scrap.", + "psutil_not_installed": "Vérification de mémoire skipée.", + "not_cloned": "Goober est pas cloné! Va donc le cloner depuis Git.", + "checks_disabled": "Les checks sont désactivées!", + "unhandled_exception": "Y'a eu une erreur pas prévue. Rapporte ça sur GitHub mon gars.", + "active_users:": "Monde actif :", + "spacy_initialized": "spaCy pis spacytextblob sont prêts.", + "spacy_model_not_found": "Le modèle spaCy est introuvable! On le télécharge...", + "env_file_not_found": "Le fichier .env est pas là! Fais-en un avec les variables nécessaires.", + "error_fetching_active_users": "Ça a chié en essayant de pogner les utilisateurs actifs : {error}", + "error_sending_alive_ping": "Ça a chié en envoyant le ping : {error}", + "already_started": "J'suis déjà parti! J'me mets pas à jour...", + "please_restart": "Redémarre-moi donc!", + "local_ahead": "La version locale {remote}/{branch} est à jour. Pas besoin d'update...", + "remote_ahead": "La version remote {remote}/{branch} est en avance. On update...", + "cant_find_local_version": "J'arrive pas à trouver la variable local_version! Ou ben elle a été modifiée pis c'est pas un chiffre!", + "running_prestart_checks": "On fait les checks avant de partir...", + "continuing_in_seconds": "On continue dans {seconds} secondes... Appuie sur une touche pour skip.", + "missing_requests_psutil": "Y manque requests pis psutil! Installe-les avec pip : `pip install requests psutil`", + "requirements_not_found": "requirements.txt introuvable à {path}, est-ce qu'il a été modifié?", + "warning_failed_parse_imports": "Attention : Ça a chié en lisant les imports de {filename} : {error}", + "cogs_dir_not_found": "Le dossier des cogs est pas à {path}, on skip le scan.", + "std_lib_local_skipped": "LIB STD / LOCAL {package} (check skipé)", + "ok_installed": "OK", + "missing_package": "MANQUANT", + "missing_package2": "est pas installé", + "missing_packages_detected": "Y'a des affaires qui manquent :", + "telling_goober_central": "J'envoie ça à goober central à {url}", + "failed_to_contact": "J'ai pas réussi à contacter {url} : {error}", + "all_requirements_satisfied": "Tout ce qu'il faut est installé.", + "ping_to": "Ping à {host} : {latency} ms", + "high_latency": "Latence élevée! Ça pourrait être lent.", + "could_not_parse_latency": "J'ai pas pu comprendre la latence.", + "ping_failed": "Le ping à {host} a chié.", + "error_running_ping": "Ça a chié en faisant le ping : {error}", + "memory_usage": "Mémoire utilisée : {used} Go / {total} Go ({percent}%)", + "memory_above_90": "La mémoire est à plus de 90% ({percent}%). Libère de la mémoire.", + "total_memory": "Mémoire totale : {total} Go", + "used_memory": "Mémoire utilisée : {used} Go", + "low_free_memory": "Y'a presque plus de mémoire! Juste {free} Go de libre.", + "measuring_cpu": "On check l'usage CPU par coeur...", + "core_usage": "Coeur {idx} : [{bar}] {usage}%", + "total_cpu_usage": "Usage total CPU : {usage}%", + "high_avg_cpu": "CPU trop élevé : {usage}%", + "really_high_cpu": "Le CPU est en tabarnak! Ça pourrait crasher.", + "memory_file": "Fichier mémoire : {size} Mo", + "memory_file_large": "Fichier mémoire de 1 Go ou plus, nettoie ça pour faire de la place.", + "memory_file_corrupted": "Fichier mémoire scrap! Erreur JSON : {error}", + "consider_backup_memory": "Pense à faire un backup pis recréer le fichier mémoire.", + "memory_file_encoding": "Problème d'encodage du fichier mémoire : {error}", + "error_reading_memory": "Ça a chié en lisant le fichier mémoire : {error}", + "memory_file_not_found": "Fichier mémoire pas trouvé.", + "modification_warning": "Goober a été modifié! Tes modifications vont être perdues à l'update!", + "reported_version": "Version rapportée :", + "current_hash": "Hash actuel :", + "not_found": "est pas trouvé!", + "version_error": "J'ai pas pu avoir les infos de version. Code d'état", + "loaded_cog": "Cog chargé :", + "loaded_cog2": "Module chargé :", + "cog_fail": "Ça a chié en chargeant le cog :", + "cog_fail2": "Ça a chié en chargeant le module :", + "no_model": "Y'a pas de modèle Markov de sauvegardé. On part de zéro.", + "folder_created": "Dossier '{folder_name}' créé.", + "folder_exists": "Le dossier '{folder_name}' existe déjà. On skip...", + "logged_in": "Connecté en tant que", + "synced_commands": "Synchronisé", + "synced_commands2": "commandes!", + "fail_commands_sync": "Ça a chié en synchronisant les commandes :", + "started": "{name} est parti!", + "name_check": "Ça a chié en checkant si le nom est libre :", + "name_taken": "Le nom est déjà pris. Choisis-en un autre.", + "name_check2": "Ça a chié en checkant si le nom est libre :", + "add_token": "Token : {token}\nAjoute ce token dans ton .env comme", + "token_exists": "Le token existe déjà dans .env. On utilise celui-là.", + "registration_error": "Ça a chié en s'enregistrant :", + "version_backup": "Backup créé :", + "backup_error": "Erreur : {LOCAL_VERSION_FILE} pas trouvé pour le backup.", + "model_loaded": "Modèle Markov chargé depuis", + "fetch_update_fail": "J'ai pas pu avoir les infos d'update.", + "invalid_server": "Erreur : Infos de version invalides du serveur.", + "goober_server_alert": "Alerte du serveur Goober central!\n", + "new_version": "Nouvelle version disponible : {latest_version} (Actuelle : {local_version})", + "changelog": "Va voir {VERSION_URL}/goob/changes.txt pour les changements\n\n", + "invalid_version": "La version : {local_version} est pas valide!", + "invalid_version2": "Si c'est fait exprès, ignore ça. Sinon, appuie sur Y pour avoir une version valide du serveur, peu importe ta version actuelle de Goober.", + "invalid_version3": "La version actuelle va être backupée dans current_version.bak..", + "input": "(Y ou n'importe quelle touche pour skip...)", + "modification_ignored": "T'as modifié", + "modification_ignored2": "IGNOREWARNING est désactivé..", + "latest_version": "T'as la dernière version :", + "latest_version2": "Va voir {VERSION_URL}/goob/changes.txt pour les changements", + "pinging_disabled": "Le ping est désactivé! J'dis pas au serveur que j'suis en ligne...", + "goober_ping_success": "Connecté à Goober central en tant que {NAME}", + "goober_ping_fail": "Ça a chié en envoyant les données. Le serveur a retourné :", + "goober_ping_fail2": "Ça a chié en envoyant les données :", + "sentence_positivity": "La phrase est positive à :", + "command_edit_fail": "Ça a chié en éditant le message :", + "command_desc_retrain": "Réentraîne le modèle Markov à la main.", + "command_markov_retrain": "Réentraînement du modèle Markov... Attend un peu.", + "command_markov_memory_not_found": "Erreur : fichier mémoire pas trouvé!", + "command_markov_memory_is_corrupt": "Erreur : fichier mémoire scrap!", + "command_markov_retraining": "Traitement de {processed_data}/{data_size} points de données...", + "command_markov_retrain_successful": "Modèle Markov réentraîné avec succès avec {data_size} points de données!", + "command_desc_talk": "parle pis toute", + "command_talk_insufficent_text": "J'ai pas assez appris pour pouvoir parler.", + "command_talk_generation_fail": "J'ai rien à dire pour l'instant!", + "command_desc_help": "aide", + "command_help_embed_title": "Aide du bot", + "command_help_embed_desc": "Liste des commandes par catégorie.", + "command_help_categories_general": "Général", + "command_help_categories_admin": "Admin", + "command_help_categories_custom": "Commandes perso", + "command_ran": "Info : {message.author.name} a fait {message.content}", + "command_ran_s": "Info : {interaction.user} a fait ", + "command_desc_ping": "ping", + "command_ping_embed_desc": "Latence du bot :", + "command_ping_footer": "Demandé par", + "command_about_desc": "à propos", + "command_about_embed_title": "À propos de moi", + "command_about_embed_field1": "Nom", + "command_about_embed_field2name": "Version", + "command_about_embed_field2value": "Locale : {local_version} \nDernière : {latest_version}", + "command_desc_stats": "stats", + "command_stats_embed_title": "Stats du bot", + "command_stats_embed_desc": "Infos sur la mémoire du bot.", + "command_stats_embed_field1name": "Stats du fichier", + "command_stats_embed_field1value": "Taille : {file_size} octets\nLignes : {line_count}", + "command_stats_embed_field2name": "Version", + "command_stats_embed_field2value": "Locale : {local_version} \nDernière : {latest_version}", + "command_stats_embed_field3name": "Infos variables", + "command_stats_embed_field3value": "Nom : {NAME} \nPréfixe : {PREFIX} \nID du proprio : {ownerid}\nLigne de ping : {PING_LINE} \nPartage de mémoire activé : {showmemenabled} \nEntraînement utilisateur activé : {USERTRAIN_ENABLED} \nChanson : {song} \nTexte de démarrage : ```{splashtext}```" +} \ No newline at end of file From 7d6c5aae513fd3d1955ceae4207c9718aa25ac18 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:19:55 +0200 Subject: [PATCH 83/84] ts fuckass minigames file will be the death of me --- assets/cogs/lyrics.py | 123 +++++++++++++++++++++++++++++++++++++++++ assets/locales/it.json | 2 +- bot.py | 5 +- modules/globalvars.py | 2 + 4 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 assets/cogs/lyrics.py diff --git a/assets/cogs/lyrics.py b/assets/cogs/lyrics.py new file mode 100644 index 0000000..db999b4 --- /dev/null +++ b/assets/cogs/lyrics.py @@ -0,0 +1,123 @@ +import discord +from discord.ext import commands +from discord import app_commands +import aiohttp +import re + +class Lyrics(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @app_commands.command(name="lyrics", description="Get lyrics for a song") + @app_commands.describe( + artist="Name of the artist", + song="Title of the song", + language="Target language code (optional)" + ) + @app_commands.choices(language=[ + app_commands.Choice(name="Bulgarian", value="bg"), + app_commands.Choice(name="Czech", value="cs"), + app_commands.Choice(name="Danish", value="da"), + app_commands.Choice(name="German", value="de"), + app_commands.Choice(name="Greek", value="el"), + app_commands.Choice(name="English", value="en"), + app_commands.Choice(name="Spanish", value="es"), + app_commands.Choice(name="Estonian", value="et"), + app_commands.Choice(name="Finnish", value="fi"), + app_commands.Choice(name="French", value="fr"), + app_commands.Choice(name="Irish", value="ga"), + app_commands.Choice(name="Croatian", value="hr"), + app_commands.Choice(name="Hungarian", value="hu"), + app_commands.Choice(name="Italian", value="it"), + app_commands.Choice(name="Lithuanian", value="lt"), + app_commands.Choice(name="Latvian", value="lv"), + app_commands.Choice(name="Maltese", value="mt"), + app_commands.Choice(name="Dutch", value="nl"), + app_commands.Choice(name="Polish", value="pl"), + app_commands.Choice(name="Portuguese", value="pt"), + app_commands.Choice(name="Romanian", value="ro"), + app_commands.Choice(name="Slovak", value="sk"), + app_commands.Choice(name="Slovene", value="sl"), + app_commands.Choice(name="Swedish", value="sv"), + ]) + async def lyrics(self, interaction: discord.Interaction, artist: str = None, song: str = None, language: app_commands.Choice[str] = None): + await interaction.response.defer() + if not artist or not song: + member = interaction.guild.get_member(interaction.user.id) + if not member: + member = await interaction.guild.fetch_member(interaction.user.id) + act_artist, act_song = await self.get_artist_song_from_presence(member) + if act_artist and act_song: + artist = artist or act_artist + song = song or act_song + else: + await interaction.followup.send("No artist or song provided and couldn't find it from your current activity.") + return + + lyrics = await self.fetch_lyrics(artist, song) + if not lyrics: + await interaction.followup.send(f"Could not find lyrics for **{artist} - {song}**") + return + + if language: + translated = await self.translate_text(lyrics, language.value) + if translated: + lyrics = translated + + if len(lyrics) > 1900: + lyrics = lyrics[:1900] + "\n\n[...lyrics truncated...]" + + embed = discord.Embed( + title=f"{artist} - {song}", + description=lyrics, + color=discord.Color.blue() + ) + embed.set_footer(text=f"Requested by {interaction.user}", icon_url=interaction.user.display_avatar.url) + await interaction.followup.send(embed=embed) + + async def get_artist_song_from_presence(self, member: discord.Member): + for activity in member.activities: + if isinstance(activity, discord.Spotify): + return activity.artist, activity.title + return None, None + + + async def fetch_lyrics(self, artist, song): + artist_q = artist.replace(' ', '+').lower() + song_q = song.replace(' ', '+').lower() + + url = f"https://lrclib.net/api/get?artist_name={artist_q}&track_name={song_q}" + print(url) + + async with aiohttp.ClientSession() as session: + try: + async with session.get(url) as resp: + if resp.status != 200: + return None + data = await resp.json() + return data.get('plainLyrics') + except Exception: + return None + + async def translate_text(self, text: str, target_lang: str) -> str | None: + translate_url = "https://translate.googleapis.com/translate_a/single" + params = { + "client": "gtx", + "sl": "auto", + "tl": target_lang, + "dt": "t", + "q": text + } + async with aiohttp.ClientSession() as session: + try: + async with session.get(translate_url, params=params) as resp: + if resp.status != 200: + return None + result = await resp.json() + translated_chunks = [item[0] for item in result[0] if item[0]] + return ''.join(translated_chunks) + except Exception: + return None + +async def setup(bot): + await bot.add_cog(Lyrics(bot)) diff --git a/assets/locales/it.json b/assets/locales/it.json index 972088d..4f97806 100644 --- a/assets/locales/it.json +++ b/assets/locales/it.json @@ -1,5 +1,5 @@ { - "minigames_hangman_game": "Parola: {display_word}\nErrori: {wrong_guesses}/{max_wrong}", + "minigames_hangman_game": "Parola: {display_word()}\nErrori: {wrong_guesses}/{max_wrong}", "minigames_hangman_lost": "Hai perso! La parola era:", "minigames_hangman_won": "Hai vinto! La parola era:", "minigames_hangman_already_guessed": "Hai già indovinato", diff --git a/bot.py b/bot.py index 12d3d87..bbee94d 100644 --- a/bot.py +++ b/bot.py @@ -50,10 +50,9 @@ from modules.version import * from modules.sentenceprocessing import * from modules.unhandledexception import handle_exception from modules.image import gen_meme, gen_demotivator -# from modules.minigames import guessthenumber, hangman +from modules.minigames import guessthenumber, hangman sys.excepthook = handle_exception check_for_update() # Check for updates (from modules/version.py) - # Type aliases T = TypeVar('T') MessageContext = Union[commands.Context, discord.Interaction] @@ -114,7 +113,6 @@ async def on_ready() -> None: logger.info(f"{_('synced_commands')} {len(synced)} {(_('synced_commands2'))}") slash_commands_enabled = True logger.info(f"{(_('started')).format(name=NAME)}") - bot.loop.create_task(send_alive_ping_periodically()) except discord.errors.Forbidden as perm_error: logger.error(f"Permission error while syncing commands: {perm_error}") @@ -136,7 +134,6 @@ async def on_ready() -> None: }.get(status.lower(), discord.Status.online) await bot.change_presence(status=status, activity=discord.Activity(type=discord.ActivityType.listening, name=f"{song}")) launched = True - @bot.event async def on_command_error(ctx: commands.Context, error: commands.CommandError) -> None: from modules.unhandledexception import handle_exception diff --git a/modules/globalvars.py b/modules/globalvars.py index 5c20f1d..ac37ed4 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -66,5 +66,7 @@ else: # 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=PREFIX, intents=intents, allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False, replied_user=True)) \ No newline at end of file From b860d0e2714e77e018e77bd522866dd6de3d79c9 Mon Sep 17 00:00:00 2001 From: WhatDidYouExpect <89535984+WhatDidYouExpect@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:25:56 +0200 Subject: [PATCH 84/84] a --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ae744a6..c1e9957 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ knockoff of genai basically :p -not that hard to setup just modify the .env and install packages and youre rollin -maybe also create custom commands (you put them in the cog folder its just drap and drop) + Special thanks to [Charlie's Computers](https://github.com/PowerPCFan) for being the only one I know of that's hosting Goober 24/7 -![the goober](https://goober.whatdidyouexpect.eu/imgs/goobs/goobs.png) +[Goober Central](https://github.com/whatdidyouexpect/goober-central) + +[Another mirror](https://forgejo.expect.ovh/gooberinc/goober) +no promises that it'll be stable