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..7c2050c 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 * @@ -334,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] = [] @@ -353,6 +354,16 @@ 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: + 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 @bot.event async def on_message(message: discord.Message) -> None: diff --git a/modules/globalvars.py b/modules/globalvars.py index 1226cc3..7dd1c8a 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 = 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/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 803c1ca..171a39a 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 @@ -9,6 +8,7 @@ import sysconfig import ast import json import re +from spacy.util import is_package import importlib.metadata # import shutil @@ -20,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 @@ -27,30 +34,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() @@ -73,6 +56,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__)) @@ -284,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 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}") diff --git a/modules/volta/main.py b/modules/volta/main.py index 677402a..bb98e0a 100644 --- a/modules/volta/main.py +++ b/modules/volta/main.py @@ -24,7 +24,10 @@ working_dir = pathlib.Path.cwd() 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): @@ -36,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)) @@ -79,19 +98,61 @@ 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 FALLBACK_LOCALE in translations: + LOCALE = FALLBACK_LOCALE + else: + 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 FALLBACK_LOCALE not in translations: + print(f"[VOLTA] {RED}Fallback translations ({FALLBACK_LOCALE}.json) missing from assets/locales.{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] {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}") + 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] - 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 + 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 def _(key: str) -> str: @@ -101,3 +162,6 @@ 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..") 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