diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7baa6f9 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +DISCORD_BOT_TOKEN="" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8e0b517..422c1ad 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,6 @@ translation_report.txt translationcompleteness.py modules/volta log.txt +settings/admin_logs.json settings/settings.json -settings/splash.txt +assets/images/cached/* \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index ca39f8a..fc06f56 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "modules/volta"] path = modules/volta - url = https://forgejo.expect.ovh/gooberinc/volta + url = https://github.com/gooberinc/volta diff --git a/README.md b/README.md index a4fa2ce..bb820eb 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,2 @@ -knockoff of genai basically :p -THIS!! IS THE ACTUAL REPO!!!! NOT THE OTHER ONE!!! THIS ONE!!! - -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) - -[Another mirror](https://forgejo.expect.ovh/gooberinc/goober) -no promises that it'll be stable +Real repo: https://forgejo.expect.ovh/gooberinc/goober +This is just a fork that was made the upstream diff --git a/assets/cogs/README.md b/assets/cogs/README.md new file mode 100644 index 0000000..7d2ddee --- /dev/null +++ b/assets/cogs/README.md @@ -0,0 +1,28 @@ +# goobers custom commands +[Hello World!](https://github.com/WhatDidYouExpect/goober/blob/main/cogs/hello.py) +by expect + +[WhoAmI (lists username and nickname)](https://github.com/WhatDidYouExpect/goober/blob/main/cogs/whoami.py) +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) + +[Status Changer](https://raw.githubusercontent.com/WhatDidYouExpect/goober/refs/heads/main/cogs/songchanger.py) +by expect (requires goober version 0.11.8 or higher) + +[Status Changer](https://raw.githubusercontent.com/WhatDidYouExpect/goober/refs/heads/main/cogs/songchanger.py) +by expect (requires goober version 0.11.8 or higher) + +[webUI](https://raw.githubusercontent.com/WhatDidYouExpect/goober/refs/heads/main/cogs/webserver.py) +by expect (requires goober version 0.11.8 or higher) + +[LastFM](https://raw.githubusercontent.com/WhatDidYouExpect/goober/refs/heads/main/cogs/webserver.py) +by expect (no idea what version it needs i've only tried it on 1.0.3) +- you have to add LASTFM_USERNAME and LASTFM_API_KEY to your .env \ No newline at end of file diff --git a/assets/cogs/breaking_news.py b/assets/cogs/breaking_news.py new file mode 100644 index 0000000..eee3f82 --- /dev/null +++ b/assets/cogs/breaking_news.py @@ -0,0 +1,153 @@ +from typing import List +import discord +from discord.ext import commands +import markovify +from PIL import Image, ImageDraw, ImageFont +import os +from modules.markovmemory import load_markov_model +from textwrap import wrap +import logging +from modules.settings import instance as settings_manager +import re +import time +from modules.sync_conenctor import instance as sync_hub + +logger = logging.getLogger("goober") + +settings = settings_manager.settings + + +class BreakingNews(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot: commands.Bot = bot + self.font_size = 90 + self.image_margin = -25 + self.font: ImageFont.FreeTypeFont = ImageFont.truetype( + os.path.join("assets", "fonts", "SpecialGothic.ttf"), self.font_size + ) + + self.model: markovify.NewlineText | None = load_markov_model() + + @commands.command() + async def auto_create(self, ctx: commands.Context, enabled: str | None): + if enabled not in ["yes", "no"]: + await ctx.send( + f'Please use {settings["bot"]["prefix"]}auto_create ' + ) + return False + + mode: bool = enabled == "yes" + + settings_manager.set_plugin_setting( + "breaking_news", {"create_from_message_content": mode} + ) + + await ctx.send("Changed setting!") + + @commands.Cog.listener() + async def on_message(self, message: discord.Message): + if not settings_manager.get_plugin_settings( + "breaking_news", {"create_from_message_content": False} + ).get("create_from_message_content"): + logger.debug("Ignoring message - create_from_message_content not enabled") + return + + if not message.content.lower().startswith("breaking news:"): + logger.debug("Ignoring message - doesnt start with breaking news:") + return + + if not sync_hub.can_breaking_news(message.id): + logger.debug("Sync hub denied breaking news request") + return + + + texts = re.split("breaking news:", message.content, flags=re.IGNORECASE) + + logger.debug(texts) + try: + text = texts[1].strip() + if not self.model: + await message.reply("No news specified and model not found!") + return False + + text = text or self.model.make_sentence(max_chars=50, tries=50) + path = self.__insert_text(text) + except IndexError: + if self.model is None: + await message.reply("No model loaded and no breaking news specified") + return False + + path = self.__insert_text( + self.model.make_sentence(max_chars=50, tries=50) or "" + ) + await message.reply("You didn't specify any breaking news!") + + with open(path, "rb") as f: + await message.reply(file=discord.File(f)) + + @commands.command() + async def breaking_news(self, ctx: commands.Context, *args): + if not self.model: + await ctx.send("Please supply a message!") + return False + + message = " ".join(args) or self.model.make_sentence(max_chars=50, tries=50) + + if not message: + await ctx.send("Please supply a message!") + return False + + with open(self.__insert_text(message), "rb") as f: + await ctx.send(content="Breaking news!", file=discord.File(f)) + + def __insert_text(self, text): + start = time.time() + base_image_data: Image.ImageFile.ImageFile = Image.open( + os.path.join("assets", "images", "breaking_news.png") + ) + + base_image: ImageDraw.ImageDraw = ImageDraw.Draw(base_image_data) + + MAX_IMAGE_WIDTH = base_image_data.width - self.image_margin + + if len(text) * self.font_size > MAX_IMAGE_WIDTH: + parts = wrap(text, MAX_IMAGE_WIDTH // self.font_size) + logger.debug(parts) + for index, part in enumerate(parts): + text_size = base_image.textlength(part, self.font) + + base_image.text( + ( + self.image_margin / 2 + ((MAX_IMAGE_WIDTH - text_size) / 2), + (base_image_data.height * 0.2) + index * self.font_size, + ), + part, + font=self.font, + ) + else: + text_size = base_image.textlength(text, self.font) + + base_image.text( + ( + self.image_margin / 2 + ((MAX_IMAGE_WIDTH - text_size) / 2), + (base_image_data.height * 0.2), + ), + text, + font=self.font, + ) + + path_folders = os.path.join("assets", "images", "cache") + os.makedirs(path_folders, exist_ok=True) + + path = os.path.join(path_folders, "breaking_news.png") + + with open(path, "wb") as f: + base_image_data.save(f) + + logger.info(f"Generation took {time.time() - start}s") + + return path + + +async def setup(bot: commands.Bot): + await bot.add_cog(BreakingNews(bot)) diff --git a/assets/cogs/eightball.py b/assets/cogs/eightball.py index ec6c955..39eb42c 100644 --- a/assets/cogs/eightball.py +++ b/assets/cogs/eightball.py @@ -2,56 +2,40 @@ import random import discord from discord.ext import commands + class eightball(commands.Cog): def __init__(self, bot): self.bot = bot @commands.command() async def eightball(self, ctx): - answer = random.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." + answer = random.choice( + [ + "It is certain.", + "It is decidedly so.", + "Without a doubt.", + "Yes definitely.", + "You may rely on it.", + "As I see it, yes.", + "Most likely.", + "Outlook good.", + "Yes.", + "Signs point to yes.", + "Reply hazy, try again.", + "Ask again later.", + "Better not tell you now.", + "Cannot predict now.", + "Concentrate and ask again.", + "Don't count on it.", + "My reply is no.", + "My sources say no.", + "Outlook not so good.", + "Very doubtful.", + ] + ) + + await ctx.send(answer) - await ctx.send(text) async def setup(bot): await bot.add_cog(eightball(bot)) diff --git a/assets/cogs/example.py b/assets/cogs/example.py new file mode 100644 index 0000000..5db4016 --- /dev/null +++ b/assets/cogs/example.py @@ -0,0 +1,131 @@ +import discord +from discord.ext import commands +from discord import app_commands + +import discord.ext +import discord.ext.commands + +import random + +from modules.permission import requires_admin +from modules.sentenceprocessing import send_message +from modules.settings import instance as settings_manager +from typing import TypedDict + +# Name according to your cog (e.g a random number generator -> RandomNumber) +class Example(commands.Cog): + # __init__ method is required with these exact parameters + def __init__(self, bot: discord.ext.commands.Bot): # type hinting (aka : discord.ext.commands.Bot) isn't necessary, but provides better intellisense in code editors + self.bot: discord.ext.commands.Bot = bot + + + # a basic ping slash command which utilizes embeds + @app_commands.command(name="ping", description="A command that sends a ping!") + async def ping(self, interaction: discord.Interaction): + await interaction.response.defer() + + example_embed = discord.Embed( + title="Pong!!", + description="The Beretta fires fast and won't make you feel any better!", + color=discord.Color.blue(), + ) + example_embed.set_footer( + text=f"Requested by {interaction.user.name}", + icon_url=interaction.user.display_avatar, + ) + + await interaction.followup.send(embed=example_embed) + + # a basic command (aka prefix.random_number) + # Shows how to get parameters, and how to send messages using goobers message thing + @commands.command() + async def random_number(self, ctx: commands.Context, minimum: int | None, maximum: int | None): # every argument after ctx is a part of the command, aka "g.random_number 0 5" would set minimum as 0 and maximum as 5 + # We should always assume that command parameters are None, since someone can gall g.randon_number. + + if minimum is None: + await send_message(ctx, message="Please specify the minimum number!") + return # make sure we dont continue + + if maximum is None: + await send_message(ctx, message="Please specify the maximum number!") + return # make sure we dont continue + + + number = random.randint(minimum, maximum) + + example_embed = discord.Embed( + title="Random number generator", + description=f"Random number: {number}", + color=discord.Color.blue(), + ) + example_embed.set_footer( + text=f"Requested by {ctx.author.name}", + icon_url=ctx.author.display_avatar, + ) + + await send_message(ctx, embed=example_embed) + + + # A command which requires the executor to be an admin, and takes a discord user as an argument + @requires_admin() # from modules.permission import requires_admin + @commands.command() + async def ban_user(self, ctx: commands.Context, target: discord.Member | None, reason: str | None): + if target is None: + await send_message(ctx, "Please specify a user by pinging them!") + return + + await target.ban(reason=reason) + await send_message(ctx, message=f"Banned user {target.name}!") + + + # Changing and getting plugin settings, defining a settings schmea + @commands.command() + async def change_hello_message(self, ctx: commands.Context, new_message: str | None): + COG_NAME = "example" # change this to whatever you want, but keep it the same accross your cog + + if new_message is None: + await send_message(ctx, "Please specify a new message!") + return + + # Generating a settings schema (optional) + # from typing import TypedDict + class IntroSettings(TypedDict): + message: str + + class SettingsType(TypedDict): + intro: IntroSettings + leave_message: str + + # End of optional typing + # Note: if you decide to do this, please place these at the top of the file! (but after imports) + + default_settings: SettingsType = { # use default_settings = { if you didnt define the types + "intro": { + "message": "Hello user!" + }, + "leave_message": "Goodbye user!" + } + + + # from modules.settings import instance as settings_manager + # get current plugin settings + # change "example" to your cog name + settings: SettingsType = settings_manager.get_plugin_settings(COG_NAME, default=default_settings) #type: ignore[assignment] + + # Now you can use settings easily! + + current_message = settings["intro"]["message"] + await send_message(ctx, message=f"Current message: {current_message}") + + # Changing plugin settings + settings["intro"]["message"] = "brand new message!" + + settings_manager.set_plugin_setting(COG_NAME, settings) + + new_message = settings["intro"]["message"] + await send_message(ctx, message=f"New message: {new_message}") + + + +async def setup(bot): + await bot.add_cog(Example(bot)) diff --git a/assets/cogs/filesharing.py b/assets/cogs/filesharing.py index 7229673..441375c 100644 --- a/assets/cogs/filesharing.py +++ b/assets/cogs/filesharing.py @@ -1,45 +1,70 @@ import discord +import discord.context_managers from discord.ext import commands +import logging +from typing import Literal, get_args, cast from modules.permission import requires_admin +from modules.settings import instance as settings_manager + +settings = settings_manager.settings + + +logger = logging.getLogger("goober") + +AvailableModes = Literal["r", "s"] + + class FileSync(commands.Cog): def __init__(self, bot): - self.bot = bot - self.mode = None + self.bot: discord.Client = bot + self.mode: AvailableModes | None = None self.peer_id = None self.awaiting_file = False + @requires_admin() @commands.command() - async def syncfile(self, ctx, mode: str, peer: discord.User): - self.mode = mode.lower() + async def syncfile(self, ctx: commands.Context, mode: str, peer: discord.User): + if self.mode not in get_args(AvailableModes): + await ctx.send("Invalid mode, use 's' or 'r'.") + return + + self.mode = cast(AvailableModes, mode.lower()) self.peer_id = peer.id + if self.mode == "s": await ctx.send(f"<@{self.peer_id}> FILE_TRANSFER_REQUEST") - await ctx.send(file=discord.File("memory.json")) + await ctx.send(file=discord.File(settings["bot"]["active_memory"])) await ctx.send("File sent in this channel.") + elif self.mode == "r": await ctx.send("Waiting for incoming file...") self.awaiting_file = True - else: - await ctx.send("Invalid mode, use 's' or 'r'.") - @commands.Cog.listener() - async def on_message(self, message): + async def on_message(self, message: discord.Message): if message.author == self.bot.user or not self.awaiting_file: return + if message.author.id != self.peer_id: return if message.content == "FILE_TRANSFER_REQUEST": - print("Ping received. Awaiting file...") - if message.attachments: - for attachment in message.attachments: - if attachment.filename.endswith(".json"): - filename = "received_memory.json" - await attachment.save(filename) - print(f"File saved as {filename}") - await message.channel.send("File received and saved.") - self.awaiting_file = False + logger.info("Ping received. Awaiting file...") + if not message.attachments: + return + + for attachment in message.attachments: + if not attachment.filename.endswith(".json"): + continue + + filename = "received_memory.json" + with open(filename, "wb") as f: + await attachment.save(f) + + logger.info(f"File saved as {filename}") + await message.channel.send("File received and saved.") + self.awaiting_file = False + async def setup(bot): await bot.add_cog(FileSync(bot)) diff --git a/assets/cogs/fuckup.py b/assets/cogs/fuckup.py new file mode 100644 index 0000000..89a3867 --- /dev/null +++ b/assets/cogs/fuckup.py @@ -0,0 +1,103 @@ +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 +import modules.keys as k + + +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(k.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(k.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(k.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/assets/cogs/internal/base_commands.py b/assets/cogs/internal/base_commands.py index ea1aaf8..d1ac354 100644 --- a/assets/cogs/internal/base_commands.py +++ b/assets/cogs/internal/base_commands.py @@ -1,57 +1,38 @@ import os -import platform -import subprocess from typing import Dict, List import discord -from discord import Colour from discord.ext import commands import discord.ext import discord.ext.commands -from modules.globalvars import local_version -from modules.volta.main import _ , set_language +import modules.keys as k from modules.permission import requires_admin from modules.sentenceprocessing import send_message from modules.settings import instance as settings_manager -from modules.version import check_for_update - import requests +import psutil +import cpuinfo +import sys +import subprocess +import updater +from modules.sync_conenctor import instance as sync_connector settings = settings_manager.settings -def get_git_origin_raw(): - try: - result = subprocess.run( - ["git", "config", "--get", "remote.origin.url"], - capture_output=True, text=True, check=True - ) - return result.stdout.strip() - except Exception: - return "Failed to get git origin" class BaseCommands(commands.Cog): def __init__(self, bot): self.bot: discord.ext.commands.Bot = bot - def get_git_origin_raw(): - try: - result = subprocess.run( - ["git", "config", "--get", "remote.origin.url"], - capture_output=True, text=True, check=True - ) - return result.stdout.strip() - except Exception: - return "https://forgejo.expect.ovh/gooberinc/goober" # fallback if git fails - @commands.command() async def help(self, ctx: commands.Context) -> None: embed: discord.Embed = discord.Embed( - title=f"{_('command_help_embed_title')}", - description=f"{_('command_help_embed_desc')}", + title=f"{k.command_help_embed_title()}", + description=f"{k.command_help_embed_desc()}", color=discord.Colour(0x000000), ) command_categories = { - f"{_('command_help_categories_general')}": [ + f"{k.command_help_categories_general()}": [ "mem", "talk", "about", @@ -60,7 +41,7 @@ class BaseCommands(commands.Cog): "demotivator", "help", ], - f"{_('command_help_categories_admin')}": ["stats", "retrain", "setlanguage"], + f"{k.command_help_categories_admin()}": ["stats", "retrain", "setlanguage"], } custom_commands: List[str] = [] @@ -68,15 +49,15 @@ class BaseCommands(commands.Cog): for command in cog.get_commands(): if ( command.name - not in command_categories[f"{_('command_help_categories_general')}"] + not in command_categories[f"{k.command_help_categories_general()}"] and command.name - not in command_categories[f"{_('command_help_categories_admin')}"] + not in command_categories[f"{k.command_help_categories_admin()}"] ): custom_commands.append(command.name) if custom_commands: embed.add_field( - name=_('command_help_categories_custom'), + name=f"{k.command_help_categories_custom()}", value="\n".join( [ f"{settings['bot']['prefix']}{command}" @@ -92,14 +73,17 @@ class BaseCommands(commands.Cog): ) embed.add_field(name=category, value=commands_in_category, inline=False) - await send_message(ctx, embed=embed) @requires_admin() @commands.command() async def setlanguage(self, ctx: commands.Context, locale: str) -> None: await ctx.defer() - set_language(locale) + k.change_language(locale) + + settings["locale"] = locale # type: ignore + settings_manager.commit() + await ctx.send(":thumbsup:") @commands.command() @@ -111,12 +95,12 @@ class BaseCommands(commands.Cog): title="Pong!!", description=( settings["bot"]["misc"]["ping_line"], - f"`{_('command_ping_embed_desc')}: {latency}ms`\n", + f"`{k.command_ping_embed_desc()}: {latency}ms`\n", ), color=discord.Colour(0x000000), ) embed.set_footer( - text=f"{_('command_ping_footer')} {ctx.author.name}", + text=f"{k.command_ping_footer()} {ctx.author.name}", icon_url=ctx.author.display_avatar.url, ) @@ -124,55 +108,87 @@ class BaseCommands(commands.Cog): @commands.command() async def about(self, ctx: commands.Context) -> None: - latest_version: str = check_for_update(slient=True) - embed: discord.Embed = discord.Embed(title=f"{(_('command_about_embed_title'))}", description="", color=Colour(0x000000)) - embed.add_field(name=f"{(_('command_about_embed_field1'))}", value=f"{settings['name']}", inline=False) - embed.add_field(name=f"{(_('command_about_embed_field2name'))}", value=f"{(_('command_about_embed_field2value')).format(local_version=local_version, latest_version=latest_version)}", inline=False) - embed.add_field(name=f"Git", value=get_git_origin_raw()) - embed.add_field(name=f"OS", value=platform.platform()) - + embed: discord.Embed = discord.Embed( + title=k.command_about_embed_title(), + description="", + color=discord.Colour(0x000000), + ) + + embed.add_field( + name=k.command_about_embed_field1(), + value=settings['name'], + inline=False, + ) + + embed.add_field(name="Github", value=f"https://github.com/gooberinc/goober") await send_message(ctx, embed=embed) @commands.command() async def stats(self, ctx: commands.Context) -> None: - memory_file: str = "memory.json" + memory_file: str = settings["bot"]["active_memory"] file_size: int = os.path.getsize(memory_file) + memory_info = psutil.virtual_memory() # type: ignore + total_memory = memory_info.total / (1024**3) + used_memory = memory_info.used / (1024**3) + + + cpu_name = cpuinfo.get_cpu_info()["brand_raw"] + + with open(memory_file, "r") as file: line_count: int = sum(1 for _ in file) embed: discord.Embed = discord.Embed( - title=f"{_('command_stats_embed_title')}", - description=f"{_('command_stats_embed_desc')}", + title=f"{k.command_stats_embed_title()}", + description=f"{k.command_stats_embed_desc()}", color=discord.Colour(0x000000), ) embed.add_field( - name=f"{_('command_stats_embed_field1name')}", - value=f"placeholder", + name=f"{k.command_stats_embed_field1name()}", + value=f"{k.command_stats_embed_field1value(file_size=file_size, line_count=line_count)}", inline=False, ) + embed.add_field( + name=k.system_info(), + value=f""" + {k.memory_usage(used=round(used_memory,2), total=round(total_memory,2), percent=round(used_memory/total_memory * 100))} + {k.cpu_info(cpu_name)} + """ + ) + with open(settings["splash_text_loc"], "r") as f: splash_text = "".join(f.readlines()) embed.add_field( - name=_('command_stats_embed_field3name'), - value=_('command_stats_embed_field3value').format( - NAME=settings["name"], - PREFIX=settings["bot"]["prefix"], - ownerid=settings["bot"]["owner_ids"][0], - PING_LINE=settings["bot"]["misc"]["ping_line"], - showmemenabled=settings["bot"]["allow_show_mem_command"], - USERTRAIN_ENABLED=settings["bot"]["user_training"], - song=settings["bot"]["misc"]["active_song"], + name=f"{k.command_stats_embed_field3name()}", + value=f"""{k.command_stats_embed_field3value( + NAME=settings["name"], PREFIX=settings["bot"]["prefix"], ownerid=settings["bot"]["owner_ids"][0], + PING_LINE=settings["bot"]["misc"]["ping_line"], showmemenabled=settings["bot"]["allow_show_mem_command"], + USERTRAIN_ENABLED=settings["bot"]["user_training"], song=settings["bot"]["misc"]["activity"]["content"], splashtext=splash_text - ), + )}""", inline=False, ) - await send_message(ctx, embed=embed) + @requires_admin() + @commands.command() + async def restart(self, ctx: commands.Context): + await ctx.send("Restarting...") + os.execv(sys.executable, [sys.executable] + sys.argv) + + @requires_admin() + @commands.command() + async def force_update(self, ctx: commands.Context): + await ctx.send("Forcefully updating...") + updater.force_update() + os.execv(sys.executable, [sys.executable] + sys.argv) + + + @requires_admin() @commands.command() async def mem(self, ctx: commands.Context) -> None: if not settings["bot"]["allow_show_mem_command"]: @@ -189,8 +205,27 @@ class BaseCommands(commands.Cog): await send_message(ctx, response.text) + @requires_admin() + @commands.command() + async def test_synchub(self, ctx: commands.Context, message_id: str | None) -> None: + message_id = message_id or "0" + status = sync_connector.can_react(int(message_id)) + + await send_message(ctx, f"Is allowed to react to message id {message_id}? {status} (connection active? {sync_connector.connected})") + + + @requires_admin() + @commands.command() + async def connect_synchub(self, ctx: commands.Context) -> None: + await send_message(ctx, "Trying to connect...") + + connected = sync_connector.try_to_connect() + if connected: + await send_message(ctx, "Succesfully connected to sync hub!") + else: + await send_message(ctx, "Failed to connect to sync hub") async def setup(bot: discord.ext.commands.Bot): print("Setting up base_commands") bot.remove_command("help") - await bot.add_cog(BaseCommands(bot)) \ No newline at end of file + await bot.add_cog(BaseCommands(bot)) diff --git a/assets/cogs/internal/cogmanager.py b/assets/cogs/internal/cogmanager.py index 1843eab..3387b7a 100644 --- a/assets/cogs/internal/cogmanager.py +++ b/assets/cogs/internal/cogmanager.py @@ -1,15 +1,52 @@ import discord from discord.ext import commands +import discord.ext +import discord.ext.commands from modules.permission import requires_admin +from modules.settings import instance as settings_manager +from modules.globalvars import available_cogs + +settings = settings_manager.settings + COG_PREFIX = "assets.cogs." + class CogManager(commands.Cog): def __init__(self, bot): self.bot = bot + @requires_admin() @commands.command() - async def load(self, ctx, cog_name: str = None): + async def enable(self, ctx, cog_name: str): + try: + await self.bot.load_extension(COG_PREFIX + cog_name) + await ctx.send(f"Loaded cog `{cog_name}` successfully.") + settings["bot"]["enabled_cogs"].append(cog_name) + settings_manager.add_admin_log_event( + { + "action": "add", + "author": ctx.author.id, + "change": "enabled_cogs", + "messageId": ctx.message.id, + "target": cog_name, + } + ) + settings_manager.commit() + + except Exception as e: + await ctx.send(f"Error enabling cog `{cog_name}`: {e}") + + @requires_admin() + @commands.command() + async def load(self, ctx, cog_name: str | None = None): + if cog_name is None: + await ctx.send("Give cog_name") + return + + if cog_name[:-3] not in settings["bot"]["enabled_cogs"]: + await ctx.send("Please enable the cog first!") + return if cog_name is None: await ctx.send("Please provide the cog name to load.") return @@ -18,9 +55,10 @@ class CogManager(commands.Cog): await ctx.send(f"Loaded cog `{cog_name}` successfully.") except Exception as e: await ctx.send(f"Error loading cog `{cog_name}`: {e}") + @requires_admin() @commands.command() - async def unload(self, ctx, cog_name: str = None): + async def unload(self, ctx, cog_name: str | None = None): if cog_name is None: await ctx.send("Please provide the cog name to unload.") return @@ -29,12 +67,40 @@ class CogManager(commands.Cog): await ctx.send(f"Unloaded cog `{cog_name}` successfully.") except Exception as e: await ctx.send(f"Error unloading cog `{cog_name}`: {e}") + @requires_admin() @commands.command() - async def reload(self, ctx, cog_name: str = None): + async def disable(self, ctx, cog_name: str | None = None): + if cog_name is None: + await ctx.send("Please provide the cog name to disable.") + return + try: + await self.bot.unload_extension(COG_PREFIX + cog_name) + await ctx.send(f"Unloaded cog `{cog_name}` successfully.") + settings["bot"]["enabled_cogs"].remove(cog_name) + settings_manager.add_admin_log_event( + { + "action": "del", + "author": ctx.author.id, + "change": "enabled_cogs", + "messageId": ctx.message.id, + "target": cog_name, + } + ) + settings_manager.commit() + except Exception as e: + await ctx.send(f"Error unloading cog `{cog_name}`: {e}") + + @requires_admin() + @commands.command() + async def reload(self, ctx, cog_name: str | None = None): if cog_name is None: await ctx.send("Please provide the cog name to reload.") return + + if cog_name[:-3] not in settings["bot"]["enabled_cogs"]: + await ctx.send("Please enable the cog first!") + return try: await self.bot.unload_extension(COG_PREFIX + cog_name) await self.bot.load_extension(COG_PREFIX + cog_name) @@ -50,9 +116,14 @@ class CogManager(commands.Cog): await ctx.send("No cogs are currently loaded.") return - embed = discord.Embed(title="Loaded Cogs", description="Here is a list of all currently loaded cogs:") - embed.add_field(name="Cogs", value="\n".join(cogs), inline=False) + embed = discord.Embed( + title="Loaded Cogs", + description="Here is a list of all currently loaded cogs:", + ) + embed.add_field(name="Loaded cogs", value="\n".join(cogs), inline=False) + embed.add_field(name="Available cogs", value="\n".join(available_cogs())) await ctx.send(embed=embed) + async def setup(bot): await bot.add_cog(CogManager(bot)) diff --git a/assets/cogs/internal/markov.py b/assets/cogs/internal/markov.py index 274c8d9..ab8e9da 100644 --- a/assets/cogs/internal/markov.py +++ b/assets/cogs/internal/markov.py @@ -7,7 +7,11 @@ from discord.ext import commands import discord.ext import discord.ext.commands -from modules.markovmemory import save_markov_model, train_markov_model, load_markov_model +from modules.markovmemory import ( + load_markov_model, + save_markov_model, + train_markov_model, +) from modules.permission import requires_admin from modules.sentenceprocessing import ( improve_sentence_coherence, @@ -15,7 +19,7 @@ from modules.sentenceprocessing import ( rephrase_for_coherence, send_message, ) -from modules.volta.main import _ +import modules.keys as k import logging from typing import List, Optional, Set import json @@ -33,13 +37,13 @@ class Markov(commands.Cog): def __init__(self, bot): self.bot: discord.ext.commands.Bot = bot + self.model: markovify.NewlineText | None = load_markov_model() @requires_admin() @commands.command() async def retrain(self, ctx: discord.ext.commands.Context): - markov_model: Optional[markovify.Text] = load_markov_model() message_ref: discord.Message | None = await send_message( - ctx, f"{_('command_markov_retrain')}" + ctx, f"{k.command_markov_retrain()}" ) if message_ref is None: @@ -50,16 +54,16 @@ class Markov(commands.Cog): with open(settings["bot"]["active_memory"], "r") as f: memory: List[str] = json.load(f) except FileNotFoundError: - await send_message(ctx, f"{_('command_markov_memory_not_found')}") + await send_message(ctx, f"{k.command_markov_memory_not_found()}") return except json.JSONDecodeError: - await send_message(ctx, f"{_('command_markov_memory_is_corrupt')}") + await send_message(ctx, f"{k.command_markov_memory_is_corrupt()}") return data_size: int = len(memory) processing_message_ref: discord.Message | None = await send_message( - ctx, f"{(_('command_markov_retraining').format(data_size=data_size))}" + ctx, f"{k.command_markov_retraining(data_size)}" ) if processing_message_ref is None: logger.error("Couldnt find message processing message!") @@ -72,41 +76,48 @@ class Markov(commands.Cog): await ctx.send("Failed to retrain!") return False - markov_model = model - save_markov_model(markov_model) + self.model = model + save_markov_model(self.model) logger.debug(f"Completed retraining in {round(time.time() - start_time,3)}s") await send_message( ctx, - _('command_markov_retraining').format(data_size=data_size), + f"{k.command_markov_retrain_successful(data_size)}", edit=True, message_reference=processing_message_ref, ) - @commands.command() async def talk(self, ctx: commands.Context, sentence_size: int = 5) -> None: - markov_model: Optional[markovify.Text] = load_markov_model() - if markov_model is None: - await send_message(ctx, _("command_markovcommand_talk_insufficent_text")) + if not self.model: + await send_message(ctx, f"{k.command_talk_insufficent_text()}") return - raw_sentence = None + response: str = "" if sentence_size == 1: - raw_sentence = markov_model.make_short_sentence(max_chars=100, tries=100) + response = ( + self.model.make_short_sentence(max_chars=200, tries=700) + or k.command_talk_generation_fail() + ) + else: - raw_sentence = markov_model.make_sentence(tries=100, max_words=sentence_size) - print(raw_sentence) + response = improve_sentence_coherence( + self.model.make_sentence(tries=100, max_words=sentence_size) + or k.command_talk_generation_fail() + ) - if random.random() < 0.9 and is_positive(raw_sentence): - gif_url = random.choice(settings["bot"]["misc"]["positive_gifs"]) - raw_sentence = f"{raw_sentence}\n[jif]({gif_url})" + cleaned_response: str = re.sub(r"[^\w\s]", "", response).lower() + coherent_response: str = rephrase_for_coherence(cleaned_response) - os.environ["gooberlatestgen"] = raw_sentence - await send_message(ctx, raw_sentence) + if random.random() < 0.9 and is_positive(coherent_response): + gif_url: str = random.choice(settings["bot"]["misc"]["positive_gifs"]) + coherent_response = f"{coherent_response}\n[jif]({gif_url})" + + os.environ["gooberlatestgen"] = coherent_response + await send_message(ctx, coherent_response) async def setup(bot): - await bot.add_cog(Markov(bot)) \ No newline at end of file + await bot.add_cog(Markov(bot)) diff --git a/assets/cogs/internal/permission.py b/assets/cogs/internal/permissions.py similarity index 72% rename from assets/cogs/internal/permission.py rename to assets/cogs/internal/permissions.py index cf03af7..e12c7f6 100644 --- a/assets/cogs/internal/permission.py +++ b/assets/cogs/internal/permissions.py @@ -15,7 +15,18 @@ class PermissionManager(commands.Cog): @commands.command() async def add_owner(self, ctx: commands.Context, member: discord.Member): settings["bot"]["owner_ids"].append(member.id) + settings_manager.add_admin_log_event( + { + "action": "add", + "author": ctx.author.id, + "change": "owner_ids", + "messageId": ctx.message.id, + "target": member.id, + } + ) + settings_manager.commit() + embed = discord.Embed( title="Permissions", description=f"Set {member.name} as an owner", @@ -29,6 +40,15 @@ class PermissionManager(commands.Cog): async def remove_owner(self, ctx: commands.Context, member: discord.Member): try: settings["bot"]["owner_ids"].remove(member.id) + settings_manager.add_admin_log_event( + { + "action": "del", + "author": ctx.author.id, + "change": "owner_ids", + "messageId": ctx.message.id, + "target": member.id, + } + ) settings_manager.commit() except ValueError: await ctx.send("User is not an owner!") @@ -70,6 +90,15 @@ class PermissionManager(commands.Cog): async def unblacklist_user(self, ctx: commands.Context, member: discord.Member): try: settings["bot"]["blacklisted_users"].remove(member.id) + settings_manager.add_admin_log_event( + { + "action": "del", + "author": ctx.author.id, + "change": "blacklisted_users", + "messageId": ctx.message.id, + "target": member.id, + } + ) settings_manager.commit() except ValueError: @@ -86,4 +115,4 @@ class PermissionManager(commands.Cog): async def setup(bot): - await bot.add_cog(PermissionManager(bot)) \ No newline at end of file + await bot.add_cog(PermissionManager(bot)) diff --git a/assets/cogs/lastfm.py b/assets/cogs/lastfm.py new file mode 100644 index 0000000..2ae3290 --- /dev/null +++ b/assets/cogs/lastfm.py @@ -0,0 +1,94 @@ +import os +import discord +from discord.ext import commands, tasks +import aiohttp +from dotenv import load_dotenv + +load_dotenv() + +# stole most of this code from my old expect bot so dont be suprised if its poorly made + +LASTFM_API_KEY = os.getenv("LASTFM_API_KEY") +LASTFM_USERNAME = os.getenv("LASTFM_USERNAME") + + +class LastFmCog(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.current_track = None + self.update_presence_task = None + self.ready = False + bot.loop.create_task(self.wait_until_ready()) + + async def wait_until_ready(self): + await self.bot.wait_until_ready() + self.ready = True + self.update_presence.start() + + @tasks.loop(seconds=60) + async def update_presence(self): + print("Looped!") + if not self.ready: + return + track = await self.fetch_current_track() + if track and track != self.current_track: + self.current_track = track + artist, song = track + activity_name = f"{artist} - {song}" + await self.bot.change_presence( + activity=discord.Activity( + type=discord.ActivityType.listening, name=activity_name + ) + ) + print(f"Updated song to {artist} - {song}") + else: + print("LastFM gave me the same track! not updating...") + + @update_presence.before_loop + async def before_update_presence(self): + await self.bot.wait_until_ready() + + @commands.command(name="lastfm") + async def lastfm_command(self, ctx): + track = await self.fetch_current_track() + if not track: + await ctx.send("No track currently playing or could not fetch data") + return + self.current_track = track + artist, song = track + activity_name = f"{artist} - {song}" + await self.bot.change_presence( + activity=discord.Activity( + type=discord.ActivityType.listening, name=activity_name + ) + ) + await ctx.send(f"Updated presence to: Listening to {activity_name}") + + async def fetch_current_track(self): + url = ( + f"http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks" + f"&user={LASTFM_USERNAME}&api_key={LASTFM_API_KEY}&format=json&limit=1" + ) + async with aiohttp.ClientSession() as session: + async with session.get(url) as resp: + if resp.status != 200: + return None + data = await resp.json() + + recenttracks = data.get("recenttracks", {}).get("track", []) + if not recenttracks: + return None + + track = recenttracks[0] + if "@attr" in track and track["@attr"].get("nowplaying") == "true": + artist = track.get("artist", {}).get("#text", "Unknown Artist") + song = track.get("name", "Unknown Song") + return artist, song + return None + + +async def setup(bot): + if not LASTFM_API_KEY or not LASTFM_USERNAME: + return + else: + await bot.add_cog(LastFmCog(bot)) diff --git a/assets/cogs/lyrics.py b/assets/cogs/lyrics.py deleted file mode 100644 index db999b4..0000000 --- a/assets/cogs/lyrics.py +++ /dev/null @@ -1,123 +0,0 @@ -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/cogs/pulse.py b/assets/cogs/pulse.py index f717221..ba031b5 100644 --- a/assets/cogs/pulse.py +++ b/assets/cogs/pulse.py @@ -2,7 +2,12 @@ from discord.ext import commands import discord from collections import defaultdict, Counter import datetime -from modules.globalvars import ownerid +from modules.permission import requires_admin +from modules.settings import instance as settings_manager + +settings = settings_manager.settings + + class StatsCog(commands.Cog): def __init__(self, bot): self.bot = bot @@ -29,38 +34,54 @@ class StatsCog(commands.Cog): async def on_command(self, ctx): self.command_usage[ctx.command.qualified_name] += 1 + @requires_admin() @commands.command() async def spyware(self, ctx): - if ctx.author.id != 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]) + peak_hour, peak_count = max( + self.messages_per_hour.items(), key=lambda x: x[1] + ) else: peak_hour, peak_count = "N/A", 0 top_users = self.user_message_counts.most_common(5) embed = discord.Embed(title="Community Stats", color=discord.Color.blue()) - embed.add_field(name="Uptime", value=str(uptime).split('.')[0], inline=False) - embed.add_field(name="Total Messages", value=str(self.total_messages), inline=True) - embed.add_field(name="Active Users", value=str(len(self.active_users)), inline=True) - embed.add_field(name="Avg Messages/Hour", value=f"{avg_per_hour:.2f}", inline=True) - embed.add_field(name="Peak Hour (UTC)", value=f"{peak_hour}: {peak_count} messages", inline=True) + embed.add_field(name="Uptime", value=str(uptime).split(".")[0], inline=False) + embed.add_field( + name="Total Messages", value=str(self.total_messages), inline=True + ) + embed.add_field( + name="Active Users", value=str(len(self.active_users)), inline=True + ) + embed.add_field( + name="Avg Messages/Hour", value=f"{avg_per_hour:.2f}", inline=True + ) + embed.add_field( + name="Peak Hour (UTC)", + value=f"{peak_hour}: {peak_count} messages", + inline=True, + ) - top_str = "\n".join( - f"<@{user_id}>: {count} messages" for user_id, count in top_users - ) or "No data" + top_str = ( + "\n".join(f"<@{user_id}>: {count} messages" for user_id, count in top_users) + or "No data" + ) embed.add_field(name="Top Chatters", value=top_str, inline=False) - cmd_str = "\n".join( - f"{cmd}: {count}" for cmd, count in self.command_usage.most_common(5) - ) or "No commands used yet" + cmd_str = ( + "\n".join( + f"{cmd}: {count}" for cmd, count in self.command_usage.most_common(5) + ) + or "No commands used yet" + ) embed.add_field(name="Top Commands", value=cmd_str, inline=False) await ctx.send(embed=embed) + async def setup(bot): await bot.add_cog(StatsCog(bot)) diff --git a/assets/cogs/slashcomandexample.py b/assets/cogs/slashcomandexample.py deleted file mode 100644 index 63b45f1..0000000 --- a/assets/cogs/slashcomandexample.py +++ /dev/null @@ -1,22 +0,0 @@ -import discord -from discord.ext import commands -from discord import app_commands - -class Ping(commands.Cog): - def __init__(self, bot): - self.bot = bot - - @app_commands.command(name="slashcommand", description="slashcommandexample") - async def ping(self, interaction: discord.Interaction): - await interaction.response.defer() - exampleembed = discord.Embed( - title="Pong!!", - description="The Beretta fires fast and won't make you feel any better!", - color=discord.Color.blue() - ) - exampleembed.set_footer(text=f"Requested by {interaction.user.name}", icon_url=interaction.user.avatar.url) - - await interaction.followup.send(embed=exampleembed) - -async def setup(bot): - await bot.add_cog(Ping(bot)) diff --git a/assets/cogs/songchanger.py b/assets/cogs/songchanger.py new file mode 100644 index 0000000..55efacc --- /dev/null +++ b/assets/cogs/songchanger.py @@ -0,0 +1,38 @@ +import discord +from discord.ext import commands +from modules.globalvars import RED, GREEN, RESET, LOCAL_VERSION_FILE +import os + +from modules.permission import requires_admin + + +class songchange(commands.Cog): + def __init__(self, bot): + self.bot = bot + + def get_local_version(): + if os.path.exists(LOCAL_VERSION_FILE): + with open(LOCAL_VERSION_FILE, "r") as f: + return f.read().strip() + return "0.0.0" + + global local_version + local_version = get_local_version() + + @requires_admin() + @commands.command() + async def changesong(self, ctx, song: str): + await ctx.send(f"Changed song to {song}") + try: + await self.bot.change_presence( + activity=discord.Activity( + type=discord.ActivityType.listening, name=f"{song}" + ) + ) + print(f"{GREEN}Changed song to {song}{RESET}") + except Exception as e: + print(f"{RED}An error occurred while changing songs..: {str(e)}{RESET}") + + +async def setup(bot): + await bot.add_cog(songchange(bot)) diff --git a/assets/cogs/tf.py b/assets/cogs/tf.py new file mode 100644 index 0000000..0b31a91 --- /dev/null +++ b/assets/cogs/tf.py @@ -0,0 +1,191 @@ +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 + import keras + from keras.preprocessing.text import Tokenizer + from keras.preprocessing.sequence import pad_sequences + from keras.models import Sequential, load_model + from keras.layers import Embedding, LSTM, Dense + from keras.backend import clear_session + + if tf.config.list_physical_devices("GPU"): + 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)) diff --git a/assets/cogs/webscraper.py b/assets/cogs/webscraper.py new file mode 100644 index 0000000..b2fe177 --- /dev/null +++ b/assets/cogs/webscraper.py @@ -0,0 +1,111 @@ +import discord +from discord.ext import commands +import aiohttp +from bs4 import BeautifulSoup +import json +import asyncio +from urllib.parse import urljoin +from modules.permission import requires_admin +from modules.settings import instance as settings_manager + +settings = settings_manager.settings + + +class WebScraper(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.visited_urls = set() + + async def fetch(self, session, url): + """Fetch the HTML content of a URL.""" + try: + async with session.get(url, timeout=10) as response: + return await response.text() + except Exception as e: + print(f"Failed to fetch {url}: {e}") + return None + + def extract_sentences(self, text): + """Extract sentences from text.""" + sentences = text.split(".") + return [sentence.strip() for sentence in sentences if sentence.strip()] + + def save_to_json(self, sentences): + """Save sentences to memory.json.""" + try: + try: + with open("memory.json", "r") as file: + data = json.load(file) + except (FileNotFoundError, json.JSONDecodeError): + data = [] + data.extend(sentences) + with open("memory.json", "w") as file: + json.dump(data, file, indent=4) + except Exception as e: + print(f"Failed to save to JSON: {e}") + + def undo_last_scrape(self): + """Undo the last scrape by removing the most recent sentences.""" + try: + with open("memory.json", "r") as file: + data = json.load(file) + + if not data: + print("No data to undo.") + return False + + data = data[:-1] + + with open("memory.json", "w") as file: + json.dump(data, file, indent=4) + + return True + except (FileNotFoundError, json.JSONDecodeError): + print("No data to undo or failed to load JSON.") + return False + except Exception as e: + print(f"Failed to undo last scrape: {e}") + return False + + async def scrape_links(self, session, url, depth=2): + print(f"Scraping: {url}") + self.visited_urls.add(url) + + html = await self.fetch(session, url) + if not html: + return + + soup = BeautifulSoup(html, "html.parser") + + for paragraph in soup.find_all("p"): + sentences = self.extract_sentences(paragraph.get_text()) + self.save_to_json(sentences) + + @requires_admin() + @commands.command() + async def start_scrape(self, ctx, start_url: str): + """Command to start the scraping process.""" + if not start_url.startswith("http"): + await ctx.send("Please provide a valid URL.") + return + + await ctx.send(f"Starting scrape from {start_url}... This may take a while!") + + async with aiohttp.ClientSession() as session: + await self.scrape_links(session, start_url) + + await ctx.send("Scraping complete! Sentences saved to memory.json.") + + @requires_admin() + @commands.command() + async def undo_scrape(self, ctx): + """Command to undo the last scrape.""" + success = self.undo_last_scrape() + if success: + await ctx.send("Last scrape undone successfully.") + else: + await ctx.send("No data to undo or an error occurred.") + + +async def setup(bot): + await bot.add_cog(WebScraper(bot)) diff --git a/assets/cogs/webserver.py b/assets/cogs/webserver.py new file mode 100644 index 0000000..d367d75 --- /dev/null +++ b/assets/cogs/webserver.py @@ -0,0 +1,921 @@ +import discord +from discord.ext import commands, tasks +import asyncio +from aiohttp import web +import psutil +import os +import json +from datetime import datetime +import time +import aiohttp +import re +from aiohttp import WSMsgType +from modules.globalvars import VERSION_URL +import sys +import subprocess + + +class GooberWeb(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.app = web.Application() + self.runner = None + self.site = None + self.last_command = "No commands executed yet" + self.last_command_time = "Never" + self.start_time = time.time() + self.websockets = set() + + self.app.add_routes( + [ + web.get("/", self.handle_index), + web.get("/changesong", self.handle_changesong), + web.get("/stats", self.handle_stats), + web.get("/data", self.handle_json_data), + web.get("/ws", self.handle_websocket), + web.get("/styles.css", self.handle_css), + web.get("/settings", self.handle_settings), + web.post("/update_settings", self.handle_update_settings), + web.post("/restart_bot", self.handle_restart_bot), + ] + ) + + self.bot.loop.create_task(self.start_web_server()) + self.update_clients.start() + + async def restart_bot(self): + await asyncio.sleep(1) + python = sys.executable + os.execl(python, python, *sys.argv) + + async def handle_restart_bot(self, request): + asyncio.create_task(self.restart_bot()) + return web.Response(text="Bot is restarting...") + + async def get_blacklisted_users(self): + blacklisted_ids = os.getenv("BLACKLISTED_USERS", "").split(",") + blacklisted_users = [] + + for user_id in blacklisted_ids: + if not user_id.strip(): + continue + + try: + user = await self.bot.fetch_user(int(user_id)) + blacklisted_users.append( + { + "name": f"{user.name}", + "avatar_url": ( + str(user.avatar.url) + if user.avatar + else str(user.default_avatar.url) + ), + "id": user.id, + } + ) + except discord.NotFound: + blacklisted_users.append( + { + "name": f"Unknown User ({user_id})", + "avatar_url": "", + "id": user_id, + } + ) + except discord.HTTPException as e: + print(f"Error fetching user {user_id}: {e}") + continue + + return blacklisted_users + + async def get_enhanced_guild_info(self): + guilds = sorted(self.bot.guilds, key=lambda g: g.member_count, reverse=True) + guild_info = [] + + for guild in guilds: + icon_url = str(guild.icon.url) if guild.icon else "" + guild_info.append( + { + "name": guild.name, + "member_count": guild.member_count, + "icon_url": icon_url, + "id": guild.id, + } + ) + + return guild_info + + async def start_web_server(self): + self.runner = web.AppRunner(self.app) + await self.runner.setup() + self.site = web.TCPSite(self.runner, "0.0.0.0", 8080) + await self.site.start() + print("Goober web server started on port 8080") + + async def stop_web_server(self): + if self.site is None or self.runner is None: + return + + await self.site.stop() + await self.runner.cleanup() + print("Web server stopped") + + def cog_unload(self): + self.update_clients.cancel() + self.bot.loop.create_task(self.stop_web_server()) + + @tasks.loop(seconds=5) + async def update_clients(self): + if not self.websockets: + return + + stats = await self.get_bot_stats() + message = json.dumps(stats) + + for ws in set(self.websockets): + try: + await ws.send_str(message) + except ConnectionResetError: + self.websockets.remove(ws) + except Exception as e: + print(f"Error sending to websocket: {e}") + self.websockets.remove(ws) + + async def handle_websocket(self, request): + ws = web.WebSocketResponse() + await ws.prepare(request) + self.websockets.add(ws) + + try: + async for msg in ws: + if msg.type == WSMsgType.ERROR: + print(f"WebSocket error: {ws.exception()}") + finally: + self.websockets.remove(ws) + + return ws + + async def handle_css(self, request): + css_path = os.path.join(os.path.dirname(__file__), "styles.css") + if os.path.exists(css_path): + return web.FileResponse(css_path) + return web.Response(text="CSS file not found", status=404) + + @commands.Cog.listener() + async def on_message(self, message): + if message.author.bot: + return + + ctx = await self.bot.get_context(message) + if ctx.valid and ctx.command: + self._update_command_stats(ctx.command.name, ctx.author) + + @commands.Cog.listener() + async def on_app_command_completion(self, interaction, command): + self._update_command_stats(command.name, interaction.user) + + def _update_command_stats(self, command_name, user): + self.last_command = f"{command_name} (by {user.name})" + self.last_command_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + if self.websockets: + asyncio.create_task(self.update_clients()) + + async def get_bot_stats(self): + process = psutil.Process(os.getpid()) + mem_info = process.memory_full_info() + cpu_percent = psutil.cpu_percent() + process_cpu = process.cpu_percent() + + memory_json_size = "N/A" + if os.path.exists("memory.json"): + memory_json_size = f"{os.path.getsize('memory.json') / 1024:.2f} KB" + + guild_info = await self.get_enhanced_guild_info() + blacklisted_users = await self.get_blacklisted_users() + + uptime_seconds = int(time.time() - self.start_time) + uptime_str = f"{uptime_seconds // 86400}d {(uptime_seconds % 86400) // 3600}h {(uptime_seconds % 3600) // 60}m {uptime_seconds % 60}s" + + return { + "ram_usage": f"{mem_info.rss / 1024 / 1024:.2f} MB", + "cpu_usage": f"{process_cpu}%", + "system_cpu": f"{cpu_percent}%", + "memory_json_size": memory_json_size, + "guild_count": len(guild_info), + "bl_count": len(blacklisted_users), + "guilds": guild_info, + "blacklisted_users": blacklisted_users, + "last_command": self.last_command, + "last_command_time": self.last_command_time, + "bot_uptime": uptime_str, + "latency": f"{self.bot.latency * 1000:.2f} ms", + "bot_name": self.bot.user.name, + "bot_avatar_url": ( + str(self.bot.user.avatar.url) if self.bot.user.avatar else "" + ), + "authenticated": os.getenv("gooberauthenticated"), + "lastmsg": os.getenv("gooberlatestgen"), + "localversion": os.getenv("gooberlocal_version"), + "latestversion": os.getenv("gooberlatest_version"), + "owner": os.getenv("ownerid"), + } + + async def handle_update(self, request): + if os.path.exists("goob/update.py"): + return web.FileResponse("goob/update.py") + return web.Response(text="Update file not found", status=404) + + async def handle_changesong(self, request): + song = request.query.get("song", "") + if song: + await self.bot.change_presence( + activity=discord.Activity( + type=discord.ActivityType.listening, name=song + ) + ) + return web.Response(text=f"Changed song to: {song}") + return web.Response(text="Please provide a song parameter", status=400) + + async def handle_changes(self, request): + if os.path.exists("goob/changes.txt"): + return web.FileResponse("goob/changes.txt") + return web.Response(text="Changelog not found", status=404) + + async def read_env_file(self): + env_vars = {} + try: + with open(".env", "r") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + + key, value = line.split("=", 1) + key = key.strip() + if key in ["splashtext", "DISCORD_BOT_TOKEN"]: + continue + + env_vars[key] = value.strip("\"'") + except FileNotFoundError: + print(".env file not found") + return env_vars + + async def handle_settings(self, request): + env_vars = await self.read_env_file() + + # Get config.py variables + config_vars = {} + try: + with open("config.py", "r") as f: + for line in f: + if line.startswith("VERSION_URL"): + config_vars["VERSION_URL"] = ( + line.split("=", 1)[1].strip().strip('"') + ) + except FileNotFoundError: + pass + + settings_html = """ + + + + Goober Settings + + + +
+

Goober Settings

+
+ """ + + for key, value in env_vars.items(): + settings_html += f""" +
+ + +
+ """ + + for key, value in config_vars.items(): + settings_html += f""" +
+ + +
+ """ + + settings_html += """ + +
+
+ +
+
+ + + """ + + return web.Response(text=settings_html, content_type="text/html") + + async def handle_update_settings(self, request): + data = await request.post() + env_text = "" + + try: + with open(".env", "r") as f: + env_text = f.read() + except FileNotFoundError: + pass + + def replace_match(match): + key = match.group(1) + value = match.group(2) + if key in ["splashtext", "DISCORD_BOT_TOKEN"]: + return match.group(0) + if key in data: + new_value = data[key] + if not (new_value.startswith('"') and new_value.endswith('"')): + new_value = f'"{new_value}"' + return f"{key}={new_value}" + return match.group(0) + + env_text = re.sub( + r"^(\w+)=([\s\S]+?)(?=\n\w+=|\Z)", + replace_match, + env_text, + flags=re.MULTILINE, + ) + + with open(".env", "w") as f: + f.write(env_text.strip() + "\n") + + if "VERSION_URL" in data: + config_text = "" + try: + with open("config.py", "r") as f: + config_text = f.read() + except FileNotFoundError: + pass + + config_text = re.sub( + r'^(VERSION_URL\s*=\s*").+?"', + f'\\1{data["VERSION_URL"]}"', + config_text, + flags=re.MULTILINE, + ) + + with open("config.py", "w") as f: + f.write(config_text.strip() + "\n") + + return aiohttp.web.Response(text="Settings updated successfully!") + + async def handle_index(self, request): + stats = await self.get_bot_stats() + + guild_list_html = "" + for guild in stats["guilds"]: + icon_html = ( + f'guild icon' + if guild["icon_url"] + else '
' + ) + guild_list_html += f""" +
+ {icon_html} +
+
{guild["name"]}
+
{guild["member_count"]} members
+
+
+ """ + blacklisted_users_html = "" + for user in stats["blacklisted_users"]: + avatar_html = ( + f'user avatar' + if user["avatar_url"] + else '
' + ) + blacklisted_users_html += f""" +
+ {avatar_html} + +
+ """ + + owner_id = stats.get("owner") + owner = None + owner_username = "Owner" + owner_pfp = "" + + if owner_id: + try: + owner = await self.bot.fetch_user(int(owner_id)) + owner_username = f"{owner.name}" + owner_pfp = str(owner.avatar.url) if owner and owner.avatar else "" + except: + pass + + html_content = f""" + + + + goobs central + + + +
+ +
Welcome, {owner_username}
+
+
+
+ RAM: + {stats['ram_usage']} +
+
+ CPU: + {stats['system_cpu']} +
+
+ Latency: + {stats['latency']} +
+
+ JSON Size: + {stats['memory_json_size']} +
+
+ Uptime: + {stats['bot_uptime']} +
+
+ +
+
+ botvatar +

{stats['bot_name']}

+
+
+

your stupid little goober that learns off other people's messages

+
+ +
+
+
Last Command
+
{stats['last_command']}
+
at {stats['last_command_time']}
+
+
Logged into goober central
+
{stats['authenticated']}
+
+
Last generated message
+
{stats['lastmsg']}
+
+
Version
+
Installed Version: {stats['localversion']}
+
Latest Version: {stats['latestversion']}
+
+
goober-central URL
+
{VERSION_URL}
+
+
Change song
+
+ + +
+
+ +
+
Servers ({stats['guild_count']})
+
+ {guild_list_html} +
+
+
Blacklisted Users ({stats['bl_count']})
+
+ {blacklisted_users_html if stats['blacklisted_users'] else "
No blacklisted users
"} +
+
+
+ + + + """ + + return web.Response(text=html_content, content_type="text/html") + + async def handle_stats(self, request): + return await self.handle_index(request) + + async def handle_json_data(self, request): + stats = await self.get_bot_stats() + return web.json_response(stats) + + +async def setup(bot): + await bot.add_cog(GooberWeb(bot)) diff --git a/assets/cogs/whoami.py b/assets/cogs/whoami.py index a60633f..55379ec 100644 --- a/assets/cogs/whoami.py +++ b/assets/cogs/whoami.py @@ -1,6 +1,7 @@ import discord from discord.ext import commands + class whoami(commands.Cog): def __init__(self, bot): self.bot = bot @@ -13,12 +14,13 @@ class whoami(commands.Cog): embed = discord.Embed( title="User Information", description=f"Your User ID is: {user_id}\n" - f"Your username is: {username}\n" - f"Your nickname in this server is: <@{user_id}>", - color=discord.Color.blue() + f"Your username is: {username}\n" + f"Your nickname in this server is: <@{user_id}>", + color=discord.Color.blue(), ) await ctx.send(embed=embed) + async def setup(bot): await bot.add_cog(whoami(bot)) diff --git a/assets/fonts/Impact.ttf b/assets/fonts/Impact.ttf new file mode 100644 index 0000000..7b7956f Binary files /dev/null and b/assets/fonts/Impact.ttf differ diff --git a/assets/fonts/SpecialGothic.ttf b/assets/fonts/SpecialGothic.ttf new file mode 100644 index 0000000..1da7c19 Binary files /dev/null and b/assets/fonts/SpecialGothic.ttf differ diff --git a/assets/fonts/TNR.ttf b/assets/fonts/TNR.ttf new file mode 100644 index 0000000..51261a0 Binary files /dev/null and b/assets/fonts/TNR.ttf differ diff --git a/assets/images/attention.webp b/assets/images/attention.webp new file mode 100644 index 0000000..f680665 Binary files /dev/null and b/assets/images/attention.webp differ diff --git a/assets/images/bibinos.png b/assets/images/bibinos.png new file mode 100644 index 0000000..5a7b846 Binary files /dev/null and b/assets/images/bibinos.png differ diff --git a/assets/images/breaking_news.png b/assets/images/breaking_news.png new file mode 100644 index 0000000..039f6f0 Binary files /dev/null and b/assets/images/breaking_news.png differ diff --git a/assets/images/cache/breaking_news.png b/assets/images/cache/breaking_news.png new file mode 100644 index 0000000..326b20d Binary files /dev/null and b/assets/images/cache/breaking_news.png differ diff --git a/assets/images/crash.webp b/assets/images/crash.webp new file mode 100644 index 0000000..60d918b Binary files /dev/null and b/assets/images/crash.webp differ diff --git a/assets/images/crash2.png b/assets/images/crash2.png new file mode 100644 index 0000000..12f7616 Binary files /dev/null and b/assets/images/crash2.png differ diff --git a/assets/images/genuineidiot.png b/assets/images/genuineidiot.png new file mode 100644 index 0000000..226ca47 Binary files /dev/null and b/assets/images/genuineidiot.png differ diff --git a/assets/images/smashedphone.webp b/assets/images/smashedphone.webp new file mode 100644 index 0000000..845070f Binary files /dev/null and b/assets/images/smashedphone.webp differ diff --git a/assets/images/thisisfine.png b/assets/images/thisisfine.png new file mode 100644 index 0000000..41977dc Binary files /dev/null and b/assets/images/thisisfine.png differ diff --git a/assets/locales/en.json b/assets/locales/en.json index 53e9dd5..71e19eb 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -1,23 +1,8 @@ { - "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 Git.", + "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.", "active_users:": "Active users:", @@ -146,6 +131,14 @@ "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}\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}```", + "no_image_available": "No images available!", + "failed_generate_image": "Failed to generate an image", + "markov_model_not_found": "Markov model not found!", + "blacklisted": "blacklisted", + "blacklisted_user": "Blacklisted user", + "edit_fail": "Failed to edit message", + "system_info": "System information", + "cpu_info": "CPU: {cpu}" } diff --git a/assets/locales/es.json b/assets/locales/es.json index 4444a9c..c2aa734 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}\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/fi.json b/assets/locales/fi.json index 93141cc..7b16005 100644 --- a/assets/locales/fi.json +++ b/assets/locales/fi.json @@ -102,7 +102,7 @@ "command_markov_retrain": "Uudelleenkoulutetaan markov-mallia... Odota.", "command_markov_memory_not_found": "Virhe: muistitiedostoa ei löytynyt!", "command_markov_memory_is_corrupt": "Virhe: muistitiedosto on korruptoitu!", - "command_markov_retraining": "Käsitellään {processed_data}/{data_size} datapisteestä...", + "command_markov_retraining": "Käsitellään {processed_data}/{data_size} datapistettä...", "command_markov_retrain_successful": "Markov-malli koulutettiin uudestaan {data_size} datapisteellä!", "command_desc_talk":"puhuu ja sillei", "command_talk_insufficent_text": "Minun pitää oppia lisää viesteistä ennen kun puhun.", @@ -131,5 +131,6 @@ "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}\nPing-linja: {PING_LINE} \nMuistin jako päällä: {showmemenabled} \nOppiminen käyttäjistä: {USERTRAIN_ENABLED}\nLaulu: {song} \nRoisketeksti: ```{splashtext}```" + } diff --git a/assets/locales/fr.json b/assets/locales/fr.json new file mode 100644 index 0000000..572d2f4 --- /dev/null +++ b/assets/locales/fr.json @@ -0,0 +1,130 @@ +{ + "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/fr_ca.json b/assets/locales/fr_ca.json deleted file mode 100644 index 782b255..0000000 --- a/assets/locales/fr_ca.json +++ /dev/null @@ -1,149 +0,0 @@ -{ - "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 diff --git a/assets/locales/it.json b/assets/locales/it.json index 4f97806..954b0ce 100644 --- a/assets/locales/it.json +++ b/assets/locales/it.json @@ -1,23 +1,8 @@ { - "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.", - "not_cloned": "Goober non è stato clonato! Clonalo da Git.", + "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.", "active_users:": "Utenti attivi:", diff --git a/main.py b/bot.py similarity index 57% rename from main.py rename to bot.py index 45d8595..6c5a30a 100644 --- a/main.py +++ b/bot.py @@ -1,3 +1,20 @@ +import logging +from modules.logger import GooberFormatter + +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) + import os import re import json @@ -25,33 +42,38 @@ from typing import ( ) import logging from modules.prestartchecks import start_checks -from modules.logger import GooberFormatter -from modules.volta.main import * +import modules.keys as k +from modules import key_compiler import logging -from modules.settings import Settings as SettingsManager +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler +from modules.settings import instance as settings_manager, ActivityType from modules.permission import requires_admin -from modules.volta.main import _ -from modules.version import check_for_update -check_for_update() -logger = logging.getLogger("goober") -logger.setLevel(logging.DEBUG) +from modules.sync_conenctor import instance as sync_connector -console_handler = logging.StreamHandler() -console_handler.setLevel(logging.DEBUG) -console_handler.setFormatter(GooberFormatter()) +import threading -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) +def build_keys(): + key_compiler.build_result( + "en", + "assets/locales", + types=True, + output_path="modules/keys.py", + generate_comments=True, + ) + + +build_keys() + -settings_manager = SettingsManager() settings = settings_manager.settings splash_text: str = "" +k.change_language(settings["locale"]) + + with open(settings["splash_text_loc"], "r", encoding="UTF-8") as f: splash_text = "".join(f.readlines()) print(splash_text) @@ -69,6 +91,7 @@ from discord.ext import commands from modules.markovmemory import * from modules.sentenceprocessing import * from modules.unhandledexception import handle_exception +from modules.image import gen_demotivator sys.excepthook = handle_exception @@ -86,18 +109,15 @@ class MessageMetadata(TypedDict): # Constants with type hints positive_gifs: List[str] = settings["bot"]["misc"]["positive_gifs"] -name = settings["name"] currenthash: str = "" launched: bool = False slash_commands_enabled: bool = False - # Set up Discord bot intents and create bot instance intents: discord.Intents = discord.Intents.default() intents.messages = True -intents.presences = True -intents.members = True intents.message_content = True + bot: commands.Bot = commands.Bot( command_prefix=settings["bot"]["prefix"], intents=intents, @@ -108,6 +128,11 @@ bot: commands.Bot = commands.Bot( # Load memory and Markov model for text generation memory: List[str | Dict[Literal["_meta"], MessageMetadata]] = load_memory() +markov_model: Optional[markovify.Text] = load_markov_model() +if not markov_model: + logger.error(k.markov_model_not_found()) + memory = load_memory() + markov_model = train_markov_model(memory) generated_sentences: Set[str] = set() used_words: Set[str] = set() @@ -128,9 +153,9 @@ async def load_cogs_from_folder(bot: commands.Bot, folder_name="assets/cogs"): try: await bot.load_extension(module_path) - logger.info(f"{_('loaded_cog')} {cog_name}") + logger.info(f"{k.loaded_cog()} {cog_name}") except Exception as e: - logger.error(f"{_('cog_fail')} {cog_name} {e}") + logger.error(f"{k.cog_fail()} {cog_name} {e}") traceback.print_exc() @@ -143,13 +168,13 @@ async def on_ready() -> None: if launched: return - await load_cogs_from_folder(bot) await load_cogs_from_folder(bot, "assets/cogs/internal") + await load_cogs_from_folder(bot) try: synced: List[discord.app_commands.AppCommand] = await bot.tree.sync() - logger.info(f"{_('synced_commands')} {len(synced)} {_('synced_commands2')}") - logger.info(_('started').format(name=name)) + logger.info(f"{k.synced_commands()} {len(synced)} {k.synced_commands2()}") + logger.info(k.started(settings["name"])) except discord.errors.Forbidden as perm_error: logger.error(f"Permission error while syncing commands: {perm_error}") @@ -158,21 +183,36 @@ async def on_ready() -> None: ) quit() except Exception as e: - logger.error(f"{_('fail_commands_sync')} {e}") + logger.error(f"{k.fail_commands_sync()} {e}") traceback.print_exc() quit() - if not settings["bot"]["misc"]["active_song"]: + if not settings["bot"]["misc"]["activity"]["content"]: return + + activity_type = discord.ActivityType.unknown + + settings_activity = settings["bot"]["misc"]["activity"]["type"] + + activities: Dict[ActivityType, discord.ActivityType] = { + "listening": discord.ActivityType.listening, + "playing": discord.ActivityType.playing, + "streaming": discord.ActivityType.streaming, + "competing": discord.ActivityType.competing, + "watching": discord.ActivityType.watching, + } + await bot.change_presence( activity=discord.Activity( - type=discord.ActivityType.listening, - name=settings["bot"]["misc"]["active_song"], + type=activities.get( + settings["bot"]["misc"]["activity"]["type"], + discord.ActivityType.unknown, + ), + name=settings["bot"]["misc"]["activity"]["content"], ) ) launched = True -bot.remove_command('help') @bot.event async def on_command_error(ctx: commands.Context, error: commands.CommandError) -> None: @@ -194,10 +234,66 @@ async def on_command_error(ctx: commands.Context, error: commands.CommandError) context=f"Command: {ctx.command} | User: {ctx.author}", ) + +# 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: str | None = 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}" + with open(temp_input, "wb") as f: + await attachment.save(f) + input_path: str = temp_input + else: + fallback_image: Optional[str] = get_random_asset_image() + if fallback_image is None: + await ctx.reply(k.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(k.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) # type: ignore + + 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) + + # Event: Called on every message @bot.event async def on_message(message: discord.Message) -> None: - global memory + global memory, markov_model EMOJIS = [ "\U0001f604", "\U0001f44d", @@ -209,7 +305,7 @@ async def on_message(message: discord.Message) -> None: if message.author.bot: return - if str(message.author.id) in settings["bot"]["blacklisted_users"]: + if message.author.id in settings["bot"]["blacklisted_users"]: return commands = [ @@ -217,15 +313,21 @@ async def on_message(message: discord.Message) -> None: ] if message.content.startswith(tuple(commands)): - logger.info(f"{(_('command_ran')).format(message=message)}") + logger.info(f"{k.command_ran(message.author.name, message.content)}") await bot.process_commands(message) return + if ( + profanity.contains_profanity(message.content) + and settings["bot"]["misc"]["block_profanity"] + ): + return + if message.content: if not settings["bot"]["user_training"]: return - formatted_message: str = message.content + formatted_message: str = append_mentions_to_18digit_integer(message.content) cleaned_message: str = preprocess_message(formatted_message) if cleaned_message: memory.append(cleaned_message) @@ -256,6 +358,11 @@ async def on_message(message: discord.Message) -> None: if sentiment_score > 0.8: if not settings["bot"]["react_to_messages"]: return + if not sync_connector.can_react(message.id): + logger.info("Sync hub determined that this instance cannot react") + return + + emoji = random.choice(EMOJIS) try: await message.add_reaction(emoji) @@ -268,7 +375,7 @@ async def on_message(message: discord.Message) -> None: # 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)}{name}") + logger.info(f"{k.command_ran_s(interaction.user.name)} {interaction.user.name}") # Global check: Block blacklisted users from running commands @@ -280,11 +387,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(k.blacklisted(), ephemeral=True) else: - await ctx.followup.send(_('blacklisted'), ephemeral=True) + await ctx.followup.send(k.blacklisted(), ephemeral=True) else: - await ctx.send(_('blacklisted_user'), ephemeral=True) + await ctx.send(k.blacklisted_user(), ephemeral=True) except: return False @@ -297,6 +404,40 @@ def improve_sentence_coherence(sentence: str) -> str: sentence = sentence.replace(" i ", " I ") return sentence + +class OnMyWatch: + watchDirectory = "assets/locales" + + def __init__(self): + self.observer = Observer() + + def run(self): + event_handler = Handler() + self.observer.schedule(event_handler, self.watchDirectory, recursive=True) + self.observer.start() + try: + while True: + time.sleep(5) + except: + self.observer.stop() + print("Observer Stopped") + + self.observer.join() + + +class Handler(FileSystemEventHandler): + def on_any_event(self, event): + if event.is_directory: + return None + + elif event.event_type == "modified": + build_keys() + + +observer = Observer() +observer.schedule(Handler(), "assets/locales") +observer.start() + # Start the bot if __name__ == "__main__": - bot.run(os.environ.get("DISCORDBOTTOKEN", "")) \ No newline at end of file + bot.run(os.environ.get("DISCORD_BOT_TOKEN", "")) diff --git a/example.env b/example.env deleted file mode 100644 index 30dfe93..0000000 --- a/example.env +++ /dev/null @@ -1 +0,0 @@ -DISCORDBOTTOKEN= \ No newline at end of file diff --git a/modules/globalvars.py b/modules/globalvars.py index 3225605..7a70f9c 100644 --- a/modules/globalvars.py +++ b/modules/globalvars.py @@ -66,6 +66,6 @@ arch = platform.machine() slash_commands_enabled = True # 100% broken, its a newer enough version so its probably enabled by default.... fix this at somepoint or hard code it in goober central code launched = False latest_version = "0.0.0" -local_version = "3.0.0" +local_version = "2.3.3" os.environ["gooberlocal_version"] = local_version -beta = get_git_branch() == "dev" \ No newline at end of file +beta = get_git_branch() == "dev" diff --git a/modules/image.py b/modules/image.py new file mode 100644 index 0000000..e5a8e2a --- /dev/null +++ b/modules/image.py @@ -0,0 +1,217 @@ +import os +import re +import random +import shutil +import tempfile +from typing import Optional, List +from PIL import Image, ImageDraw, ImageFont, ImageOps +from modules.markovmemory import load_markov_model +from modules.sentenceprocessing import ( + improve_sentence_coherence, + rephrase_for_coherence, +) + +generated_sentences = set() + + +def load_font(size): + return ImageFont.truetype("assets/fonts/Impact.ttf", size=size) + + +def load_tnr(size): + return ImageFont.truetype("assets/fonts/TNR.ttf", size=size) + + +def draw_text_with_outline(draw, text, x, y, font): + outline_offsets = [ + (-2, -2), + (-2, 2), + (2, -2), + (2, 2), + (0, -2), + (0, 2), + (-2, 0), + (2, 0), + ] + for ox, oy in outline_offsets: + draw.text((x + ox, y + oy), text, font=font, fill="black") + draw.text((x, y), text, font=font, fill="white") + + +def fits_in_width(text, font, max_width, draw): + bbox = draw.textbbox((0, 0), text, font=font) + text_width = bbox[2] - bbox[0] + return text_width <= max_width + + +def split_text_to_fit(text, font, max_width, draw): + words = text.split() + for i in range(len(words), 0, -1): + top_text = " ".join(words[:i]) + bottom_text = " ".join(words[i:]) + if fits_in_width(top_text, font, max_width, draw) and fits_in_width( + bottom_text, font, max_width, draw + ): + return top_text, bottom_text + midpoint = len(words) // 2 + return " ".join(words[:midpoint]), " ".join(words[midpoint:]) + + +async def gen_meme(input_image_path, sentence_size=5, max_attempts=10): + markov_model = load_markov_model() + if not markov_model or not os.path.isfile(input_image_path): + return None + + attempt = 0 + while attempt < max_attempts: + with Image.open(input_image_path).convert("RGBA") as img: + draw = ImageDraw.Draw(img) + width, height = img.size + + font_size = int(height / 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 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: + response = "NO TEXT GENERATED" + + cleaned_response = re.sub(r"[^\w\s]", "", response).lower() + coherent_response = rephrase_for_coherence(cleaned_response).upper() + + bbox = draw.textbbox((0, 0), coherent_response, font=font) + text_width = bbox[2] - bbox[0] + text_height_px = bbox[3] - bbox[1] + max_text_height = height // 4 + + if text_width <= width and text_height_px <= max_text_height: + draw_text_with_outline( + draw, coherent_response, (width - text_width) / 2, 0, font + ) + img.save(input_image_path) + return input_image_path + else: + top_text, bottom_text = split_text_to_fit( + coherent_response, font, width, draw + ) + + top_bbox = draw.textbbox((0, 0), top_text, font=font) + bottom_bbox = draw.textbbox((0, 0), bottom_text, font=font) + + top_height = top_bbox[3] - top_bbox[1] + bottom_height = bottom_bbox[3] - bottom_bbox[1] + + if top_height <= max_text_height and bottom_height <= max_text_height: + draw_text_with_outline( + draw, + top_text, + (width - (top_bbox[2] - top_bbox[0])) / 2, + 0, + font, + ) + y_bottom = height - bottom_height - int(height * 0.04) + draw_text_with_outline( + draw, + bottom_text, + (width - (bottom_bbox[2] - bottom_bbox[0])) / 2, + y_bottom, + font, + ) + img.save(input_image_path) + return input_image_path + + attempt += 1 + + with Image.open(input_image_path).convert("RGBA") as img: + draw = ImageDraw.Draw(img) + width, height = img.size + font_size = int(height / 10) + font = load_font(font_size) + + truncated = coherent_response[:100] + bbox = draw.textbbox((0, 0), truncated, font=font) + text_width = bbox[2] - bbox[0] + draw_text_with_outline(draw, truncated, (width - text_width) / 2, 0, font) + img.save(input_image_path) + return input_image_path + + +async def gen_demotivator(input_image_path, max_attempts=5): + markov_model = load_markov_model() + if not markov_model or not os.path.isfile(input_image_path): + return None + + attempt = 0 + while attempt < max_attempts: + with Image.open(input_image_path).convert("RGB") as img: + size = max(img.width, img.height) + frame_thick = int(size * 0.0054) + inner_size = size - 2 * frame_thick + resized_img = img.resize((inner_size, inner_size), Image.LANCZOS) + framed = Image.new("RGB", (size, size), "white") + framed.paste(resized_img, (frame_thick, frame_thick)) + landscape_w = int(size * 1.5) + caption_h = int(size * 0.3) + canvas_h = framed.height + caption_h + canvas = Image.new("RGB", (landscape_w, canvas_h), "black") + # the above logic didnt even work, fml + fx = (landscape_w - framed.width) // 2 + canvas.paste(framed, (fx, 0)) + + draw = ImageDraw.Draw(canvas) + + title = subtitle = None + for _ in range(20): + t = markov_model.make_sentence(tries=100, max_words=4) + s = markov_model.make_sentence(tries=100, max_words=5) + if t and s and t != s: + title = t.upper() + subtitle = s.capitalize() + break + if not title: + title = "DEMOTIVATOR" + if not subtitle: + subtitle = "no text generated" + + title_sz = int(caption_h * 0.4) + sub_sz = int(caption_h * 0.25) + title_font = load_tnr(title_sz) + sub_font = load_tnr(sub_sz) + + bbox = draw.textbbox((0, 0), title, font=title_font) + txw, txh = bbox[2] - bbox[0], bbox[3] - bbox[1] + tx = (landscape_w - txw) // 2 + ty = framed.height + int(caption_h * 0.1) + draw_text_with_outline(draw, title, tx, ty, title_font) + + bbox = draw.textbbox((0, 0), subtitle, font=sub_font) + sxw, sxh = bbox[2] - bbox[0], bbox[3] - bbox[1] + sx = (landscape_w - sxw) // 2 + sy = ty + txh + int(caption_h * 0.05) + for ox, oy in [(-1, -1), (1, -1), (-1, 1), (1, 1)]: + draw.text((sx + ox, sy + oy), subtitle, font=sub_font, fill="black") + draw.text((sx, sy), subtitle, font=sub_font, fill="#AAAAAA") + + canvas.save(input_image_path) + return input_image_path + + attempt += 1 + return None diff --git a/modules/key_compiler.py b/modules/key_compiler.py new file mode 100644 index 0000000..7385dbd --- /dev/null +++ b/modules/key_compiler.py @@ -0,0 +1,220 @@ +# The MIT License (MIT) + +# Copyright (c) 2025 ctih1 + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import os +from typing import Dict, List, Literal +import json +import sys +import logging +import time + +NOTICE = """ +# This file was automatically created from localization JSON files. +# DO NOT EDIT THIS FILE DIRECTLY. If you want to edit a translation, please use the language's JSON file. + +#fmt: off +""" + + +logger = logging.getLogger("kaannos") + + +class LanguageCollector: + def __init__(self, language_dir: str) -> None: + self.path: str = language_dir + self.languages: Dict[str, Dict[str, str]] = {} + + for file in os.listdir(self.path): + if not file.endswith(".json") or len(file) > 7: + logger.debug(f"Skipping {file}") + continue + + locale: str = file.split(".json")[0] + logger.info(f"Discovered {file}") + with open(os.path.join(self.path, file), "r", encoding="UTF-8") as f: + keys: Dict[str, str] = json.load(f) + self.languages[locale] = keys + + self.find_missing_keys() + + def find_missing_keys(self) -> None: + primary_language_keys: Dict[str, str] = self.languages["en"] + + for key in primary_language_keys: + for language in self.languages: + if key not in self.languages[language]: + logger.warning(f"Key {key} missing from {language}") + + for language in self.languages: + for key in self.languages[language]: + if key not in primary_language_keys: + logger.warning(f"Leftover key {key} found from {language}") + + +class Script: + def __init__(self) -> None: + self.script: str = "" + + def add_line(self, content, indent: int = 0, newline: bool = True) -> None: + tabs = "\t" * indent + newline_content = "\n" if newline else "" + + self.script += f"{tabs}{content}{newline_content}" + + +def process_name(key: str) -> str: + return key.replace(" ", "_").replace(":", "").lower() + + +def find_args(string: str) -> List[str]: + variable_open: bool = False + temp_content: str = "" + + variables: List[str] = [] + for char in string: + if variable_open: + if char == "}": + variable_open = False + variables.append(temp_content) + temp_content = "" + continue + + if char == "{": + raise SyntaxError("Variable already open!") + + temp_content += char + + else: + if char == "}": + raise SyntaxError("Trying to close a nonexistant variable") + + if char == "{": + variable_open = True + + return variables + + +def convert_args( + inp: str, vars: List[str], mode: Literal["brackets", "none"] = "brackets" +) -> str: + replacements = {".": "_", ",": "_"} + + for var in vars: + cleaned_var = var + for key, val in replacements.items(): + cleaned_var = cleaned_var.replace(key, val) + + if mode == "none": + inp = inp.replace(f"{var}", f"{cleaned_var}") + else: + inp = inp.replace(f"{{{var}}}", f"{{{cleaned_var}}}") + + return inp + + +class GenerateScript: + def __init__( + self, + primary_lang: str, + language_data: Dict[str, Dict[str, str]], + use_typing: bool = True, + output_path: str = "out.py", + generate_comments: bool = True, + ): + self.data = language_data + self.primary = primary_lang + self.script = Script() + self.uses_typing: bool = use_typing + self.output = output_path + self.generate_comments = generate_comments + + def create(self): + # I really don't like this implementation but also it works + self.script.add_line(NOTICE) + if self.uses_typing: + self.script.add_line("from typing import Literal, List") + self.script.add_line(f"Language=Literal{list(self.data.keys())}") + self.script.add_line( + f"languages: List[Language] = {list(self.data.keys())}" + ) + self.script.add_line(f"default_lang: Language | str='{self.primary}'") + self.script.add_line( + "def change_language(new_lang: Language | str) -> None: global default_lang; default_lang = new_lang" + ) + else: + self.script.add_line(f"languages = {list(self.data.keys())}") + self.script.add_line(f"default_lang='{self.primary}'") + self.script.add_line( + "def change_language(new_lang): global default_lang; default_lang = new_lang" + ) + + self.primary_data = self.data[self.primary] + + for key in self.primary_data: + args = find_args(self.primary_data[key]) + + self.script.add_line( + f"def {process_name(key)}({convert_args(','.join([*args, 'lang:str|None=None' if self.uses_typing else 'lang']), args, 'none')}):" + ) + if self.generate_comments: + self.script.add_line('"""', 1) + self.script.add_line("### Locales", 1) + for language in self.data: + self.script.add_line( + f"- {language.capitalize()}: **{self.data[language].get(key, self.primary_data[key])}**", + 1, + ) + self.script.add_line('"""', 1) + self.script.add_line("if not lang: lang=default_lang", 1) + for language in self.data: + formatted_map = "{" + for arg in args: + formatted_map += f'"{convert_args(arg, args, "none")}": {convert_args(arg, args, "none")},' + formatted_map = formatted_map[:-1] + "}" + self.script.add_line( + f"""if lang == '{language}': return {convert_args(json.dumps( + self.data[language].get(key,self.primary_data[key]), + ensure_ascii=False + ), args)}{f'.format_map({formatted_map})' if len(args) > 0 else ''}""", + 1, + ) + + self.script.add_line( + "else: raise ValueError(f'Invalid language {lang}')", 1 + ) + with open(self.output, "w", encoding="UTF-8") as f: + f.write(self.script.script) + + +def build_result( + primary_lang: str, + locale_dir: str, + types: bool, + output_path: str, + generate_comments: bool = True, +): + start = time.time() + lc = LanguageCollector(locale_dir) + GenerateScript( + primary_lang, lc.languages, types, output_path, generate_comments + ).create() + logger.info(f"Done in {time.time() - start}s") diff --git a/modules/keys.py b/modules/keys.py new file mode 100644 index 0000000..3000f50 --- /dev/null +++ b/modules/keys.py @@ -0,0 +1,2340 @@ + +# This file was automatically created from localization JSON files. +# DO NOT EDIT THIS FILE DIRECTLY. If you want to edit a translation, please use the language's JSON file. + +#fmt: off + +from typing import Literal, List +Language=Literal['en', 'es', 'fi', 'fr', 'it'] +languages: List[Language] = ['en', 'es', 'fi', 'fr', 'it'] +default_lang: Language | str='en' +def change_language(new_lang: Language | str) -> None: global default_lang; default_lang = new_lang +def memory_file_valid(lang:str|None=None): + """ + ### Locales + - En: **The memory.json file is valid!** + - Es: **The memory.json file is valid!** + - Fi: **memory.json on toimiva!** + - Fr: **The memory.json file is valid!** + - It: **Il file JSON è valido!** + """ + if not lang: lang=default_lang + if lang == 'en': return "The memory.json file is valid!" + if lang == 'es': return "The memory.json file is valid!" + if lang == 'fi': return "memory.json on toimiva!" + if lang == 'fr': return "The memory.json file is valid!" + if lang == 'it': return "Il file JSON è valido!" + else: raise ValueError(f'Invalid language {lang}') +def file_aint_uft8(lang:str|None=None): + """ + ### Locales + - En: **File is not valid UTF-8 text. Might be binary or corrupted.** + - Es: **File is not valid UTF-8 text. Might be binary or corrupted.** + - Fi: **Tiedosto ei ole UTF-8 tekstiä. Saattaa olla binääriä tai korruptoitunut.** + - Fr: **File is not valid UTF-8 text. Might be binary or corrupted.** + - It: **File is not valid UTF-8 text. Might be binary or corrupted.** + """ + if not lang: lang=default_lang + if lang == 'en': return "File is not valid UTF-8 text. Might be binary or corrupted." + if lang == 'es': return "File is not valid UTF-8 text. Might be binary or corrupted." + if lang == 'fi': return "Tiedosto ei ole UTF-8 tekstiä. Saattaa olla binääriä tai korruptoitunut." + if lang == 'fr': return "File is not valid UTF-8 text. Might be binary or corrupted." + if lang == 'it': return "File is not valid UTF-8 text. Might be binary or corrupted." + else: raise ValueError(f'Invalid language {lang}') +def psutil_not_installed(lang:str|None=None): + """ + ### Locales + - En: **Memory check skipped.** + - Es: **Memory check skipped.** + - Fi: **Memory check skipped.** + - Fr: **Memory check skipped.** + - It: **Controllo memoria saltato.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Memory check skipped." + if lang == 'es': return "Memory check skipped." + if lang == 'fi': return "Memory check skipped." + if lang == 'fr': return "Memory check skipped." + if lang == 'it': return "Controllo memoria saltato." + else: raise ValueError(f'Invalid language {lang}') +def not_cloned(lang:str|None=None): + """ + ### Locales + - En: **Goober is not cloned! Please clone it from GitHub.** + - Es: **Goober is not cloned! Please clone it from GitHub.** + - Fi: **Goober is not cloned! Please clone it from GitHub.** + - Fr: **Goober is not cloned! Please clone it from GitHub.** + - It: **Goober non è stato clonato! Clonalo da GitHub.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Goober is not cloned! Please clone it from GitHub." + if lang == 'es': return "Goober is not cloned! Please clone it from GitHub." + if lang == 'fi': return "Goober is not cloned! Please clone it from GitHub." + if lang == 'fr': return "Goober is not cloned! Please clone it from GitHub." + if lang == 'it': return "Goober non è stato clonato! Clonalo da GitHub." + else: raise ValueError(f'Invalid language {lang}') +def checks_disabled(lang:str|None=None): + """ + ### Locales + - En: **Checks are disabled!** + - Es: **Checks are disabled!** + - Fi: **Tarkistukset on poistettu käytöstä!** + - Fr: **Les vérifications sont désactivées !** + - It: **I controlli sono disabilitati!** + """ + if not lang: lang=default_lang + if lang == 'en': return "Checks are disabled!" + if lang == 'es': return "Checks are disabled!" + if lang == 'fi': return "Tarkistukset on poistettu käytöstä!" + if lang == 'fr': return "Les vérifications sont désactivées !" + if lang == 'it': return "I controlli sono disabilitati!" + else: raise ValueError(f'Invalid language {lang}') +def unhandled_exception(lang:str|None=None): + """ + ### Locales + - En: **An unhandled exception occurred. Please report this issue on GitHub.** + - Es: **An unhandled exception occurred. Please report this issue on GitHub.** + - Fi: **Käsittelemätön virhe tapahtui. Ilmoita tästä GitHubissa.** + - Fr: **Une exception non gérée est survenue. Merci de rapporter ce problème sur GitHub.** + - It: **Si è verificata un'eccezione non gestita. Segnala questo problema su GitHub, per favore.** + """ + if not lang: lang=default_lang + if lang == 'en': return "An unhandled exception occurred. Please report this issue on GitHub." + if lang == 'es': return "An unhandled exception occurred. Please report this issue on GitHub." + if lang == 'fi': return "Käsittelemätön virhe tapahtui. Ilmoita tästä GitHubissa." + if lang == 'fr': return "Une exception non gérée est survenue. Merci de rapporter ce problème sur GitHub." + if lang == 'it': return "Si è verificata un'eccezione non gestita. Segnala questo problema su GitHub, per favore." + else: raise ValueError(f'Invalid language {lang}') +def active_users(lang:str|None=None): + """ + ### Locales + - En: **Active users:** + - Es: **Active users:** + - Fi: **Aktiiviset käyttäjät:** + - Fr: **Utilisateurs actifs :** + - It: **Utenti attivi:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Active users:" + if lang == 'es': return "Active users:" + if lang == 'fi': return "Aktiiviset käyttäjät:" + if lang == 'fr': return "Utilisateurs actifs :" + if lang == 'it': return "Utenti attivi:" + else: raise ValueError(f'Invalid language {lang}') +def spacy_initialized(lang:str|None=None): + """ + ### Locales + - En: **spaCy and spacytextblob are ready.** + - Es: **spaCy and spacytextblob are ready.** + - Fi: **spaCy ja spacytextblob ovat valmiita.** + - Fr: **spaCy et spacytextblob sont prêts.** + - It: **spaCy e spacytextblob sono pronti.** + """ + if not lang: lang=default_lang + if lang == 'en': return "spaCy and spacytextblob are ready." + if lang == 'es': return "spaCy and spacytextblob are ready." + if lang == 'fi': return "spaCy ja spacytextblob ovat valmiita." + if lang == 'fr': return "spaCy et spacytextblob sont prêts." + if lang == 'it': return "spaCy e spacytextblob sono pronti." + else: raise ValueError(f'Invalid language {lang}') +def spacy_model_not_found(lang:str|None=None): + """ + ### Locales + - En: **The spaCy model was not found! Downloading it....`** + - Es: **The spaCy model was not found! Downloading it....`** + - Fi: **spaCy mallia ei löytynyt! Ladataan se....`** + - Fr: **Le modèle spaCy est introuvable ! Téléchargement en cours...** + - It: **Il modello spaCy non è stato trovato! Lo sto scaricando...** + """ + if not lang: lang=default_lang + if lang == 'en': return "The spaCy model was not found! Downloading it....`" + if lang == 'es': return "The spaCy model was not found! Downloading it....`" + if lang == 'fi': return "spaCy mallia ei löytynyt! Ladataan se....`" + if lang == 'fr': return "Le modèle spaCy est introuvable ! Téléchargement en cours..." + if lang == 'it': return "Il modello spaCy non è stato trovato! Lo sto scaricando..." + else: raise ValueError(f'Invalid language {lang}') +def env_file_not_found(lang:str|None=None): + """ + ### Locales + - En: **The .env file was not found! Please create one with the required variables.** + - Es: **The .env file was not found! Please create one with the required variables.** + - Fi: **.env-tiedostoa ei löytnyt! Luo tiedosto jossa on tarvittavat muuttujat** + - Fr: **Le fichier .env est introuvable ! Créez-en un avec les variables nécessaires.** + - It: **Il file .env non è stato trovato! Crea un file con le variabili richieste.** + """ + if not lang: lang=default_lang + if lang == 'en': return "The .env file was not found! Please create one with the required variables." + if lang == 'es': return "The .env file was not found! Please create one with the required variables." + if lang == 'fi': return ".env-tiedostoa ei löytnyt! Luo tiedosto jossa on tarvittavat muuttujat" + if lang == 'fr': return "Le fichier .env est introuvable ! Créez-en un avec les variables nécessaires." + if lang == 'it': return "Il file .env non è stato trovato! Crea un file con le variabili richieste." + else: raise ValueError(f'Invalid language {lang}') +def error_fetching_active_users(error,lang:str|None=None): + """ + ### Locales + - En: **Error fetching active users: {error}** + - Es: **Error fetching active users: {error}** + - Fi: **Aktiivisten käyttäjien hankkimisessa tapahtui ongelma: {error}** + - Fr: **Erreur lors de la récupération des utilisateurs actifs : {error}** + - It: **Errore nel recupero degli utenti attivi: {error}** + """ + if not lang: lang=default_lang + if lang == 'en': return "Error fetching active users: {error}".format_map({"error": error}) + if lang == 'es': return "Error fetching active users: {error}".format_map({"error": error}) + if lang == 'fi': return "Aktiivisten käyttäjien hankkimisessa tapahtui ongelma: {error}".format_map({"error": error}) + if lang == 'fr': return "Erreur lors de la récupération des utilisateurs actifs : {error}".format_map({"error": error}) + if lang == 'it': return "Errore nel recupero degli utenti attivi: {error}".format_map({"error": error}) + else: raise ValueError(f'Invalid language {lang}') +def error_sending_alive_ping(error,lang:str|None=None): + """ + ### Locales + - En: **Error sending alive ping: {error}** + - Es: **Error sending alive ping: {error}** + - Fi: **Pingin lähettäminen goober centraliin epäonnistui: {error}** + - Fr: **Erreur lors de l’envoi du ping actif : {error}** + - It: **Errore nell'invio di aliveping:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Error sending alive ping: {error}".format_map({"error": error}) + if lang == 'es': return "Error sending alive ping: {error}".format_map({"error": error}) + if lang == 'fi': return "Pingin lähettäminen goober centraliin epäonnistui: {error}".format_map({"error": error}) + if lang == 'fr': return "Erreur lors de l’envoi du ping actif : {error}".format_map({"error": error}) + if lang == 'it': return "Errore nell'invio di aliveping:".format_map({"error": error}) + else: raise ValueError(f'Invalid language {lang}') +def already_started(lang:str|None=None): + """ + ### Locales + - En: **I've already started! I'm not updating...** + - Es: **I've already started! I'm not updating...** + - Fi: **Olen jo käynnistynyt! Ei päivitetä...** + - Fr: **J’ai déjà démarré ! Je ne me mets pas à jour...** + - It: **Sono già avviato! Non aggiorno...** + """ + if not lang: lang=default_lang + if lang == 'en': return "I've already started! I'm not updating..." + if lang == 'es': return "I've already started! I'm not updating..." + if lang == 'fi': return "Olen jo käynnistynyt! Ei päivitetä..." + if lang == 'fr': return "J’ai déjà démarré ! Je ne me mets pas à jour..." + if lang == 'it': return "Sono già avviato! Non aggiorno..." + else: raise ValueError(f'Invalid language {lang}') +def please_restart(lang:str|None=None): + """ + ### Locales + - En: **Please Restart goober!** + - Es: **Please Restart goober!** + - Fi: **Käynnistä uudelleen, hölmö!** + - Fr: **Redémarre, stp !** + - It: **Riavvia goober!** + """ + if not lang: lang=default_lang + if lang == 'en': return "Please Restart goober!" + if lang == 'es': return "Please Restart goober!" + if lang == 'fi': return "Käynnistä uudelleen, hölmö!" + if lang == 'fr': return "Redémarre, stp !" + if lang == 'it': return "Riavvia goober!" + else: raise ValueError(f'Invalid language {lang}') +def local_ahead(remote,branch,lang:str|None=None): + """ + ### Locales + - En: **Local {remote}/{branch} is ahead and/or up to par. Not Updating...** + - Es: **Local {remote}/{branch} is ahead and/or up to par. Not Updating...** + - Fi: **Paikallinen {remote}/{branch} on edellä ja/tai ajan tasalla. Ohitetaan päivitys...** + - Fr: **Local {remote}/{branch} est en avance ou à jour. Pas de mise à jour...** + - It: **Il ramo locale {remote}/{branch} è aggiornato o avanti. Nessun aggiornamento...** + """ + if not lang: lang=default_lang + if lang == 'en': return "Local {remote}/{branch} is ahead and/or up to par. Not Updating...".format_map({"remote": remote,"branch": branch}) + if lang == 'es': return "Local {remote}/{branch} is ahead and/or up to par. Not Updating...".format_map({"remote": remote,"branch": branch}) + if lang == 'fi': return "Paikallinen {remote}/{branch} on edellä ja/tai ajan tasalla. Ohitetaan päivitys...".format_map({"remote": remote,"branch": branch}) + if lang == 'fr': return "Local {remote}/{branch} est en avance ou à jour. Pas de mise à jour...".format_map({"remote": remote,"branch": branch}) + if lang == 'it': return "Il ramo locale {remote}/{branch} è aggiornato o avanti. Nessun aggiornamento...".format_map({"remote": remote,"branch": branch}) + else: raise ValueError(f'Invalid language {lang}') +def remote_ahead(remote,branch,lang:str|None=None): + """ + ### Locales + - En: **Remote {remote}/{branch} is ahead. Updating...** + - Es: **Remote {remote}/{branch} is ahead. Updating...** + - Fi: **Etärepositorio {remote}/{branch} on edellä. Päivitetään...** + - Fr: **Remote {remote}/{branch} est en avance. Mise à jour en cours...** + - It: **Il ramo remoto {remote}/{branch} è avanti. Aggiornamento in corso...** + """ + if not lang: lang=default_lang + if lang == 'en': return "Remote {remote}/{branch} is ahead. Updating...".format_map({"remote": remote,"branch": branch}) + if lang == 'es': return "Remote {remote}/{branch} is ahead. Updating...".format_map({"remote": remote,"branch": branch}) + if lang == 'fi': return "Etärepositorio {remote}/{branch} on edellä. Päivitetään...".format_map({"remote": remote,"branch": branch}) + if lang == 'fr': return "Remote {remote}/{branch} est en avance. Mise à jour en cours...".format_map({"remote": remote,"branch": branch}) + if lang == 'it': return "Il ramo remoto {remote}/{branch} è avanti. Aggiornamento in corso...".format_map({"remote": remote,"branch": branch}) + else: raise ValueError(f'Invalid language {lang}') +def cant_find_local_version(lang:str|None=None): + """ + ### Locales + - En: **I can't find the local_version variable! Or it's been tampered with and it's not an integer!** + - Es: **I can't find the local_version variable! Or it's been tampered with and it's not an integer!** + - Fi: **Muuttujaa local_version ei löytynyt, tai sitä on muokattu eikä ole kokonaisluku!** + - Fr: **Je ne trouve pas la variable local_version ! Ou elle a été modifiée et ce n’est pas un entier !** + - It: **Impossibile trovare la variabile local_version! O è stata manomessa e non è un intero!** + """ + if not lang: lang=default_lang + if lang == 'en': return "I can't find the local_version variable! Or it's been tampered with and it's not an integer!" + if lang == 'es': return "I can't find the local_version variable! Or it's been tampered with and it's not an integer!" + if lang == 'fi': return "Muuttujaa local_version ei löytynyt, tai sitä on muokattu eikä ole kokonaisluku!" + if lang == 'fr': return "Je ne trouve pas la variable local_version ! Ou elle a été modifiée et ce n’est pas un entier !" + if lang == 'it': return "Impossibile trovare la variabile local_version! O è stata manomessa e non è un intero!" + else: raise ValueError(f'Invalid language {lang}') +def running_prestart_checks(lang:str|None=None): + """ + ### Locales + - En: **Running pre-start checks...** + - Es: **Running pre-start checks...** + - Fi: **Suoritetaan esikäynnistystarkistuksia...** + - Fr: **Exécution des vérifications préalables au démarrage...** + - It: **Esecuzione dei controlli pre-avvio...** + """ + if not lang: lang=default_lang + if lang == 'en': return "Running pre-start checks..." + if lang == 'es': return "Running pre-start checks..." + if lang == 'fi': return "Suoritetaan esikäynnistystarkistuksia..." + if lang == 'fr': return "Exécution des vérifications préalables au démarrage..." + if lang == 'it': return "Esecuzione dei controlli pre-avvio..." + else: raise ValueError(f'Invalid language {lang}') +def continuing_in_seconds(seconds,lang:str|None=None): + """ + ### Locales + - En: **Continuing in {seconds} seconds... Press any key to skip.** + - Es: **Continuing in {seconds} seconds... Press any key to skip.** + - Fi: **Jatketaan {seconds} sekunnin kuluttua... Paina mitä tahansa näppäintä ohittaaksesi.** + - Fr: **Reprise dans {seconds} secondes... Appuie sur une touche pour passer.** + - It: **Continuo tra {seconds} secondi... Premi un tasto per saltare.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Continuing in {seconds} seconds... Press any key to skip.".format_map({"seconds": seconds}) + if lang == 'es': return "Continuing in {seconds} seconds... Press any key to skip.".format_map({"seconds": seconds}) + if lang == 'fi': return "Jatketaan {seconds} sekunnin kuluttua... Paina mitä tahansa näppäintä ohittaaksesi.".format_map({"seconds": seconds}) + if lang == 'fr': return "Reprise dans {seconds} secondes... Appuie sur une touche pour passer.".format_map({"seconds": seconds}) + if lang == 'it': return "Continuo tra {seconds} secondi... Premi un tasto per saltare.".format_map({"seconds": seconds}) + else: raise ValueError(f'Invalid language {lang}') +def missing_requests_psutil(lang:str|None=None): + """ + ### Locales + - En: **Missing requests and psutil! Please install them using pip: `pip install requests psutil`** + - Es: **Missing requests and psutil! Please install them using pip: `pip install requests psutil`** + - Fi: **Kirjastot requests ja psutil puuttuvat! Asenna ne komennolla: `pip install requests psutil`** + - Fr: **requests et psutil manquants ! Installe-les avec pip : `pip install requests psutil`** + - It: **Mancano requests e psutil! Installali con pip: `pip install requests psutil`** + """ + if not lang: lang=default_lang + if lang == 'en': return "Missing requests and psutil! Please install them using pip: `pip install requests psutil`" + if lang == 'es': return "Missing requests and psutil! Please install them using pip: `pip install requests psutil`" + if lang == 'fi': return "Kirjastot requests ja psutil puuttuvat! Asenna ne komennolla: `pip install requests psutil`" + if lang == 'fr': return "requests et psutil manquants ! Installe-les avec pip : `pip install requests psutil`" + if lang == 'it': return "Mancano requests e psutil! Installali con pip: `pip install requests psutil`" + else: raise ValueError(f'Invalid language {lang}') +def requirements_not_found(path,lang:str|None=None): + """ + ### Locales + - En: **requirements.txt not found at {path} was it tampered with?** + - Es: **requirements.txt not found at {path} was it tampered with?** + - Fi: **Tiedostoa requirements.txt ei löytynyt polusta {path} – onko sitä muokattu?** + - Fr: **requirements.txt introuvable à {path}, a-t-il été modifié ?** + - It: **requirements.txt non trovato in {path} è stato manomesso?** + """ + if not lang: lang=default_lang + if lang == 'en': return "requirements.txt not found at {path} was it tampered with?".format_map({"path": path}) + if lang == 'es': return "requirements.txt not found at {path} was it tampered with?".format_map({"path": path}) + if lang == 'fi': return "Tiedostoa requirements.txt ei löytynyt polusta {path} – onko sitä muokattu?".format_map({"path": path}) + if lang == 'fr': return "requirements.txt introuvable à {path}, a-t-il été modifié ?".format_map({"path": path}) + if lang == 'it': return "requirements.txt non trovato in {path} è stato manomesso?".format_map({"path": path}) + else: raise ValueError(f'Invalid language {lang}') +def warning_failed_parse_imports(filename,error,lang:str|None=None): + """ + ### Locales + - En: **Warning: Failed to parse imports from {filename}: {error}** + - Es: **Warning: Failed to parse imports from {filename}: {error}** + - Fi: **Varoitus: tuontien jäsentäminen epäonnistui tiedostossa {filename}: {error}** + - Fr: **Avertissement : Échec du parsing des imports depuis {filename} : {error}** + - It: **Attenzione: impossibile analizzare le importazioni da {filename}: {error}** + """ + if not lang: lang=default_lang + if lang == 'en': return "Warning: Failed to parse imports from {filename}: {error}".format_map({"filename": filename,"error": error}) + if lang == 'es': return "Warning: Failed to parse imports from {filename}: {error}".format_map({"filename": filename,"error": error}) + if lang == 'fi': return "Varoitus: tuontien jäsentäminen epäonnistui tiedostossa {filename}: {error}".format_map({"filename": filename,"error": error}) + if lang == 'fr': return "Avertissement : Échec du parsing des imports depuis {filename} : {error}".format_map({"filename": filename,"error": error}) + if lang == 'it': return "Attenzione: impossibile analizzare le importazioni da {filename}: {error}".format_map({"filename": filename,"error": error}) + else: raise ValueError(f'Invalid language {lang}') +def cogs_dir_not_found(path,lang:str|None=None): + """ + ### Locales + - En: **Cogs directory not found at {path}, skipping scan.** + - Es: **Cogs directory not found at {path}, skipping scan.** + - Fi: **Cogs-kansiota ei löytynyt polusta {path}, ohitetaan tarkistus.** + - Fr: **Répertoire des cogs introuvable à {path}, scan ignoré.** + - It: **Cartella cogs non trovata in {path}, scansione saltata.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Cogs directory not found at {path}, skipping scan.".format_map({"path": path}) + if lang == 'es': return "Cogs directory not found at {path}, skipping scan.".format_map({"path": path}) + if lang == 'fi': return "Cogs-kansiota ei löytynyt polusta {path}, ohitetaan tarkistus.".format_map({"path": path}) + if lang == 'fr': return "Répertoire des cogs introuvable à {path}, scan ignoré.".format_map({"path": path}) + if lang == 'it': return "Cartella cogs non trovata in {path}, scansione saltata.".format_map({"path": path}) + else: raise ValueError(f'Invalid language {lang}') +def std_lib_local_skipped(package,lang:str|None=None): + """ + ### Locales + - En: **STD LIB / LOCAL {package} (skipped check)** + - Es: **STD LIB / LOCAL {package} (skipped check)** + - Fi: **STD LIB / PAIKALLINEN {package} (tarkistus ohitettu)** + - Fr: **LIB STD / LOCAL {package} (vérification sautée)** + - It: **LIB STD / LOCALE {package} (controllo saltato)** + """ + if not lang: lang=default_lang + if lang == 'en': return "STD LIB / LOCAL {package} (skipped check)".format_map({"package": package}) + if lang == 'es': return "STD LIB / LOCAL {package} (skipped check)".format_map({"package": package}) + if lang == 'fi': return "STD LIB / PAIKALLINEN {package} (tarkistus ohitettu)".format_map({"package": package}) + if lang == 'fr': return "LIB STD / LOCAL {package} (vérification sautée)".format_map({"package": package}) + if lang == 'it': return "LIB STD / LOCALE {package} (controllo saltato)".format_map({"package": package}) + else: raise ValueError(f'Invalid language {lang}') +def ok_installed(lang:str|None=None): + """ + ### Locales + - En: **OK** + - Es: **OK** + - Fi: **OK** + - Fr: **OK** + - It: **OK** + """ + if not lang: lang=default_lang + if lang == 'en': return "OK" + if lang == 'es': return "OK" + if lang == 'fi': return "OK" + if lang == 'fr': return "OK" + if lang == 'it': return "OK" + else: raise ValueError(f'Invalid language {lang}') +def missing_package(lang:str|None=None): + """ + ### Locales + - En: **MISSING** + - Es: **MISSING** + - Fi: **PUUTTUU** + - Fr: **MANQUANT** + - It: **REQUISITO MANCANTE** + """ + if not lang: lang=default_lang + if lang == 'en': return "MISSING" + if lang == 'es': return "MISSING" + if lang == 'fi': return "PUUTTUU" + if lang == 'fr': return "MANQUANT" + if lang == 'it': return "REQUISITO MANCANTE" + else: raise ValueError(f'Invalid language {lang}') +def missing_package2(lang:str|None=None): + """ + ### Locales + - En: **is not installed** + - Es: **is not installed** + - Fi: **ei ole asennettu** + - Fr: **n’est pas installé** + - It: **non è installato** + """ + if not lang: lang=default_lang + if lang == 'en': return "is not installed" + if lang == 'es': return "is not installed" + if lang == 'fi': return "ei ole asennettu" + if lang == 'fr': return "n’est pas installé" + if lang == 'it': return "non è installato" + else: raise ValueError(f'Invalid language {lang}') +def missing_packages_detected(lang:str|None=None): + """ + ### Locales + - En: **Missing packages detected:** + - Es: **Missing packages detected:** + - Fi: **Puuttuvia kirjastoja havaittu:** + - Fr: **Packages manquants détectés :** + - It: **Pacchetti mancanti rilevati:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Missing packages detected:" + if lang == 'es': return "Missing packages detected:" + if lang == 'fi': return "Puuttuvia kirjastoja havaittu:" + if lang == 'fr': return "Packages manquants détectés :" + if lang == 'it': return "Pacchetti mancanti rilevati:" + else: raise ValueError(f'Invalid language {lang}') +def telling_goober_central(url,lang:str|None=None): + """ + ### Locales + - En: **Telling goober central at {url}** + - Es: **Telling goober central at {url}** + - Fi: **Ilmoitetaan goober-centralille osoitteessa {url}** + - Fr: **Envoi à goober central à {url}** + - It: **Segnalazione a goober central su {url}** + """ + if not lang: lang=default_lang + if lang == 'en': return "Telling goober central at {url}".format_map({"url": url}) + if lang == 'es': return "Telling goober central at {url}".format_map({"url": url}) + if lang == 'fi': return "Ilmoitetaan goober-centralille osoitteessa {url}".format_map({"url": url}) + if lang == 'fr': return "Envoi à goober central à {url}".format_map({"url": url}) + if lang == 'it': return "Segnalazione a goober central su {url}".format_map({"url": url}) + else: raise ValueError(f'Invalid language {lang}') +def failed_to_contact(url,error,lang:str|None=None): + """ + ### Locales + - En: **Failed to contact {url}: {error}** + - Es: **Failed to contact {url}: {error}** + - Fi: **Yhteyden muodostus epäonnistui osoitteeseen {url}: {error}** + - Fr: **Impossible de contacter {url} : {error}** + - It: **Impossibile contattare {url}: {error}** + """ + if not lang: lang=default_lang + if lang == 'en': return "Failed to contact {url}: {error}".format_map({"url": url,"error": error}) + if lang == 'es': return "Failed to contact {url}: {error}".format_map({"url": url,"error": error}) + if lang == 'fi': return "Yhteyden muodostus epäonnistui osoitteeseen {url}: {error}".format_map({"url": url,"error": error}) + if lang == 'fr': return "Impossible de contacter {url} : {error}".format_map({"url": url,"error": error}) + if lang == 'it': return "Impossibile contattare {url}: {error}".format_map({"url": url,"error": error}) + else: raise ValueError(f'Invalid language {lang}') +def all_requirements_satisfied(lang:str|None=None): + """ + ### Locales + - En: **All requirements are satisfied.** + - Es: **All requirements are satisfied.** + - Fi: **Kaikki vaatimukset täyttyvät.** + - Fr: **Toutes les dépendances sont satisfaites.** + - It: **Tutti i requisiti sono soddisfatti.** + """ + if not lang: lang=default_lang + if lang == 'en': return "All requirements are satisfied." + if lang == 'es': return "All requirements are satisfied." + if lang == 'fi': return "Kaikki vaatimukset täyttyvät." + if lang == 'fr': return "Toutes les dépendances sont satisfaites." + if lang == 'it': return "Tutti i requisiti sono soddisfatti." + else: raise ValueError(f'Invalid language {lang}') +def ping_to(host,latency,lang:str|None=None): + """ + ### Locales + - En: **Ping to {host}: {latency} ms** + - Es: **Ping to {host}: {latency} ms** + - Fi: **Ping osoitteeseen {host}: {latency} ms** + - Fr: **Ping vers {host} : {latency} ms** + - It: **Ping a {host}: {latency} ms** + """ + if not lang: lang=default_lang + if lang == 'en': return "Ping to {host}: {latency} ms".format_map({"host": host,"latency": latency}) + if lang == 'es': return "Ping to {host}: {latency} ms".format_map({"host": host,"latency": latency}) + if lang == 'fi': return "Ping osoitteeseen {host}: {latency} ms".format_map({"host": host,"latency": latency}) + if lang == 'fr': return "Ping vers {host} : {latency} ms".format_map({"host": host,"latency": latency}) + if lang == 'it': return "Ping a {host}: {latency} ms".format_map({"host": host,"latency": latency}) + else: raise ValueError(f'Invalid language {lang}') +def high_latency(lang:str|None=None): + """ + ### Locales + - En: **High latency detected! You may experience delays in response times.** + - Es: **High latency detected! You may experience delays in response times.** + - Fi: **Korkea viive havaittu! Vastaukset saattavat hidastua.** + - Fr: **Latence élevée détectée ! Tu pourrais avoir des délais de réponse.** + - It: **Latenza elevata rilevata! Potresti riscontrare ritardi nelle risposte.** + """ + if not lang: lang=default_lang + if lang == 'en': return "High latency detected! You may experience delays in response times." + if lang == 'es': return "High latency detected! You may experience delays in response times." + if lang == 'fi': return "Korkea viive havaittu! Vastaukset saattavat hidastua." + if lang == 'fr': return "Latence élevée détectée ! Tu pourrais avoir des délais de réponse." + if lang == 'it': return "Latenza elevata rilevata! Potresti riscontrare ritardi nelle risposte." + else: raise ValueError(f'Invalid language {lang}') +def could_not_parse_latency(lang:str|None=None): + """ + ### Locales + - En: **Could not parse latency.** + - Es: **Could not parse latency.** + - Fi: **Viivettä ei voitu tulkita.** + - Fr: **Impossible d’analyser la latence.** + - It: **Impossibile analizzare la latenza.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Could not parse latency." + if lang == 'es': return "Could not parse latency." + if lang == 'fi': return "Viivettä ei voitu tulkita." + if lang == 'fr': return "Impossible d’analyser la latence." + if lang == 'it': return "Impossibile analizzare la latenza." + else: raise ValueError(f'Invalid language {lang}') +def ping_failed(host,lang:str|None=None): + """ + ### Locales + - En: **Ping to {host} failed.** + - Es: **Ping to {host} failed.** + - Fi: **Ping osoitteeseen {host} epäonnistui.** + - Fr: **Ping vers {host} échoué.** + - It: **Ping a {host} fallito.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Ping to {host} failed.".format_map({"host": host}) + if lang == 'es': return "Ping to {host} failed.".format_map({"host": host}) + if lang == 'fi': return "Ping osoitteeseen {host} epäonnistui.".format_map({"host": host}) + if lang == 'fr': return "Ping vers {host} échoué.".format_map({"host": host}) + if lang == 'it': return "Ping a {host} fallito.".format_map({"host": host}) + else: raise ValueError(f'Invalid language {lang}') +def error_running_ping(error,lang:str|None=None): + """ + ### Locales + - En: **Error running ping: {error}** + - Es: **Error running ping: {error}** + - Fi: **Virhe ping-komennon suorittamisessa: {error}** + - Fr: **Erreur lors du ping : {error}** + - It: **Errore durante l'esecuzione del ping: {error}** + """ + if not lang: lang=default_lang + if lang == 'en': return "Error running ping: {error}".format_map({"error": error}) + if lang == 'es': return "Error running ping: {error}".format_map({"error": error}) + if lang == 'fi': return "Virhe ping-komennon suorittamisessa: {error}".format_map({"error": error}) + if lang == 'fr': return "Erreur lors du ping : {error}".format_map({"error": error}) + if lang == 'it': return "Errore durante l'esecuzione del ping: {error}".format_map({"error": error}) + else: raise ValueError(f'Invalid language {lang}') +def memory_usage(used,total,percent,lang:str|None=None): + """ + ### Locales + - En: **Memory Usage: {used} GB / {total} GB ({percent}%)** + - Es: **Memory Usage: {used} GB / {total} GB ({percent}%)** + - Fi: **Muistin käyttö: {used} Gt / {total} Gt ({percent}%)** + - Fr: **Utilisation mémoire : {used} Go / {total} Go ({percent}%)** + - It: **Utilizzo memoria: {used} GB / {total} GB ({percent}%)** + """ + if not lang: lang=default_lang + if lang == 'en': return "Memory Usage: {used} GB / {total} GB ({percent}%)".format_map({"used": used,"total": total,"percent": percent}) + if lang == 'es': return "Memory Usage: {used} GB / {total} GB ({percent}%)".format_map({"used": used,"total": total,"percent": percent}) + if lang == 'fi': return "Muistin käyttö: {used} Gt / {total} Gt ({percent}%)".format_map({"used": used,"total": total,"percent": percent}) + if lang == 'fr': return "Utilisation mémoire : {used} Go / {total} Go ({percent}%)".format_map({"used": used,"total": total,"percent": percent}) + if lang == 'it': return "Utilizzo memoria: {used} GB / {total} GB ({percent}%)".format_map({"used": used,"total": total,"percent": percent}) + else: raise ValueError(f'Invalid language {lang}') +def memory_above_90(percent,lang:str|None=None): + """ + ### Locales + - En: **Memory usage is above 90% ({percent}%). Consider freeing up memory.** + - Es: **Memory usage is above 90% ({percent}%). Consider freeing up memory.** + - Fi: **Muistin käyttö ylittää 90 % ({percent}%). Harkitse muistin vapauttamista.** + - Fr: **Usage mémoire au-dessus de 90% ({percent}%). Pense à libérer de la mémoire.** + - It: **Utilizzo memoria sopra il 90% ({percent}%). Considera di liberare memoria.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Memory usage is above 90% ({percent}%). Consider freeing up memory.".format_map({"percent": percent}) + if lang == 'es': return "Memory usage is above 90% ({percent}%). Consider freeing up memory.".format_map({"percent": percent}) + if lang == 'fi': return "Muistin käyttö ylittää 90 % ({percent}%). Harkitse muistin vapauttamista.".format_map({"percent": percent}) + if lang == 'fr': return "Usage mémoire au-dessus de 90% ({percent}%). Pense à libérer de la mémoire.".format_map({"percent": percent}) + if lang == 'it': return "Utilizzo memoria sopra il 90% ({percent}%). Considera di liberare memoria.".format_map({"percent": percent}) + else: raise ValueError(f'Invalid language {lang}') +def total_memory(total,lang:str|None=None): + """ + ### Locales + - En: **Total Memory: {total} GB** + - Es: **Total Memory: {total} GB** + - Fi: **Kokonaismuisti: {total} Gt** + - Fr: **Mémoire totale : {total} Go** + - It: **Memoria totale: {total} GB** + """ + if not lang: lang=default_lang + if lang == 'en': return "Total Memory: {total} GB".format_map({"total": total}) + if lang == 'es': return "Total Memory: {total} GB".format_map({"total": total}) + if lang == 'fi': return "Kokonaismuisti: {total} Gt".format_map({"total": total}) + if lang == 'fr': return "Mémoire totale : {total} Go".format_map({"total": total}) + if lang == 'it': return "Memoria totale: {total} GB".format_map({"total": total}) + else: raise ValueError(f'Invalid language {lang}') +def used_memory(used,lang:str|None=None): + """ + ### Locales + - En: **Used Memory: {used} GB** + - Es: **Used Memory: {used} GB** + - Fi: **Käytetty muisti: {used} Gt** + - Fr: **Mémoire utilisée : {used} Go** + - It: **Memoria usata: {used} GB** + """ + if not lang: lang=default_lang + if lang == 'en': return "Used Memory: {used} GB".format_map({"used": used}) + if lang == 'es': return "Used Memory: {used} GB".format_map({"used": used}) + if lang == 'fi': return "Käytetty muisti: {used} Gt".format_map({"used": used}) + if lang == 'fr': return "Mémoire utilisée : {used} Go".format_map({"used": used}) + if lang == 'it': return "Memoria usata: {used} GB".format_map({"used": used}) + else: raise ValueError(f'Invalid language {lang}') +def low_free_memory(free,lang:str|None=None): + """ + ### Locales + - En: **Low free memory detected! Only {free} GB available.** + - Es: **Low free memory detected! Only {free} GB available.** + - Fi: **Vapaa muisti vähissä! Vain {free} Gt jäljellä.** + - Fr: **Mémoire libre faible détectée ! Seulement {free} Go disponibles.** + - It: **Poca memoria libera! Solo {free} GB disponibili.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Low free memory detected! Only {free} GB available.".format_map({"free": free}) + if lang == 'es': return "Low free memory detected! Only {free} GB available.".format_map({"free": free}) + if lang == 'fi': return "Vapaa muisti vähissä! Vain {free} Gt jäljellä.".format_map({"free": free}) + if lang == 'fr': return "Mémoire libre faible détectée ! Seulement {free} Go disponibles.".format_map({"free": free}) + if lang == 'it': return "Poca memoria libera! Solo {free} GB disponibili.".format_map({"free": free}) + else: raise ValueError(f'Invalid language {lang}') +def measuring_cpu(lang:str|None=None): + """ + ### Locales + - En: **Measuring CPU usage per core...** + - Es: **Measuring CPU usage per core...** + - Fi: **Mitataan suorittimen käyttöä ytimittäin...** + - Fr: **Mesure de l’usage CPU par cœur...** + - It: **Misurazione utilizzo CPU per core...** + """ + if not lang: lang=default_lang + if lang == 'en': return "Measuring CPU usage per core..." + if lang == 'es': return "Measuring CPU usage per core..." + if lang == 'fi': return "Mitataan suorittimen käyttöä ytimittäin..." + if lang == 'fr': return "Mesure de l’usage CPU par cœur..." + if lang == 'it': return "Misurazione utilizzo CPU per core..." + else: raise ValueError(f'Invalid language {lang}') +def core_usage(idx,bar,usage,lang:str|None=None): + """ + ### Locales + - En: **Core {idx}: [{bar}] {usage}%** + - Es: **Core {idx}: [{bar}] {usage}%** + - Fi: **Ydin {idx}: [{bar}] {usage}%** + - Fr: **Cœur {idx} : [{bar}] {usage}%** + - It: **Core {idx}: [{bar}] {usage}%** + """ + if not lang: lang=default_lang + if lang == 'en': return "Core {idx}: [{bar}] {usage}%".format_map({"idx": idx,"bar": bar,"usage": usage}) + if lang == 'es': return "Core {idx}: [{bar}] {usage}%".format_map({"idx": idx,"bar": bar,"usage": usage}) + if lang == 'fi': return "Ydin {idx}: [{bar}] {usage}%".format_map({"idx": idx,"bar": bar,"usage": usage}) + if lang == 'fr': return "Cœur {idx} : [{bar}] {usage}%".format_map({"idx": idx,"bar": bar,"usage": usage}) + if lang == 'it': return "Core {idx}: [{bar}] {usage}%".format_map({"idx": idx,"bar": bar,"usage": usage}) + else: raise ValueError(f'Invalid language {lang}') +def total_cpu_usage(usage,lang:str|None=None): + """ + ### Locales + - En: **Total CPU Usage: {usage}%** + - Es: **Total CPU Usage: {usage}%** + - Fi: **Kokonaisprosessorin käyttö: {usage}%** + - Fr: **Usage total CPU : {usage}%** + - It: **Utilizzo totale CPU: {usage}%** + """ + if not lang: lang=default_lang + if lang == 'en': return "Total CPU Usage: {usage}%".format_map({"usage": usage}) + if lang == 'es': return "Total CPU Usage: {usage}%".format_map({"usage": usage}) + if lang == 'fi': return "Kokonaisprosessorin käyttö: {usage}%".format_map({"usage": usage}) + if lang == 'fr': return "Usage total CPU : {usage}%".format_map({"usage": usage}) + if lang == 'it': return "Utilizzo totale CPU: {usage}%".format_map({"usage": usage}) + else: raise ValueError(f'Invalid language {lang}') +def high_avg_cpu(usage,lang:str|None=None): + """ + ### Locales + - En: **High average CPU usage: {usage}%** + - Es: **High average CPU usage: {usage}%** + - Fi: **Korkea keskimääräinen prosessorin käyttö: {usage}%** + - Fr: **Moyenne CPU élevée : {usage}%** + - It: **Utilizzo medio CPU elevato: {usage}%** + """ + if not lang: lang=default_lang + if lang == 'en': return "High average CPU usage: {usage}%".format_map({"usage": usage}) + if lang == 'es': return "High average CPU usage: {usage}%".format_map({"usage": usage}) + if lang == 'fi': return "Korkea keskimääräinen prosessorin käyttö: {usage}%".format_map({"usage": usage}) + if lang == 'fr': return "Moyenne CPU élevée : {usage}%".format_map({"usage": usage}) + if lang == 'it': return "Utilizzo medio CPU elevato: {usage}%".format_map({"usage": usage}) + else: raise ValueError(f'Invalid language {lang}') +def really_high_cpu(lang:str|None=None): + """ + ### Locales + - En: **Really high CPU load! System may throttle or hang.** + - Es: **Really high CPU load! System may throttle or hang.** + - Fi: **Erittäin korkea prosessorikuorma! Järjestelmä saattaa hidastua tai jumittua.** + - Fr: **Charge CPU vraiment élevée ! Le système pourrait ralentir ou planter.** + - It: **Carico CPU molto alto! Il sistema potrebbe rallentare o bloccarsi.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Really high CPU load! System may throttle or hang." + if lang == 'es': return "Really high CPU load! System may throttle or hang." + if lang == 'fi': return "Erittäin korkea prosessorikuorma! Järjestelmä saattaa hidastua tai jumittua." + if lang == 'fr': return "Charge CPU vraiment élevée ! Le système pourrait ralentir ou planter." + if lang == 'it': return "Carico CPU molto alto! Il sistema potrebbe rallentare o bloccarsi." + else: raise ValueError(f'Invalid language {lang}') +def memory_file(size,lang:str|None=None): + """ + ### Locales + - En: **Memory file: {size} MB** + - Es: **Memory file: {size} MB** + - Fi: **Muistitiedosto: {size} Mt** + - Fr: **Fichier mémoire : {size} Mo** + - It: **File memoria: {size} MB** + """ + if not lang: lang=default_lang + if lang == 'en': return "Memory file: {size} MB".format_map({"size": size}) + if lang == 'es': return "Memory file: {size} MB".format_map({"size": size}) + if lang == 'fi': return "Muistitiedosto: {size} Mt".format_map({"size": size}) + if lang == 'fr': return "Fichier mémoire : {size} Mo".format_map({"size": size}) + if lang == 'it': return "File memoria: {size} MB".format_map({"size": size}) + else: raise ValueError(f'Invalid language {lang}') +def memory_file_large(lang:str|None=None): + """ + ### Locales + - En: **Memory file is 1GB or higher, consider clearing it to free up space.** + - Es: **Memory file is 1GB or higher, consider clearing it to free up space.** + - Fi: **Muistitiedosto on enemmän kuin 1 Gt – harkitse sen tyhjentämistä tilan vapauttamiseksi.** + - Fr: **Fichier mémoire de 1 Go ou plus, pense à le nettoyer pour libérer de l’espace.** + - It: **Il file di memoria è 1GB o più, valuta di svuotarlo.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Memory file is 1GB or higher, consider clearing it to free up space." + if lang == 'es': return "Memory file is 1GB or higher, consider clearing it to free up space." + if lang == 'fi': return "Muistitiedosto on enemmän kuin 1 Gt – harkitse sen tyhjentämistä tilan vapauttamiseksi." + if lang == 'fr': return "Fichier mémoire de 1 Go ou plus, pense à le nettoyer pour libérer de l’espace." + if lang == 'it': return "Il file di memoria è 1GB o più, valuta di svuotarlo." + else: raise ValueError(f'Invalid language {lang}') +def memory_file_corrupted(error,lang:str|None=None): + """ + ### Locales + - En: **Memory file is corrupted! JSON decode error: {error}** + - Es: **Memory file is corrupted! JSON decode error: {error}** + - Fi: **Muistitiedosto on vioittunut! JSON purkuvirhe: {error}** + - Fr: **Fichier mémoire corrompu ! Erreur JSON : {error}** + - It: **File memoria corrotto! Errore JSON decode: {error}** + """ + if not lang: lang=default_lang + if lang == 'en': return "Memory file is corrupted! JSON decode error: {error}".format_map({"error": error}) + if lang == 'es': return "Memory file is corrupted! JSON decode error: {error}".format_map({"error": error}) + if lang == 'fi': return "Muistitiedosto on vioittunut! JSON purkuvirhe: {error}".format_map({"error": error}) + if lang == 'fr': return "Fichier mémoire corrompu ! Erreur JSON : {error}".format_map({"error": error}) + if lang == 'it': return "File memoria corrotto! Errore JSON decode: {error}".format_map({"error": error}) + else: raise ValueError(f'Invalid language {lang}') +def consider_backup_memory(lang:str|None=None): + """ + ### Locales + - En: **Consider backing up and recreating the memory file.** + - Es: **Consider backing up and recreating the memory file.** + - Fi: **Harkitse muistitiedoston varmuuskopioimista ja uudelleenluontia.** + - Fr: **Pense à sauvegarder et recréer le fichier mémoire.** + - It: **Valuta di fare un backup e ricreare il file di memoria.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Consider backing up and recreating the memory file." + if lang == 'es': return "Consider backing up and recreating the memory file." + if lang == 'fi': return "Harkitse muistitiedoston varmuuskopioimista ja uudelleenluontia." + if lang == 'fr': return "Pense à sauvegarder et recréer le fichier mémoire." + if lang == 'it': return "Valuta di fare un backup e ricreare il file di memoria." + else: raise ValueError(f'Invalid language {lang}') +def memory_file_encoding(error,lang:str|None=None): + """ + ### Locales + - En: **Memory file has encoding issues: {error}** + - Es: **Memory file has encoding issues: {error}** + - Fi: **Muistitiedostossa on koodausongelmia: {error}** + - Fr: **Problèmes d’encodage du fichier mémoire : {error}** + - It: **Problemi di codifica nel file memoria: {error}** + """ + if not lang: lang=default_lang + if lang == 'en': return "Memory file has encoding issues: {error}".format_map({"error": error}) + if lang == 'es': return "Memory file has encoding issues: {error}".format_map({"error": error}) + if lang == 'fi': return "Muistitiedostossa on koodausongelmia: {error}".format_map({"error": error}) + if lang == 'fr': return "Problèmes d’encodage du fichier mémoire : {error}".format_map({"error": error}) + if lang == 'it': return "Problemi di codifica nel file memoria: {error}".format_map({"error": error}) + else: raise ValueError(f'Invalid language {lang}') +def error_reading_memory(error,lang:str|None=None): + """ + ### Locales + - En: **Error reading memory file: {error}** + - Es: **Error reading memory file: {error}** + - Fi: **Virhe muistitiedoston lukemisessa: {error}** + - Fr: **Erreur lecture fichier mémoire : {error}** + - It: **Errore nella lettura del file memoria: {error}** + """ + if not lang: lang=default_lang + if lang == 'en': return "Error reading memory file: {error}".format_map({"error": error}) + if lang == 'es': return "Error reading memory file: {error}".format_map({"error": error}) + if lang == 'fi': return "Virhe muistitiedoston lukemisessa: {error}".format_map({"error": error}) + if lang == 'fr': return "Erreur lecture fichier mémoire : {error}".format_map({"error": error}) + if lang == 'it': return "Errore nella lettura del file memoria: {error}".format_map({"error": error}) + else: raise ValueError(f'Invalid language {lang}') +def memory_file_not_found(lang:str|None=None): + """ + ### Locales + - En: **Memory file not found.** + - Es: **Memory file not found.** + - Fi: **Muistitiedostoa ei löytynyt.** + - Fr: **Fichier mémoire introuvable.** + - It: **File memoria non trovato.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Memory file not found." + if lang == 'es': return "Memory file not found." + if lang == 'fi': return "Muistitiedostoa ei löytynyt." + if lang == 'fr': return "Fichier mémoire introuvable." + if lang == 'it': return "File memoria non trovato." + else: raise ValueError(f'Invalid language {lang}') +def modification_warning(lang:str|None=None): + """ + ### Locales + - En: **Goober has been modified! Any changes will be lost in an update!** + - Es: **Goober ha sido modificado! Se omiten las comprobaciones del servidor por completo...** + - Fi: **Gooberia on muokattu! Ohitetaan palvelimen tarkistus kokonaan...** + - Fr: **Goober a été modifié ! Toutes les modifications seront perdues lors d'une mise à jour !** + - It: **Goober è stato modificato! Verifiche del server saltate completamente...** + """ + if not lang: lang=default_lang + if lang == 'en': return "Goober has been modified! Any changes will be lost in an update!" + if lang == 'es': return "Goober ha sido modificado! Se omiten las comprobaciones del servidor por completo..." + if lang == 'fi': return "Gooberia on muokattu! Ohitetaan palvelimen tarkistus kokonaan..." + if lang == 'fr': return "Goober a été modifié ! Toutes les modifications seront perdues lors d'une mise à jour !" + if lang == 'it': return "Goober è stato modificato! Verifiche del server saltate completamente..." + else: raise ValueError(f'Invalid language {lang}') +def reported_version(lang:str|None=None): + """ + ### Locales + - En: **Reported Version:** + - Es: **Version reportada:** + - Fi: **Ilmoitettu versio:** + - Fr: **Version rapportée :** + - It: **Versione segnalata:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Reported Version:" + if lang == 'es': return "Version reportada:" + if lang == 'fi': return "Ilmoitettu versio:" + if lang == 'fr': return "Version rapportée :" + if lang == 'it': return "Versione segnalata:" + else: raise ValueError(f'Invalid language {lang}') +def current_hash(lang:str|None=None): + """ + ### Locales + - En: **Current Hash:** + - Es: **Hash actual:** + - Fi: **Tämänhetkinen hash:** + - Fr: **Hachage actuel :** + - It: **Hash attuale:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Current Hash:" + if lang == 'es': return "Hash actual:" + if lang == 'fi': return "Tämänhetkinen hash:" + if lang == 'fr': return "Hachage actuel :" + if lang == 'it': return "Hash attuale:" + else: raise ValueError(f'Invalid language {lang}') +def not_found(lang:str|None=None): + """ + ### Locales + - En: **is not found!** + - Es: **no existe!** + - Fi: **ei löytynyt!** + - Fr: **n'est pas trouvé !** + - It: **non trovato!** + """ + if not lang: lang=default_lang + if lang == 'en': return "is not found!" + if lang == 'es': return "no existe!" + if lang == 'fi': return "ei löytynyt!" + if lang == 'fr': return "n'est pas trouvé !" + if lang == 'it': return "non trovato!" + else: raise ValueError(f'Invalid language {lang}') +def version_error(lang:str|None=None): + """ + ### Locales + - En: **Unable to fetch version info. Status code** + - Es: **No se puede obtener la informacion de la version. Codigo de estado** + - Fi: **Versiotietojen saanti epäonnistui.. Tilakoodi:** + - Fr: **Impossible de récupérer les informations de version. Code d'état** + - It: **Impossibile recuperare le informazioni sulla versione. Codice di stato** + """ + if not lang: lang=default_lang + if lang == 'en': return "Unable to fetch version info. Status code" + if lang == 'es': return "No se puede obtener la informacion de la version. Codigo de estado" + if lang == 'fi': return "Versiotietojen saanti epäonnistui.. Tilakoodi:" + if lang == 'fr': return "Impossible de récupérer les informations de version. Code d'état" + if lang == 'it': return "Impossibile recuperare le informazioni sulla versione. Codice di stato" + else: raise ValueError(f'Invalid language {lang}') +def loaded_cog(lang:str|None=None): + """ + ### Locales + - En: **Loaded cog:** + - Es: **Engranaje cog:** + - Fi: **Ladatut cogit:** + - Fr: **Cog chargé :** + - It: **Cog caricato:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Loaded cog:" + if lang == 'es': return "Engranaje cog:" + if lang == 'fi': return "Ladatut cogit:" + if lang == 'fr': return "Cog chargé :" + if lang == 'it': return "Cog caricato:" + else: raise ValueError(f'Invalid language {lang}') +def loaded_cog2(lang:str|None=None): + """ + ### Locales + - En: **Loaded module:** + - Es: **Loaded module:** + - Fi: **Ladattiin moduuli:** + - Fr: **Module chargé :** + - It: **Module caricato:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Loaded module:" + if lang == 'es': return "Loaded module:" + if lang == 'fi': return "Ladattiin moduuli:" + if lang == 'fr': return "Module chargé :" + if lang == 'it': return "Module caricato:" + else: raise ValueError(f'Invalid language {lang}') +def cog_fail(lang:str|None=None): + """ + ### Locales + - En: **Failed to load cog:** + - Es: **No se pudo cargar el cog:** + - Fi: **Cogin lataus epäonnistui kohteelle:** + - Fr: **Échec du chargement du cog :** + - It: **Impossibile caricare il cog:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Failed to load cog:" + if lang == 'es': return "No se pudo cargar el cog:" + if lang == 'fi': return "Cogin lataus epäonnistui kohteelle:" + if lang == 'fr': return "Échec du chargement du cog :" + if lang == 'it': return "Impossibile caricare il cog:" + else: raise ValueError(f'Invalid language {lang}') +def cog_fail2(lang:str|None=None): + """ + ### Locales + - En: **Failed to load module:** + - Es: **Failed to load module:** + - Fi: **Moduulin lataaminen epäonnistui:** + - Fr: **Échec du chargement du module :** + - It: **Impossibile caricare il module:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Failed to load module:" + if lang == 'es': return "Failed to load module:" + if lang == 'fi': return "Moduulin lataaminen epäonnistui:" + if lang == 'fr': return "Échec du chargement du module :" + if lang == 'it': return "Impossibile caricare il module:" + else: raise ValueError(f'Invalid language {lang}') +def no_model(lang:str|None=None): + """ + ### Locales + - En: **No saved Markov model found. Starting from scratch.** + - Es: **No se encontro ningún modelo de Markov guardado. Empezando desde cero.** + - Fi: **Olemassaolevaa markov-mallia ei löydetty. Aloitetaan alusta.** + - Fr: **Aucun modèle Markov sauvegardé trouvé. Démarrage à partir de zéro.** + - It: **Nessun modello Markov salvato trovato. Iniziamo da zero.** + """ + if not lang: lang=default_lang + if lang == 'en': return "No saved Markov model found. Starting from scratch." + if lang == 'es': return "No se encontro ningún modelo de Markov guardado. Empezando desde cero." + if lang == 'fi': return "Olemassaolevaa markov-mallia ei löydetty. Aloitetaan alusta." + if lang == 'fr': return "Aucun modèle Markov sauvegardé trouvé. Démarrage à partir de zéro." + if lang == 'it': return "Nessun modello Markov salvato trovato. Iniziamo da zero." + else: raise ValueError(f'Invalid language {lang}') +def folder_created(folder_name,lang:str|None=None): + """ + ### Locales + - En: **Folder '{folder_name}' created.** + - Es: **Directorio '{folder_name}' creado.** + - Fi: **Kansio '{folder_name}' luotu.** + - Fr: **Dossier '{folder_name}' créé.** + - It: **Cartella '{folder_name}' creata.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Folder '{folder_name}' created.".format_map({"folder_name": folder_name}) + if lang == 'es': return "Directorio '{folder_name}' creado.".format_map({"folder_name": folder_name}) + if lang == 'fi': return "Kansio '{folder_name}' luotu.".format_map({"folder_name": folder_name}) + if lang == 'fr': return "Dossier '{folder_name}' créé.".format_map({"folder_name": folder_name}) + if lang == 'it': return "Cartella '{folder_name}' creata.".format_map({"folder_name": folder_name}) + else: raise ValueError(f'Invalid language {lang}') +def folder_exists(folder_name,lang:str|None=None): + """ + ### Locales + - En: **Folder '{folder_name}' already exists. skipping...** + - Es: **El directorio '{folder_name}' ya existe. Se omite...** + - Fi: **Kansio '{folder_name}' on jo olemassa...** + - Fr: **Le dossier '{folder_name}' existe déjà. Ignorons...** + - It: **La cartella '{folder_name}' esiste già. Saltando...** + """ + if not lang: lang=default_lang + if lang == 'en': return "Folder '{folder_name}' already exists. skipping...".format_map({"folder_name": folder_name}) + if lang == 'es': return "El directorio '{folder_name}' ya existe. Se omite...".format_map({"folder_name": folder_name}) + if lang == 'fi': return "Kansio '{folder_name}' on jo olemassa...".format_map({"folder_name": folder_name}) + if lang == 'fr': return "Le dossier '{folder_name}' existe déjà. Ignorons...".format_map({"folder_name": folder_name}) + if lang == 'it': return "La cartella '{folder_name}' esiste già. Saltando...".format_map({"folder_name": folder_name}) + else: raise ValueError(f'Invalid language {lang}') +def logged_in(lang:str|None=None): + """ + ### Locales + - En: **Logged in as** + - Es: **Inicio sesion como** + - Fi: **Kirjauduttiin sisään käyttäjänä** + - Fr: **Connecté en tant que** + - It: **Accesso effettuato come** + """ + if not lang: lang=default_lang + if lang == 'en': return "Logged in as" + if lang == 'es': return "Inicio sesion como" + if lang == 'fi': return "Kirjauduttiin sisään käyttäjänä" + if lang == 'fr': return "Connecté en tant que" + if lang == 'it': return "Accesso effettuato come" + else: raise ValueError(f'Invalid language {lang}') +def synced_commands(lang:str|None=None): + """ + ### Locales + - En: **Synced** + - Es: **Sincronizado** + - Fi: **Synkronoitiin** + - Fr: **Synchronisé** + - It: **Sincronizzati** + """ + if not lang: lang=default_lang + if lang == 'en': return "Synced" + if lang == 'es': return "Sincronizado" + if lang == 'fi': return "Synkronoitiin" + if lang == 'fr': return "Synchronisé" + if lang == 'it': return "Sincronizzati" + else: raise ValueError(f'Invalid language {lang}') +def synced_commands2(lang:str|None=None): + """ + ### Locales + - En: **commands!** + - Es: **comandos!** + - Fi: **komennot!** + - Fr: **commandes !** + - It: **comandi!** + """ + if not lang: lang=default_lang + if lang == 'en': return "commands!" + if lang == 'es': return "comandos!" + if lang == 'fi': return "komennot!" + if lang == 'fr': return "commandes !" + if lang == 'it': return "comandi!" + else: raise ValueError(f'Invalid language {lang}') +def fail_commands_sync(lang:str|None=None): + """ + ### Locales + - En: **Failed to sync commands:** + - Es: **Error al sincronizar comandos:** + - Fi: **Komentojen synkronointi epäonnistui:** + - Fr: **Échec de la synchronisation des commandes :** + - It: **Impossibile sincronizzare i comandi:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Failed to sync commands:" + if lang == 'es': return "Error al sincronizar comandos:" + if lang == 'fi': return "Komentojen synkronointi epäonnistui:" + if lang == 'fr': return "Échec de la synchronisation des commandes :" + if lang == 'it': return "Impossibile sincronizzare i comandi:" + else: raise ValueError(f'Invalid language {lang}') +def started(name,lang:str|None=None): + """ + ### Locales + - En: **{name} has started! +You're the star of the show now baby!** + - Es: **{name} ha empezado!** + - Fi: **{name} on käynnistynyt! +Olet nyt sarjan tähti, beibi!** + - Fr: **{name} a démarré !** + - It: **{name} è stato avviato! +Il palco è tuo!** + """ + if not lang: lang=default_lang + if lang == 'en': return "{name} has started!\nYou're the star of the show now baby!".format_map({"name": name}) + if lang == 'es': return "{name} ha empezado!".format_map({"name": name}) + if lang == 'fi': return "{name} on käynnistynyt!\nOlet nyt sarjan tähti, beibi!".format_map({"name": name}) + if lang == 'fr': return "{name} a démarré !".format_map({"name": name}) + if lang == 'it': return "{name} è stato avviato!\nIl palco è tuo!".format_map({"name": name}) + else: raise ValueError(f'Invalid language {lang}') +def name_check(lang:str|None=None): + """ + ### Locales + - En: **Error checking name availability:** + - Es: **Error al comprobar la disponibilidad del nombre:** + - Fi: **Nimen saatavuuden tarkistus epäonnistui:** + - Fr: **Erreur lors de la vérification de la disponibilité du nom :** + - It: **Errore nel controllo disponibilità del nome:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Error checking name availability:" + if lang == 'es': return "Error al comprobar la disponibilidad del nombre:" + if lang == 'fi': return "Nimen saatavuuden tarkistus epäonnistui:" + if lang == 'fr': return "Erreur lors de la vérification de la disponibilité du nom :" + if lang == 'it': return "Errore nel controllo disponibilità del nome:" + else: raise ValueError(f'Invalid language {lang}') +def name_taken(lang:str|None=None): + """ + ### Locales + - En: **Name is already taken. Please choose a different name.** + - Es: **El nombre ya está en uso. Elija otro.** + - Fi: **Nimi on jo käytössä. Valitse toinen nimi.** + - Fr: **Le nom est déjà pris. Veuillez choisir un autre nom.** + - It: **Il nome è già preso. Scegli un nome diverso.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Name is already taken. Please choose a different name." + if lang == 'es': return "El nombre ya está en uso. Elija otro." + if lang == 'fi': return "Nimi on jo käytössä. Valitse toinen nimi." + if lang == 'fr': return "Le nom est déjà pris. Veuillez choisir un autre nom." + if lang == 'it': return "Il nome è già preso. Scegli un nome diverso." + else: raise ValueError(f'Invalid language {lang}') +def name_check2(lang:str|None=None): + """ + ### Locales + - En: **Error during name availability check:** + - Es: **Error durante la comprobacion de disponibilidad del nombre:** + - Fi: **Virhe tapahtui nimen saatavuuden tarkistamisessa:** + - Fr: **Erreur lors de la vérification de la disponibilité du nom :** + - It: **Errore durante il controllo della disponibilità del nome:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Error during name availability check:" + if lang == 'es': return "Error durante la comprobacion de disponibilidad del nombre:" + if lang == 'fi': return "Virhe tapahtui nimen saatavuuden tarkistamisessa:" + if lang == 'fr': return "Erreur lors de la vérification de la disponibilité du nom :" + if lang == 'it': return "Errore durante il controllo della disponibilità del nome:" + else: raise ValueError(f'Invalid language {lang}') +def add_token(token,lang:str|None=None): + """ + ### Locales + - En: **Token: {token} +Please add this token to your .env file as** + - Es: **Token: {token} +Agregue este token a su archivo .env como** + - Fi: **Token: {token} +Lisää tämä .env-tiedostoosi nimellä** + - Fr: **Token : {token} +Veuillez ajouter ce token à votre fichier .env comme** + - It: **Token: {token} +Aggiungi questo token al tuo file .env come** + """ + if not lang: lang=default_lang + if lang == 'en': return "Token: {token}\nPlease add this token to your .env file as".format_map({"token": token}) + if lang == 'es': return "Token: {token}\nAgregue este token a su archivo .env como".format_map({"token": token}) + if lang == 'fi': return "Token: {token}\nLisää tämä .env-tiedostoosi nimellä".format_map({"token": token}) + if lang == 'fr': return "Token : {token}\nVeuillez ajouter ce token à votre fichier .env comme".format_map({"token": token}) + if lang == 'it': return "Token: {token}\nAggiungi questo token al tuo file .env come".format_map({"token": token}) + else: raise ValueError(f'Invalid language {lang}') +def token_exists(lang:str|None=None): + """ + ### Locales + - En: **Token already exists in .env. Continuing with the existing token.** + - Es: **Hay un token en el archivo .env. Continue con el token existente.** + - Fi: **Token on jo olemassa .env-tiedostossa. Jatketaan määritetyllä tokenilla.** + - Fr: **Le token existe déjà dans .env. Utilisation du token existant.** + - It: **Il token esiste già in .env. Continuando con il token esistente.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Token already exists in .env. Continuing with the existing token." + if lang == 'es': return "Hay un token en el archivo .env. Continue con el token existente." + if lang == 'fi': return "Token on jo olemassa .env-tiedostossa. Jatketaan määritetyllä tokenilla." + if lang == 'fr': return "Le token existe déjà dans .env. Utilisation du token existant." + if lang == 'it': return "Il token esiste già in .env. Continuando con il token esistente." + else: raise ValueError(f'Invalid language {lang}') +def registration_error(lang:str|None=None): + """ + ### Locales + - En: **Error during registration:** + - Es: **Error durante el registro:** + - Fi: **Virhe rekisteröinnissä:** + - Fr: **Erreur lors de l'enregistrement :** + - It: **Errore durante la registrazione:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Error during registration:" + if lang == 'es': return "Error durante el registro:" + if lang == 'fi': return "Virhe rekisteröinnissä:" + if lang == 'fr': return "Erreur lors de l'enregistrement :" + if lang == 'it': return "Errore durante la registrazione:" + else: raise ValueError(f'Invalid language {lang}') +def version_backup(lang:str|None=None): + """ + ### Locales + - En: **Backup created:** + - Es: **Copia de seguridad creada:** + - Fi: **Varmuuskopio luotu:** + - Fr: **Sauvegarde créée :** + - It: **Backup creato:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Backup created:" + if lang == 'es': return "Copia de seguridad creada:" + if lang == 'fi': return "Varmuuskopio luotu:" + if lang == 'fr': return "Sauvegarde créée :" + if lang == 'it': return "Backup creato:" + else: raise ValueError(f'Invalid language {lang}') +def backup_error(LOCAL_VERSION_FILE,lang:str|None=None): + """ + ### Locales + - En: **Error: {LOCAL_VERSION_FILE} not found for backup.** + - Es: **Error: {LOCAL_VERSION_FILE} no encontrado para la copia de seguridad.** + - Fi: **Virhe: {LOCAL_VERSION_FILE}-tiedostoa ei löytynyt varmuuskopiota varten.** + - Fr: **Erreur : {LOCAL_VERSION_FILE} introuvable pour la sauvegarde.** + - It: **Errore: {LOCAL_VERSION_FILE} non trovato per il backup.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Error: {LOCAL_VERSION_FILE} not found for backup.".format_map({"LOCAL_VERSION_FILE": LOCAL_VERSION_FILE}) + if lang == 'es': return "Error: {LOCAL_VERSION_FILE} no encontrado para la copia de seguridad.".format_map({"LOCAL_VERSION_FILE": LOCAL_VERSION_FILE}) + if lang == 'fi': return "Virhe: {LOCAL_VERSION_FILE}-tiedostoa ei löytynyt varmuuskopiota varten.".format_map({"LOCAL_VERSION_FILE": LOCAL_VERSION_FILE}) + if lang == 'fr': return "Erreur : {LOCAL_VERSION_FILE} introuvable pour la sauvegarde.".format_map({"LOCAL_VERSION_FILE": LOCAL_VERSION_FILE}) + if lang == 'it': return "Errore: {LOCAL_VERSION_FILE} non trovato per il backup.".format_map({"LOCAL_VERSION_FILE": LOCAL_VERSION_FILE}) + else: raise ValueError(f'Invalid language {lang}') +def model_loaded(lang:str|None=None): + """ + ### Locales + - En: **Markov model loaded from** + - Es: **Modelo de Markov cargado desde** + - Fi: **Markov-malli ladattu** + - Fr: **Modèle Markov chargé depuis** + - It: **Modello Markov caricato da** + """ + if not lang: lang=default_lang + if lang == 'en': return "Markov model loaded from" + if lang == 'es': return "Modelo de Markov cargado desde" + if lang == 'fi': return "Markov-malli ladattu" + if lang == 'fr': return "Modèle Markov chargé depuis" + if lang == 'it': return "Modello Markov caricato da" + else: raise ValueError(f'Invalid language {lang}') +def fetch_update_fail(lang:str|None=None): + """ + ### Locales + - En: **Could not fetch update information.** + - Es: **No se pudo obtener la informacion de actualizacion.** + - Fi: **Päivitystietojen hankkiminen epäonnistui.** + - Fr: **Impossible de récupérer les informations de mise à jour.** + - It: **Impossibile recuperare le informazioni sull'aggiornamento.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Could not fetch update information." + if lang == 'es': return "No se pudo obtener la informacion de actualizacion." + if lang == 'fi': return "Päivitystietojen hankkiminen epäonnistui." + if lang == 'fr': return "Impossible de récupérer les informations de mise à jour." + if lang == 'it': return "Impossibile recuperare le informazioni sull'aggiornamento." + else: raise ValueError(f'Invalid language {lang}') +def invalid_server(lang:str|None=None): + """ + ### Locales + - En: **Error: Invalid version information received from server.** + - Es: **Error: Se recibio informacion de version no valida del servidor.** + - Fi: **Virhe: Palvelin antoi virheellisen versiotietoraportin.** + - Fr: **Erreur : Informations de version invalides reçues du serveur.** + - It: **Errore: informazioni sulla versione non valide ricevute dal server.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Error: Invalid version information received from server." + if lang == 'es': return "Error: Se recibio informacion de version no valida del servidor." + if lang == 'fi': return "Virhe: Palvelin antoi virheellisen versiotietoraportin." + if lang == 'fr': return "Erreur : Informations de version invalides reçues du serveur." + if lang == 'it': return "Errore: informazioni sulla versione non valide ricevute dal server." + else: raise ValueError(f'Invalid language {lang}') +def goober_server_alert(lang:str|None=None): + """ + ### Locales + - En: **Alert from goober central! +** + - Es: **Alert from goober central! +** + - Fi: **Viesti goober centralista! +** + - Fr: **Alerte du serveur Goober central ! +** + - It: **Avviso da goober central! +** + """ + if not lang: lang=default_lang + if lang == 'en': return "Alert from goober central!\n" + if lang == 'es': return "Alert from goober central!\n" + if lang == 'fi': return "Viesti goober centralista!\n" + if lang == 'fr': return "Alerte du serveur Goober central !\n" + if lang == 'it': return "Avviso da goober central!\n" + else: raise ValueError(f'Invalid language {lang}') +def new_version(latest_version,local_version,lang:str|None=None): + """ + ### Locales + - En: **New version available: {latest_version} (Current: {local_version})** + - Es: **Nueva version disponible: {latest_version} (Version actual: {local_version})** + - Fi: **Uusi versio saatavilla: {latest_version} (Tämänhetkinen: {local_version})** + - Fr: **Nouvelle version disponible : {latest_version} (Actuelle : {local_version})** + - It: **Nuova versione disponibile: {latest_version} (Attuale: {local_version})** + """ + if not lang: lang=default_lang + if lang == 'en': return "New version available: {latest_version} (Current: {local_version})".format_map({"latest_version": latest_version,"local_version": local_version}) + if lang == 'es': return "Nueva version disponible: {latest_version} (Version actual: {local_version})".format_map({"latest_version": latest_version,"local_version": local_version}) + if lang == 'fi': return "Uusi versio saatavilla: {latest_version} (Tämänhetkinen: {local_version})".format_map({"latest_version": latest_version,"local_version": local_version}) + if lang == 'fr': return "Nouvelle version disponible : {latest_version} (Actuelle : {local_version})".format_map({"latest_version": latest_version,"local_version": local_version}) + if lang == 'it': return "Nuova versione disponibile: {latest_version} (Attuale: {local_version})".format_map({"latest_version": latest_version,"local_version": local_version}) + else: raise ValueError(f'Invalid language {lang}') +def changelog(VERSION_URL,lang:str|None=None): + """ + ### Locales + - En: **Check {VERSION_URL}/goob/changes.txt to check out the changelog + +** + - Es: **Consulte {VERSION_URL}/goob/changes.txt para ver el registro de cambios + +** + - Fi: **Mene osoitteeseen {VERSION_URL}/goob/changes.txt katsotakseen muutoslokin + +** + - Fr: **Consultez {VERSION_URL}/goob/changes.txt pour voir les modifications + +** + - It: **Controlla {VERSION_URL}/goob/changes.txt per vedere il changelog + +** + """ + if not lang: lang=default_lang + if lang == 'en': return "Check {VERSION_URL}/goob/changes.txt to check out the changelog\n\n".format_map({"VERSION_URL": VERSION_URL}) + if lang == 'es': return "Consulte {VERSION_URL}/goob/changes.txt para ver el registro de cambios\n\n".format_map({"VERSION_URL": VERSION_URL}) + if lang == 'fi': return "Mene osoitteeseen {VERSION_URL}/goob/changes.txt katsotakseen muutoslokin\n\n".format_map({"VERSION_URL": VERSION_URL}) + if lang == 'fr': return "Consultez {VERSION_URL}/goob/changes.txt pour voir les modifications\n\n".format_map({"VERSION_URL": VERSION_URL}) + if lang == 'it': return "Controlla {VERSION_URL}/goob/changes.txt per vedere il changelog\n\n".format_map({"VERSION_URL": VERSION_URL}) + else: raise ValueError(f'Invalid language {lang}') +def invalid_version(local_version,lang:str|None=None): + """ + ### Locales + - En: **The version: {local_version} isnt valid!** + - Es: **La version: {local_version} no es valida!** + - Fi: **Versio: {local_version} on virheellinen!** + - Fr: **La version : {local_version} n'est pas valide !** + - It: **La versione: {local_version} non è valida!** + """ + if not lang: lang=default_lang + if lang == 'en': return "The version: {local_version} isnt valid!".format_map({"local_version": local_version}) + if lang == 'es': return "La version: {local_version} no es valida!".format_map({"local_version": local_version}) + if lang == 'fi': return "Versio: {local_version} on virheellinen!".format_map({"local_version": local_version}) + if lang == 'fr': return "La version : {local_version} n'est pas valide !".format_map({"local_version": local_version}) + if lang == 'it': return "La versione: {local_version} non è valida!".format_map({"local_version": local_version}) + else: raise ValueError(f'Invalid language {lang}') +def invalid_version2(lang:str|None=None): + """ + ### Locales + - En: **If this is intended then ignore this message, else press Y to pull a valid version from the server regardless of the version of goober currently running** + - Es: **Si esto es lo que pretende, ignore este mensaje; si no, haga clic en Y en su teclado para descargar una version valida del servidor, independientemente de la version que se este ejecutando actualmente.** + - Fi: **Jos tämä on tahallista, voit jättää tämän viestin huomiotta. Jos tämä ei ole tahallista, paina Y-näppäintä hankkiaksesi kelvollisen version, riippumatta Gooberin tämänhetkisestä versiosta.** + - Fr: **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.** + - It: **Se è intenzionale ignora questo messaggio, altrimenti premi Y per scaricare una versione valida dal server indipendentemente dalla versione attuale di goober** + """ + if not lang: lang=default_lang + if lang == 'en': return "If this is intended then ignore this message, else press Y to pull a valid version from the server regardless of the version of goober currently running" + if lang == 'es': return "Si esto es lo que pretende, ignore este mensaje; si no, haga clic en Y en su teclado para descargar una version valida del servidor, independientemente de la version que se este ejecutando actualmente." + if lang == 'fi': return "Jos tämä on tahallista, voit jättää tämän viestin huomiotta. Jos tämä ei ole tahallista, paina Y-näppäintä hankkiaksesi kelvollisen version, riippumatta Gooberin tämänhetkisestä versiosta." + if lang == 'fr': return "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." + if lang == 'it': return "Se è intenzionale ignora questo messaggio, altrimenti premi Y per scaricare una versione valida dal server indipendentemente dalla versione attuale di goober" + else: raise ValueError(f'Invalid language {lang}') +def invalid_version3(lang:str|None=None): + """ + ### Locales + - En: **The current version will be backed up to current_version.bak..** + - Es: **La version actual se copiara a current_version.bak..** + - Fi: **Tämänhetkinen versio varmuuskopioidaan kohteeseen current_version.bak..** + - Fr: **La version actuelle sera sauvegardée dans current_version.bak..** + - It: **La versione attuale sarà salvata come current_version.bak..** + """ + if not lang: lang=default_lang + if lang == 'en': return "The current version will be backed up to current_version.bak.." + if lang == 'es': return "La version actual se copiara a current_version.bak.." + if lang == 'fi': return "Tämänhetkinen versio varmuuskopioidaan kohteeseen current_version.bak.." + if lang == 'fr': return "La version actuelle sera sauvegardée dans current_version.bak.." + if lang == 'it': return "La versione attuale sarà salvata come current_version.bak.." + else: raise ValueError(f'Invalid language {lang}') +def input(lang:str|None=None): + """ + ### Locales + - En: **(Y or any other key to ignore....)** + - Es: **(Y o cualquier otra tecla para ignorar....)** + - Fi: **(Y:tä tai mitä vaan muuta näppäintä jättää tämän huomioimatta....)** + - Fr: **(Y ou toute autre touche pour ignorer...)** + - It: **(Y o qualsiasi altro tasto per ignorare....)** + """ + if not lang: lang=default_lang + if lang == 'en': return "(Y or any other key to ignore....)" + if lang == 'es': return "(Y o cualquier otra tecla para ignorar....)" + if lang == 'fi': return "(Y:tä tai mitä vaan muuta näppäintä jättää tämän huomioimatta....)" + if lang == 'fr': return "(Y ou toute autre touche pour ignorer...)" + if lang == 'it': return "(Y o qualsiasi altro tasto per ignorare....)" + else: raise ValueError(f'Invalid language {lang}') +def modification_ignored(lang:str|None=None): + """ + ### Locales + - En: **You've modified** + - Es: **Has modificado** + - Fi: **Olet muokannut** + - Fr: **Vous avez modifié** + - It: **Hai modificato** + """ + if not lang: lang=default_lang + if lang == 'en': return "You've modified" + if lang == 'es': return "Has modificado" + if lang == 'fi': return "Olet muokannut" + if lang == 'fr': return "Vous avez modifié" + if lang == 'it': return "Hai modificato" + else: raise ValueError(f'Invalid language {lang}') +def modification_ignored2(lang:str|None=None): + """ + ### Locales + - En: **IGNOREWARNING is set to false..** + - Es: **IGNOREWARNING es falso** + - Fi: **IGNOREWARNING on asetettu false:ksi..** + - Fr: **IGNOREWARNING est désactivé..** + - It: **IGNOREWARNING è impostato su false..** + """ + if not lang: lang=default_lang + if lang == 'en': return "IGNOREWARNING is set to false.." + if lang == 'es': return "IGNOREWARNING es falso" + if lang == 'fi': return "IGNOREWARNING on asetettu false:ksi.." + if lang == 'fr': return "IGNOREWARNING est désactivé.." + if lang == 'it': return "IGNOREWARNING è impostato su false.." + else: raise ValueError(f'Invalid language {lang}') +def latest_version(lang:str|None=None): + """ + ### Locales + - En: **You're using the latest version:** + - Es: **Usando la ultima version:** + - Fi: **Käytät uusinta versiota:** + - Fr: **Vous utilisez la dernière version :** + - It: **Stai utilizzando l'ultima versione:** + """ + if not lang: lang=default_lang + if lang == 'en': return "You're using the latest version:" + if lang == 'es': return "Usando la ultima version:" + if lang == 'fi': return "Käytät uusinta versiota:" + if lang == 'fr': return "Vous utilisez la dernière version :" + if lang == 'it': return "Stai utilizzando l'ultima versione:" + else: raise ValueError(f'Invalid language {lang}') +def latest_version2(VERSION_URL,lang:str|None=None): + """ + ### Locales + - En: **Check {VERSION_URL}/goob/changes.txt to check out the changelog** + - Es: **Consulte {VERSION_URL}/goob/changes.txt para ver el registro de cambios** + - Fi: **Tarkista {VERSION_URL}/goob/changes.txt katsotakseen muutosloki** + - Fr: **Consultez {VERSION_URL}/goob/changes.txt pour voir les modifications** + - It: **Controlla {VERSION_URL}/goob/changes.txt per vedere il changelog** + """ + if not lang: lang=default_lang + if lang == 'en': return "Check {VERSION_URL}/goob/changes.txt to check out the changelog".format_map({"VERSION_URL": VERSION_URL}) + if lang == 'es': return "Consulte {VERSION_URL}/goob/changes.txt para ver el registro de cambios".format_map({"VERSION_URL": VERSION_URL}) + if lang == 'fi': return "Tarkista {VERSION_URL}/goob/changes.txt katsotakseen muutosloki".format_map({"VERSION_URL": VERSION_URL}) + if lang == 'fr': return "Consultez {VERSION_URL}/goob/changes.txt pour voir les modifications".format_map({"VERSION_URL": VERSION_URL}) + if lang == 'it': return "Controlla {VERSION_URL}/goob/changes.txt per vedere il changelog".format_map({"VERSION_URL": VERSION_URL}) + else: raise ValueError(f'Invalid language {lang}') +def pinging_disabled(lang:str|None=None): + """ + ### Locales + - En: **Pinging is disabled! Not telling the server im on...** + - Es: **El ping esta deshabilitado** + - Fi: **Pingaus on poistettu käytöstä! En kerro palvelimelle, että olen päällä...** + - Fr: **Le ping est désactivé ! Je ne préviens pas le serveur que je suis en ligne...** + - It: **Il ping è disabilitato! Non dico al server che sono online...** + """ + if not lang: lang=default_lang + if lang == 'en': return "Pinging is disabled! Not telling the server im on..." + if lang == 'es': return "El ping esta deshabilitado" + if lang == 'fi': return "Pingaus on poistettu käytöstä! En kerro palvelimelle, että olen päällä..." + if lang == 'fr': return "Le ping est désactivé ! Je ne préviens pas le serveur que je suis en ligne..." + if lang == 'it': return "Il ping è disabilitato! Non dico al server che sono online..." + else: raise ValueError(f'Invalid language {lang}') +def goober_ping_success(NAME,lang:str|None=None): + """ + ### Locales + - En: **Logged into goober central as {NAME}** + - Es: **Envie ping a Goober Central!** + - Fi: **Lähetettiin olemassaolo ping goober centraliin!** + - Fr: **Connecté à Goober central en tant que {NAME}** + - It: **Accesso a goober central come {NAME}** + """ + if not lang: lang=default_lang + if lang == 'en': return "Logged into goober central as {NAME}".format_map({"NAME": NAME}) + if lang == 'es': return "Envie ping a Goober Central!".format_map({"NAME": NAME}) + if lang == 'fi': return "Lähetettiin olemassaolo ping goober centraliin!".format_map({"NAME": NAME}) + if lang == 'fr': return "Connecté à Goober central en tant que {NAME}".format_map({"NAME": NAME}) + if lang == 'it': return "Accesso a goober central come {NAME}".format_map({"NAME": NAME}) + else: raise ValueError(f'Invalid language {lang}') +def goober_ping_fail(lang:str|None=None): + """ + ### Locales + - En: **Failed to send data. Server returned status code:** + - Es: **Error al enviar datos. El servidor devolvio el codigo de estado:** + - Fi: **Tiedon lähetys epäonnistui. Palvelin antoi tilakoodin:** + - Fr: **Échec de l'envoi des données. Le serveur a retourné le code d'état :** + - It: **Impossibile inviare i dati. Il server ha restituito il codice di stato:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Failed to send data. Server returned status code:" + if lang == 'es': return "Error al enviar datos. El servidor devolvio el codigo de estado:" + if lang == 'fi': return "Tiedon lähetys epäonnistui. Palvelin antoi tilakoodin:" + if lang == 'fr': return "Échec de l'envoi des données. Le serveur a retourné le code d'état :" + if lang == 'it': return "Impossibile inviare i dati. Il server ha restituito il codice di stato:" + else: raise ValueError(f'Invalid language {lang}') +def goober_ping_fail2(lang:str|None=None): + """ + ### Locales + - En: **An error occurred while sending data:** + - Es: **Se produjo un error al enviar los datos:** + - Fi: **Tiedon lähettämisen aikana tapahtui virhe:** + - Fr: **Une erreur est survenue lors de l'envoi des données :** + - It: **Si è verificato un errore durante l'invio dei dati:** + """ + if not lang: lang=default_lang + if lang == 'en': return "An error occurred while sending data:" + if lang == 'es': return "Se produjo un error al enviar los datos:" + if lang == 'fi': return "Tiedon lähettämisen aikana tapahtui virhe:" + if lang == 'fr': return "Une erreur est survenue lors de l'envoi des données :" + if lang == 'it': return "Si è verificato un errore durante l'invio dei dati:" + else: raise ValueError(f'Invalid language {lang}') +def sentence_positivity(lang:str|None=None): + """ + ### Locales + - En: **Positivity of sentence is:** + - Es: **La positividad de la sentencia es:** + - Fi: **Lauseen positiivisuus on:** + - Fr: **La positivité de la phrase est :** + - It: **La positività della frase è:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Positivity of sentence is:" + if lang == 'es': return "La positividad de la sentencia es:" + if lang == 'fi': return "Lauseen positiivisuus on:" + if lang == 'fr': return "La positivité de la phrase est :" + if lang == 'it': return "La positività della frase è:" + else: raise ValueError(f'Invalid language {lang}') +def command_edit_fail(lang:str|None=None): + """ + ### Locales + - En: **Failed to edit message:** + - Es: **No se pudo editar el mensaje:** + - Fi: **Viestin muokkaus epäonnistui:** + - Fr: **Échec de la modification du message :** + - It: **Impossibile modificare il messaggio:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Failed to edit message:" + if lang == 'es': return "No se pudo editar el mensaje:" + if lang == 'fi': return "Viestin muokkaus epäonnistui:" + if lang == 'fr': return "Échec de la modification du message :" + if lang == 'it': return "Impossibile modificare il messaggio:" + else: raise ValueError(f'Invalid language {lang}') +def command_desc_retrain(lang:str|None=None): + """ + ### Locales + - En: **Retrains the Markov model manually.** + - Es: **Vuelve a entrenar el modelo de Markov manualmente.** + - Fi: **Uudelleenkouluttaa markov-mallin manuaalisesti.** + - Fr: **Réentraîne manuellement le modèle Markov.** + - It: **Rafforza manualmente il modello Markov.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Retrains the Markov model manually." + if lang == 'es': return "Vuelve a entrenar el modelo de Markov manualmente." + if lang == 'fi': return "Uudelleenkouluttaa markov-mallin manuaalisesti." + if lang == 'fr': return "Réentraîne manuellement le modèle Markov." + if lang == 'it': return "Rafforza manualmente il modello Markov." + else: raise ValueError(f'Invalid language {lang}') +def command_markov_retrain(lang:str|None=None): + """ + ### Locales + - En: **Retraining the Markov model... Please wait.** + - Es: **Reentrenando el modelo de Markov... Por favor espere** + - Fi: **Uudelleenkoulutetaan markov-mallia... Odota.** + - Fr: **Réentraînement du modèle Markov... Veuillez patienter.** + - It: **Rafforzamento del modello Markov in corso... Attendere.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Retraining the Markov model... Please wait." + if lang == 'es': return "Reentrenando el modelo de Markov... Por favor espere" + if lang == 'fi': return "Uudelleenkoulutetaan markov-mallia... Odota." + if lang == 'fr': return "Réentraînement du modèle Markov... Veuillez patienter." + if lang == 'it': return "Rafforzamento del modello Markov in corso... Attendere." + else: raise ValueError(f'Invalid language {lang}') +def command_markov_memory_not_found(lang:str|None=None): + """ + ### Locales + - En: **Error: memory file not found!** + - Es: **Error: no hay archivo de memoria!** + - Fi: **Virhe: muistitiedostoa ei löytynyt!** + - Fr: **Erreur : fichier de mémoire introuvable !** + - It: **Errore: file di memoria non trovato!** + """ + if not lang: lang=default_lang + if lang == 'en': return "Error: memory file not found!" + if lang == 'es': return "Error: no hay archivo de memoria!" + if lang == 'fi': return "Virhe: muistitiedostoa ei löytynyt!" + if lang == 'fr': return "Erreur : fichier de mémoire introuvable !" + if lang == 'it': return "Errore: file di memoria non trovato!" + else: raise ValueError(f'Invalid language {lang}') +def command_markov_memory_is_corrupt(lang:str|None=None): + """ + ### Locales + - En: **Error: memory file is corrupt!** + - Es: **Error: el archivo de memoria esta danado!** + - Fi: **Virhe: muistitiedosto on korruptoitu!** + - Fr: **Erreur : le fichier de mémoire est corrompu !** + - It: **Errore: file di memoria corrotto!** + """ + if not lang: lang=default_lang + if lang == 'en': return "Error: memory file is corrupt!" + if lang == 'es': return "Error: el archivo de memoria esta danado!" + if lang == 'fi': return "Virhe: muistitiedosto on korruptoitu!" + if lang == 'fr': return "Erreur : le fichier de mémoire est corrompu !" + if lang == 'it': return "Errore: file di memoria corrotto!" + else: raise ValueError(f'Invalid language {lang}') +def command_markov_retraining(data_size,lang:str|None=None): + """ + ### Locales + - En: **Processing {data_size} data points...** + - Es: **Procesando {processed_data}/{data_size} puntos de datos...** + - Fi: **Käsitellään {processed_data}/{data_size} datapistettä...** + - Fr: **Traitement de {processed_data}/{data_size} points de données...** + - It: **Elaborazione di {data_size} punti dati...** + """ + if not lang: lang=default_lang + if lang == 'en': return "Processing {data_size} data points...".format_map({"data_size": data_size}) + if lang == 'es': return "Procesando {processed_data}/{data_size} puntos de datos...".format_map({"data_size": data_size}) + if lang == 'fi': return "Käsitellään {processed_data}/{data_size} datapistettä...".format_map({"data_size": data_size}) + if lang == 'fr': return "Traitement de {processed_data}/{data_size} points de données...".format_map({"data_size": data_size}) + if lang == 'it': return "Elaborazione di {data_size} punti dati...".format_map({"data_size": data_size}) + else: raise ValueError(f'Invalid language {lang}') +def command_markov_retrain_successful(data_size,lang:str|None=None): + """ + ### Locales + - En: **Markov model retrained successfully using {data_size} data points!** + - Es: **Modelo de Markov reentrenado exitosamente usando {data_size} puntos de datos!** + - Fi: **Markov-malli koulutettiin uudestaan {data_size} datapisteellä!** + - Fr: **Modèle Markov réentraîné avec succès en utilisant {data_size} points de données !** + - It: **Modello Markov rafforzato con successo utilizzando {data_size} punti dati!** + """ + if not lang: lang=default_lang + if lang == 'en': return "Markov model retrained successfully using {data_size} data points!".format_map({"data_size": data_size}) + if lang == 'es': return "Modelo de Markov reentrenado exitosamente usando {data_size} puntos de datos!".format_map({"data_size": data_size}) + if lang == 'fi': return "Markov-malli koulutettiin uudestaan {data_size} datapisteellä!".format_map({"data_size": data_size}) + if lang == 'fr': return "Modèle Markov réentraîné avec succès en utilisant {data_size} points de données !".format_map({"data_size": data_size}) + if lang == 'it': return "Modello Markov rafforzato con successo utilizzando {data_size} punti dati!".format_map({"data_size": data_size}) + else: raise ValueError(f'Invalid language {lang}') +def command_desc_talk(lang:str|None=None): + """ + ### Locales + - En: **talks n like stuf** + - Es: **hace que el bot hable** + - Fi: **puhuu ja sillei** + - Fr: **parle et tout ça** + - It: **parla n come stuf** + """ + if not lang: lang=default_lang + if lang == 'en': return "talks n like stuf" + if lang == 'es': return "hace que el bot hable" + if lang == 'fi': return "puhuu ja sillei" + if lang == 'fr': return "parle et tout ça" + if lang == 'it': return "parla n come stuf" + else: raise ValueError(f'Invalid language {lang}') +def command_talk_insufficent_text(lang:str|None=None): + """ + ### Locales + - En: **I need to learn more from messages before I can talk.** + - Es: **Necesito aprender más sobre los mensajes antes de hablar.** + - Fi: **Minun pitää oppia lisää viesteistä ennen kun puhun.** + - Fr: **Je dois apprendre plus de messages avant de pouvoir parler.** + - It: **Ho bisogno di imparare di più dai messaggi prima di poter parlare.** + """ + if not lang: lang=default_lang + if lang == 'en': return "I need to learn more from messages before I can talk." + if lang == 'es': return "Necesito aprender más sobre los mensajes antes de hablar." + if lang == 'fi': return "Minun pitää oppia lisää viesteistä ennen kun puhun." + if lang == 'fr': return "Je dois apprendre plus de messages avant de pouvoir parler." + if lang == 'it': return "Ho bisogno di imparare di più dai messaggi prima di poter parlare." + else: raise ValueError(f'Invalid language {lang}') +def command_talk_generation_fail(lang:str|None=None): + """ + ### Locales + - En: **I have nothing to say right now!** + - Es: **No tengo nada que decir ahora!** + - Fi: **Minulla ei ole mitään sanottavaa!** + - Fr: **Je n'ai rien à dire pour le moment !** + - It: **Non ho nulla da dire in questo momento!** + """ + if not lang: lang=default_lang + if lang == 'en': return "I have nothing to say right now!" + if lang == 'es': return "No tengo nada que decir ahora!" + if lang == 'fi': return "Minulla ei ole mitään sanottavaa!" + if lang == 'fr': return "Je n'ai rien à dire pour le moment !" + if lang == 'it': return "Non ho nulla da dire in questo momento!" + else: raise ValueError(f'Invalid language {lang}') +def command_desc_help(lang:str|None=None): + """ + ### Locales + - En: **help** + - Es: **Ayuda** + - Fi: **auta** + - Fr: **aide** + - It: **aiuto** + """ + if not lang: lang=default_lang + if lang == 'en': return "help" + if lang == 'es': return "Ayuda" + if lang == 'fi': return "auta" + if lang == 'fr': return "aide" + if lang == 'it': return "aiuto" + else: raise ValueError(f'Invalid language {lang}') +def command_help_embed_title(lang:str|None=None): + """ + ### Locales + - En: **Bot Help** + - Es: **Ayuda del bot** + - Fi: **Botin apu** + - Fr: **Aide du bot** + - It: **Aiuto Bot** + """ + if not lang: lang=default_lang + if lang == 'en': return "Bot Help" + if lang == 'es': return "Ayuda del bot" + if lang == 'fi': return "Botin apu" + if lang == 'fr': return "Aide du bot" + if lang == 'it': return "Aiuto Bot" + else: raise ValueError(f'Invalid language {lang}') +def command_help_embed_desc(lang:str|None=None): + """ + ### Locales + - En: **List of commands grouped by category.** + - Es: **Lista de comandos agrupados por categoria** + - Fi: **Komennot ryhmitelty kategorioilla** + - Fr: **Liste des commandes regroupées par catégorie.** + - It: **Elenco dei comandi raggruppati per categoria.** + """ + if not lang: lang=default_lang + if lang == 'en': return "List of commands grouped by category." + if lang == 'es': return "Lista de comandos agrupados por categoria" + if lang == 'fi': return "Komennot ryhmitelty kategorioilla" + if lang == 'fr': return "Liste des commandes regroupées par catégorie." + if lang == 'it': return "Elenco dei comandi raggruppati per categoria." + else: raise ValueError(f'Invalid language {lang}') +def command_help_categories_general(lang:str|None=None): + """ + ### Locales + - En: **General** + - Es: **General** + - Fi: **Yleiset** + - Fr: **Général** + - It: **Generale** + """ + if not lang: lang=default_lang + if lang == 'en': return "General" + if lang == 'es': return "General" + if lang == 'fi': return "Yleiset" + if lang == 'fr': return "Général" + if lang == 'it': return "Generale" + else: raise ValueError(f'Invalid language {lang}') +def command_help_categories_admin(lang:str|None=None): + """ + ### Locales + - En: **Administration** + - Es: **Administracion** + - Fi: **Ylläpito** + - Fr: **Administration** + - It: **Amministrazione** + """ + if not lang: lang=default_lang + if lang == 'en': return "Administration" + if lang == 'es': return "Administracion" + if lang == 'fi': return "Ylläpito" + if lang == 'fr': return "Administration" + if lang == 'it': return "Amministrazione" + else: raise ValueError(f'Invalid language {lang}') +def command_help_categories_custom(lang:str|None=None): + """ + ### Locales + - En: **Custom Commands** + - Es: **Comandos personalizados** + - Fi: **Mukautetut komennot** + - Fr: **Commandes personnalisées** + - It: **Comandi personalizzati** + """ + if not lang: lang=default_lang + if lang == 'en': return "Custom Commands" + if lang == 'es': return "Comandos personalizados" + if lang == 'fi': return "Mukautetut komennot" + if lang == 'fr': return "Commandes personnalisées" + if lang == 'it': return "Comandi personalizzati" + else: raise ValueError(f'Invalid language {lang}') +def command_ran(message_author_name,message_content,lang:str|None=None): + """ + ### Locales + - En: **Info: {message.author.name} ran {message.content}** + - Es: **Informacion: {message.author.name} ejecuto {message.content}** + - Fi: **Tietoa: {message.author.name} suoritti {message.content}** + - Fr: **Info : {message.author.name} a exécuté {message.content}** + - It: **Info: {message.author.name} ha eseguito {message.content}** + """ + if not lang: lang=default_lang + if lang == 'en': return "Info: {message_author_name} ran {message_content}".format_map({"message_author_name": message_author_name,"message_content": message_content}) + if lang == 'es': return "Informacion: {message_author_name} ejecuto {message_content}".format_map({"message_author_name": message_author_name,"message_content": message_content}) + if lang == 'fi': return "Tietoa: {message_author_name} suoritti {message_content}".format_map({"message_author_name": message_author_name,"message_content": message_content}) + if lang == 'fr': return "Info : {message_author_name} a exécuté {message_content}".format_map({"message_author_name": message_author_name,"message_content": message_content}) + if lang == 'it': return "Info: {message_author_name} ha eseguito {message_content}".format_map({"message_author_name": message_author_name,"message_content": message_content}) + else: raise ValueError(f'Invalid language {lang}') +def command_ran_s(interaction_user,lang:str|None=None): + """ + ### Locales + - En: **Info: {interaction.user} ran ** + - Es: **Info: {interaction.user} ran ** + - Fi: **Info: {interaction.user} suoritti** + - Fr: **Info : {interaction.user} a exécuté ** + - It: **Info: {interaction.user} ha eseguito ** + """ + if not lang: lang=default_lang + if lang == 'en': return "Info: {interaction_user} ran ".format_map({"interaction_user": interaction_user}) + if lang == 'es': return "Info: {interaction_user} ran ".format_map({"interaction_user": interaction_user}) + if lang == 'fi': return "Info: {interaction_user} suoritti".format_map({"interaction_user": interaction_user}) + if lang == 'fr': return "Info : {interaction_user} a exécuté ".format_map({"interaction_user": interaction_user}) + if lang == 'it': return "Info: {interaction_user} ha eseguito ".format_map({"interaction_user": interaction_user}) + else: raise ValueError(f'Invalid language {lang}') +def command_desc_ping(lang:str|None=None): + """ + ### Locales + - En: **ping** + - Es: **ping** + - Fi: **ping** + - Fr: **ping** + - It: **ping** + """ + if not lang: lang=default_lang + if lang == 'en': return "ping" + if lang == 'es': return "ping" + if lang == 'fi': return "ping" + if lang == 'fr': return "ping" + if lang == 'it': return "ping" + else: raise ValueError(f'Invalid language {lang}') +def command_desc_setlang(lang:str|None=None): + """ + ### Locales + - En: **Set a new language for the bot (temporarily)** + - Es: **Set a new language for the bot (temporarily)** + - Fi: **Set a new language for the bot (temporarily)** + - Fr: **Set a new language for the bot (temporarily)** + - It: **Imposta una nuova lingua per il bot (temporaneamente)** + """ + if not lang: lang=default_lang + if lang == 'en': return "Set a new language for the bot (temporarily)" + if lang == 'es': return "Set a new language for the bot (temporarily)" + if lang == 'fi': return "Set a new language for the bot (temporarily)" + if lang == 'fr': return "Set a new language for the bot (temporarily)" + if lang == 'it': return "Imposta una nuova lingua per il bot (temporaneamente)" + else: raise ValueError(f'Invalid language {lang}') +def command_ping_embed_desc(lang:str|None=None): + """ + ### Locales + - En: **Bot Latency:** + - Es: **Latencia del bot:** + - Fi: **Botin viive:** + - Fr: **Latence du bot :** + - It: **Latenza del bot:** + """ + if not lang: lang=default_lang + if lang == 'en': return "Bot Latency:" + if lang == 'es': return "Latencia del bot:" + if lang == 'fi': return "Botin viive:" + if lang == 'fr': return "Latence du bot :" + if lang == 'it': return "Latenza del bot:" + else: raise ValueError(f'Invalid language {lang}') +def command_ping_footer(lang:str|None=None): + """ + ### Locales + - En: **Requested by** + - Es: **Solicitado por** + - Fi: **Pyytäjä: ** + - Fr: **Demandé par** + - It: **Richiesto da** + """ + if not lang: lang=default_lang + if lang == 'en': return "Requested by" + if lang == 'es': return "Solicitado por" + if lang == 'fi': return "Pyytäjä: " + if lang == 'fr': return "Demandé par" + if lang == 'it': return "Richiesto da" + else: raise ValueError(f'Invalid language {lang}') +def command_about_desc(lang:str|None=None): + """ + ### Locales + - En: **about** + - Es: **Acerca** + - Fi: **tietoa** + - Fr: **à propos** + - It: **informazioni** + """ + if not lang: lang=default_lang + if lang == 'en': return "about" + if lang == 'es': return "Acerca" + if lang == 'fi': return "tietoa" + if lang == 'fr': return "à propos" + if lang == 'it': return "informazioni" + else: raise ValueError(f'Invalid language {lang}') +def command_about_embed_title(lang:str|None=None): + """ + ### Locales + - En: **About me** + - Es: **Acerca de mi** + - Fi: **Tietoa minusta** + - Fr: **À propos de moi** + - It: **Informazioni su di me** + """ + if not lang: lang=default_lang + if lang == 'en': return "About me" + if lang == 'es': return "Acerca de mi" + if lang == 'fi': return "Tietoa minusta" + if lang == 'fr': return "À propos de moi" + if lang == 'it': return "Informazioni su di me" + else: raise ValueError(f'Invalid language {lang}') +def command_about_embed_field1(lang:str|None=None): + """ + ### Locales + - En: **Name** + - Es: **Nombre** + - Fi: **Nimi** + - Fr: **Nom** + - It: **Nome** + """ + if not lang: lang=default_lang + if lang == 'en': return "Name" + if lang == 'es': return "Nombre" + if lang == 'fi': return "Nimi" + if lang == 'fr': return "Nom" + if lang == 'it': return "Nome" + else: raise ValueError(f'Invalid language {lang}') +def command_about_embed_field2name(lang:str|None=None): + """ + ### Locales + - En: **Version** + - Es: **Version** + - Fi: **Versio** + - Fr: **Version** + - It: **Versione** + """ + if not lang: lang=default_lang + if lang == 'en': return "Version" + if lang == 'es': return "Version" + if lang == 'fi': return "Versio" + if lang == 'fr': return "Version" + if lang == 'it': return "Versione" + else: raise ValueError(f'Invalid language {lang}') +def command_about_embed_field2value(local_version,latest_version,lang:str|None=None): + """ + ### Locales + - En: **Local: {local_version} +Latest: {latest_version}** + - Es: **Version local: {local_version} +Ultima version: {latest_version}** + - Fi: **Paikallinen: {local_version} +Uusin: {latest_version}** + - Fr: **Locale : {local_version} +Dernière : {latest_version}** + - It: **Locale: {local_version} +Ultima: {latest_version}** + """ + if not lang: lang=default_lang + if lang == 'en': return "Local: {local_version} \nLatest: {latest_version}".format_map({"local_version": local_version,"latest_version": latest_version}) + if lang == 'es': return "Version local: {local_version} \nUltima version: {latest_version}".format_map({"local_version": local_version,"latest_version": latest_version}) + if lang == 'fi': return "Paikallinen: {local_version} \nUusin: {latest_version}".format_map({"local_version": local_version,"latest_version": latest_version}) + if lang == 'fr': return "Locale : {local_version} \nDernière : {latest_version}".format_map({"local_version": local_version,"latest_version": latest_version}) + if lang == 'it': return "Locale: {local_version} \nUltima: {latest_version}".format_map({"local_version": local_version,"latest_version": latest_version}) + else: raise ValueError(f'Invalid language {lang}') +def command_desc_stats(lang:str|None=None): + """ + ### Locales + - En: **stats** + - Es: **Estadistica** + - Fi: **statistiikat** + - Fr: **statistiques** + - It: **statistiche** + """ + if not lang: lang=default_lang + if lang == 'en': return "stats" + if lang == 'es': return "Estadistica" + if lang == 'fi': return "statistiikat" + if lang == 'fr': return "statistiques" + if lang == 'it': return "statistiche" + else: raise ValueError(f'Invalid language {lang}') +def command_stats_embed_title(lang:str|None=None): + """ + ### Locales + - En: **Bot stats** + - Es: **Estadisticas de bot** + - Fi: **Botin statistiikat** + - Fr: **Statistiques du bot** + - It: **Statistiche del bot** + """ + if not lang: lang=default_lang + if lang == 'en': return "Bot stats" + if lang == 'es': return "Estadisticas de bot" + if lang == 'fi': return "Botin statistiikat" + if lang == 'fr': return "Statistiques du bot" + if lang == 'it': return "Statistiche del bot" + else: raise ValueError(f'Invalid language {lang}') +def command_stats_embed_desc(lang:str|None=None): + """ + ### Locales + - En: **Data about the the bot's memory.** + - Es: **Datos sobre la memoria del bot** + - Fi: **Tietoa botin muistista.** + - Fr: **Données sur la mémoire du bot.** + - It: **Dati sulla memoria del bot.** + """ + if not lang: lang=default_lang + if lang == 'en': return "Data about the the bot's memory." + if lang == 'es': return "Datos sobre la memoria del bot" + if lang == 'fi': return "Tietoa botin muistista." + if lang == 'fr': return "Données sur la mémoire du bot." + if lang == 'it': return "Dati sulla memoria del bot." + else: raise ValueError(f'Invalid language {lang}') +def command_stats_embed_field1name(lang:str|None=None): + """ + ### Locales + - En: **File Stats** + - Es: **Estadisticas** + - Fi: **Tiedostostatistiikat** + - Fr: **Statistiques du fichier** + - It: **Statistiche del file** + """ + if not lang: lang=default_lang + if lang == 'en': return "File Stats" + if lang == 'es': return "Estadisticas" + if lang == 'fi': return "Tiedostostatistiikat" + if lang == 'fr': return "Statistiques du fichier" + if lang == 'it': return "Statistiche del file" + else: raise ValueError(f'Invalid language {lang}') +def command_stats_embed_field1value(file_size,line_count,lang:str|None=None): + """ + ### Locales + - En: **Size: {file_size} bytes +Lines: {line_count}** + - Es: **Tamano: {file_size} bytes +Lineas: {line_count}** + - Fi: **Koko: {file_size} tavua +Linjoja: {line_count}** + - Fr: **Taille : {file_size} octets +Lignes : {line_count}** + - It: **Dimensione: {file_size} byte +Linee: {line_count}** + """ + if not lang: lang=default_lang + if lang == 'en': return "Size: {file_size} bytes\nLines: {line_count}".format_map({"file_size": file_size,"line_count": line_count}) + if lang == 'es': return "Tamano: {file_size} bytes\nLineas: {line_count}".format_map({"file_size": file_size,"line_count": line_count}) + if lang == 'fi': return "Koko: {file_size} tavua\nLinjoja: {line_count}".format_map({"file_size": file_size,"line_count": line_count}) + if lang == 'fr': return "Taille : {file_size} octets\nLignes : {line_count}".format_map({"file_size": file_size,"line_count": line_count}) + if lang == 'it': return "Dimensione: {file_size} byte\nLinee: {line_count}".format_map({"file_size": file_size,"line_count": line_count}) + else: raise ValueError(f'Invalid language {lang}') +def command_stats_embed_field2name(lang:str|None=None): + """ + ### Locales + - En: **Version** + - Es: **Version** + - Fi: **Versio** + - Fr: **Version** + - It: **Versione** + """ + if not lang: lang=default_lang + if lang == 'en': return "Version" + if lang == 'es': return "Version" + if lang == 'fi': return "Versio" + if lang == 'fr': return "Version" + if lang == 'it': return "Versione" + else: raise ValueError(f'Invalid language {lang}') +def command_stats_embed_field2value(local_version,latest_version,lang:str|None=None): + """ + ### Locales + - En: **Local: {local_version} +Latest: {latest_version}** + - Es: **Version local: {local_version} +Ultima version: {latest_version}** + - Fi: **Paikallinen: {local_version} +Uusin: {latest_version}** + - Fr: **Locale : {local_version} +Dernière : {latest_version}** + - It: **Locale: {local_version} +Ultima: {latest_version}** + """ + if not lang: lang=default_lang + if lang == 'en': return "Local: {local_version} \nLatest: {latest_version}".format_map({"local_version": local_version,"latest_version": latest_version}) + if lang == 'es': return "Version local: {local_version} \nUltima version: {latest_version}".format_map({"local_version": local_version,"latest_version": latest_version}) + if lang == 'fi': return "Paikallinen: {local_version} \nUusin: {latest_version}".format_map({"local_version": local_version,"latest_version": latest_version}) + if lang == 'fr': return "Locale : {local_version} \nDernière : {latest_version}".format_map({"local_version": local_version,"latest_version": latest_version}) + if lang == 'it': return "Locale: {local_version} \nUltima: {latest_version}".format_map({"local_version": local_version,"latest_version": latest_version}) + else: raise ValueError(f'Invalid language {lang}') +def command_stats_embed_field3name(lang:str|None=None): + """ + ### Locales + - En: **Variable Info** + - Es: **informacion sobre las variables** + - Fi: **Muuttajainformaatio** + - Fr: **Informations variables** + - It: **Informazioni sulle variabili** + """ + if not lang: lang=default_lang + if lang == 'en': return "Variable Info" + if lang == 'es': return "informacion sobre las variables" + if lang == 'fi': return "Muuttajainformaatio" + if lang == 'fr': return "Informations variables" + if lang == 'it': return "Informazioni sulle variabili" + else: raise ValueError(f'Invalid language {lang}') +def command_stats_embed_field3value(NAME,PREFIX,ownerid,PING_LINE,showmemenabled,USERTRAIN_ENABLED,song,splashtext,lang:str|None=None): + """ + ### Locales + - En: **Name: {NAME} +Prefix: {PREFIX} +Owner ID: {ownerid} +Ping line: {PING_LINE} +Memory Sharing Enabled: {showmemenabled} +User Training Enabled: {USERTRAIN_ENABLED} +Song: {song} +Splashtext: ```{splashtext}```** + - Es: **Nombre: {NAME} +Prefijo: {PREFIX} +ID del propietario: {ownerid} +Linea de ping: {PING_LINE} +Compartir memoria habilitada: {showmemenabled} +Entrenamiento de usuario habilitado: {USERTRAIN_ENABLED} +Cancion: {song} +Texto de bienvenida: ```{splashtext}```** + - Fi: **Nimi: {NAME} +Etuliite: {PREFIX} +Omistajan ID: {ownerid} +Ping-linja: {PING_LINE} +Muistin jako päällä: {showmemenabled} +Oppiminen käyttäjistä: {USERTRAIN_ENABLED} +Laulu: {song} +Roisketeksti: ```{splashtext}```** + - Fr: **Nom : {NAME} +Préfixe : {PREFIX} +ID du propriétaire : {ownerid} +Ligne de ping : {PING_LINE} +Partage de mémoire activé : {showmemenabled} +Entraînement utilisateur activé : {USERTRAIN_ENABLED} +Chanson : {song} +Texte de démarrage : ```{splashtext}```** + - It: **Nome: {NAME} +Prefisso: {PREFIX} +ID Proprietario: {ownerid} +Linea ping: {PING_LINE} +Memoria Condivisa Abilitata: {showmemenabled} +Addestramento Utente Abilitato: {USERTRAIN_ENABLED} +Canzone: {song} +Splashtext: ```{splashtext}```** + """ + if not lang: lang=default_lang + if lang == 'en': return "Name: {NAME} \nPrefix: {PREFIX} \nOwner ID: {ownerid}\nPing line: {PING_LINE} \nMemory Sharing Enabled: {showmemenabled} \nUser Training Enabled: {USERTRAIN_ENABLED}\nSong: {song} \nSplashtext: ```{splashtext}```".format_map({"NAME": NAME,"PREFIX": PREFIX,"ownerid": ownerid,"PING_LINE": PING_LINE,"showmemenabled": showmemenabled,"USERTRAIN_ENABLED": USERTRAIN_ENABLED,"song": song,"splashtext": splashtext}) + if lang == 'es': return "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}```".format_map({"NAME": NAME,"PREFIX": PREFIX,"ownerid": ownerid,"PING_LINE": PING_LINE,"showmemenabled": showmemenabled,"USERTRAIN_ENABLED": USERTRAIN_ENABLED,"song": song,"splashtext": splashtext}) + if lang == 'fi': return "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}```".format_map({"NAME": NAME,"PREFIX": PREFIX,"ownerid": ownerid,"PING_LINE": PING_LINE,"showmemenabled": showmemenabled,"USERTRAIN_ENABLED": USERTRAIN_ENABLED,"song": song,"splashtext": splashtext}) + if lang == 'fr': return "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}```".format_map({"NAME": NAME,"PREFIX": PREFIX,"ownerid": ownerid,"PING_LINE": PING_LINE,"showmemenabled": showmemenabled,"USERTRAIN_ENABLED": USERTRAIN_ENABLED,"song": song,"splashtext": splashtext}) + if lang == 'it': return "Nome: {NAME} \nPrefisso: {PREFIX} \nID Proprietario: {ownerid}\nLinea ping: {PING_LINE} \nMemoria Condivisa Abilitata: {showmemenabled} \nAddestramento Utente Abilitato: {USERTRAIN_ENABLED}\nCanzone: {song} \nSplashtext: ```{splashtext}```".format_map({"NAME": NAME,"PREFIX": PREFIX,"ownerid": ownerid,"PING_LINE": PING_LINE,"showmemenabled": showmemenabled,"USERTRAIN_ENABLED": USERTRAIN_ENABLED,"song": song,"splashtext": splashtext}) + else: raise ValueError(f'Invalid language {lang}') +def no_image_available(lang:str|None=None): + """ + ### Locales + - En: **No images available!** + - Es: **No images available!** + - Fi: **No images available!** + - Fr: **No images available!** + - It: **No images available!** + """ + if not lang: lang=default_lang + if lang == 'en': return "No images available!" + if lang == 'es': return "No images available!" + if lang == 'fi': return "No images available!" + if lang == 'fr': return "No images available!" + if lang == 'it': return "No images available!" + else: raise ValueError(f'Invalid language {lang}') +def failed_generate_image(lang:str|None=None): + """ + ### Locales + - En: **Failed to generate an image** + - Es: **Failed to generate an image** + - Fi: **Failed to generate an image** + - Fr: **Failed to generate an image** + - It: **Failed to generate an image** + """ + if not lang: lang=default_lang + if lang == 'en': return "Failed to generate an image" + if lang == 'es': return "Failed to generate an image" + if lang == 'fi': return "Failed to generate an image" + if lang == 'fr': return "Failed to generate an image" + if lang == 'it': return "Failed to generate an image" + else: raise ValueError(f'Invalid language {lang}') +def markov_model_not_found(lang:str|None=None): + """ + ### Locales + - En: **Markov model not found!** + - Es: **Markov model not found!** + - Fi: **Markov model not found!** + - Fr: **Markov model not found!** + - It: **Markov model not found!** + """ + if not lang: lang=default_lang + if lang == 'en': return "Markov model not found!" + if lang == 'es': return "Markov model not found!" + if lang == 'fi': return "Markov model not found!" + if lang == 'fr': return "Markov model not found!" + if lang == 'it': return "Markov model not found!" + else: raise ValueError(f'Invalid language {lang}') +def blacklisted(lang:str|None=None): + """ + ### Locales + - En: **blacklisted** + - Es: **blacklisted** + - Fi: **blacklisted** + - Fr: **blacklisted** + - It: **blacklisted** + """ + if not lang: lang=default_lang + if lang == 'en': return "blacklisted" + if lang == 'es': return "blacklisted" + if lang == 'fi': return "blacklisted" + if lang == 'fr': return "blacklisted" + if lang == 'it': return "blacklisted" + else: raise ValueError(f'Invalid language {lang}') +def blacklisted_user(lang:str|None=None): + """ + ### Locales + - En: **Blacklisted user** + - Es: **Blacklisted user** + - Fi: **Blacklisted user** + - Fr: **Blacklisted user** + - It: **Blacklisted user** + """ + if not lang: lang=default_lang + if lang == 'en': return "Blacklisted user" + if lang == 'es': return "Blacklisted user" + if lang == 'fi': return "Blacklisted user" + if lang == 'fr': return "Blacklisted user" + if lang == 'it': return "Blacklisted user" + else: raise ValueError(f'Invalid language {lang}') +def edit_fail(lang:str|None=None): + """ + ### Locales + - En: **Failed to edit message** + - Es: **Failed to edit message** + - Fi: **Failed to edit message** + - Fr: **Failed to edit message** + - It: **Failed to edit message** + """ + if not lang: lang=default_lang + if lang == 'en': return "Failed to edit message" + if lang == 'es': return "Failed to edit message" + if lang == 'fi': return "Failed to edit message" + if lang == 'fr': return "Failed to edit message" + if lang == 'it': return "Failed to edit message" + else: raise ValueError(f'Invalid language {lang}') +def system_info(lang:str|None=None): + """ + ### Locales + - En: **System information** + - Es: **System information** + - Fi: **System information** + - Fr: **System information** + - It: **System information** + """ + if not lang: lang=default_lang + if lang == 'en': return "System information" + if lang == 'es': return "System information" + if lang == 'fi': return "System information" + if lang == 'fr': return "System information" + if lang == 'it': return "System information" + else: raise ValueError(f'Invalid language {lang}') +def cpu_info(cpu,lang:str|None=None): + """ + ### Locales + - En: **CPU: {cpu}** + - Es: **CPU: {cpu}** + - Fi: **CPU: {cpu}** + - Fr: **CPU: {cpu}** + - It: **CPU: {cpu}** + """ + if not lang: lang=default_lang + if lang == 'en': return "CPU: {cpu}".format_map({"cpu": cpu}) + if lang == 'es': return "CPU: {cpu}".format_map({"cpu": cpu}) + if lang == 'fi': return "CPU: {cpu}".format_map({"cpu": cpu}) + if lang == 'fr': return "CPU: {cpu}".format_map({"cpu": cpu}) + if lang == 'it': return "CPU: {cpu}".format_map({"cpu": cpu}) + else: raise ValueError(f'Invalid language {lang}') diff --git a/modules/logger.py b/modules/logger.py index 79d9029..3b9d361 100644 --- a/modules/logger.py +++ b/modules/logger.py @@ -1,30 +1,28 @@ import logging -import re from modules.globalvars import * + class GooberFormatter(logging.Formatter): - def __init__(self, colors: bool = True): # Disable colors for TXT output + 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.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 + logging.CRITICAL: PURPLE + self._format + RESET, } def format(self, record: logging.LogRecord): - ansiescape = re.compile(r'\x1B[@-_][0-?]*[ -/]*[@-~]') if self.colors: - log_fmt = self.FORMATS.get(record.levelno) # Add colors + log_fmt = self.FORMATS.get(record.levelno) # Add colors else: - log_fmt = self._format # Just use the default format - + log_fmt = self._format # Just use the default format + formatter = logging.Formatter(log_fmt, datefmt="%m/%d/%y %H:%M:%S") - formatted = formatter.format(record) - if not self.colors: - formatted = ansiescape.sub('', formatted) - return formatted + return formatter.format(record) diff --git a/modules/markovmemory.py b/modules/markovmemory.py index d7ce6cd..cce5cf2 100644 --- a/modules/markovmemory.py +++ b/modules/markovmemory.py @@ -4,7 +4,7 @@ import markovify import pickle from modules.globalvars import * import logging -from modules.volta.main import _ +import modules.keys as k from modules.settings import instance as settings_manager settings = settings_manager.settings @@ -61,17 +61,21 @@ def train_markov_model(memory, additional_data=None) -> markovify.NewlineText | model = markovify.NewlineText(text, state_size=2) return model + +# Save the Markov model to a pickle file def save_markov_model(model, filename="markov_model.pkl"): with open(filename, "wb") as f: pickle.dump(model, f) logger.info(f"Markov model saved to {filename}.") + +# 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) - logger.info(f"{_('model_loaded')} {filename}.{RESET}") + logger.info(f"{k.model_loaded()} {filename}.{RESET}") return model except FileNotFoundError: - logger.error(f"{filename} {_('not_found')}{RESET}") - return None \ No newline at end of file + logger.error(f"{filename} {k.not_found()}{RESET}") + return None diff --git a/modules/permission.py b/modules/permission.py index 5434c61..86b1dd4 100644 --- a/modules/permission.py +++ b/modules/permission.py @@ -4,12 +4,11 @@ import discord import discord.ext import discord.ext.commands -from modules.settings import Settings as SettingsManager +from modules.settings import instance as settings_manager import logging logger = logging.getLogger("goober") -settings_manager = SettingsManager() settings = settings_manager.settings @@ -19,11 +18,11 @@ class PermissionError(Exception): def requires_admin(): async def wrapper(ctx: discord.ext.commands.Context): - print(ctx.author.id) if ctx.author.id not in settings["bot"]["owner_ids"]: - await ctx.send("You don't have the necessary permissions to run this command!") - return - + await ctx.send( + "You don't have the necessary permissions to run this command!" + ) + return False command = ctx.command if not command: @@ -34,4 +33,4 @@ def requires_admin(): ) return True - return discord.ext.commands.check(wrapper) \ No newline at end of file + return discord.ext.commands.check(wrapper) diff --git a/modules/prestartchecks.py b/modules/prestartchecks.py index cd9fd72..f4f4898 100644 --- a/modules/prestartchecks.py +++ b/modules/prestartchecks.py @@ -1,6 +1,4 @@ from modules.globalvars import * -from modules.settings import Settings as SettingsManager -from modules.volta.main import _, check_missing_translations import time import os import sys @@ -12,16 +10,15 @@ import re from spacy.util import is_package import importlib.metadata import logging +import modules.keys as k +from modules.settings import instance as settings_manager +from modules.sync_conenctor import instance as sync_hub + +settings = settings_manager.settings + logger = logging.getLogger("goober") -settings_manager = SettingsManager() -settings = settings_manager.settings -MEMORY_FILE = settings["bot"]["active_memory"] -with open(settings["splash_text_loc"], "r", encoding="UTF-8") as f: - splash_text = "".join(f.readlines()) - - # import shutil psutilavaliable = True try: @@ -29,165 +26,213 @@ try: import psutil except ImportError: psutilavaliable = False - logger.error(_('missing_requests_psutil')) + logger.error(k.missing_requests_psutil()) + def check_for_model(): if is_package("en_core_web_sm"): logger.info("Model is installed.") else: logger.info("Model is not installed.") - + + def iscloned(): if os.path.exists(".git"): return True else: - logger.error(f"{_('not_cloned')}") + logger.error(f"{k.not_cloned()}") sys.exit(1) -def get_stdlib_modules(): - stdlib = pathlib.Path(sysconfig.get_paths()['stdlib']) - modules = set(sys.builtin_module_names) - modules.update( - f.stem for f in stdlib.glob('*.py') if f.stem != '__init__' - ) - modules.update( - d.name for d in stdlib.iterdir() if (d / '__init__.py').exists() - ) - modules.update( - f.stem for f in stdlib.glob('*') if f.suffix in ('.so', '.pyd') - ) +def get_stdlib_modules(): + stdlib_path = pathlib.Path(sysconfig.get_paths()["stdlib"]) + modules = set() + if hasattr(sys, "builtin_module_names"): + modules.update(sys.builtin_module_names) + for file in stdlib_path.glob("*.py"): + if file.stem != "__init__": + modules.add(file.stem) + for folder in stdlib_path.iterdir(): + if folder.is_dir() and (folder / "__init__.py").exists(): + modules.add(folder.name) + for file in stdlib_path.glob("*.*"): + if file.suffix in (".so", ".pyd"): + modules.add(file.stem) return modules + def check_requirements(): - stdlib = get_stdlib_modules() - aliases = { + STD_LIB_MODULES = get_stdlib_modules() + PACKAGE_ALIASES = { "discord": "discord.py", "better_profanity": "better-profanity", "dotenv": "python-dotenv", - "pil": "pillow" + "pil": "pillow", + "websocket": "websocket-client" } - req_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'requirements.txt')) - if not os.path.exists(req_path): - logger.error(_('requirements_not_found').format(path=req_path)) + parent_dir = os.path.dirname(os.path.abspath(__file__)) + requirements_path = os.path.abspath( + os.path.join(parent_dir, "..", "requirements.txt") + ) + + if not os.path.exists(requirements_path): + logger.error(f"{k.requirements_not_found(path=requirements_path)}") return - with open(req_path) as f: - requirements = { - aliases.get(line.split('==')[0].strip().lower(), line.split('==')[0].strip().lower()) - for line in f if line.strip() and not line.startswith('#') - } + with open(requirements_path, "r") as f: + lines = f.readlines() + 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 = {d.metadata['Name'].lower() for d in importlib.metadata.distributions()} + installed_packages = { + dist.metadata["Name"].lower() for dist in importlib.metadata.distributions() + } missing = [] - for pkg in sorted(requirements): - if pkg in stdlib or pkg == 'modules': - print(_('std_lib_local_skipped').format(package=pkg)) + for req in sorted(requirements): + if req in STD_LIB_MODULES or req == "modules": + print(k.std_lib_local_skipped(package=req)) continue - if pkg in installed: - logger.info(_('ok_installed').format(package=pkg)) + + check_name = req.lower() + + if check_name in installed_packages: + logger.info(f"{k.ok_installed()} {check_name}") else: - logger.error(f"{_('missing_package').format(package=pkg)} {pkg} {_('missing_package2')}") - missing.append(pkg) + logger.error(f"{k.missing_package()} {check_name} {k.missing_package2()}") + missing.append(check_name) if missing: - logger.error(_('missing_packages_detected')) + logger.error(k.missing_packages_detected()) for pkg in missing: print(f" - {pkg}") sys.exit(1) - - logger.info(_('all_requirements_satisfied')) + else: + logger.info(k.all_requirements_satisfied()) def check_latency(): host = "1.1.1.1" system = platform.system() - cmd, pattern = { - "Windows": (["ping", "-n", "1", "-w", "1000", host], r"Average = (\d+)ms"), - "Darwin": (["ping", "-c", "1", host], r"time=([\d\.]+) ms") - }.get(system, (["ping", "-c", "1", "-W", "1", host], r"time=([\d\.]+) ms")) + 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=([\d\.]+) ms" try: - result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - if result.returncode != 0: + result = subprocess.run( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + ) + + if result.returncode == 0: + match = re.search(latency_pattern, result.stdout) + if match: + latency_ms = float(match.group(1)) + logger.info(k.ping_to(host=host, latency=latency_ms)) + if latency_ms > 300: + logger.warning(f"{k.high_latency()}") + else: + logger.warning(k.could_not_parse_latency()) + else: print(result.stderr) - return logger.error(_('ping_failed').format(host=host) + RESET) - - match = re.search(pattern, result.stdout) - if not match: - return logger.warning(_('could_not_parse_latency')) - - latency = float(match.group(1)) - logger.info(_('ping_to').format(host=host, latency=latency)) - if latency > 300: - logger.warning(_('high_latency')) - + logger.error(f"{k.ping_failed(host=host)}{RESET}") except Exception as e: - logger.error(_('error_running_ping').format(error=e)) + logger.error(k.error_running_ping(error=e)) + def check_memory(): - if not psutilavaliable: + if psutilavaliable == False: return - try: - mem = psutil.virtual_memory() # type: ignore - total = mem.total / 1e9 - used = mem.used / 1e9 - free = mem.available / 1e9 - percent_used = (used / total) * 100 + 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) - logger.info(_('memory_usage').format(used=used, total=total, percent=percent_used)) - if percent_used > 90: - print(f"{YELLOW}{_('memory_above_90').format(percent=percent_used)}{RESET}") - logger.info(_('total_memory').format(total=total)) - logger.info(_('used_memory').format(used=used)) - if free < 1: - logger.warning(_('low_free_memory').format(free=free)) - sys.exit(1) + logger.info( + k.memory_usage( + used=used_memory, + total=total_memory, + percent=(used_memory / total_memory) * 100, + ) + ) + if used_memory > total_memory * 0.9: + print( + f"{YELLOW}{k.memory_above_90(percent=(used_memory / total_memory) * 100)}{RESET}" + ) + logger.info(k.total_memory(total=total_memory)) + logger.info(k.used_memory(used=used_memory)) + if free_memory < 1: + logger.warning(f"{k.low_free_memory(free=free_memory)}") except ImportError: - logger.error(_('psutil_not_installed')) + logger.error( + k.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 - logger.info((_('measuring_cpu'))) + logger.info(k.measuring_cpu()) cpu_per_core = psutil.cpu_percent(interval=1, percpu=True) # type: ignore total_cpu = sum(cpu_per_core) / len(cpu_per_core) - logger.info((_('total_cpu_usage')).format(usage=total_cpu)) + logger.info(k.total_cpu_usage(usage=total_cpu)) + if total_cpu > 85: - logger.warning(f"{(_('high_avg_cpu')).format(usage=total_cpu)}") + logger.warning(f"{k.high_avg_cpu(usage=total_cpu)}") + if total_cpu > 95: - logger.error(_('really_high_cpu')) + logger.error(k.really_high_cpu()) sys.exit(1) + def check_memoryjson(): try: - size_mb = os.path.getsize(MEMORY_FILE) / (1024 ** 2) - logger.info(_('memory_file').format(size=size_mb)) - if size_mb > 1024: - logger.warning(_('memory_file_large')) - + logger.info( + k.memory_file( + size=os.path.getsize(settings["bot"]["active_memory"]) / (1024**2) + ) + ) + if os.path.getsize(settings["bot"]["active_memory"]) > 1_073_741_824: + logger.warning(f"{k.memory_file_large()}") try: - with open(MEMORY_FILE, 'r', encoding='utf-8') as f: + with open(settings["bot"]["active_memory"], "r", encoding="utf-8") as f: json.load(f) - except (json.JSONDecodeError, UnicodeDecodeError) as e: - msg = _('memory_file_corrupted') if isinstance(e, json.JSONDecodeError) else _('memory_file_encoding') - logger.error(msg.format(error=e)) - logger.warning(_('consider_backup_memory')) - except Exception as e: - logger.error(_('error_reading_memory').format(error=e)) + except json.JSONDecodeError as e: + logger.error(f"{k.memory_file_corrupted(error=e)}") + logger.warning(f"{k.consider_backup_memory()}") + + except UnicodeDecodeError as e: + logger.error(f"{k.memory_file_encoding(error=e)}") + logger.warning(f"{k.consider_backup_memory()}") + + except Exception as e: + logger.error(f"{k.error_reading_memory(error=e)}") except FileNotFoundError: - logger.error(_('memory_file_not_found')) + logger.info(f"{k.memory_file_not_found()}") + def presskey2skip(timeout): - if os.name == 'nt': + if os.name == "nt": import msvcrt + start_time = time.time() while True: if msvcrt.kbhit(): @@ -216,29 +261,44 @@ def presskey2skip(timeout): time.sleep(0.1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + +def check_synchub(): + if not sync_hub.connected: + logger.warning("Sync hub not connected properly! The bot will not be able to react to messages, or create breaking news unless you disable synchub in settings") + else: + logger.info("Sync hub is conencted") + beta = beta + + def start_checks(): if settings["disable_checks"]: - logger.warning(f"{_('checks_disabled')}") + logger.warning(f"{k.checks_disabled()}") return - logger.info(_('running_prestart_checks')) + + logger.info(k.running_prestart_checks()) check_for_model() iscloned() - check_missing_translations() check_requirements() check_latency() check_memory() check_memoryjson() check_cpu() + check_synchub() if os.path.exists(".env"): pass else: - logger.warning(f"{(_('env_file_not_found'))}") + logger.warning(f"{k.env_file_not_found()}") sys.exit(1) if beta == True: - logger.warning(f"this build isnt finished yet, some things might not work as expected") + logger.warning( + f"this build isnt finished yet, some things might not work as expected" + ) else: pass - logger.info(_('continuing_in_seconds').format(seconds=5)) + logger.info(k.continuing_in_seconds(seconds=5)) presskey2skip(timeout=5) - os.system('cls' if os.name == 'nt' else 'clear') \ No newline at end of file + os.system("cls" if os.name == "nt" else "clear") + + with open(settings["splash_text_loc"], "r") as f: + print("".join(f.readlines())) diff --git a/modules/sentenceprocessing.py b/modules/sentenceprocessing.py index f7a933a..839c36a 100644 --- a/modules/sentenceprocessing.py +++ b/modules/sentenceprocessing.py @@ -1,12 +1,15 @@ import re +import discord.ext +import discord.ext.commands from modules.globalvars import * -from modules.volta.main import _ - import spacy from spacy.tokens import Doc from spacytextblob.spacytextblob import SpacyTextBlob +import discord +import modules.keys as k import logging + logger = logging.getLogger("goober") @@ -14,12 +17,13 @@ def check_resources(): try: nlp = spacy.load("en_core_web_sm") except OSError: - logging.critical((_('spacy_model_not_found'))) - spacy.cli.download("en_core_web_sm") + logging.critical(k.spacy_model_not_found()) + spacy.cli.download("en_core_web_sm") # type: ignore nlp = spacy.load("en_core_web_sm") if "spacytextblob" not in nlp.pipe_names: nlp.add_pipe("spacytextblob") - logger.info((_('spacy_initialized'))) + logger.info(k.spacy_initialized()) + check_resources() @@ -27,47 +31,65 @@ 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 - debug_message = f"{(_('sentence_positivity'))} {sentiment_score}{RESET}" + debug_message = f"{k.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 + 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 -async def send_message(ctx, message=None, embed=None, file=None, edit=False, message_reference=None): - try: - if edit and message_reference: + +async def send_message( + ctx: discord.ext.commands.Context, + message: str | None = None, + embed: discord.Embed | None = None, + file: discord.File | None = None, + edit: bool = False, + message_reference: discord.Message | None = None, +) -> discord.Message | None: + + sent_message: discord.Message | None = None + + if edit and message_reference: + try: await message_reference.edit(content=message, embed=embed) return message_reference + except Exception as e: + await ctx.send(f"{k.edit_fail()} {e}") + return None - send_kwargs = {} - if message: - send_kwargs['content'] = message - if embed: - send_kwargs['embed'] = embed - if file: - send_kwargs['file'] = file + if embed: + sent_message = await ctx.send(embed=embed, content=message) + elif file: + sent_message = await ctx.send(file=file, content=message) + else: + sent_message = await ctx.send(content=message) - if hasattr(ctx, "respond"): - return await ctx.respond(**send_kwargs, ephemeral=False) - else: - return await ctx.send(**send_kwargs) + return sent_message - except Exception as e: - await ctx.send(f"{RED}{(_('edit_fail'))} {e}{RESET}") + +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 = message + message = append_mentions_to_18digit_integer(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 "" + return re.sub(r"\bi\b", "I", sentence) + def rephrase_for_coherence(sentence): - coherent_sentence = sentence + words = sentence.split() + coherent_sentence = " ".join(words) return coherent_sentence diff --git a/modules/settings.py b/modules/settings.py index 1dfc377..8943453 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -1,14 +1,30 @@ import json import os -from typing import List, TypedDict +from typing import Dict, List, Literal, Mapping, Any, TypedDict +from modules.keys import Language +import logging import copy +logger = logging.getLogger("goober") + +ActivityType = Literal["listening", "playing", "streaming", "competing", "watching"] + +class SyncHub(TypedDict): + url: str + enabled: bool + +class Activity(TypedDict): + content: str + type: ActivityType + + class MiscBotOptions(TypedDict): ping_line: str - active_song: str + activity: Activity positive_gifs: List[str] block_profanity: bool + class BotSettings(TypedDict): prefix: str owner_ids: List[int] @@ -19,40 +35,121 @@ class BotSettings(TypedDict): misc: MiscBotOptions enabled_cogs: List[str] active_memory: str + sync_hub: SyncHub + class SettingsType(TypedDict): bot: BotSettings - locale: str + locale: Language name: str auto_update: bool disable_checks: bool splash_text_loc: str + cog_settings: Dict[str, Mapping[Any, Any]] + + +class AdminLogEvent(TypedDict): + messageId: int + author: int + target: str | int + action: Literal["del", "add", "set"] + change: Literal["owner_ids", "blacklisted_users", "enabled_cogs"] + class Settings: def __init__(self) -> None: - self.path = os.path.join(".", "settings", "settings.json") + global instance + instance = self + + self.path: str = os.path.join(".", "settings", "settings.json") + if not os.path.exists(self.path): - raise FileNotFoundError("settings.json file does not exist!") + logger.critical( + f"Missing settings file from {self.path}! Did you forget to copy settings.example.json?" + ) + raise ValueError("settings.json file does not exist!") + + self.settings: SettingsType + self.original_settings: SettingsType with open(self.path, "r") as f: - self._kv_store = json.load(f) + self.__kv_store: dict = json.load(f) - self.settings: SettingsType = self._kv_store # type: ignore + self.settings = SettingsType(self.__kv_store) # type: ignore self.original_settings = copy.deepcopy(self.settings) - def get_locale(self) -> str: - # Return locale or None if missing - return self.settings.get("locale", None) + self.log_path: str = os.path.join(".", "settings", "admin_logs.json") + + self.migrate() + + def migrate(self): + active_song: str | None = ( + self.settings.get("bot", {}).get("misc", {}).get("active_song") + ) + + if active_song: + logger.warning("Found deprecated active_song, migrating") + + self.settings["bot"]["misc"]["activity"] = { + "content": active_song, + "type": "listening", + } + + del self.settings["bot"]["misc"]["active_song"] # type: ignore + + sync_hub: SyncHub | None = self.settings.get("bot", {}).get("sync_hub") + + if not sync_hub: + logger.warning("Adding sync hub settings") + self.settings["bot"]["sync_hub"] = { + "enabled": True, + "url": "ws://goober.frii.site" + } + + self.commit() + + def reload_settings(self) -> None: + with open(self.path, "r") as f: + self.__kv_store: dict = json.load(f) + + self.settings = SettingsType(self.__kv_store) # type: ignore + self.original_settings = copy.deepcopy(self.settings) def commit(self) -> None: with open(self.path, "w") as f: - json.dump(self.settings, f, indent=4) - self.original_settings = copy.deepcopy(self.settings) + json.dump(self.settings, f, ensure_ascii=False, indent=4) + + self.original_settings = self.settings def discard(self) -> None: - self.settings = copy.deepcopy(self.original_settings) + self.settings = self.original_settings -# Usage -instance = Settings() -locale = instance.get_locale() -print("Locale:", locale) + def get_plugin_settings( + self, plugin_name: str, default: Mapping[Any, Any] + ) -> Mapping[Any, Any]: + return self.settings["cog_settings"].get(plugin_name, default) + + def set_plugin_setting( + self, plugin_name: str, new_settings: Mapping[Any, Any] + ) -> None: + """Changes a plugin setting. Commits changes""" + self.settings["cog_settings"][plugin_name] = new_settings + + self.commit() + + def add_admin_log_event(self, event: AdminLogEvent): + if not os.path.exists(self.log_path): + logger.warning("Admin log doesn't exist!") + with open(self.log_path, "w") as f: + json.dump([], f) + + with open(self.log_path, "r") as f: + logs: List[AdminLogEvent] = json.load(f) + + logs.append(event) + + with open(self.log_path, "w") as f: + json.dump(logs, f, ensure_ascii=False, indent=4) + + +instance: Settings = Settings() diff --git a/modules/sync_conenctor.py b/modules/sync_conenctor.py new file mode 100644 index 0000000..588c772 --- /dev/null +++ b/modules/sync_conenctor.py @@ -0,0 +1,94 @@ +import websocket +from modules.settings import instance as settings_manager +import logging + + +logger = logging.getLogger("goober") +settings = settings_manager.settings + +class SyncConnector: + def __init__(self, url: str): + self.connected: bool = True + self.url = url + self.client: websocket.WebSocket | None = None + + self.try_to_connect() + + def __connect(self) -> bool: + try: + self.client = websocket.create_connection(self.url) + except OSError as e: + logger.debug(e) + logger.debug(e.strerror) + return False + + return True + + def try_to_connect(self) -> bool: + if self.__connect(): + logger.info("Connected to sync hub!") + self.connected = True + else: + logger.error("Failed to connect to sync hub.. Disabling for the time being") + self.connected = False + + return self.connected + + + def can_react(self, message_id: int) -> bool: + """ + Checks if goober can react to a messsage + """ + + return self.can_event(message_id, "react") + + + def can_breaking_news(self, message_id: int) -> bool: + """ + Checks if goober can send a breaking news alert + """ + + return self.can_event(message_id, "breaking_news") + + + def can_event(self, message_id: int, event: str, retry_depth: int = 0) -> bool: + """ + Checks if goober can send a breaking news alert + """ + + logger.debug(f"Checking {event} for message {message_id}") + + if not settings["bot"]["sync_hub"]["enabled"]: + logger.info("Skipping sync hub check") + return True + + if retry_depth > 2: + logger.error("Too many retries. Returning false") + return False + + if not self.client: + logger.error("Client no connected") + return False + + if not self.connected: + logger.warning("Not connected to sync hub.. Returning False to avoid conflicts") + return False + + try: + self.client.send(f"event={event};ref={message_id}") + return self.client.recv() == "unhandled" + except ConnectionResetError: + logger.error("Connection to sync hub reset! Retrying...") + + if not self.__connect(): + logger.error("Failed to reconnect to sync hub... Disabling") + self.connected = False + return False + + logger.info("Managed to reconnect to sync hub! Retrying requests") + self.connected = True + return self.can_event(message_id, event, retry_depth+1) + + + +instance = SyncConnector(settings["bot"]["sync_hub"]["url"]) \ No newline at end of file diff --git a/modules/unhandledexception.py b/modules/unhandledexception.py index c1dd5f6..1118329 100644 --- a/modules/unhandledexception.py +++ b/modules/unhandledexception.py @@ -1,18 +1,29 @@ import sys import traceback +import os +from modules.settings import instance as settings_manager +import logging from modules.globalvars import RED, RESET -from modules.volta.main import _ +import modules.keys as k + +settings = settings_manager.settings +logger = logging.getLogger("goober") + def handle_exception(exc_type, exc_value, exc_traceback, *, context=None): + os.system("cls" if os.name == "nt" else "clear") + if issubclass(exc_type, KeyboardInterrupt): sys.__excepthook__(exc_type, exc_value, exc_traceback) return + + with open(settings["splash_text_loc"], "r") as f: + print("".join(f.readlines())) + print(f"{RED}=====BEGINNING OF TRACEBACK====={RESET}") traceback.print_exception(exc_type, exc_value, exc_traceback) print(f"{RED}========END OF TRACEBACK========{RESET}") - print(f"{RED}{_('unhandled_exception')}{RESET}") + print(f"{RED}{k.unhandled_exception()}{RESET}") + if context: print(f"{RED}Context: {context}{RESET}") - - - diff --git a/modules/version.py b/modules/version.py deleted file mode 100644 index 8be107f..0000000 --- a/modules/version.py +++ /dev/null @@ -1,105 +0,0 @@ -from modules.volta.main import _ -from modules.globalvars import * -import requests -import subprocess -import sys -import logging -import json -import time -import random -logger = logging.getLogger("goober") -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) - return result.stdout.strip() - -# Check if the remote branch is ahead of the local branch -def is_remote_ahead(branch='main', remote='origin'): - run_cmd(f'git fetch {remote}') - count = run_cmd(f'git rev-list --count HEAD..{remote}/{branch}') - return int(count) > 0 - -def auto_update(branch='main', remote='origin'): - if launched == True: - print(_("already_started")) - return - if AUTOUPDATE != "True": - pass # Auto-update is disabled - if is_remote_ahead(branch, remote): - print(_( "remote_ahead").format(remote=remote, branch=branch)) - pull_result = run_cmd(f'git pull {remote} {branch}') - logger.info(pull_result) - logger.info(_( "please_restart")) - sys.exit(0) - else: - logger.info(_( "local_ahead").format(remote=remote, branch=branch)) - -def get_latest_version_info(): - try: - 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 - - 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 -def check_for_update(slient=False): - global latest_version, local_version, launched - - latest_version_info = get_latest_version_info() - if not latest_version_info: - logger.error(f"{_('fetch_update_fail')}") - return None, None - - latest_version = latest_version_info.get("version") - os.environ['gooberlatest_version'] = latest_version - download_url = latest_version_info.get("download_url") - - 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 slient != True: - if local_version < latest_version: - 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: - 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: - 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 diff --git a/modules/volta/main.py b/modules/volta/main.py deleted file mode 100644 index ec1edad..0000000 --- a/modules/volta/main.py +++ /dev/null @@ -1,258 +0,0 @@ -# If you're seeing this after cloning the Goober repo, note that this is a standalone module for translations. -# While it's used by Goober Core, it lives in its own repository and should not be modified here. -# 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 -import platform -import sys -import time -from dotenv import load_dotenv -from functools import lru_cache - -ANSI = "\033[" -RED = f"{ANSI}31m" -GREEN = f"{ANSI}32m" -YELLOW = f"{ANSI}33m" -DEBUG = f"{ANSI}1;30m" -RESET = f"{ANSI}0m" - -LOCALE = os.getenv("LOCALE") -module_dir = pathlib.Path(__file__).parent.parent -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): - dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS] - - if 'locales' in dirs: - locales_path = pathlib.Path(root) / 'locales' - found.append(locales_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 - -def load_settings_json() -> dict | None: - start_path = working_dir.resolve() - current = start_path.resolve() - while current != current.parent: - candidate = current / "settings.json" - if candidate.exists(): - try: - with open(candidate, "r", encoding="utf-8") as f: - data = json.load(f) - print(f"[VOLTA] {GREEN}Loaded settings.json locale '{data.get('locale')}' from {candidate}{RESET}") - return data - except Exception as e: - print(f"[VOLTA] {RED}Failed to load settings.json at {candidate}: {e}{RESET}") - return None - current = current.parent - - for root, dirs, files in os.walk(start_path): - if "settings.json" in files: - candidate = pathlib.Path(root) / "settings.json" - try: - with open(candidate, "r", encoding="utf-8") as f: - data = json.load(f) - print(f"[VOLTA] {GREEN}Loaded settings.json locale '{data.get('locale')}' from {candidate}{RESET}") - return data - except Exception as e: - print(f"[VOLTA] {RED}Failed to load settings.json at {candidate}: {e}{RESET}") - return None - - print(f"[VOLTA] {YELLOW}No settings.json found scanning up or down from {start_path}{RESET}") - return None - -settings = load_settings_json() -if settings and "locale" in settings: - LOCALE = settings["locale"] -else: - 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}") - - LOCALE = os.getenv("LOCALE") or None - - -locales_dirs.extend(find_locales_dirs(module_dir)) -if working_dir != module_dir: - locales_dirs.extend(find_locales_dirs(working_dir)) - -translations = {} -_file_mod_times = {} - - -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() - _file_mod_times.clear() - - for locales_dir in locales_dirs: - for filename in os.listdir(locales_dir): - if filename.endswith(".json"): - lang_code = filename[:-5] - file_path = locales_dir / filename - try: - with open(file_path, "r", encoding="utf-8") as f: - data = json.load(f) - if lang_code not in translations: - translations[lang_code] = {} - translations[lang_code].update(data) - _file_mod_times[(lang_code, file_path)] = file_path.stat().st_mtime - except Exception as e: - print(f"[VOLTA] {RED}Failed loading {file_path}: {e}{RESET}") - -def reload_if_changed(): - while True: - for (lang_code, file_path), last_mtime in list(_file_mod_times.items()): - try: - 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: - print(f"[VOLTA] {RED}Translation file removed: {file_path}{RESET}") - _file_mod_times.pop((lang_code, file_path), None) - if lang_code in translations: - translations.pop(lang_code, None) - -def set_language(lang: str): - global LOCALE, ENGLISH_MISSING - 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}") - 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 - _lookup_translation.cache_clear() - -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}") - ENGLISH_MISSING = True - return - if LOCALE == "en": - print("[VOLTA] 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"[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(f"[VOLTA] All translation keys present for locale: {LOCALE}") - -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}" - - 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) - -load_translations() - -watchdog_thread = threading.Thread(target=reload_if_changed, daemon=True) -watchdog_thread.start() - -if __name__ == '__main__': - 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}") diff --git a/replace_volta.py b/replace_volta.py new file mode 100644 index 0000000..3c7b86f --- /dev/null +++ b/replace_volta.py @@ -0,0 +1,51 @@ +import os +import re + +folder_path = "." + +# Real trap regex 😮‍💨 — group(1)=key, group(2)=format args (optional) +pattern = re.compile( + r""" + (? None: + logger.info("Forcefully updating...") + stash = subprocess.run(["git", "stash"], capture_output=True) + logger.info(stash) + pull = subprocess.run(["git", "pull", "origin", "main"], check=True, capture_output=True) + logger.info(pull) +