Compare commits
36 commits
Author | SHA1 | Date | |
---|---|---|---|
19c55634b3 | |||
4f4821d9fa | |||
a20e9eb9f0 | |||
991264620a | |||
2c3946b67d | |||
d6c6605e2a | |||
c0030b77b0 | |||
3705fd1866 | |||
185ec16131 | |||
9215602ecc | |||
e8779f16df | |||
6acb1f68d3 | |||
7b951b5597 | |||
4043f3b5ff | |||
541c19e3ad | |||
97cdd7be79 | |||
068829702e | |||
b67aebd9b1 | |||
13038e3d20 | |||
375e5deaf2 | |||
90d301c054 | |||
5773e5d083 | |||
fe17dfb552 | |||
f83f8deab5 | |||
369b9833d2 | |||
9454ee9384 | |||
9f79928efd | |||
ade9d88086 | |||
7e21a10dde | |||
92dbc06b26 | |||
f186e079da | |||
f7042ed8a7 | |||
99ab5d334e | |||
93b6589643 | |||
18aa1c998f | |||
![]() |
5ff2a24dde |
48 changed files with 4811 additions and 1182 deletions
1
.env.example
Normal file
1
.env.example
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DISCORD_BOT_TOKEN=""
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -13,3 +13,6 @@ translation_report.txt
|
||||||
translationcompleteness.py
|
translationcompleteness.py
|
||||||
modules/volta
|
modules/volta
|
||||||
log.txt
|
log.txt
|
||||||
|
settings/admin_logs.json
|
||||||
|
settings/settings.json
|
||||||
|
assets/images/cached/*
|
11
README.md
11
README.md
|
@ -1,9 +1,2 @@
|
||||||
knockoff of genai basically :p
|
Real repo: https://forgejo.expect.ovh/gooberinc/goober
|
||||||
|
This is just a fork that was made the upstream
|
||||||
|
|
||||||
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
|
|
||||||
|
|
153
assets/cogs/breaking_news.py
Normal file
153
assets/cogs/breaking_news.py
Normal file
|
@ -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 <yes | no>'
|
||||||
|
)
|
||||||
|
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))
|
|
@ -1,67 +0,0 @@
|
||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
from modules.globalvars import ownerid
|
|
||||||
|
|
||||||
COG_PREFIX = "assets.cogs."
|
|
||||||
|
|
||||||
class CogManager(commands.Cog):
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def load(self, ctx, cog_name: str = None):
|
|
||||||
if ctx.author.id != ownerid:
|
|
||||||
await ctx.send("You do not have permission to use this command.")
|
|
||||||
return
|
|
||||||
if cog_name is None:
|
|
||||||
await ctx.send("Please provide the cog name to load.")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await self.bot.load_extension(COG_PREFIX + cog_name)
|
|
||||||
await ctx.send(f"Loaded cog `{cog_name}` successfully.")
|
|
||||||
except Exception as e:
|
|
||||||
await ctx.send(f"Error loading cog `{cog_name}`: {e}")
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def unload(self, ctx, cog_name: str = None):
|
|
||||||
if ctx.author.id != ownerid:
|
|
||||||
await ctx.send("You do not have permission to use this command.")
|
|
||||||
return
|
|
||||||
if cog_name is None:
|
|
||||||
await ctx.send("Please provide the cog name to unload.")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await self.bot.unload_extension(COG_PREFIX + cog_name)
|
|
||||||
await ctx.send(f"Unloaded cog `{cog_name}` successfully.")
|
|
||||||
except Exception as e:
|
|
||||||
await ctx.send(f"Error unloading cog `{cog_name}`: {e}")
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def reload(self, ctx, cog_name: str = None):
|
|
||||||
if ctx.author.id != ownerid:
|
|
||||||
await ctx.send("You do not have permission to use this command.")
|
|
||||||
return
|
|
||||||
if cog_name is None:
|
|
||||||
await ctx.send("Please provide the cog name to reload.")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await self.bot.unload_extension(COG_PREFIX + cog_name)
|
|
||||||
await self.bot.load_extension(COG_PREFIX + cog_name)
|
|
||||||
await ctx.send(f"Reloaded cog `{cog_name}` successfully.")
|
|
||||||
except Exception as e:
|
|
||||||
await ctx.send(f"Error reloading cog `{cog_name}`: {e}")
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def listcogs(self, ctx):
|
|
||||||
"""Lists all currently loaded cogs in an embed."""
|
|
||||||
cogs = list(self.bot.cogs.keys())
|
|
||||||
if not cogs:
|
|
||||||
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)
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
async def setup(bot):
|
|
||||||
await bot.add_cog(CogManager(bot))
|
|
|
@ -2,56 +2,40 @@ import random
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
|
|
||||||
class eightball(commands.Cog):
|
class eightball(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def eightball(self, ctx):
|
async def eightball(self, ctx):
|
||||||
answer = random.randint(1, 20)
|
answer = random.choice(
|
||||||
text = "Nothing"
|
[
|
||||||
if answer==1:
|
"It is certain.",
|
||||||
text = "It is certain."
|
"It is decidedly so.",
|
||||||
elif answer==2:
|
"Without a doubt.",
|
||||||
text = "It is decidedly so."
|
"Yes definitely.",
|
||||||
elif answer==3:
|
"You may rely on it.",
|
||||||
text = "Without a doubt."
|
"As I see it, yes.",
|
||||||
elif answer==4:
|
"Most likely.",
|
||||||
text = "Yes definitely."
|
"Outlook good.",
|
||||||
elif answer==5:
|
"Yes.",
|
||||||
text = "You may rely on it."
|
"Signs point to yes.",
|
||||||
elif answer==6:
|
"Reply hazy, try again.",
|
||||||
text = "As I see it, yes."
|
"Ask again later.",
|
||||||
elif answer==7:
|
"Better not tell you now.",
|
||||||
text = "Most likely."
|
"Cannot predict now.",
|
||||||
elif answer==8:
|
"Concentrate and ask again.",
|
||||||
text = "Outlook good."
|
"Don't count on it.",
|
||||||
elif answer==9:
|
"My reply is no.",
|
||||||
text = "Yes."
|
"My sources say no.",
|
||||||
elif answer==10:
|
"Outlook not so good.",
|
||||||
text = "Signs point to yes."
|
"Very doubtful.",
|
||||||
elif answer==11:
|
]
|
||||||
text = "Reply hazy, try again."
|
)
|
||||||
elif answer==12:
|
|
||||||
text = "Ask again later."
|
await ctx.send(answer)
|
||||||
elif answer==13:
|
|
||||||
text = "Better not tell you now."
|
|
||||||
elif answer==14:
|
|
||||||
text = "Cannot predict now."
|
|
||||||
elif answer==15:
|
|
||||||
text = "Concentrate and ask again."
|
|
||||||
elif answer==16:
|
|
||||||
text = "Don't count on it."
|
|
||||||
elif answer==17:
|
|
||||||
text = "My reply is no."
|
|
||||||
elif answer==18:
|
|
||||||
text = "My sources say no."
|
|
||||||
elif answer==19:
|
|
||||||
text = "Outlook not so good."
|
|
||||||
elif answer==20:
|
|
||||||
text = "Very doubtful."
|
|
||||||
|
|
||||||
await ctx.send(text)
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
await bot.add_cog(eightball(bot))
|
await bot.add_cog(eightball(bot))
|
||||||
|
|
131
assets/cogs/example.py
Normal file
131
assets/cogs/example.py
Normal file
|
@ -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))
|
|
@ -1,48 +1,70 @@
|
||||||
import discord
|
import discord
|
||||||
|
import discord.context_managers
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from modules.globalvars import ownerid
|
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):
|
class FileSync(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot: discord.Client = bot
|
||||||
self.mode = None
|
self.mode: AvailableModes | None = None
|
||||||
self.peer_id = None
|
self.peer_id = None
|
||||||
self.awaiting_file = False
|
self.awaiting_file = False
|
||||||
|
|
||||||
|
@requires_admin()
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def syncfile(self, ctx, mode: str, peer: discord.User):
|
async def syncfile(self, ctx: commands.Context, mode: str, peer: discord.User):
|
||||||
self.mode = mode.lower()
|
if self.mode not in get_args(AvailableModes):
|
||||||
self.peer_id = peer.id
|
await ctx.send("Invalid mode, use 's' or 'r'.")
|
||||||
if ctx.author.id != ownerid:
|
|
||||||
await ctx.send("You don't have permission to execute this command.")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.mode = cast(AvailableModes, mode.lower())
|
||||||
|
self.peer_id = peer.id
|
||||||
|
|
||||||
if self.mode == "s":
|
if self.mode == "s":
|
||||||
await ctx.send(f"<@{self.peer_id}> FILE_TRANSFER_REQUEST")
|
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.")
|
await ctx.send("File sent in this channel.")
|
||||||
|
|
||||||
elif self.mode == "r":
|
elif self.mode == "r":
|
||||||
await ctx.send("Waiting for incoming file...")
|
await ctx.send("Waiting for incoming file...")
|
||||||
self.awaiting_file = True
|
self.awaiting_file = True
|
||||||
else:
|
|
||||||
await ctx.send("Invalid mode, use 's' or 'r'.")
|
|
||||||
|
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@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:
|
if message.author == self.bot.user or not self.awaiting_file:
|
||||||
return
|
return
|
||||||
|
|
||||||
if message.author.id != self.peer_id:
|
if message.author.id != self.peer_id:
|
||||||
return
|
return
|
||||||
|
|
||||||
if message.content == "FILE_TRANSFER_REQUEST":
|
if message.content == "FILE_TRANSFER_REQUEST":
|
||||||
print("Ping received. Awaiting file...")
|
logger.info("Ping received. Awaiting file...")
|
||||||
if message.attachments:
|
if not message.attachments:
|
||||||
|
return
|
||||||
|
|
||||||
for attachment in message.attachments:
|
for attachment in message.attachments:
|
||||||
if attachment.filename.endswith(".json"):
|
if not attachment.filename.endswith(".json"):
|
||||||
|
continue
|
||||||
|
|
||||||
filename = "received_memory.json"
|
filename = "received_memory.json"
|
||||||
await attachment.save(filename)
|
with open(filename, "wb") as f:
|
||||||
print(f"File saved as {filename}")
|
await attachment.save(f)
|
||||||
|
|
||||||
|
logger.info(f"File saved as {filename}")
|
||||||
await message.channel.send("File received and saved.")
|
await message.channel.send("File received and saved.")
|
||||||
self.awaiting_file = False
|
self.awaiting_file = False
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
await bot.add_cog(FileSync(bot))
|
await bot.add_cog(FileSync(bot))
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from modules.image import *
|
from modules.image import *
|
||||||
from modules.volta.main import _
|
|
||||||
from PIL import Image, ImageEnhance, ImageFilter, ImageOps, ImageChops, ImageColor
|
from PIL import Image, ImageEnhance, ImageFilter, ImageOps, ImageChops, ImageColor
|
||||||
import os, random, shutil, tempfile
|
import os, random, shutil, tempfile
|
||||||
|
import modules.keys as k
|
||||||
|
|
||||||
|
|
||||||
async def deepfryimage(path):
|
async def deepfryimage(path):
|
||||||
with Image.open(path).convert("RGB") as im:
|
with Image.open(path).convert("RGB") as im:
|
||||||
|
@ -44,14 +45,17 @@ class whami(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def fuckup(self, ctx):
|
async def fuckup(self, ctx):
|
||||||
assets_folder = "assets/images"
|
assets_folder = "assets/images"
|
||||||
temp_input = None
|
temp_input = None
|
||||||
|
|
||||||
def get_random_asset_image():
|
def get_random_asset_image():
|
||||||
files = [f for f in os.listdir(assets_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp'))]
|
files = [
|
||||||
|
f
|
||||||
|
for f in os.listdir(assets_folder)
|
||||||
|
if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp"))
|
||||||
|
]
|
||||||
if not files:
|
if not files:
|
||||||
return None
|
return None
|
||||||
return os.path.join(assets_folder, random.choice(files))
|
return os.path.join(assets_folder, random.choice(files))
|
||||||
|
@ -66,7 +70,7 @@ class whami(commands.Cog):
|
||||||
else:
|
else:
|
||||||
fallback_image = get_random_asset_image()
|
fallback_image = get_random_asset_image()
|
||||||
if fallback_image is None:
|
if fallback_image is None:
|
||||||
await ctx.reply(_('no_image_available'))
|
await ctx.reply(k.no_image_available())
|
||||||
return
|
return
|
||||||
temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1])
|
temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1])
|
||||||
shutil.copy(fallback_image, temp_input)
|
shutil.copy(fallback_image, temp_input)
|
||||||
|
@ -74,7 +78,7 @@ class whami(commands.Cog):
|
||||||
else:
|
else:
|
||||||
fallback_image = get_random_asset_image()
|
fallback_image = get_random_asset_image()
|
||||||
if fallback_image is None:
|
if fallback_image is None:
|
||||||
await ctx.reply(_('no_image_available'))
|
await ctx.reply(k.no_image_available())
|
||||||
return
|
return
|
||||||
temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1])
|
temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1])
|
||||||
shutil.copy(fallback_image, temp_input)
|
shutil.copy(fallback_image, temp_input)
|
||||||
|
@ -85,7 +89,7 @@ class whami(commands.Cog):
|
||||||
if output_path is None or not os.path.isfile(output_path):
|
if output_path is None or not os.path.isfile(output_path):
|
||||||
if temp_input and os.path.exists(temp_input):
|
if temp_input and os.path.exists(temp_input):
|
||||||
os.remove(temp_input)
|
os.remove(temp_input)
|
||||||
await ctx.reply(_('failed_generate_image'))
|
await ctx.reply(k.failed_generate_image())
|
||||||
return
|
return
|
||||||
|
|
||||||
deepfried_path = await deepfryimage(output_path)
|
deepfried_path = await deepfryimage(output_path)
|
||||||
|
@ -94,5 +98,6 @@ class whami(commands.Cog):
|
||||||
if temp_input and os.path.exists(temp_input):
|
if temp_input and os.path.exists(temp_input):
|
||||||
os.remove(temp_input)
|
os.remove(temp_input)
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
await bot.add_cog(whami(bot))
|
await bot.add_cog(whami(bot))
|
||||||
|
|
231
assets/cogs/internal/base_commands.py
Normal file
231
assets/cogs/internal/base_commands.py
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
import os
|
||||||
|
from typing import Dict, List
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
import discord.ext
|
||||||
|
import discord.ext.commands
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class BaseCommands(commands.Cog):
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot: discord.ext.commands.Bot = bot
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
async def help(self, ctx: commands.Context) -> None:
|
||||||
|
embed: discord.Embed = discord.Embed(
|
||||||
|
title=f"{k.command_help_embed_title()}",
|
||||||
|
description=f"{k.command_help_embed_desc()}",
|
||||||
|
color=discord.Colour(0x000000),
|
||||||
|
)
|
||||||
|
|
||||||
|
command_categories = {
|
||||||
|
f"{k.command_help_categories_general()}": [
|
||||||
|
"mem",
|
||||||
|
"talk",
|
||||||
|
"about",
|
||||||
|
"ping",
|
||||||
|
"impact",
|
||||||
|
"demotivator",
|
||||||
|
"help",
|
||||||
|
],
|
||||||
|
f"{k.command_help_categories_admin()}": ["stats", "retrain", "setlanguage"],
|
||||||
|
}
|
||||||
|
|
||||||
|
custom_commands: List[str] = []
|
||||||
|
for cog_name, cog in self.bot.cogs.items():
|
||||||
|
for command in cog.get_commands():
|
||||||
|
if (
|
||||||
|
command.name
|
||||||
|
not in command_categories[f"{k.command_help_categories_general()}"]
|
||||||
|
and command.name
|
||||||
|
not in command_categories[f"{k.command_help_categories_admin()}"]
|
||||||
|
):
|
||||||
|
custom_commands.append(command.name)
|
||||||
|
|
||||||
|
if custom_commands:
|
||||||
|
embed.add_field(
|
||||||
|
name=f"{k.command_help_categories_custom()}",
|
||||||
|
value="\n".join(
|
||||||
|
[
|
||||||
|
f"{settings['bot']['prefix']}{command}"
|
||||||
|
for command in custom_commands
|
||||||
|
]
|
||||||
|
),
|
||||||
|
inline=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
for category, commands_list in command_categories.items():
|
||||||
|
commands_in_category: str = "\n".join(
|
||||||
|
[f"{settings['bot']['prefix']}{command}" for command in commands_list]
|
||||||
|
)
|
||||||
|
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()
|
||||||
|
k.change_language(locale)
|
||||||
|
|
||||||
|
settings["locale"] = locale # type: ignore
|
||||||
|
settings_manager.commit()
|
||||||
|
|
||||||
|
await ctx.send(":thumbsup:")
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
async def ping(self, ctx: commands.Context) -> None:
|
||||||
|
await ctx.defer()
|
||||||
|
latency: int = round(self.bot.latency * 1000)
|
||||||
|
|
||||||
|
embed: discord.Embed = discord.Embed(
|
||||||
|
title="Pong!!",
|
||||||
|
description=(
|
||||||
|
settings["bot"]["misc"]["ping_line"],
|
||||||
|
f"`{k.command_ping_embed_desc()}: {latency}ms`\n",
|
||||||
|
),
|
||||||
|
color=discord.Colour(0x000000),
|
||||||
|
)
|
||||||
|
embed.set_footer(
|
||||||
|
text=f"{k.command_ping_footer()} {ctx.author.name}",
|
||||||
|
icon_url=ctx.author.display_avatar.url,
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
async def about(self, ctx: commands.Context) -> None:
|
||||||
|
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 = 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"{k.command_stats_embed_title()}",
|
||||||
|
description=f"{k.command_stats_embed_desc()}",
|
||||||
|
color=discord.Colour(0x000000),
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
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=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"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(settings["bot"]["active_memory"], "rb") as f:
|
||||||
|
data: bytes = f.read()
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
"https://litterbox.catbox.moe/resources/internals/api.php",
|
||||||
|
data={"reqtype": "fileupload", "time": "1h"},
|
||||||
|
files={"fileToUpload": data},
|
||||||
|
)
|
||||||
|
|
||||||
|
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))
|
129
assets/cogs/internal/cogmanager.py
Normal file
129
assets/cogs/internal/cogmanager.py
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
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 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
|
||||||
|
try:
|
||||||
|
await self.bot.load_extension(COG_PREFIX + cog_name)
|
||||||
|
await ctx.send(f"Loaded cog `{cog_name}` successfully.")
|
||||||
|
except Exception as e:
|
||||||
|
await ctx.send(f"Error loading cog `{cog_name}`: {e}")
|
||||||
|
|
||||||
|
@requires_admin()
|
||||||
|
@commands.command()
|
||||||
|
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
|
||||||
|
try:
|
||||||
|
await self.bot.unload_extension(COG_PREFIX + cog_name)
|
||||||
|
await ctx.send(f"Unloaded cog `{cog_name}` successfully.")
|
||||||
|
except Exception as e:
|
||||||
|
await ctx.send(f"Error unloading cog `{cog_name}`: {e}")
|
||||||
|
|
||||||
|
@requires_admin()
|
||||||
|
@commands.command()
|
||||||
|
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)
|
||||||
|
await ctx.send(f"Reloaded cog `{cog_name}` successfully.")
|
||||||
|
except Exception as e:
|
||||||
|
await ctx.send(f"Error reloading cog `{cog_name}`: {e}")
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
async def listcogs(self, ctx):
|
||||||
|
"""Lists all currently loaded cogs in an embed."""
|
||||||
|
cogs = list(self.bot.cogs.keys())
|
||||||
|
if not cogs:
|
||||||
|
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="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))
|
123
assets/cogs/internal/markov.py
Normal file
123
assets/cogs/internal/markov.py
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
import discord.ext
|
||||||
|
import discord.ext.commands
|
||||||
|
|
||||||
|
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,
|
||||||
|
is_positive,
|
||||||
|
rephrase_for_coherence,
|
||||||
|
send_message,
|
||||||
|
)
|
||||||
|
import modules.keys as k
|
||||||
|
import logging
|
||||||
|
from typing import List, Optional, Set
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import markovify
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger("goober")
|
||||||
|
from modules.settings import instance as settings_manager
|
||||||
|
|
||||||
|
settings = settings_manager.settings
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
message_ref: discord.Message | None = await send_message(
|
||||||
|
ctx, f"{k.command_markov_retrain()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if message_ref is None:
|
||||||
|
logger.error("Failed to send message!")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(settings["bot"]["active_memory"], "r") as f:
|
||||||
|
memory: List[str] = json.load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
await send_message(ctx, f"{k.command_markov_memory_not_found()}")
|
||||||
|
return
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
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"{k.command_markov_retraining(data_size)}"
|
||||||
|
)
|
||||||
|
if processing_message_ref is None:
|
||||||
|
logger.error("Couldnt find message processing message!")
|
||||||
|
|
||||||
|
start_time: float = time.time()
|
||||||
|
|
||||||
|
model = train_markov_model(memory)
|
||||||
|
if not model:
|
||||||
|
logger.error("Failed to train markov model")
|
||||||
|
await ctx.send("Failed to retrain!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
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,
|
||||||
|
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:
|
||||||
|
if not self.model:
|
||||||
|
await send_message(ctx, f"{k.command_talk_insufficent_text()}")
|
||||||
|
return
|
||||||
|
|
||||||
|
response: str = ""
|
||||||
|
if sentence_size == 1:
|
||||||
|
response = (
|
||||||
|
self.model.make_short_sentence(max_chars=200, tries=700)
|
||||||
|
or k.command_talk_generation_fail()
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
response = improve_sentence_coherence(
|
||||||
|
self.model.make_sentence(tries=100, max_words=sentence_size)
|
||||||
|
or k.command_talk_generation_fail()
|
||||||
|
)
|
||||||
|
|
||||||
|
cleaned_response: str = re.sub(r"[^\w\s]", "", response).lower()
|
||||||
|
coherent_response: str = rephrase_for_coherence(cleaned_response)
|
||||||
|
|
||||||
|
if random.random() < 0.9 and is_positive(coherent_response):
|
||||||
|
gif_url: str = random.choice(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))
|
118
assets/cogs/internal/permissions.py
Normal file
118
assets/cogs/internal/permissions.py
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from modules.permission import requires_admin
|
||||||
|
from modules.settings import instance as settings_manager
|
||||||
|
|
||||||
|
settings = settings_manager.settings
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionManager(commands.Cog):
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@requires_admin()
|
||||||
|
@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",
|
||||||
|
color=discord.Color.blue(),
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@requires_admin()
|
||||||
|
@commands.command()
|
||||||
|
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!")
|
||||||
|
return
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Permissions",
|
||||||
|
description=f"Removed {member.name} from being an owner",
|
||||||
|
color=discord.Color.blue(),
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@requires_admin()
|
||||||
|
@commands.command()
|
||||||
|
async def blacklist_user(self, ctx: commands.Context, member: discord.Member):
|
||||||
|
settings["bot"]["blacklisted_users"].append(member.id)
|
||||||
|
settings_manager.add_admin_log_event(
|
||||||
|
{
|
||||||
|
"action": "add",
|
||||||
|
"author": ctx.author.id,
|
||||||
|
"change": "blacklisted_users",
|
||||||
|
"messageId": ctx.message.id,
|
||||||
|
"target": member.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
settings_manager.commit()
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Blacklist",
|
||||||
|
description=f"Added {member.name} to the blacklist",
|
||||||
|
color=discord.Color.blue(),
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@requires_admin()
|
||||||
|
@commands.command()
|
||||||
|
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:
|
||||||
|
await ctx.send("User is not on the blacklist!")
|
||||||
|
return
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Blacklist",
|
||||||
|
description=f"Removed {member.name} from blacklist",
|
||||||
|
color=discord.Color.blue(),
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup(bot):
|
||||||
|
await bot.add_cog(PermissionManager(bot))
|
|
@ -11,6 +11,7 @@ load_dotenv()
|
||||||
LASTFM_API_KEY = os.getenv("LASTFM_API_KEY")
|
LASTFM_API_KEY = os.getenv("LASTFM_API_KEY")
|
||||||
LASTFM_USERNAME = os.getenv("LASTFM_USERNAME")
|
LASTFM_USERNAME = os.getenv("LASTFM_USERNAME")
|
||||||
|
|
||||||
|
|
||||||
class LastFmCog(commands.Cog):
|
class LastFmCog(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
@ -34,7 +35,11 @@ class LastFmCog(commands.Cog):
|
||||||
self.current_track = track
|
self.current_track = track
|
||||||
artist, song = track
|
artist, song = track
|
||||||
activity_name = f"{artist} - {song}"
|
activity_name = f"{artist} - {song}"
|
||||||
await self.bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=activity_name))
|
await self.bot.change_presence(
|
||||||
|
activity=discord.Activity(
|
||||||
|
type=discord.ActivityType.listening, name=activity_name
|
||||||
|
)
|
||||||
|
)
|
||||||
print(f"Updated song to {artist} - {song}")
|
print(f"Updated song to {artist} - {song}")
|
||||||
else:
|
else:
|
||||||
print("LastFM gave me the same track! not updating...")
|
print("LastFM gave me the same track! not updating...")
|
||||||
|
@ -52,7 +57,11 @@ class LastFmCog(commands.Cog):
|
||||||
self.current_track = track
|
self.current_track = track
|
||||||
artist, song = track
|
artist, song = track
|
||||||
activity_name = f"{artist} - {song}"
|
activity_name = f"{artist} - {song}"
|
||||||
await self.bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=activity_name))
|
await self.bot.change_presence(
|
||||||
|
activity=discord.Activity(
|
||||||
|
type=discord.ActivityType.listening, name=activity_name
|
||||||
|
)
|
||||||
|
)
|
||||||
await ctx.send(f"Updated presence to: Listening to {activity_name}")
|
await ctx.send(f"Updated presence to: Listening to {activity_name}")
|
||||||
|
|
||||||
async def fetch_current_track(self):
|
async def fetch_current_track(self):
|
||||||
|
@ -71,12 +80,13 @@ class LastFmCog(commands.Cog):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
track = recenttracks[0]
|
track = recenttracks[0]
|
||||||
if '@attr' in track and track['@attr'].get('nowplaying') == 'true':
|
if "@attr" in track and track["@attr"].get("nowplaying") == "true":
|
||||||
artist = track.get('artist', {}).get('#text', 'Unknown Artist')
|
artist = track.get("artist", {}).get("#text", "Unknown Artist")
|
||||||
song = track.get('name', 'Unknown Song')
|
song = track.get("name", "Unknown Song")
|
||||||
return artist, song
|
return artist, song
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
if not LASTFM_API_KEY or not LASTFM_USERNAME:
|
if not LASTFM_API_KEY or not LASTFM_USERNAME:
|
||||||
return
|
return
|
||||||
|
|
|
@ -2,7 +2,12 @@ from discord.ext import commands
|
||||||
import discord
|
import discord
|
||||||
from collections import defaultdict, Counter
|
from collections import defaultdict, Counter
|
||||||
import datetime
|
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):
|
class StatsCog(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
@ -29,38 +34,54 @@ class StatsCog(commands.Cog):
|
||||||
async def on_command(self, ctx):
|
async def on_command(self, ctx):
|
||||||
self.command_usage[ctx.command.qualified_name] += 1
|
self.command_usage[ctx.command.qualified_name] += 1
|
||||||
|
|
||||||
|
@requires_admin()
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def spyware(self, ctx):
|
async def spyware(self, ctx):
|
||||||
if ctx.author.id != ownerid:
|
|
||||||
return
|
|
||||||
uptime = datetime.datetime.utcnow() - self.start_time
|
uptime = datetime.datetime.utcnow() - self.start_time
|
||||||
hours_elapsed = max((uptime.total_seconds() / 3600), 1)
|
hours_elapsed = max((uptime.total_seconds() / 3600), 1)
|
||||||
avg_per_hour = self.total_messages / hours_elapsed
|
avg_per_hour = self.total_messages / hours_elapsed
|
||||||
if self.messages_per_hour:
|
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:
|
else:
|
||||||
peak_hour, peak_count = "N/A", 0
|
peak_hour, peak_count = "N/A", 0
|
||||||
|
|
||||||
top_users = self.user_message_counts.most_common(5)
|
top_users = self.user_message_counts.most_common(5)
|
||||||
|
|
||||||
embed = discord.Embed(title="Community Stats", color=discord.Color.blue())
|
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="Uptime", value=str(uptime).split(".")[0], inline=False)
|
||||||
embed.add_field(name="Total Messages", value=str(self.total_messages), inline=True)
|
embed.add_field(
|
||||||
embed.add_field(name="Active Users", value=str(len(self.active_users)), inline=True)
|
name="Total Messages", value=str(self.total_messages), 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="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(
|
top_str = (
|
||||||
f"<@{user_id}>: {count} messages" for user_id, count in top_users
|
"\n".join(f"<@{user_id}>: {count} messages" for user_id, count in top_users)
|
||||||
) or "No data"
|
or "No data"
|
||||||
|
)
|
||||||
embed.add_field(name="Top Chatters", value=top_str, inline=False)
|
embed.add_field(name="Top Chatters", value=top_str, inline=False)
|
||||||
|
|
||||||
cmd_str = "\n".join(
|
cmd_str = (
|
||||||
|
"\n".join(
|
||||||
f"{cmd}: {count}" for cmd, count in self.command_usage.most_common(5)
|
f"{cmd}: {count}" for cmd, count in self.command_usage.most_common(5)
|
||||||
) or "No commands used yet"
|
)
|
||||||
|
or "No commands used yet"
|
||||||
|
)
|
||||||
embed.add_field(name="Top Commands", value=cmd_str, inline=False)
|
embed.add_field(name="Top Commands", value=cmd_str, inline=False)
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
await bot.add_cog(StatsCog(bot))
|
await bot.add_cog(StatsCog(bot))
|
||||||
|
|
|
@ -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))
|
|
|
@ -3,6 +3,9 @@ from discord.ext import commands
|
||||||
from modules.globalvars import RED, GREEN, RESET, LOCAL_VERSION_FILE
|
from modules.globalvars import RED, GREEN, RESET, LOCAL_VERSION_FILE
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from modules.permission import requires_admin
|
||||||
|
|
||||||
|
|
||||||
class songchange(commands.Cog):
|
class songchange(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
@ -16,18 +19,20 @@ class songchange(commands.Cog):
|
||||||
global local_version
|
global local_version
|
||||||
local_version = get_local_version()
|
local_version = get_local_version()
|
||||||
|
|
||||||
|
@requires_admin()
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def changesong(self, ctx):
|
async def changesong(self, ctx, song: str):
|
||||||
if LOCAL_VERSION_FILE > "0.11.8":
|
await ctx.send(f"Changed song to {song}")
|
||||||
await ctx.send(f"Goober is too old! you must have version 0.11.8 you have {local_version}")
|
|
||||||
return
|
|
||||||
await ctx.send("Check the terminal! (this does not persist across restarts)")
|
|
||||||
song = input("\nEnter a song:\n")
|
|
||||||
try:
|
try:
|
||||||
await self.bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=f"{song}"))
|
await self.bot.change_presence(
|
||||||
|
activity=discord.Activity(
|
||||||
|
type=discord.ActivityType.listening, name=f"{song}"
|
||||||
|
)
|
||||||
|
)
|
||||||
print(f"{GREEN}Changed song to {song}{RESET}")
|
print(f"{GREEN}Changed song to {song}{RESET}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"{RED}An error occurred while changing songs..: {str(e)}{RESET}")
|
print(f"{RED}An error occurred while changing songs..: {str(e)}{RESET}")
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
await bot.add_cog(songchange(bot))
|
await bot.add_cog(songchange(bot))
|
||||||
|
|
|
@ -14,19 +14,21 @@ MODEL_MATCH_STRING = r"[0-9]{2}_[0-9]{2}_[0-9]{4}-[0-9]{2}_[0-9]{2}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import tensorflow as tf
|
import tensorflow as tf
|
||||||
from tensorflow import keras
|
import keras
|
||||||
from tensorflow.keras.preprocessing.text import Tokenizer
|
from keras.preprocessing.text import Tokenizer
|
||||||
from tensorflow.keras.preprocessing.sequence import pad_sequences
|
from keras.preprocessing.sequence import pad_sequences
|
||||||
from tensorflow.keras.models import Sequential, load_model
|
from keras.models import Sequential, load_model
|
||||||
from tensorflow.keras.layers import Embedding, LSTM, Dense
|
from keras.layers import Embedding, LSTM, Dense
|
||||||
from tensorflow.keras.backend import clear_session
|
from keras.backend import clear_session
|
||||||
|
|
||||||
if tf.config.list_physical_devices('GPU'):
|
if tf.config.list_physical_devices("GPU"):
|
||||||
print("Using GPU acceleration")
|
print("Using GPU acceleration")
|
||||||
elif tf.config.list_physical_devices('Metal'):
|
elif tf.config.list_physical_devices("Metal"):
|
||||||
print("Using Metal for macOS acceleration")
|
print("Using Metal for macOS acceleration")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("ERROR: Failed to import TensorFlow. Ensure you have the correct dependencies:")
|
print(
|
||||||
|
"ERROR: Failed to import TensorFlow. Ensure you have the correct dependencies:"
|
||||||
|
)
|
||||||
print("tensorflow>=2.15.0")
|
print("tensorflow>=2.15.0")
|
||||||
print("For macOS (Apple Silicon): tensorflow-metal")
|
print("For macOS (Apple Silicon): tensorflow-metal")
|
||||||
ready = False
|
ready = False
|
||||||
|
@ -44,17 +46,32 @@ class TFCallback(keras.callbacks.Callback):
|
||||||
self.times.append(time.time())
|
self.times.append(time.time())
|
||||||
avg_epoch_time = np.mean(np.diff(self.times))
|
avg_epoch_time = np.mean(np.diff(self.times))
|
||||||
description = f"ETA: {round(avg_epoch_time)}s"
|
description = f"ETA: {round(avg_epoch_time)}s"
|
||||||
self.embed.add_field(name=f"<t:{round(time.time())}:t> - {message}", value=description, inline=False)
|
self.embed.add_field(
|
||||||
|
name=f"<t:{round(time.time())}:t> - {message}",
|
||||||
|
value=description,
|
||||||
|
inline=False,
|
||||||
|
)
|
||||||
await self.message.edit(embed=self.embed)
|
await self.message.edit(embed=self.embed)
|
||||||
|
|
||||||
def on_train_end(self, logs=None):
|
def on_train_end(self, logs=None):
|
||||||
self.bot.loop.create_task(self.send_message("Training stopped", "Training has been stopped."))
|
self.bot.loop.create_task(
|
||||||
|
self.send_message("Training stopped", "Training has been stopped.")
|
||||||
|
)
|
||||||
|
|
||||||
def on_epoch_begin(self, epoch, logs=None):
|
def on_epoch_begin(self, epoch, logs=None):
|
||||||
self.bot.loop.create_task(self.send_message(f"Starting epoch {epoch}", "This might take a while", epoch=True))
|
self.bot.loop.create_task(
|
||||||
|
self.send_message(
|
||||||
|
f"Starting epoch {epoch}", "This might take a while", epoch=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def on_epoch_end(self, epoch, logs=None):
|
def on_epoch_end(self, epoch, logs=None):
|
||||||
self.bot.loop.create_task(self.send_message(f"Epoch {epoch} ended", f"Accuracy: {round(logs.get('accuracy', 0.0), 4)}"))
|
self.bot.loop.create_task(
|
||||||
|
self.send_message(
|
||||||
|
f"Epoch {epoch} ended",
|
||||||
|
f"Accuracy: {round(logs.get('accuracy', 0.0), 4)}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Ai:
|
class Ai:
|
||||||
|
@ -66,7 +83,7 @@ class Ai:
|
||||||
self.batch_size = 64
|
self.batch_size = 64
|
||||||
|
|
||||||
def generate_model_name(self):
|
def generate_model_name(self):
|
||||||
return time.strftime('%d_%m_%Y-%H_%M', time.localtime())
|
return time.strftime("%d_%m_%Y-%H_%M", time.localtime())
|
||||||
|
|
||||||
def __load_model(self, model_path):
|
def __load_model(self, model_path):
|
||||||
clear_session()
|
clear_session()
|
||||||
|
@ -90,7 +107,9 @@ class Ai:
|
||||||
self.is_loaded = True
|
self.is_loaded = True
|
||||||
|
|
||||||
async def run_async(self, func, bot, *args, **kwargs):
|
async def run_async(self, func, bot, *args, **kwargs):
|
||||||
return await bot.loop.run_in_executor(None, functools.partial(func, *args, **kwargs))
|
return await bot.loop.run_in_executor(
|
||||||
|
None, functools.partial(func, *args, **kwargs)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Learning(Ai):
|
class Learning(Ai):
|
||||||
|
@ -108,13 +127,19 @@ class Learning(Ai):
|
||||||
X = pad_sequences(X, maxlen=maxlen, padding="pre")
|
X = pad_sequences(X, maxlen=maxlen, padding="pre")
|
||||||
y = np.array(y)
|
y = np.array(y)
|
||||||
|
|
||||||
model = Sequential([
|
model = Sequential(
|
||||||
|
[
|
||||||
Embedding(input_dim=VOCAB_SIZE, output_dim=128, input_length=maxlen),
|
Embedding(input_dim=VOCAB_SIZE, output_dim=128, input_length=maxlen),
|
||||||
LSTM(64),
|
LSTM(64),
|
||||||
Dense(VOCAB_SIZE, activation="softmax")
|
Dense(VOCAB_SIZE, activation="softmax"),
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
|
||||||
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])
|
model.compile(
|
||||||
|
optimizer="adam",
|
||||||
|
loss="sparse_categorical_crossentropy",
|
||||||
|
metrics=["accuracy"],
|
||||||
|
)
|
||||||
history = model.fit(X, y, epochs=epochs, batch_size=64, callbacks=[tf_callback])
|
history = model.fit(X, y, epochs=epochs, batch_size=64, callbacks=[tf_callback])
|
||||||
self.save_model(model, tokenizer, history)
|
self.save_model(model, tokenizer, history)
|
||||||
|
|
||||||
|
@ -136,9 +161,20 @@ class Generation(Ai):
|
||||||
return False
|
return False
|
||||||
for _ in range(word_amount):
|
for _ in range(word_amount):
|
||||||
token_list = self.tokenizer.texts_to_sequences([seed])[0]
|
token_list = self.tokenizer.texts_to_sequences([seed])[0]
|
||||||
token_list = pad_sequences([token_list], maxlen=self.model.input_shape[1], padding="pre")
|
token_list = pad_sequences(
|
||||||
predicted_word_index = np.argmax(self.model.predict(token_list, verbose=0), axis=-1)[0]
|
[token_list], maxlen=self.model.input_shape[1], padding="pre"
|
||||||
output_word = next((w for w, i in self.tokenizer.word_index.items() if i == predicted_word_index), "")
|
)
|
||||||
|
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
|
seed += " " + output_word
|
||||||
return seed
|
return seed
|
||||||
|
|
|
@ -5,7 +5,12 @@ from bs4 import BeautifulSoup
|
||||||
import json
|
import json
|
||||||
import asyncio
|
import asyncio
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
from modules.globalvars import ownerid
|
from modules.permission import requires_admin
|
||||||
|
from modules.settings import instance as settings_manager
|
||||||
|
|
||||||
|
settings = settings_manager.settings
|
||||||
|
|
||||||
|
|
||||||
class WebScraper(commands.Cog):
|
class WebScraper(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
@ -22,7 +27,7 @@ class WebScraper(commands.Cog):
|
||||||
|
|
||||||
def extract_sentences(self, text):
|
def extract_sentences(self, text):
|
||||||
"""Extract sentences from text."""
|
"""Extract sentences from text."""
|
||||||
sentences = text.split('.')
|
sentences = text.split(".")
|
||||||
return [sentence.strip() for sentence in sentences if sentence.strip()]
|
return [sentence.strip() for sentence in sentences if sentence.strip()]
|
||||||
|
|
||||||
def save_to_json(self, sentences):
|
def save_to_json(self, sentences):
|
||||||
|
@ -49,7 +54,6 @@ class WebScraper(commands.Cog):
|
||||||
print("No data to undo.")
|
print("No data to undo.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
data = data[:-1]
|
data = data[:-1]
|
||||||
|
|
||||||
with open("memory.json", "w") as file:
|
with open("memory.json", "w") as file:
|
||||||
|
@ -73,18 +77,14 @@ class WebScraper(commands.Cog):
|
||||||
|
|
||||||
soup = BeautifulSoup(html, "html.parser")
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
|
||||||
for paragraph in soup.find_all('p'):
|
for paragraph in soup.find_all("p"):
|
||||||
sentences = self.extract_sentences(paragraph.get_text())
|
sentences = self.extract_sentences(paragraph.get_text())
|
||||||
self.save_to_json(sentences)
|
self.save_to_json(sentences)
|
||||||
|
|
||||||
|
@requires_admin()
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def start_scrape(self, ctx, start_url: str):
|
async def start_scrape(self, ctx, start_url: str):
|
||||||
"""Command to start the scraping process."""
|
"""Command to start the scraping process."""
|
||||||
if ctx.author.id != ownerid:
|
|
||||||
await ctx.send("You do not have permission to use this command.")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not start_url.startswith("http"):
|
if not start_url.startswith("http"):
|
||||||
await ctx.send("Please provide a valid URL.")
|
await ctx.send("Please provide a valid URL.")
|
||||||
return
|
return
|
||||||
|
@ -96,18 +96,16 @@ class WebScraper(commands.Cog):
|
||||||
|
|
||||||
await ctx.send("Scraping complete! Sentences saved to memory.json.")
|
await ctx.send("Scraping complete! Sentences saved to memory.json.")
|
||||||
|
|
||||||
|
@requires_admin()
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def undo_scrape(self, ctx):
|
async def undo_scrape(self, ctx):
|
||||||
"""Command to undo the last scrape."""
|
"""Command to undo the last scrape."""
|
||||||
if ctx.author.id != ownerid:
|
|
||||||
await ctx.send("You do not have permission to use this command.")
|
|
||||||
return
|
|
||||||
|
|
||||||
success = self.undo_last_scrape()
|
success = self.undo_last_scrape()
|
||||||
if success:
|
if success:
|
||||||
await ctx.send("Last scrape undone successfully.")
|
await ctx.send("Last scrape undone successfully.")
|
||||||
else:
|
else:
|
||||||
await ctx.send("No data to undo or an error occurred.")
|
await ctx.send("No data to undo or an error occurred.")
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
await bot.add_cog(WebScraper(bot))
|
await bot.add_cog(WebScraper(bot))
|
|
@ -14,6 +14,7 @@ from modules.globalvars import VERSION_URL
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
class GooberWeb(commands.Cog):
|
class GooberWeb(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
@ -25,17 +26,19 @@ class GooberWeb(commands.Cog):
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
self.websockets = set()
|
self.websockets = set()
|
||||||
|
|
||||||
self.app.add_routes([
|
self.app.add_routes(
|
||||||
web.get('/', self.handle_index),
|
[
|
||||||
web.get('/changesong', self.handle_changesong),
|
web.get("/", self.handle_index),
|
||||||
web.get('/stats', self.handle_stats),
|
web.get("/changesong", self.handle_changesong),
|
||||||
web.get('/data', self.handle_json_data),
|
web.get("/stats", self.handle_stats),
|
||||||
web.get('/ws', self.handle_websocket),
|
web.get("/data", self.handle_json_data),
|
||||||
web.get('/styles.css', self.handle_css),
|
web.get("/ws", self.handle_websocket),
|
||||||
web.get('/settings', self.handle_settings),
|
web.get("/styles.css", self.handle_css),
|
||||||
web.post('/update_settings', self.handle_update_settings),
|
web.get("/settings", self.handle_settings),
|
||||||
web.post('/restart_bot', self.handle_restart_bot),
|
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.bot.loop.create_task(self.start_web_server())
|
||||||
self.update_clients.start()
|
self.update_clients.start()
|
||||||
|
@ -59,17 +62,25 @@ class GooberWeb(commands.Cog):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = await self.bot.fetch_user(int(user_id))
|
user = await self.bot.fetch_user(int(user_id))
|
||||||
blacklisted_users.append({
|
blacklisted_users.append(
|
||||||
|
{
|
||||||
"name": f"{user.name}",
|
"name": f"{user.name}",
|
||||||
"avatar_url": str(user.avatar.url) if user.avatar else str(user.default_avatar.url),
|
"avatar_url": (
|
||||||
"id": user.id
|
str(user.avatar.url)
|
||||||
})
|
if user.avatar
|
||||||
|
else str(user.default_avatar.url)
|
||||||
|
),
|
||||||
|
"id": user.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
except discord.NotFound:
|
except discord.NotFound:
|
||||||
blacklisted_users.append({
|
blacklisted_users.append(
|
||||||
|
{
|
||||||
"name": f"Unknown User ({user_id})",
|
"name": f"Unknown User ({user_id})",
|
||||||
"avatar_url": "",
|
"avatar_url": "",
|
||||||
"id": user_id
|
"id": user_id,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
except discord.HTTPException as e:
|
except discord.HTTPException as e:
|
||||||
print(f"Error fetching user {user_id}: {e}")
|
print(f"Error fetching user {user_id}: {e}")
|
||||||
continue
|
continue
|
||||||
|
@ -82,23 +93,28 @@ class GooberWeb(commands.Cog):
|
||||||
|
|
||||||
for guild in guilds:
|
for guild in guilds:
|
||||||
icon_url = str(guild.icon.url) if guild.icon else ""
|
icon_url = str(guild.icon.url) if guild.icon else ""
|
||||||
guild_info.append({
|
guild_info.append(
|
||||||
|
{
|
||||||
"name": guild.name,
|
"name": guild.name,
|
||||||
"member_count": guild.member_count,
|
"member_count": guild.member_count,
|
||||||
"icon_url": icon_url,
|
"icon_url": icon_url,
|
||||||
"id": guild.id
|
"id": guild.id,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return guild_info
|
return guild_info
|
||||||
|
|
||||||
async def start_web_server(self):
|
async def start_web_server(self):
|
||||||
self.runner = web.AppRunner(self.app)
|
self.runner = web.AppRunner(self.app)
|
||||||
await self.runner.setup()
|
await self.runner.setup()
|
||||||
self.site = web.TCPSite(self.runner, '0.0.0.0', 8080)
|
self.site = web.TCPSite(self.runner, "0.0.0.0", 8080)
|
||||||
await self.site.start()
|
await self.site.start()
|
||||||
print("Goober web server started on port 8080")
|
print("Goober web server started on port 8080")
|
||||||
|
|
||||||
async def stop_web_server(self):
|
async def stop_web_server(self):
|
||||||
|
if self.site is None or self.runner is None:
|
||||||
|
return
|
||||||
|
|
||||||
await self.site.stop()
|
await self.site.stop()
|
||||||
await self.runner.cleanup()
|
await self.runner.cleanup()
|
||||||
print("Web server stopped")
|
print("Web server stopped")
|
||||||
|
@ -139,7 +155,7 @@ class GooberWeb(commands.Cog):
|
||||||
return ws
|
return ws
|
||||||
|
|
||||||
async def handle_css(self, request):
|
async def handle_css(self, request):
|
||||||
css_path = os.path.join(os.path.dirname(__file__), 'styles.css')
|
css_path = os.path.join(os.path.dirname(__file__), "styles.css")
|
||||||
if os.path.exists(css_path):
|
if os.path.exists(css_path):
|
||||||
return web.FileResponse(css_path)
|
return web.FileResponse(css_path)
|
||||||
return web.Response(text="CSS file not found", status=404)
|
return web.Response(text="CSS file not found", status=404)
|
||||||
|
@ -193,12 +209,14 @@ class GooberWeb(commands.Cog):
|
||||||
"bot_uptime": uptime_str,
|
"bot_uptime": uptime_str,
|
||||||
"latency": f"{self.bot.latency * 1000:.2f} ms",
|
"latency": f"{self.bot.latency * 1000:.2f} ms",
|
||||||
"bot_name": self.bot.user.name,
|
"bot_name": self.bot.user.name,
|
||||||
"bot_avatar_url": str(self.bot.user.avatar.url) if self.bot.user.avatar else "",
|
"bot_avatar_url": (
|
||||||
|
str(self.bot.user.avatar.url) if self.bot.user.avatar else ""
|
||||||
|
),
|
||||||
"authenticated": os.getenv("gooberauthenticated"),
|
"authenticated": os.getenv("gooberauthenticated"),
|
||||||
"lastmsg": os.getenv("gooberlatestgen"),
|
"lastmsg": os.getenv("gooberlatestgen"),
|
||||||
"localversion": os.getenv("gooberlocal_version"),
|
"localversion": os.getenv("gooberlocal_version"),
|
||||||
"latestversion": os.getenv("gooberlatest_version"),
|
"latestversion": os.getenv("gooberlatest_version"),
|
||||||
"owner": os.getenv("ownerid")
|
"owner": os.getenv("ownerid"),
|
||||||
}
|
}
|
||||||
|
|
||||||
async def handle_update(self, request):
|
async def handle_update(self, request):
|
||||||
|
@ -207,9 +225,13 @@ class GooberWeb(commands.Cog):
|
||||||
return web.Response(text="Update file not found", status=404)
|
return web.Response(text="Update file not found", status=404)
|
||||||
|
|
||||||
async def handle_changesong(self, request):
|
async def handle_changesong(self, request):
|
||||||
song = request.query.get('song', '')
|
song = request.query.get("song", "")
|
||||||
if song:
|
if song:
|
||||||
await self.bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=song))
|
await self.bot.change_presence(
|
||||||
|
activity=discord.Activity(
|
||||||
|
type=discord.ActivityType.listening, name=song
|
||||||
|
)
|
||||||
|
)
|
||||||
return web.Response(text=f"Changed song to: {song}")
|
return web.Response(text=f"Changed song to: {song}")
|
||||||
return web.Response(text="Please provide a song parameter", status=400)
|
return web.Response(text="Please provide a song parameter", status=400)
|
||||||
|
|
||||||
|
@ -221,33 +243,34 @@ class GooberWeb(commands.Cog):
|
||||||
async def read_env_file(self):
|
async def read_env_file(self):
|
||||||
env_vars = {}
|
env_vars = {}
|
||||||
try:
|
try:
|
||||||
with open('.env', 'r') as f:
|
with open(".env", "r") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line or line.startswith('#') or '=' not in line:
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
key, value = line.split('=', 1)
|
key, value = line.split("=", 1)
|
||||||
key = key.strip()
|
key = key.strip()
|
||||||
if key in ['splashtext', 'DISCORD_BOT_TOKEN']:
|
if key in ["splashtext", "DISCORD_BOT_TOKEN"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
env_vars[key] = value.strip('"\'')
|
env_vars[key] = value.strip("\"'")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(".env file not found")
|
print(".env file not found")
|
||||||
return env_vars
|
return env_vars
|
||||||
|
|
||||||
|
|
||||||
async def handle_settings(self, request):
|
async def handle_settings(self, request):
|
||||||
env_vars = await self.read_env_file()
|
env_vars = await self.read_env_file()
|
||||||
|
|
||||||
# Get config.py variables
|
# Get config.py variables
|
||||||
config_vars = {}
|
config_vars = {}
|
||||||
try:
|
try:
|
||||||
with open('config.py', 'r') as f:
|
with open("config.py", "r") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
if line.startswith('VERSION_URL'):
|
if line.startswith("VERSION_URL"):
|
||||||
config_vars['VERSION_URL'] = line.split('=', 1)[1].strip().strip('"')
|
config_vars["VERSION_URL"] = (
|
||||||
|
line.split("=", 1)[1].strip().strip('"')
|
||||||
|
)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -300,14 +323,14 @@ class GooberWeb(commands.Cog):
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return web.Response(text=settings_html, content_type='text/html')
|
return web.Response(text=settings_html, content_type="text/html")
|
||||||
|
|
||||||
async def handle_update_settings(self, request):
|
async def handle_update_settings(self, request):
|
||||||
data = await request.post()
|
data = await request.post()
|
||||||
env_text = ""
|
env_text = ""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open('.env', 'r') as f:
|
with open(".env", "r") as f:
|
||||||
env_text = f.read()
|
env_text = f.read()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
@ -315,32 +338,42 @@ class GooberWeb(commands.Cog):
|
||||||
def replace_match(match):
|
def replace_match(match):
|
||||||
key = match.group(1)
|
key = match.group(1)
|
||||||
value = match.group(2)
|
value = match.group(2)
|
||||||
if key in ['splashtext', 'DISCORD_BOT_TOKEN']:
|
if key in ["splashtext", "DISCORD_BOT_TOKEN"]:
|
||||||
return match.group(0)
|
return match.group(0)
|
||||||
if key in data:
|
if key in data:
|
||||||
new_value = data[key]
|
new_value = data[key]
|
||||||
if not (new_value.startswith('"') and new_value.endswith('"')):
|
if not (new_value.startswith('"') and new_value.endswith('"')):
|
||||||
new_value = f'"{new_value}"'
|
new_value = f'"{new_value}"'
|
||||||
return f'{key}={new_value}'
|
return f"{key}={new_value}"
|
||||||
return match.group(0)
|
return match.group(0)
|
||||||
|
|
||||||
env_text = re.sub(r'^(\w+)=([\s\S]+?)(?=\n\w+=|\Z)', replace_match, env_text, flags=re.MULTILINE)
|
env_text = re.sub(
|
||||||
|
r"^(\w+)=([\s\S]+?)(?=\n\w+=|\Z)",
|
||||||
|
replace_match,
|
||||||
|
env_text,
|
||||||
|
flags=re.MULTILINE,
|
||||||
|
)
|
||||||
|
|
||||||
with open('.env', 'w') as f:
|
with open(".env", "w") as f:
|
||||||
f.write(env_text.strip() + '\n')
|
f.write(env_text.strip() + "\n")
|
||||||
|
|
||||||
if 'VERSION_URL' in data:
|
if "VERSION_URL" in data:
|
||||||
config_text = ""
|
config_text = ""
|
||||||
try:
|
try:
|
||||||
with open('config.py', 'r') as f:
|
with open("config.py", "r") as f:
|
||||||
config_text = f.read()
|
config_text = f.read()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
config_text = re.sub(r'^(VERSION_URL\s*=\s*").+?"', f'\\1{data["VERSION_URL"]}"', config_text, flags=re.MULTILINE)
|
config_text = re.sub(
|
||||||
|
r'^(VERSION_URL\s*=\s*").+?"',
|
||||||
|
f'\\1{data["VERSION_URL"]}"',
|
||||||
|
config_text,
|
||||||
|
flags=re.MULTILINE,
|
||||||
|
)
|
||||||
|
|
||||||
with open('config.py', 'w') as f:
|
with open("config.py", "w") as f:
|
||||||
f.write(config_text.strip() + '\n')
|
f.write(config_text.strip() + "\n")
|
||||||
|
|
||||||
return aiohttp.web.Response(text="Settings updated successfully!")
|
return aiohttp.web.Response(text="Settings updated successfully!")
|
||||||
|
|
||||||
|
@ -348,8 +381,12 @@ class GooberWeb(commands.Cog):
|
||||||
stats = await self.get_bot_stats()
|
stats = await self.get_bot_stats()
|
||||||
|
|
||||||
guild_list_html = ""
|
guild_list_html = ""
|
||||||
for guild in stats['guilds']:
|
for guild in stats["guilds"]:
|
||||||
icon_html = f'<img src="{guild["icon_url"]}" alt="guild icon" class="guild-icon">' if guild["icon_url"] else '<div class="guild-icon-placeholder"></div>'
|
icon_html = (
|
||||||
|
f'<img src="{guild["icon_url"]}" alt="guild icon" class="guild-icon">'
|
||||||
|
if guild["icon_url"]
|
||||||
|
else '<div class="guild-icon-placeholder"></div>'
|
||||||
|
)
|
||||||
guild_list_html += f"""
|
guild_list_html += f"""
|
||||||
<div class="guild-item">
|
<div class="guild-item">
|
||||||
{icon_html}
|
{icon_html}
|
||||||
|
@ -360,8 +397,12 @@ class GooberWeb(commands.Cog):
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
blacklisted_users_html = ""
|
blacklisted_users_html = ""
|
||||||
for user in stats['blacklisted_users']:
|
for user in stats["blacklisted_users"]:
|
||||||
avatar_html = f'<img src="{user["avatar_url"]}" alt="user avatar" class="user-avatar">' if user["avatar_url"] else '<div class="user-avatar-placeholder"></div>'
|
avatar_html = (
|
||||||
|
f'<img src="{user["avatar_url"]}" alt="user avatar" class="user-avatar">'
|
||||||
|
if user["avatar_url"]
|
||||||
|
else '<div class="user-avatar-placeholder"></div>'
|
||||||
|
)
|
||||||
blacklisted_users_html += f"""
|
blacklisted_users_html += f"""
|
||||||
<div class="blacklisted-user">
|
<div class="blacklisted-user">
|
||||||
{avatar_html}
|
{avatar_html}
|
||||||
|
@ -372,7 +413,7 @@ class GooberWeb(commands.Cog):
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
owner_id = stats.get('owner')
|
owner_id = stats.get("owner")
|
||||||
owner = None
|
owner = None
|
||||||
owner_username = "Owner"
|
owner_username = "Owner"
|
||||||
owner_pfp = ""
|
owner_pfp = ""
|
||||||
|
@ -385,7 +426,6 @@ class GooberWeb(commands.Cog):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
html_content = f"""
|
html_content = f"""
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
@ -867,7 +907,7 @@ class GooberWeb(commands.Cog):
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return web.Response(text=html_content, content_type='text/html')
|
return web.Response(text=html_content, content_type="text/html")
|
||||||
|
|
||||||
async def handle_stats(self, request):
|
async def handle_stats(self, request):
|
||||||
return await self.handle_index(request)
|
return await self.handle_index(request)
|
||||||
|
@ -876,5 +916,6 @@ class GooberWeb(commands.Cog):
|
||||||
stats = await self.get_bot_stats()
|
stats = await self.get_bot_stats()
|
||||||
return web.json_response(stats)
|
return web.json_response(stats)
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
await bot.add_cog(GooberWeb(bot))
|
await bot.add_cog(GooberWeb(bot))
|
|
@ -1,6 +1,7 @@
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
|
|
||||||
class whoami(commands.Cog):
|
class whoami(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
@ -15,10 +16,11 @@ class whoami(commands.Cog):
|
||||||
description=f"Your User ID is: {user_id}\n"
|
description=f"Your User ID is: {user_id}\n"
|
||||||
f"Your username is: {username}\n"
|
f"Your username is: {username}\n"
|
||||||
f"Your nickname in this server is: <@{user_id}>",
|
f"Your nickname in this server is: <@{user_id}>",
|
||||||
color=discord.Color.blue()
|
color=discord.Color.blue(),
|
||||||
)
|
)
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
await bot.add_cog(whoami(bot))
|
await bot.add_cog(whoami(bot))
|
||||||
|
|
BIN
assets/fonts/SpecialGothic.ttf
Normal file
BIN
assets/fonts/SpecialGothic.ttf
Normal file
Binary file not shown.
BIN
assets/images/breaking_news.png
Normal file
BIN
assets/images/breaking_news.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 MiB |
BIN
assets/images/cache/breaking_news.png
vendored
Normal file
BIN
assets/images/cache/breaking_news.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 MiB |
|
@ -131,6 +131,14 @@
|
||||||
"command_stats_embed_field2name": "Version",
|
"command_stats_embed_field2name": "Version",
|
||||||
"command_stats_embed_field2value": "Local: {local_version} \nLatest: {latest_version}",
|
"command_stats_embed_field2value": "Local: {local_version} \nLatest: {latest_version}",
|
||||||
"command_stats_embed_field3name": "Variable Info",
|
"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}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -102,7 +102,7 @@
|
||||||
"command_markov_retrain": "Uudelleenkoulutetaan markov-mallia... Odota.",
|
"command_markov_retrain": "Uudelleenkoulutetaan markov-mallia... Odota.",
|
||||||
"command_markov_memory_not_found": "Virhe: muistitiedostoa ei löytynyt!",
|
"command_markov_memory_not_found": "Virhe: muistitiedostoa ei löytynyt!",
|
||||||
"command_markov_memory_is_corrupt": "Virhe: muistitiedosto on korruptoitu!",
|
"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_markov_retrain_successful": "Markov-malli koulutettiin uudestaan {data_size} datapisteellä!",
|
||||||
"command_desc_talk":"puhuu ja sillei",
|
"command_desc_talk":"puhuu ja sillei",
|
||||||
"command_talk_insufficent_text": "Minun pitää oppia lisää viesteistä ennen kun puhun.",
|
"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_field2value": "Paikallinen: {local_version} \nUusin: {latest_version}",
|
||||||
"command_stats_embed_field3name": "Muuttajainformaatio",
|
"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}```"
|
"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}```"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
528
bot.py
528
bot.py
|
@ -1,21 +1,5 @@
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
import random
|
|
||||||
import traceback
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
import shutil
|
|
||||||
import uuid
|
|
||||||
import asyncio
|
|
||||||
import sys
|
|
||||||
from typing import List, Dict, Set, Optional, Tuple, Any, Union, Callable, Coroutine, TypeVar, Type
|
|
||||||
import logging
|
import logging
|
||||||
from modules.globalvars import *
|
|
||||||
from modules.prestartchecks import start_checks
|
|
||||||
from modules.logger import GooberFormatter
|
from modules.logger import GooberFormatter
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger("goober")
|
logger = logging.getLogger("goober")
|
||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(logging.DEBUG)
|
||||||
|
@ -31,37 +15,100 @@ file_handler.setFormatter(GooberFormatter(colors=False))
|
||||||
logger.addHandler(console_handler)
|
logger.addHandler(console_handler)
|
||||||
logger.addHandler(file_handler)
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
# Print splash text and check for updates
|
import os
|
||||||
print(splashtext) # Print splash text (from modules/globalvars.py)
|
import re
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import traceback
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
from typing import (
|
||||||
|
List,
|
||||||
|
Dict,
|
||||||
|
Literal,
|
||||||
|
Set,
|
||||||
|
Optional,
|
||||||
|
Tuple,
|
||||||
|
Any,
|
||||||
|
TypedDict,
|
||||||
|
Union,
|
||||||
|
Callable,
|
||||||
|
Coroutine,
|
||||||
|
TypeVar,
|
||||||
|
Type,
|
||||||
|
)
|
||||||
|
import logging
|
||||||
|
from modules.prestartchecks import start_checks
|
||||||
|
import modules.keys as k
|
||||||
|
from modules import key_compiler
|
||||||
|
import logging
|
||||||
|
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.sync_conenctor import instance as sync_connector
|
||||||
|
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
|
def build_keys():
|
||||||
|
key_compiler.build_result(
|
||||||
|
"en",
|
||||||
|
"assets/locales",
|
||||||
|
types=True,
|
||||||
|
output_path="modules/keys.py",
|
||||||
|
generate_comments=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
build_keys()
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
start_checks()
|
start_checks()
|
||||||
|
|
||||||
import requests
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from discord import app_commands
|
from discord import app_commands
|
||||||
from discord import Colour, Embed, File, Interaction, Message
|
from discord import Colour, Message
|
||||||
from discord.abc import Messageable
|
|
||||||
|
|
||||||
from better_profanity import profanity
|
from better_profanity import profanity
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from modules.volta.main import _, set_language
|
|
||||||
from modules.markovmemory import *
|
from modules.markovmemory import *
|
||||||
from modules.version import *
|
|
||||||
from modules.sentenceprocessing import *
|
from modules.sentenceprocessing import *
|
||||||
from modules.unhandledexception import handle_exception
|
from modules.unhandledexception import handle_exception
|
||||||
from modules.image import gen_meme, gen_demotivator
|
from modules.image import gen_demotivator
|
||||||
|
|
||||||
sys.excepthook = handle_exception
|
sys.excepthook = handle_exception
|
||||||
check_for_update() # Check for updates (from modules/version.py)
|
|
||||||
|
|
||||||
# Type aliases
|
|
||||||
T = TypeVar('T')
|
class MessageMetadata(TypedDict):
|
||||||
MessageContext = Union[commands.Context, discord.Interaction]
|
user_id: str
|
||||||
MessageReference = Union[Message, discord.WebhookMessage]
|
user_name: str
|
||||||
|
guild_id: str | Literal["DM"]
|
||||||
|
guild_name: str | Literal["DM"]
|
||||||
|
channel_id: str
|
||||||
|
channel_name: str
|
||||||
|
message: str
|
||||||
|
timestamp: float
|
||||||
|
|
||||||
|
|
||||||
# Constants with type hints
|
# Constants with type hints
|
||||||
positive_gifs: List[str] = os.getenv("POSITIVE_GIFS", "").split(',')
|
positive_gifs: List[str] = settings["bot"]["misc"]["positive_gifs"]
|
||||||
currenthash: str = ""
|
currenthash: str = ""
|
||||||
launched: bool = False
|
launched: bool = False
|
||||||
slash_commands_enabled: bool = False
|
slash_commands_enabled: bool = False
|
||||||
|
@ -70,76 +117,103 @@ slash_commands_enabled: bool = False
|
||||||
intents: discord.Intents = discord.Intents.default()
|
intents: discord.Intents = discord.Intents.default()
|
||||||
intents.messages = True
|
intents.messages = True
|
||||||
intents.message_content = True
|
intents.message_content = True
|
||||||
|
|
||||||
bot: commands.Bot = commands.Bot(
|
bot: commands.Bot = commands.Bot(
|
||||||
command_prefix=PREFIX,
|
command_prefix=settings["bot"]["prefix"],
|
||||||
intents=intents,
|
intents=intents,
|
||||||
allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False, replied_user=True)
|
allowed_mentions=discord.AllowedMentions(
|
||||||
|
everyone=False, roles=False, users=False, replied_user=True
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Load memory and Markov model for text generation
|
# Load memory and Markov model for text generation
|
||||||
memory: List[str] = load_memory()
|
memory: List[str | Dict[Literal["_meta"], MessageMetadata]] = load_memory()
|
||||||
markov_model: Optional[markovify.Text] = load_markov_model()
|
markov_model: Optional[markovify.Text] = load_markov_model()
|
||||||
if not markov_model:
|
if not markov_model:
|
||||||
logger.error(_('markov_model_not_found'))
|
logger.error(k.markov_model_not_found())
|
||||||
memory = load_memory()
|
memory = load_memory()
|
||||||
markov_model = train_markov_model(memory)
|
markov_model = train_markov_model(memory)
|
||||||
|
|
||||||
generated_sentences: Set[str] = set()
|
generated_sentences: Set[str] = set()
|
||||||
used_words: Set[str] = set()
|
used_words: Set[str] = set()
|
||||||
|
|
||||||
async def load_cogs_from_folder(bot, folder_name="assets/cogs"):
|
|
||||||
for filename in os.listdir(folder_name):
|
async def load_cogs_from_folder(bot: commands.Bot, folder_name="assets/cogs"):
|
||||||
if filename.endswith(".py") and not filename.startswith("_"):
|
for filename in [file for file in os.listdir(folder_name) if file.endswith(".py")]:
|
||||||
cog_name = filename[:-3]
|
cog_name: str = filename[:-3]
|
||||||
|
|
||||||
|
if (
|
||||||
|
"internal" not in folder_name
|
||||||
|
and cog_name not in settings["bot"]["enabled_cogs"]
|
||||||
|
):
|
||||||
|
logger.debug(f"Skipping cog {cog_name} (not in enabled cogs)")
|
||||||
|
continue
|
||||||
|
|
||||||
module_path = folder_name.replace("/", ".").replace("\\", ".") + f".{cog_name}"
|
module_path = folder_name.replace("/", ".").replace("\\", ".") + f".{cog_name}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await bot.load_extension(module_path)
|
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:
|
except Exception as e:
|
||||||
logger.error(f"{(_('cog_fail'))} {cog_name} {e}")
|
logger.error(f"{k.cog_fail()} {cog_name} {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
async def send_alive_ping_periodically() -> None:
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
requests.post(f"{VERSION_URL}/aliveping", json={"name": NAME})
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{(_('error_sending_alive_ping'))}{RESET} {e}")
|
|
||||||
await asyncio.sleep(60)
|
|
||||||
|
|
||||||
# Event: Called when the bot is ready
|
# Event: Called when the bot is ready
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_ready() -> None:
|
async def on_ready() -> None:
|
||||||
global launched
|
global launched
|
||||||
global slash_commands_enabled
|
|
||||||
global NAME
|
|
||||||
|
|
||||||
folder_name: str = "cogs"
|
folder_name: str = "cogs"
|
||||||
if launched:
|
if launched:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
await load_cogs_from_folder(bot, "assets/cogs/internal")
|
||||||
await load_cogs_from_folder(bot)
|
await load_cogs_from_folder(bot)
|
||||||
try:
|
try:
|
||||||
synced: List[discord.app_commands.AppCommand] = await bot.tree.sync()
|
synced: List[discord.app_commands.AppCommand] = await bot.tree.sync()
|
||||||
logger.info(f"{_('synced_commands')} {len(synced)} {(_('synced_commands2'))}")
|
|
||||||
slash_commands_enabled = True
|
|
||||||
logger.info(f"{(_('started')).format(name=NAME)}")
|
|
||||||
|
|
||||||
bot.loop.create_task(send_alive_ping_periodically())
|
logger.info(f"{k.synced_commands()} {len(synced)} {k.synced_commands2()}")
|
||||||
|
logger.info(k.started(settings["name"]))
|
||||||
|
|
||||||
except discord.errors.Forbidden as perm_error:
|
except discord.errors.Forbidden as perm_error:
|
||||||
logger.error(f"Permission error while syncing commands: {perm_error}")
|
logger.error(f"Permission error while syncing commands: {perm_error}")
|
||||||
logger.error("Make sure the bot has the 'applications.commands' scope and is invited with the correct permissions.")
|
logger.error(
|
||||||
|
"Make sure the bot has the 'applications.commands' scope and is invited with the correct permissions."
|
||||||
|
)
|
||||||
quit()
|
quit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{_('fail_commands_sync')} {e}")
|
logger.error(f"{k.fail_commands_sync()} {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
if not song:
|
if not settings["bot"]["misc"]["activity"]["content"]:
|
||||||
return
|
return
|
||||||
await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=f"{song}"))
|
|
||||||
|
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=activities.get(
|
||||||
|
settings["bot"]["misc"]["activity"]["type"],
|
||||||
|
discord.ActivityType.unknown,
|
||||||
|
),
|
||||||
|
name=settings["bot"]["misc"]["activity"]["content"],
|
||||||
|
)
|
||||||
|
)
|
||||||
launched = True
|
launched = True
|
||||||
|
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_command_error(ctx: commands.Context, error: commands.CommandError) -> None:
|
async def on_command_error(ctx: commands.Context, error: commands.CommandError) -> None:
|
||||||
from modules.unhandledexception import handle_exception
|
from modules.unhandledexception import handle_exception
|
||||||
|
@ -147,139 +221,32 @@ async def on_command_error(ctx: commands.Context, error: commands.CommandError)
|
||||||
if isinstance(error, commands.CommandInvokeError):
|
if isinstance(error, commands.CommandInvokeError):
|
||||||
original: Exception = error.original
|
original: Exception = error.original
|
||||||
handle_exception(
|
handle_exception(
|
||||||
type(original), original, original.__traceback__,
|
type(original),
|
||||||
context=f"Command: {ctx.command} | User: {ctx.author}"
|
original,
|
||||||
|
original.__traceback__,
|
||||||
|
context=f"Command: {ctx.command} | User: {ctx.author}",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
handle_exception(
|
handle_exception(
|
||||||
type(error), error, error.__traceback__,
|
type(error),
|
||||||
context=f"Command: {ctx.command} | User: {ctx.author}"
|
error,
|
||||||
|
error.__traceback__,
|
||||||
|
context=f"Command: {ctx.command} | User: {ctx.author}",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Command: Retrain the Markov model from memory
|
|
||||||
@bot.hybrid_command(description=f"{(_('command_desc_retrain'))}")
|
|
||||||
async def retrain(ctx: commands.Context) -> None:
|
|
||||||
if ctx.author.id != ownerid:
|
|
||||||
return
|
|
||||||
|
|
||||||
message_ref: MessageReference = await send_message(ctx, f"{(_('command_markov_retrain'))}")
|
|
||||||
try:
|
|
||||||
with open(MEMORY_FILE, 'r') as f:
|
|
||||||
memory: List[str] = json.load(f)
|
|
||||||
except FileNotFoundError:
|
|
||||||
await send_message(ctx, f"{(_('command_markov_memory_not_found'))}")
|
|
||||||
return
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
await send_message(ctx, f"{(_('command_markov_memory_is_corrupt'))}")
|
|
||||||
return
|
|
||||||
|
|
||||||
data_size: int = len(memory)
|
|
||||||
processed_data: int = 0
|
|
||||||
processing_message_ref: MessageReference = await send_message(ctx, f"{(_('command_markov_retraining')).format(processed_data=processed_data, data_size=data_size)}")
|
|
||||||
start_time: float = time.time()
|
|
||||||
|
|
||||||
for i, data in enumerate(memory):
|
|
||||||
processed_data += 1
|
|
||||||
|
|
||||||
global markov_model
|
|
||||||
markov_model = train_markov_model(memory)
|
|
||||||
save_markov_model(markov_model)
|
|
||||||
|
|
||||||
await send_message(ctx, f"{_('command_markov_retrain_successful').format(data_size=data_size)}", edit=True, message_reference=processing_message_ref)
|
|
||||||
|
|
||||||
# Command: Generate a sentence using the Markov model
|
|
||||||
@bot.hybrid_command(description=f"{(_('command_desc_talk'))}")
|
|
||||||
async def talk(ctx: commands.Context, sentence_size: int = 5) -> None:
|
|
||||||
if not markov_model:
|
|
||||||
await send_message(ctx, f"{(_('command_talk_insufficent_text'))}")
|
|
||||||
return
|
|
||||||
|
|
||||||
response: Optional[str] = None
|
|
||||||
for _ in range(20):
|
|
||||||
if sentence_size == 1:
|
|
||||||
response = markov_model.make_short_sentence(max_chars=100, tries=100)
|
|
||||||
if response:
|
|
||||||
response = response.split()[0]
|
|
||||||
else:
|
|
||||||
response = markov_model.make_sentence(tries=100, max_words=sentence_size)
|
|
||||||
|
|
||||||
if response and response not in generated_sentences:
|
|
||||||
if sentence_size > 1:
|
|
||||||
response = improve_sentence_coherence(response)
|
|
||||||
generated_sentences.add(response)
|
|
||||||
break
|
|
||||||
|
|
||||||
if response:
|
|
||||||
cleaned_response: str = re.sub(r'[^\w\s]', '', response).lower()
|
|
||||||
coherent_response: str = rephrase_for_coherence(cleaned_response)
|
|
||||||
if random.random() < 0.9 and is_positive(coherent_response):
|
|
||||||
gif_url: str = random.choice(positive_gifs)
|
|
||||||
combined_message: str = f"{coherent_response}\n[jif]({gif_url})"
|
|
||||||
else:
|
|
||||||
combined_message: str = coherent_response
|
|
||||||
logger.info(combined_message)
|
|
||||||
os.environ['gooberlatestgen'] = combined_message
|
|
||||||
await send_message(ctx, combined_message)
|
|
||||||
else:
|
|
||||||
await send_message(ctx, f"{(_('command_talk_generation_fail'))}")
|
|
||||||
|
|
||||||
# Command: Generate an image
|
|
||||||
@bot.hybrid_command(description=f"{(_('command_desc_help'))}")
|
|
||||||
async def impact(ctx: commands.Context) -> None:
|
|
||||||
assets_folder: str = "assets/images"
|
|
||||||
temp_input: Optional[str] = None
|
|
||||||
|
|
||||||
def get_random_asset_image() -> Optional[str]:
|
|
||||||
files: List[str] = [f for f in os.listdir(assets_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp'))]
|
|
||||||
if not files:
|
|
||||||
return None
|
|
||||||
return os.path.join(assets_folder, random.choice(files))
|
|
||||||
|
|
||||||
if ctx.message.attachments:
|
|
||||||
attachment: discord.Attachment = ctx.message.attachments[0]
|
|
||||||
if attachment.content_type and attachment.content_type.startswith("image/"):
|
|
||||||
ext: str = os.path.splitext(attachment.filename)[1]
|
|
||||||
temp_input = f"tempy{ext}"
|
|
||||||
await attachment.save(temp_input)
|
|
||||||
input_path: str = temp_input
|
|
||||||
else:
|
|
||||||
fallback_image: Optional[str] = get_random_asset_image()
|
|
||||||
if fallback_image is None:
|
|
||||||
await ctx.reply(_('no_image_available'))
|
|
||||||
return
|
|
||||||
temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1])
|
|
||||||
shutil.copy(fallback_image, temp_input)
|
|
||||||
input_path = temp_input
|
|
||||||
else:
|
|
||||||
fallback_image = get_random_asset_image()
|
|
||||||
if fallback_image is None:
|
|
||||||
await ctx.reply(_('no_image_available'))
|
|
||||||
return
|
|
||||||
temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1])
|
|
||||||
shutil.copy(fallback_image, temp_input)
|
|
||||||
input_path = temp_input
|
|
||||||
|
|
||||||
output_path: Optional[str] = await gen_meme(input_path)
|
|
||||||
|
|
||||||
if output_path is None or not os.path.isfile(output_path):
|
|
||||||
if temp_input and os.path.exists(temp_input):
|
|
||||||
os.remove(temp_input)
|
|
||||||
await ctx.reply(_('failed_generate_image'))
|
|
||||||
return
|
|
||||||
|
|
||||||
await ctx.send(file=discord.File(output_path))
|
|
||||||
|
|
||||||
if temp_input and os.path.exists(temp_input):
|
|
||||||
os.remove(temp_input)
|
|
||||||
|
|
||||||
# New demotivator command
|
# New demotivator command
|
||||||
@bot.hybrid_command(description="Generate a demotivator poster with two lines of text")
|
@bot.hybrid_command(description="Generate a demotivator poster with two lines of text")
|
||||||
async def demotivator(ctx: commands.Context) -> None:
|
async def demotivator(ctx: commands.Context) -> None:
|
||||||
assets_folder: str = "assets/images"
|
assets_folder: str = "assets/images"
|
||||||
temp_input: Optional[str] = None
|
temp_input: str | None = None
|
||||||
|
|
||||||
def get_random_asset_image() -> Optional[str]:
|
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'))]
|
files: List[str] = [
|
||||||
|
f
|
||||||
|
for f in os.listdir(assets_folder)
|
||||||
|
if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp"))
|
||||||
|
]
|
||||||
if not files:
|
if not files:
|
||||||
return None
|
return None
|
||||||
return os.path.join(assets_folder, random.choice(files))
|
return os.path.join(assets_folder, random.choice(files))
|
||||||
|
@ -289,12 +256,13 @@ async def demotivator(ctx: commands.Context) -> None:
|
||||||
if attachment.content_type and attachment.content_type.startswith("image/"):
|
if attachment.content_type and attachment.content_type.startswith("image/"):
|
||||||
ext: str = os.path.splitext(attachment.filename)[1]
|
ext: str = os.path.splitext(attachment.filename)[1]
|
||||||
temp_input = f"tempy{ext}"
|
temp_input = f"tempy{ext}"
|
||||||
await attachment.save(temp_input)
|
with open(temp_input, "wb") as f:
|
||||||
|
await attachment.save(f)
|
||||||
input_path: str = temp_input
|
input_path: str = temp_input
|
||||||
else:
|
else:
|
||||||
fallback_image: Optional[str] = get_random_asset_image()
|
fallback_image: Optional[str] = get_random_asset_image()
|
||||||
if fallback_image is None:
|
if fallback_image is None:
|
||||||
await ctx.reply(_('no_image_available'))
|
await ctx.reply(k.no_image_available())
|
||||||
return
|
return
|
||||||
temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1])
|
temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1])
|
||||||
shutil.copy(fallback_image, temp_input)
|
shutil.copy(fallback_image, temp_input)
|
||||||
|
@ -302,13 +270,13 @@ async def demotivator(ctx: commands.Context) -> None:
|
||||||
else:
|
else:
|
||||||
fallback_image = get_random_asset_image()
|
fallback_image = get_random_asset_image()
|
||||||
if fallback_image is None:
|
if fallback_image is None:
|
||||||
await ctx.reply(_('no_image_available'))
|
await ctx.reply(k.no_image_available())
|
||||||
return
|
return
|
||||||
temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1])
|
temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1])
|
||||||
shutil.copy(fallback_image, temp_input)
|
shutil.copy(fallback_image, temp_input)
|
||||||
input_path = temp_input
|
input_path = temp_input
|
||||||
|
|
||||||
output_path: Optional[str] = await gen_demotivator(input_path)
|
output_path: Optional[str] = await gen_demotivator(input_path) # type: ignore
|
||||||
|
|
||||||
if output_path is None or not os.path.isfile(output_path):
|
if output_path is None or not os.path.isfile(output_path):
|
||||||
if temp_input and os.path.exists(temp_input):
|
if temp_input and os.path.exists(temp_input):
|
||||||
|
@ -321,73 +289,50 @@ async def demotivator(ctx: commands.Context) -> None:
|
||||||
if temp_input and os.path.exists(temp_input):
|
if temp_input and os.path.exists(temp_input):
|
||||||
os.remove(temp_input)
|
os.remove(temp_input)
|
||||||
|
|
||||||
bot.remove_command('help')
|
|
||||||
# Command: Show help information
|
|
||||||
@bot.hybrid_command(description=f"{(_('command_desc_help'))}")
|
|
||||||
async def help(ctx: commands.Context) -> None:
|
|
||||||
embed: discord.Embed = discord.Embed(
|
|
||||||
title=f"{(_('command_help_embed_title'))}",
|
|
||||||
description=f"{(_('command_help_embed_desc'))}",
|
|
||||||
color=Colour(0x000000)
|
|
||||||
)
|
|
||||||
|
|
||||||
command_categories: Dict[str, List[str]] = {
|
|
||||||
f"{(_('command_help_categories_general'))}": ["mem", "talk", "about", "ping", "impact", "demotivator", "help"],
|
|
||||||
f"{(_('command_help_categories_admin'))}": ["stats", "retrain", "setlanguage"]
|
|
||||||
}
|
|
||||||
|
|
||||||
custom_commands: List[str] = []
|
|
||||||
for cog_name, cog in bot.cogs.items():
|
|
||||||
for command in cog.get_commands():
|
|
||||||
if command.name not in command_categories[f"{(_('command_help_categories_general'))}"] and command.name not in command_categories[f"{(_('command_help_categories_admin'))}"]:
|
|
||||||
custom_commands.append(command.name)
|
|
||||||
|
|
||||||
if custom_commands:
|
|
||||||
embed.add_field(name=f"{(_('command_help_categories_custom'))}", value="\n".join([f"{PREFIX}{command}" for command in custom_commands]), inline=False)
|
|
||||||
|
|
||||||
for category, commands_list in command_categories.items():
|
|
||||||
commands_in_category: str = "\n".join([f"{PREFIX}{command}" for command in commands_list])
|
|
||||||
embed.add_field(name=category, value=commands_in_category, inline=False)
|
|
||||||
|
|
||||||
await send_message(ctx, embed=embed)
|
|
||||||
|
|
||||||
@bot.hybrid_command(description=f"{(_('command_desc_setlang'))}")
|
|
||||||
@app_commands.describe(locale="Choose your language")
|
|
||||||
async def setlanguage(ctx: commands.Context, locale: str) -> None:
|
|
||||||
if ctx.author.id != ownerid:
|
|
||||||
await ctx.send(":thumbsdown:")
|
|
||||||
return
|
|
||||||
await ctx.defer()
|
|
||||||
set_language(locale)
|
|
||||||
await ctx.send(":thumbsup:")
|
|
||||||
|
|
||||||
# Event: Called on every message
|
# Event: Called on every message
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_message(message: discord.Message) -> None:
|
async def on_message(message: discord.Message) -> None:
|
||||||
global memory, markov_model
|
global memory, markov_model
|
||||||
EMOJIS = ["\U0001F604", "\U0001F44D", "\U0001F525", "\U0001F4AF", "\U0001F389", "\U0001F60E"] # originally was emojis but it would probably shit itself on systems without unicode so....
|
EMOJIS = [
|
||||||
|
"\U0001f604",
|
||||||
|
"\U0001f44d",
|
||||||
|
"\U0001f525",
|
||||||
|
"\U0001f4af",
|
||||||
|
"\U0001f389",
|
||||||
|
"\U0001f60e",
|
||||||
|
] # originally was emojis but it would probably shit itself on systems without unicode so....
|
||||||
if message.author.bot:
|
if message.author.bot:
|
||||||
return
|
return
|
||||||
|
|
||||||
if str(message.author.id) in BLACKLISTED_USERS:
|
if message.author.id in settings["bot"]["blacklisted_users"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
if message.content.startswith((f"{PREFIX}talk", f"{PREFIX}mem", f"{PREFIX}help", f"{PREFIX}stats", f"{PREFIX}")):
|
commands = [
|
||||||
logger.info(f"{(_('command_ran')).format(message=message)}")
|
settings["bot"]["prefix"] + command.name for command in bot.tree.get_commands()
|
||||||
|
]
|
||||||
|
|
||||||
|
if message.content.startswith(tuple(commands)):
|
||||||
|
logger.info(f"{k.command_ran(message.author.name, message.content)}")
|
||||||
await bot.process_commands(message)
|
await bot.process_commands(message)
|
||||||
return
|
return
|
||||||
|
|
||||||
if profanity.contains_profanity(message.content):
|
if (
|
||||||
|
profanity.contains_profanity(message.content)
|
||||||
|
and settings["bot"]["misc"]["block_profanity"]
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
if message.content:
|
if message.content:
|
||||||
if not USERTRAIN_ENABLED:
|
if not settings["bot"]["user_training"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
formatted_message: str = append_mentions_to_18digit_integer(message.content)
|
formatted_message: str = append_mentions_to_18digit_integer(message.content)
|
||||||
cleaned_message: str = preprocess_message(formatted_message)
|
cleaned_message: str = preprocess_message(formatted_message)
|
||||||
if cleaned_message:
|
if cleaned_message:
|
||||||
memory.append(cleaned_message)
|
memory.append(cleaned_message)
|
||||||
message_metadata = {
|
|
||||||
|
message_metadata: MessageMetadata = {
|
||||||
"user_id": str(message.author.id),
|
"user_id": str(message.author.id),
|
||||||
"user_name": str(message.author),
|
"user_name": str(message.author),
|
||||||
"guild_id": str(message.guild.id) if message.guild else "DM",
|
"guild_id": str(message.guild.id) if message.guild else "DM",
|
||||||
|
@ -395,7 +340,7 @@ async def on_message(message: discord.Message) -> None:
|
||||||
"channel_id": str(message.channel.id),
|
"channel_id": str(message.channel.id),
|
||||||
"channel_name": str(message.channel),
|
"channel_name": str(message.channel),
|
||||||
"message": message.content,
|
"message": message.content,
|
||||||
"timestamp": time.time()
|
"timestamp": time.time(),
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
if isinstance(memory, list):
|
if isinstance(memory, list):
|
||||||
|
@ -407,10 +352,17 @@ async def on_message(message: discord.Message) -> None:
|
||||||
|
|
||||||
save_memory(memory)
|
save_memory(memory)
|
||||||
|
|
||||||
sentiment_score = is_positive(message.content) # doesnt work but im scared to change the logic now please ignore
|
sentiment_score = is_positive(
|
||||||
|
message.content
|
||||||
|
) # doesnt work but im scared to change the logic now please ignore
|
||||||
if sentiment_score > 0.8:
|
if sentiment_score > 0.8:
|
||||||
if REACT != "True":
|
if not settings["bot"]["react_to_messages"]:
|
||||||
return
|
return
|
||||||
|
if not sync_connector.can_react(message.id):
|
||||||
|
logger.info("Sync hub determined that this instance cannot react")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
emoji = random.choice(EMOJIS)
|
emoji = random.choice(EMOJIS)
|
||||||
try:
|
try:
|
||||||
await message.add_reaction(emoji)
|
await message.add_reaction(emoji)
|
||||||
|
@ -419,89 +371,32 @@ async def on_message(message: discord.Message) -> None:
|
||||||
|
|
||||||
await bot.process_commands(message)
|
await bot.process_commands(message)
|
||||||
|
|
||||||
|
|
||||||
# Event: Called on every interaction (slash command, etc.)
|
# Event: Called on every interaction (slash command, etc.)
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_interaction(interaction: discord.Interaction) -> None:
|
async def on_interaction(interaction: discord.Interaction) -> None:
|
||||||
logger.info(f"{(_('command_ran_s')).format(interaction=interaction)}{interaction.data['name']}")
|
logger.info(f"{k.command_ran_s(interaction.user.name)} {interaction.user.name}")
|
||||||
|
|
||||||
|
|
||||||
# Global check: Block blacklisted users from running commands
|
# Global check: Block blacklisted users from running commands
|
||||||
@bot.check
|
@bot.check
|
||||||
async def block_blacklisted(ctx: commands.Context) -> bool:
|
async def block_blacklisted(ctx: commands.Context) -> bool:
|
||||||
if str(ctx.author.id) in BLACKLISTED_USERS:
|
if ctx.author.id not in settings["bot"]["blacklisted_users"]:
|
||||||
|
return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if isinstance(ctx, discord.Interaction):
|
if isinstance(ctx, discord.Interaction):
|
||||||
if not ctx.response.is_done():
|
if not ctx.response.is_done():
|
||||||
await ctx.response.send_message(_('blacklisted'), ephemeral=True)
|
await ctx.response.send_message(k.blacklisted(), ephemeral=True)
|
||||||
else:
|
else:
|
||||||
await ctx.followup.send(_('blacklisted'), ephemeral=True)
|
await ctx.followup.send(k.blacklisted(), ephemeral=True)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_('blacklisted_user'), ephemeral=True)
|
await ctx.send(k.blacklisted_user(), ephemeral=True)
|
||||||
except:
|
except:
|
||||||
pass
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Command: Show bot latency
|
|
||||||
@bot.hybrid_command(description=f"{(_('command_desc_ping'))}")
|
|
||||||
async def ping(ctx: commands.Context) -> None:
|
|
||||||
await ctx.defer()
|
|
||||||
latency: int = round(bot.latency * 1000)
|
|
||||||
|
|
||||||
LOLembed: discord.Embed = discord.Embed(
|
|
||||||
title="Pong!!",
|
|
||||||
description=(
|
|
||||||
f"{PING_LINE}\n"
|
|
||||||
f"`{(_('command_ping_embed_desc'))}: {latency}ms`\n"
|
|
||||||
),
|
|
||||||
color=Colour(0x000000)
|
|
||||||
)
|
|
||||||
LOLembed.set_footer(text=f"{(_('command_ping_footer'))} {ctx.author.name}", icon_url=ctx.author.avatar.url)
|
|
||||||
|
|
||||||
await ctx.send(embed=LOLembed)
|
|
||||||
|
|
||||||
# Command: Show about information
|
|
||||||
@bot.hybrid_command(description=f"{(_('command_about_desc'))}")
|
|
||||||
async def about(ctx: commands.Context) -> None:
|
|
||||||
print("-----------------------------------\n\n")
|
|
||||||
latest_version: str = check_for_update()
|
|
||||||
print("-----------------------------------")
|
|
||||||
embed: discord.Embed = discord.Embed(title=f"{(_('command_about_embed_title'))}", description="", color=Colour(0x000000))
|
|
||||||
embed.add_field(name=f"{(_('command_about_embed_field1'))}", value=f"{NAME}", inline=False)
|
|
||||||
embed.add_field(name=f"{(_('command_about_embed_field2name'))}", value=f"{(_('command_about_embed_field2value')).format(local_version=local_version, latest_version=latest_version)}", inline=False)
|
|
||||||
embed.add_field(name=f"Github", value=f"https://github.com/gooberinc/goober")
|
|
||||||
|
|
||||||
await send_message(ctx, embed=embed)
|
|
||||||
|
|
||||||
# Command: Show bot statistics (admin only)
|
|
||||||
@bot.hybrid_command(description="stats")
|
|
||||||
async def stats(ctx: commands.Context) -> None:
|
|
||||||
if ctx.author.id != ownerid:
|
|
||||||
return
|
|
||||||
print("-----------------------------------\n\n")
|
|
||||||
latest_version: str = check_for_update()
|
|
||||||
print("-----------------------------------")
|
|
||||||
memory_file: str = 'memory.json'
|
|
||||||
file_size: int = os.path.getsize(memory_file)
|
|
||||||
|
|
||||||
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'))}", color=Colour(0x000000))
|
|
||||||
embed.add_field(name=f"{(_('command_stats_embed_field1name'))}", value=f"{(_('command_stats_embed_field1value')).format(file_size=file_size, line_count=line_count)}", inline=False)
|
|
||||||
embed.add_field(name=f"{(_('command_stats_embed_field2name'))}", value=f"{(_('command_stats_embed_field2value')).format(local_version=local_version, latest_version=latest_version)}", inline=False)
|
|
||||||
embed.add_field(name=f"{(_('command_stats_embed_field3name'))}", value=f"{(_('command_stats_embed_field3value')).format(NAME=NAME, PREFIX=PREFIX, ownerid=ownerid, PING_LINE=PING_LINE, showmemenabled=showmemenabled, USERTRAIN_ENABLED=USERTRAIN_ENABLED, song=song, splashtext=splashtext)}", inline=False)
|
|
||||||
|
|
||||||
await send_message(ctx, embed=embed)
|
|
||||||
|
|
||||||
# Command: Upload memory.json to litterbox.catbox.moe and return the link
|
|
||||||
@bot.hybrid_command()
|
|
||||||
async def mem(ctx: commands.Context) -> None:
|
|
||||||
if showmemenabled != "true":
|
|
||||||
return
|
|
||||||
command: str = """curl -F "reqtype=fileupload" -F "time=1h" -F "fileToUpload=@memory.json" https://litterbox.catbox.moe/resources/internals/api.php"""
|
|
||||||
memorylitter: subprocess.CompletedProcess = subprocess.run(command, shell=True, capture_output=True, text=True)
|
|
||||||
logger.debug(memorylitter)
|
|
||||||
await send_message(ctx, memorylitter.stdout.strip())
|
|
||||||
|
|
||||||
# Helper: Improve sentence coherence (simple capitalization fix)
|
# Helper: Improve sentence coherence (simple capitalization fix)
|
||||||
def improve_sentence_coherence(sentence: str) -> str:
|
def improve_sentence_coherence(sentence: str) -> str:
|
||||||
|
@ -509,5 +404,40 @@ def improve_sentence_coherence(sentence: str) -> str:
|
||||||
sentence = sentence.replace(" i ", " I ")
|
sentence = sentence.replace(" i ", " I ")
|
||||||
return sentence
|
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
|
# Start the bot
|
||||||
bot.run(TOKEN)
|
if __name__ == "__main__":
|
||||||
|
bot.run(os.environ.get("DISCORD_BOT_TOKEN", ""))
|
||||||
|
|
28
example.env
28
example.env
|
@ -1,28 +0,0 @@
|
||||||
DISCORDBOTTOKEN=
|
|
||||||
BOTPREFIX="g."
|
|
||||||
PINGLINE="The Beretta fires fast and won't make you feel any better!"
|
|
||||||
BLACKLISTEDUSERS=
|
|
||||||
OWNERID=
|
|
||||||
USERTRAINENABLED="true"
|
|
||||||
SHOWMEMENABLED="true"
|
|
||||||
LOCALE=fi
|
|
||||||
NAME=goober
|
|
||||||
AUTOUPDATE="True"
|
|
||||||
SONG="Basket Case - Green Day"
|
|
||||||
CHECKSDISABLED="Frue"
|
|
||||||
REACT="True"
|
|
||||||
POSITIVEGIFS="https://media.discordapp.net/attachments/821047460151427135/1181371808566493184/jjpQGeno.gif, https://tenor.com/view/chill-guy-my-new-character-gif-2777893510283028272,https://tenor.com/view/goodnight-goodnight-friends-weezer-weezer-goodnight-gif-7322052181075806988"
|
|
||||||
SPLASHTEXT="
|
|
||||||
|
|
||||||
SS\
|
|
||||||
SS |
|
|
||||||
SSSSSS\ SSSSSS\ SSSSSS\ SSSSSSS\ SSSSSS\ SSSSSS\
|
|
||||||
SS __SS\ SS __SS\ SS __SS\ SS __SS\ SS __SS\ SS __SS\
|
|
||||||
SS / SS |SS / SS |SS / SS |SS | SS |SSSSSSSS |SS | \__|
|
|
||||||
SS | SS |SS | SS |SS | SS |SS | SS |SS ____|SS |
|
|
||||||
\SSSSSSS |\SSSSSS |\SSSSSS |SSSSSSS |\SSSSSSS\ SS |
|
|
||||||
\____SS | \______/ \______/ \_______/ \_______|\__|
|
|
||||||
SS\ SS |
|
|
||||||
\SSSSSS |
|
|
||||||
\______/
|
|
||||||
"
|
|
|
@ -1,59 +1,71 @@
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
from typing import Callable, List
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import pathlib
|
import pathlib
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
def get_git_branch():
|
def get_git_branch():
|
||||||
try:
|
try:
|
||||||
branch = subprocess.check_output(
|
branch = (
|
||||||
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
subprocess.check_output(
|
||||||
stderr=subprocess.DEVNULL
|
["git", "rev-parse", "--abbrev-ref", "HEAD"], stderr=subprocess.DEVNULL
|
||||||
).decode('utf-8').strip()
|
)
|
||||||
|
.decode("utf-8")
|
||||||
|
.strip()
|
||||||
|
)
|
||||||
return branch
|
return branch
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
env_path = pathlib.Path(__file__).parent.parent / '.env'
|
|
||||||
|
env_path = pathlib.Path(__file__).parent.parent / ".env"
|
||||||
load_dotenv(dotenv_path=env_path)
|
load_dotenv(dotenv_path=env_path)
|
||||||
|
|
||||||
|
available_cogs: Callable[[], List[str]] = lambda: [
|
||||||
|
file[:-3] for file in os.listdir("assets/cogs") if file.endswith(".py")
|
||||||
|
]
|
||||||
|
|
||||||
ANSI = "\033["
|
ANSI = "\033["
|
||||||
RED = f"{ANSI}31m"
|
RED = f"{ANSI}31m"
|
||||||
GREEN = f"{ANSI}32m"
|
GREEN = f"{ANSI}32m"
|
||||||
YELLOW = f"{ANSI}33m"
|
YELLOW = f"{ANSI}33m"
|
||||||
PURPLE = f"{ANSI}35m"
|
PURPLE = f"{ANSI}35m"
|
||||||
DEBUG = f"{ANSI}1;30m"
|
DEBUG = f"{ANSI}90m"
|
||||||
RESET = f"{ANSI}0m"
|
RESET = f"{ANSI}0m"
|
||||||
|
|
||||||
VERSION_URL = "https://raw.githubusercontent.com/gooberinc/version/main"
|
VERSION_URL = "https://raw.githubusercontent.com/gooberinc/version/main"
|
||||||
UPDATE_URL = VERSION_URL + "/latest_version.json"
|
UPDATE_URL = VERSION_URL + "/latest_version.json"
|
||||||
print(UPDATE_URL)
|
print(UPDATE_URL)
|
||||||
LOCAL_VERSION_FILE = "current_version.txt"
|
LOCAL_VERSION_FILE = "current_version.txt"
|
||||||
TOKEN = os.getenv("DISCORDBOTTOKEN", "0")
|
|
||||||
PREFIX = os.getenv("BOTPREFIX", "g.")
|
# TOKEN = os.getenv("DISCORDBOTTOKEN", "0")
|
||||||
PING_LINE = os.getenv("PINGLINE")
|
# PREFIX = os.getenv("BOTPREFIX", "g.")
|
||||||
CHECKS_DISABLED = os.getenv("CHECKSDISABLED")
|
# PING_LINE = os.getenv("PINGLINE")
|
||||||
LOCALE = os.getenv("LOCALE", "en")
|
# CHECKS_DISABLED = os.getenv("CHECKSDISABLED")
|
||||||
gooberTOKEN = os.getenv("GOOBERTOKEN")
|
# LOCALE = os.getenv("LOCALE", "en")
|
||||||
splashtext = os.getenv("SPLASHTEXT")
|
# BLACKLISTED_USERS = os.getenv("BLACKLISTEDUSERS", "").split(",")
|
||||||
ownerid = int(os.getenv("OWNERID", "0"))
|
# USERTRAIN_ENABLED = os.getenv("USERTRAINENABLED", "true").lower() == "true"
|
||||||
showmemenabled = os.getenv("SHOWMEMENABLED")
|
# NAME = os.getenv("NAME")
|
||||||
BLACKLISTED_USERS = os.getenv("BLACKLISTEDUSERS", "").split(",")
|
# MEMORY_FILE = "memory.json"
|
||||||
USERTRAIN_ENABLED = os.getenv("USERTRAINENABLED", "true").lower() == "true"
|
# MEMORY_LOADED_FILE = "MEMORY_LOADED" # is this still even used?? okay just checked its used in the markov module
|
||||||
NAME = os.getenv("NAME")
|
# ALIVEPING = os.getenv("ALIVEPING")
|
||||||
MEMORY_FILE = "memory.json"
|
# AUTOUPDATE = os.getenv("AUTOUPDATE")
|
||||||
MEMORY_LOADED_FILE = "MEMORY_LOADED" # is this still even used?? okay just checked its used in the markov module
|
# REACT = os.getenv("REACT")
|
||||||
ALIVEPING = os.getenv("ALIVEPING")
|
|
||||||
AUTOUPDATE = os.getenv("AUTOUPDATE")
|
# gooberTOKEN = os.getenv("GOOBERTOKEN")
|
||||||
|
# splashtext = os.getenv("SPLASHTEXT")
|
||||||
|
# ownerid = int(os.getenv("OWNERID", "0"))
|
||||||
|
# showmemenabled = os.getenv("SHOWMEMENABLED")
|
||||||
|
|
||||||
|
|
||||||
# IGNOREWARNING = False # is this either??? i don't think so?
|
# IGNOREWARNING = False # is this either??? i don't think so?
|
||||||
song = os.getenv("song")
|
# song = os.getenv("song")
|
||||||
arch = platform.machine()
|
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
|
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
|
launched = False
|
||||||
latest_version = "0.0.0"
|
latest_version = "0.0.0"
|
||||||
local_version = "2.3.3"
|
local_version = "2.3.3"
|
||||||
os.environ['gooberlocal_version'] = local_version
|
os.environ["gooberlocal_version"] = local_version
|
||||||
REACT = os.getenv("REACT")
|
beta = get_git_branch() == "dev"
|
||||||
if get_git_branch() == "dev":
|
|
||||||
beta = True
|
|
||||||
# this makes goober think its a beta version, so it will not update to the latest stable version or run any version checks
|
|
||||||
else:
|
|
||||||
beta = False
|
|
||||||
|
|
|
@ -6,37 +6,57 @@ import tempfile
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
||||||
from modules.markovmemory import load_markov_model
|
from modules.markovmemory import load_markov_model
|
||||||
from modules.sentenceprocessing import improve_sentence_coherence, rephrase_for_coherence
|
from modules.sentenceprocessing import (
|
||||||
|
improve_sentence_coherence,
|
||||||
|
rephrase_for_coherence,
|
||||||
|
)
|
||||||
|
|
||||||
generated_sentences = set()
|
generated_sentences = set()
|
||||||
|
|
||||||
|
|
||||||
def load_font(size):
|
def load_font(size):
|
||||||
return ImageFont.truetype("assets/fonts/Impact.ttf", size=size)
|
return ImageFont.truetype("assets/fonts/Impact.ttf", size=size)
|
||||||
|
|
||||||
|
|
||||||
def load_tnr(size):
|
def load_tnr(size):
|
||||||
return ImageFont.truetype("assets/fonts/TNR.ttf", size=size)
|
return ImageFont.truetype("assets/fonts/TNR.ttf", size=size)
|
||||||
|
|
||||||
|
|
||||||
def draw_text_with_outline(draw, text, x, y, font):
|
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)]
|
outline_offsets = [
|
||||||
|
(-2, -2),
|
||||||
|
(-2, 2),
|
||||||
|
(2, -2),
|
||||||
|
(2, 2),
|
||||||
|
(0, -2),
|
||||||
|
(0, 2),
|
||||||
|
(-2, 0),
|
||||||
|
(2, 0),
|
||||||
|
]
|
||||||
for ox, oy in outline_offsets:
|
for ox, oy in outline_offsets:
|
||||||
draw.text((x + ox, y + oy), text, font=font, fill="black")
|
draw.text((x + ox, y + oy), text, font=font, fill="black")
|
||||||
draw.text((x, y), text, font=font, fill="white")
|
draw.text((x, y), text, font=font, fill="white")
|
||||||
|
|
||||||
|
|
||||||
def fits_in_width(text, font, max_width, draw):
|
def fits_in_width(text, font, max_width, draw):
|
||||||
bbox = draw.textbbox((0, 0), text, font=font)
|
bbox = draw.textbbox((0, 0), text, font=font)
|
||||||
text_width = bbox[2] - bbox[0]
|
text_width = bbox[2] - bbox[0]
|
||||||
return text_width <= max_width
|
return text_width <= max_width
|
||||||
|
|
||||||
|
|
||||||
def split_text_to_fit(text, font, max_width, draw):
|
def split_text_to_fit(text, font, max_width, draw):
|
||||||
words = text.split()
|
words = text.split()
|
||||||
for i in range(len(words), 0, -1):
|
for i in range(len(words), 0, -1):
|
||||||
top_text = " ".join(words[:i])
|
top_text = " ".join(words[:i])
|
||||||
bottom_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):
|
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
|
return top_text, bottom_text
|
||||||
midpoint = len(words) // 2
|
midpoint = len(words) // 2
|
||||||
return " ".join(words[:midpoint]), " ".join(words[midpoint:])
|
return " ".join(words[:midpoint]), " ".join(words[midpoint:])
|
||||||
|
|
||||||
|
|
||||||
async def gen_meme(input_image_path, sentence_size=5, max_attempts=10):
|
async def gen_meme(input_image_path, sentence_size=5, max_attempts=10):
|
||||||
markov_model = load_markov_model()
|
markov_model = load_markov_model()
|
||||||
if not markov_model or not os.path.isfile(input_image_path):
|
if not markov_model or not os.path.isfile(input_image_path):
|
||||||
|
@ -54,11 +74,15 @@ async def gen_meme(input_image_path, sentence_size=5, max_attempts=10):
|
||||||
response = None
|
response = None
|
||||||
for _ in range(20):
|
for _ in range(20):
|
||||||
if sentence_size == 1:
|
if sentence_size == 1:
|
||||||
candidate = markov_model.make_short_sentence(max_chars=100, tries=100)
|
candidate = markov_model.make_short_sentence(
|
||||||
|
max_chars=100, tries=100
|
||||||
|
)
|
||||||
if candidate:
|
if candidate:
|
||||||
candidate = candidate.split()[0]
|
candidate = candidate.split()[0]
|
||||||
else:
|
else:
|
||||||
candidate = markov_model.make_sentence(tries=100, max_words=sentence_size)
|
candidate = markov_model.make_sentence(
|
||||||
|
tries=100, max_words=sentence_size
|
||||||
|
)
|
||||||
|
|
||||||
if candidate and candidate not in generated_sentences:
|
if candidate and candidate not in generated_sentences:
|
||||||
if sentence_size > 1:
|
if sentence_size > 1:
|
||||||
|
@ -70,7 +94,7 @@ async def gen_meme(input_image_path, sentence_size=5, max_attempts=10):
|
||||||
if not response:
|
if not response:
|
||||||
response = "NO TEXT GENERATED"
|
response = "NO TEXT GENERATED"
|
||||||
|
|
||||||
cleaned_response = re.sub(r'[^\w\s]', '', response).lower()
|
cleaned_response = re.sub(r"[^\w\s]", "", response).lower()
|
||||||
coherent_response = rephrase_for_coherence(cleaned_response).upper()
|
coherent_response = rephrase_for_coherence(cleaned_response).upper()
|
||||||
|
|
||||||
bbox = draw.textbbox((0, 0), coherent_response, font=font)
|
bbox = draw.textbbox((0, 0), coherent_response, font=font)
|
||||||
|
@ -79,11 +103,15 @@ async def gen_meme(input_image_path, sentence_size=5, max_attempts=10):
|
||||||
max_text_height = height // 4
|
max_text_height = height // 4
|
||||||
|
|
||||||
if text_width <= width and text_height_px <= max_text_height:
|
if text_width <= width and text_height_px <= max_text_height:
|
||||||
draw_text_with_outline(draw, coherent_response, (width - text_width) / 2, 0, font)
|
draw_text_with_outline(
|
||||||
|
draw, coherent_response, (width - text_width) / 2, 0, font
|
||||||
|
)
|
||||||
img.save(input_image_path)
|
img.save(input_image_path)
|
||||||
return input_image_path
|
return input_image_path
|
||||||
else:
|
else:
|
||||||
top_text, bottom_text = split_text_to_fit(coherent_response, font, width, draw)
|
top_text, bottom_text = split_text_to_fit(
|
||||||
|
coherent_response, font, width, draw
|
||||||
|
)
|
||||||
|
|
||||||
top_bbox = draw.textbbox((0, 0), top_text, font=font)
|
top_bbox = draw.textbbox((0, 0), top_text, font=font)
|
||||||
bottom_bbox = draw.textbbox((0, 0), bottom_text, font=font)
|
bottom_bbox = draw.textbbox((0, 0), bottom_text, font=font)
|
||||||
|
@ -92,9 +120,21 @@ async def gen_meme(input_image_path, sentence_size=5, max_attempts=10):
|
||||||
bottom_height = bottom_bbox[3] - bottom_bbox[1]
|
bottom_height = bottom_bbox[3] - bottom_bbox[1]
|
||||||
|
|
||||||
if top_height <= max_text_height and bottom_height <= max_text_height:
|
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)
|
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)
|
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)
|
draw_text_with_outline(
|
||||||
|
draw,
|
||||||
|
bottom_text,
|
||||||
|
(width - (bottom_bbox[2] - bottom_bbox[0])) / 2,
|
||||||
|
y_bottom,
|
||||||
|
font,
|
||||||
|
)
|
||||||
img.save(input_image_path)
|
img.save(input_image_path)
|
||||||
return input_image_path
|
return input_image_path
|
||||||
|
|
||||||
|
@ -113,6 +153,7 @@ async def gen_meme(input_image_path, sentence_size=5, max_attempts=10):
|
||||||
img.save(input_image_path)
|
img.save(input_image_path)
|
||||||
return input_image_path
|
return input_image_path
|
||||||
|
|
||||||
|
|
||||||
async def gen_demotivator(input_image_path, max_attempts=5):
|
async def gen_demotivator(input_image_path, max_attempts=5):
|
||||||
markov_model = load_markov_model()
|
markov_model = load_markov_model()
|
||||||
if not markov_model or not os.path.isfile(input_image_path):
|
if not markov_model or not os.path.isfile(input_image_path):
|
||||||
|
@ -145,8 +186,10 @@ async def gen_demotivator(input_image_path, max_attempts=5):
|
||||||
title = t.upper()
|
title = t.upper()
|
||||||
subtitle = s.capitalize()
|
subtitle = s.capitalize()
|
||||||
break
|
break
|
||||||
if not title: title = "DEMOTIVATOR"
|
if not title:
|
||||||
if not subtitle: subtitle = "no text generated"
|
title = "DEMOTIVATOR"
|
||||||
|
if not subtitle:
|
||||||
|
subtitle = "no text generated"
|
||||||
|
|
||||||
title_sz = int(caption_h * 0.4)
|
title_sz = int(caption_h * 0.4)
|
||||||
sub_sz = int(caption_h * 0.25)
|
sub_sz = int(caption_h * 0.25)
|
||||||
|
|
220
modules/key_compiler.py
Normal file
220
modules/key_compiler.py
Normal file
|
@ -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")
|
2340
modules/keys.py
Normal file
2340
modules/keys.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
from modules.globalvars import *
|
from modules.globalvars import *
|
||||||
|
|
||||||
|
|
||||||
class GooberFormatter(logging.Formatter):
|
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.colors = colors
|
||||||
|
@ -9,10 +10,12 @@ class GooberFormatter(logging.Formatter):
|
||||||
|
|
||||||
self.FORMATS = {
|
self.FORMATS = {
|
||||||
logging.DEBUG: DEBUG + self._format + RESET,
|
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.WARNING: YELLOW + self._format + RESET,
|
||||||
logging.ERROR: RED + 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):
|
def format(self, record: logging.LogRecord):
|
||||||
|
|
|
@ -3,9 +3,16 @@ import json
|
||||||
import markovify
|
import markovify
|
||||||
import pickle
|
import pickle
|
||||||
from modules.globalvars import *
|
from modules.globalvars import *
|
||||||
from modules.volta.main import _
|
|
||||||
import logging
|
import logging
|
||||||
|
import modules.keys as k
|
||||||
|
from modules.settings import instance as settings_manager
|
||||||
|
|
||||||
|
settings = settings_manager.settings
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger("goober")
|
logger = logging.getLogger("goober")
|
||||||
|
|
||||||
|
|
||||||
# Get file size and line count for a given file path
|
# Get file size and line count for a given file path
|
||||||
def get_file_info(file_path):
|
def get_file_info(file_path):
|
||||||
try:
|
try:
|
||||||
|
@ -16,49 +23,59 @@ def get_file_info(file_path):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"error": str(e)}
|
return {"error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
# Load memory data from file, or use default dataset if not loaded yet
|
# Load memory data from file, or use default dataset if not loaded yet
|
||||||
def load_memory():
|
def load_memory():
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
# Try to load data from MEMORY_FILE
|
# Try to load data from MEMORY_FILE
|
||||||
try:
|
try:
|
||||||
with open(MEMORY_FILE, "r") as f:
|
with open(settings["bot"]["active_memory"], "r") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
# Save memory data to MEMORY_FILE
|
# Save memory data to MEMORY_FILE
|
||||||
def save_memory(memory):
|
def save_memory(memory):
|
||||||
with open(MEMORY_FILE, "w") as f:
|
with open(settings["bot"]["active_memory"], "w") as f:
|
||||||
json.dump(memory, f, indent=4)
|
json.dump(memory, f, indent=4)
|
||||||
|
|
||||||
def train_markov_model(memory, additional_data=None):
|
|
||||||
|
def train_markov_model(memory, additional_data=None) -> markovify.NewlineText | None:
|
||||||
if not memory:
|
if not memory:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
filtered_memory = [line for line in memory if isinstance(line, str)]
|
filtered_memory = [line for line in memory if isinstance(line, str)]
|
||||||
if additional_data:
|
if additional_data:
|
||||||
filtered_memory.extend(line for line in additional_data if isinstance(line, str))
|
filtered_memory.extend(
|
||||||
|
line for line in additional_data if isinstance(line, str)
|
||||||
|
)
|
||||||
|
|
||||||
if not filtered_memory:
|
if not filtered_memory:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
text = "\n".join(filtered_memory)
|
text = "\n".join(filtered_memory)
|
||||||
model = markovify.NewlineText(text, state_size=2)
|
model = markovify.NewlineText(text, state_size=2)
|
||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
# Save the Markov model to a pickle file
|
# Save the Markov model to a pickle file
|
||||||
def save_markov_model(model, filename='markov_model.pkl'):
|
def save_markov_model(model, filename="markov_model.pkl"):
|
||||||
with open(filename, 'wb') as f:
|
with open(filename, "wb") as f:
|
||||||
pickle.dump(model, f)
|
pickle.dump(model, f)
|
||||||
logger.info(f"Markov model saved to {filename}.")
|
logger.info(f"Markov model saved to {filename}.")
|
||||||
|
|
||||||
|
|
||||||
# Load the Markov model from a pickle file
|
# Load the Markov model from a pickle file
|
||||||
def load_markov_model(filename='markov_model.pkl'):
|
def load_markov_model(filename="markov_model.pkl"):
|
||||||
try:
|
try:
|
||||||
with open(filename, 'rb') as f:
|
with open(filename, "rb") as f:
|
||||||
model = pickle.load(f)
|
model = pickle.load(f)
|
||||||
logger.info(f"{_('model_loaded')} {filename}.{RESET}")
|
logger.info(f"{k.model_loaded()} {filename}.{RESET}")
|
||||||
return model
|
return model
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger.error(f"{filename} {_('not_found')}{RESET}")
|
logger.error(f"{filename} {k.not_found()}{RESET}")
|
||||||
return None
|
return None
|
36
modules/permission.py
Normal file
36
modules/permission.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
from functools import wraps
|
||||||
|
import discord
|
||||||
|
|
||||||
|
import discord.ext
|
||||||
|
import discord.ext.commands
|
||||||
|
|
||||||
|
from modules.settings import instance as settings_manager
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("goober")
|
||||||
|
|
||||||
|
settings = settings_manager.settings
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def requires_admin():
|
||||||
|
async def wrapper(ctx: discord.ext.commands.Context):
|
||||||
|
if ctx.author.id not in settings["bot"]["owner_ids"]:
|
||||||
|
await ctx.send(
|
||||||
|
"You don't have the necessary permissions to run this command!"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
command = ctx.command
|
||||||
|
if not command:
|
||||||
|
logger.info(f"Unknown command ran {ctx.message}")
|
||||||
|
else:
|
||||||
|
logger.info(
|
||||||
|
f'Command {settings["bot"]["prefix"]}{command.name} @{ctx.author.name}'
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return discord.ext.commands.check(wrapper)
|
|
@ -1,5 +1,4 @@
|
||||||
from modules.globalvars import *
|
from modules.globalvars import *
|
||||||
from modules.volta.main import _, check_missing_translations
|
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
@ -11,6 +10,12 @@ import re
|
||||||
from spacy.util import is_package
|
from spacy.util import is_package
|
||||||
import importlib.metadata
|
import importlib.metadata
|
||||||
import logging
|
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")
|
logger = logging.getLogger("goober")
|
||||||
|
|
||||||
|
@ -21,7 +26,8 @@ try:
|
||||||
import psutil
|
import psutil
|
||||||
except ImportError:
|
except ImportError:
|
||||||
psutilavaliable = False
|
psutilavaliable = False
|
||||||
logger.error(_('missing_requests_psutil'))
|
logger.error(k.missing_requests_psutil())
|
||||||
|
|
||||||
|
|
||||||
def check_for_model():
|
def check_for_model():
|
||||||
if is_package("en_core_web_sm"):
|
if is_package("en_core_web_sm"):
|
||||||
|
@ -34,75 +40,83 @@ def iscloned():
|
||||||
if os.path.exists(".git"):
|
if os.path.exists(".git"):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logger.error(f"{_('not_cloned')}")
|
logger.error(f"{k.not_cloned()}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def get_stdlib_modules():
|
def get_stdlib_modules():
|
||||||
stdlib_path = pathlib.Path(sysconfig.get_paths()['stdlib'])
|
stdlib_path = pathlib.Path(sysconfig.get_paths()["stdlib"])
|
||||||
modules = set()
|
modules = set()
|
||||||
if hasattr(sys, 'builtin_module_names'):
|
if hasattr(sys, "builtin_module_names"):
|
||||||
modules.update(sys.builtin_module_names)
|
modules.update(sys.builtin_module_names)
|
||||||
for file in stdlib_path.glob('*.py'):
|
for file in stdlib_path.glob("*.py"):
|
||||||
if file.stem != '__init__':
|
if file.stem != "__init__":
|
||||||
modules.add(file.stem)
|
modules.add(file.stem)
|
||||||
for folder in stdlib_path.iterdir():
|
for folder in stdlib_path.iterdir():
|
||||||
if folder.is_dir() and (folder / '__init__.py').exists():
|
if folder.is_dir() and (folder / "__init__.py").exists():
|
||||||
modules.add(folder.name)
|
modules.add(folder.name)
|
||||||
for file in stdlib_path.glob('*.*'):
|
for file in stdlib_path.glob("*.*"):
|
||||||
if file.suffix in ('.so', '.pyd'):
|
if file.suffix in (".so", ".pyd"):
|
||||||
modules.add(file.stem)
|
modules.add(file.stem)
|
||||||
|
|
||||||
return modules
|
return modules
|
||||||
|
|
||||||
|
|
||||||
def check_requirements():
|
def check_requirements():
|
||||||
STD_LIB_MODULES = get_stdlib_modules()
|
STD_LIB_MODULES = get_stdlib_modules()
|
||||||
PACKAGE_ALIASES = {
|
PACKAGE_ALIASES = {
|
||||||
"discord": "discord.py",
|
"discord": "discord.py",
|
||||||
"better_profanity": "better-profanity",
|
"better_profanity": "better-profanity",
|
||||||
"dotenv": "python-dotenv",
|
"dotenv": "python-dotenv",
|
||||||
"pil": "pillow"
|
"pil": "pillow",
|
||||||
|
"websocket": "websocket-client"
|
||||||
}
|
}
|
||||||
|
|
||||||
parent_dir = os.path.dirname(os.path.abspath(__file__))
|
parent_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
requirements_path = os.path.abspath(os.path.join(parent_dir, '..', 'requirements.txt'))
|
requirements_path = os.path.abspath(
|
||||||
|
os.path.join(parent_dir, "..", "requirements.txt")
|
||||||
|
)
|
||||||
|
|
||||||
if not os.path.exists(requirements_path):
|
if not os.path.exists(requirements_path):
|
||||||
logger.error(f"{(_('requirements_not_found')).format(path=requirements_path)}")
|
logger.error(f"{k.requirements_not_found(path=requirements_path)}")
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(requirements_path, 'r') as f:
|
with open(requirements_path, "r") as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
requirements = set()
|
requirements = set()
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line and not line.startswith('#'):
|
if line and not line.startswith("#"):
|
||||||
base_pkg = line.split('==')[0].lower()
|
base_pkg = line.split("==")[0].lower()
|
||||||
aliased_pkg = PACKAGE_ALIASES.get(base_pkg, base_pkg)
|
aliased_pkg = PACKAGE_ALIASES.get(base_pkg, base_pkg)
|
||||||
requirements.add(aliased_pkg)
|
requirements.add(aliased_pkg)
|
||||||
|
|
||||||
installed_packages = {dist.metadata['Name'].lower() for dist in importlib.metadata.distributions()}
|
installed_packages = {
|
||||||
|
dist.metadata["Name"].lower() for dist in importlib.metadata.distributions()
|
||||||
|
}
|
||||||
missing = []
|
missing = []
|
||||||
|
|
||||||
for req in sorted(requirements):
|
for req in sorted(requirements):
|
||||||
if req in STD_LIB_MODULES or req == 'modules':
|
if req in STD_LIB_MODULES or req == "modules":
|
||||||
print((_('std_lib_local_skipped')).format(package=req))
|
print(k.std_lib_local_skipped(package=req))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
check_name = req.lower()
|
check_name = req.lower()
|
||||||
|
|
||||||
if check_name in installed_packages:
|
if check_name in installed_packages:
|
||||||
logger.info(f"{_('ok_installed').format(package=check_name)} {check_name}")
|
logger.info(f"{k.ok_installed()} {check_name}")
|
||||||
else:
|
else:
|
||||||
logger.error(f"{(_('missing_package')).format(package=check_name)} {check_name} {(_('missing_package2'))}")
|
logger.error(f"{k.missing_package()} {check_name} {k.missing_package2()}")
|
||||||
missing.append(check_name)
|
missing.append(check_name)
|
||||||
|
|
||||||
if missing:
|
if missing:
|
||||||
logger.error(_('missing_packages_detected'))
|
logger.error(k.missing_packages_detected())
|
||||||
for pkg in missing:
|
for pkg in missing:
|
||||||
print(f" - {pkg}")
|
print(f" - {pkg}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
logger.info(_('all_requirements_satisfied'))
|
logger.info(k.all_requirements_satisfied())
|
||||||
|
|
||||||
|
|
||||||
def check_latency():
|
def check_latency():
|
||||||
host = "1.1.1.1"
|
host = "1.1.1.1"
|
||||||
|
@ -122,26 +136,24 @@ def check_latency():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
cmd,
|
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
match = re.search(latency_pattern, result.stdout)
|
match = re.search(latency_pattern, result.stdout)
|
||||||
if match:
|
if match:
|
||||||
latency_ms = float(match.group(1))
|
latency_ms = float(match.group(1))
|
||||||
logger.info((_('ping_to')).format(host=host, latency=latency_ms))
|
logger.info(k.ping_to(host=host, latency=latency_ms))
|
||||||
if latency_ms > 300:
|
if latency_ms > 300:
|
||||||
logger.warning(f"{(_('high_latency'))}")
|
logger.warning(f"{k.high_latency()}")
|
||||||
else:
|
else:
|
||||||
logger.warning((_('could_not_parse_latency')))
|
logger.warning(k.could_not_parse_latency())
|
||||||
else:
|
else:
|
||||||
print(result.stderr)
|
print(result.stderr)
|
||||||
logger.error(f"{(_('ping_failed')).format(host=host)}{RESET}")
|
logger.error(f"{k.ping_failed(host=host)}{RESET}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error((_('error_running_ping')).format(error=e))
|
logger.error(k.error_running_ping(error=e))
|
||||||
|
|
||||||
|
|
||||||
def check_memory():
|
def check_memory():
|
||||||
if psutilavaliable == False:
|
if psutilavaliable == False:
|
||||||
|
@ -152,52 +164,75 @@ def check_memory():
|
||||||
used_memory = memory_info.used / (1024**3)
|
used_memory = memory_info.used / (1024**3)
|
||||||
free_memory = memory_info.available / (1024**3)
|
free_memory = memory_info.available / (1024**3)
|
||||||
|
|
||||||
logger.info((_('memory_usage')).format(used=used_memory, total=total_memory, percent=(used_memory / total_memory) * 100))
|
logger.info(
|
||||||
|
k.memory_usage(
|
||||||
|
used=used_memory,
|
||||||
|
total=total_memory,
|
||||||
|
percent=(used_memory / total_memory) * 100,
|
||||||
|
)
|
||||||
|
)
|
||||||
if used_memory > total_memory * 0.9:
|
if used_memory > total_memory * 0.9:
|
||||||
print(f"{YELLOW}{(_('memory_above_90')).format(percent=(used_memory / total_memory) * 100)}{RESET}")
|
print(
|
||||||
logger.info((_('total_memory')).format(total=total_memory))
|
f"{YELLOW}{k.memory_above_90(percent=(used_memory / total_memory) * 100)}{RESET}"
|
||||||
logger.info((_('used_memory')).format(used=used_memory))
|
)
|
||||||
|
logger.info(k.total_memory(total=total_memory))
|
||||||
|
logger.info(k.used_memory(used=used_memory))
|
||||||
if free_memory < 1:
|
if free_memory < 1:
|
||||||
logger.warning(f"{(_('low_free_memory')).format(free=free_memory)}")
|
logger.warning(f"{k.low_free_memory(free=free_memory)}")
|
||||||
sys.exit(1)
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.error(_('psutil_not_installed')) # todo: translate this into italian and put it in the translations "psutil is not installed. Memory check skipped."
|
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():
|
def check_cpu():
|
||||||
if psutilavaliable == False:
|
if psutilavaliable == False:
|
||||||
return
|
return
|
||||||
logger.info((_('measuring_cpu')))
|
logger.info(k.measuring_cpu())
|
||||||
cpu_per_core = psutil.cpu_percent(interval=1, percpu=True) # type: ignore
|
cpu_per_core = psutil.cpu_percent(interval=1, percpu=True) # type: ignore
|
||||||
total_cpu = sum(cpu_per_core) / len(cpu_per_core)
|
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:
|
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:
|
if total_cpu > 95:
|
||||||
logger.error(_('really_high_cpu'))
|
logger.error(k.really_high_cpu())
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def check_memoryjson():
|
def check_memoryjson():
|
||||||
try:
|
try:
|
||||||
logger.info((_('memory_file')).format(size=os.path.getsize(MEMORY_FILE) / (1024 ** 2)))
|
logger.info(
|
||||||
if os.path.getsize(MEMORY_FILE) > 1_073_741_824:
|
k.memory_file(
|
||||||
logger.warning(f"{(_('memory_file_large'))}")
|
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:
|
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)
|
json.load(f)
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
logger.error(f"{(_('memory_file_corrupted')).format(error=e)}")
|
logger.error(f"{k.memory_file_corrupted(error=e)}")
|
||||||
logger.warning(f"{(_('consider_backup_memory'))}")
|
logger.warning(f"{k.consider_backup_memory()}")
|
||||||
|
|
||||||
except UnicodeDecodeError as e:
|
except UnicodeDecodeError as e:
|
||||||
logger.error(f"{(_('memory_file_encoding')).format(error=e)}")
|
logger.error(f"{k.memory_file_encoding(error=e)}")
|
||||||
logger.warning(f"{(_('consider_backup_memory'))}")
|
logger.warning(f"{k.consider_backup_memory()}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{(_('error_reading_memory')).format(error=e)}")
|
logger.error(f"{k.error_reading_memory(error=e)}")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger(f"{(_('memory_file_not_found'))}")
|
logger.info(f"{k.memory_file_not_found()}")
|
||||||
|
|
||||||
|
|
||||||
def presskey2skip(timeout):
|
def presskey2skip(timeout):
|
||||||
if os.name == 'nt':
|
if os.name == "nt":
|
||||||
import msvcrt
|
import msvcrt
|
||||||
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
while True:
|
while True:
|
||||||
if msvcrt.kbhit():
|
if msvcrt.kbhit():
|
||||||
|
@ -226,30 +261,44 @@ def presskey2skip(timeout):
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
finally:
|
finally:
|
||||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
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
|
beta = beta
|
||||||
|
|
||||||
|
|
||||||
def start_checks():
|
def start_checks():
|
||||||
if CHECKS_DISABLED == "True":
|
if settings["disable_checks"]:
|
||||||
logger.warning(f"{(_('checks_disabled'))}")
|
logger.warning(f"{k.checks_disabled()}")
|
||||||
return
|
return
|
||||||
logger.info(_('running_prestart_checks'))
|
|
||||||
|
logger.info(k.running_prestart_checks())
|
||||||
check_for_model()
|
check_for_model()
|
||||||
iscloned()
|
iscloned()
|
||||||
check_missing_translations()
|
|
||||||
check_requirements()
|
check_requirements()
|
||||||
check_latency()
|
check_latency()
|
||||||
check_memory()
|
check_memory()
|
||||||
check_memoryjson()
|
check_memoryjson()
|
||||||
check_cpu()
|
check_cpu()
|
||||||
|
check_synchub()
|
||||||
if os.path.exists(".env"):
|
if os.path.exists(".env"):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
logger.warning(f"{(_('env_file_not_found'))}")
|
logger.warning(f"{k.env_file_not_found()}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if beta == True:
|
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:
|
else:
|
||||||
pass
|
pass
|
||||||
logger.info(_('continuing_in_seconds').format(seconds=5))
|
logger.info(k.continuing_in_seconds(seconds=5))
|
||||||
presskey2skip(timeout=5)
|
presskey2skip(timeout=5)
|
||||||
os.system('cls' if os.name == 'nt' else 'clear')
|
os.system("cls" if os.name == "nt" else "clear")
|
||||||
print(splashtext)
|
|
||||||
|
with open(settings["splash_text_loc"], "r") as f:
|
||||||
|
print("".join(f.readlines()))
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import re
|
import re
|
||||||
|
import discord.ext
|
||||||
|
import discord.ext.commands
|
||||||
from modules.globalvars import *
|
from modules.globalvars import *
|
||||||
from modules.volta.main import _
|
|
||||||
|
|
||||||
import spacy
|
import spacy
|
||||||
from spacy.tokens import Doc
|
from spacy.tokens import Doc
|
||||||
from spacytextblob.spacytextblob import SpacyTextBlob
|
from spacytextblob.spacytextblob import SpacyTextBlob
|
||||||
|
import discord
|
||||||
|
import modules.keys as k
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger("goober")
|
logger = logging.getLogger("goober")
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,12 +17,13 @@ def check_resources():
|
||||||
try:
|
try:
|
||||||
nlp = spacy.load("en_core_web_sm")
|
nlp = spacy.load("en_core_web_sm")
|
||||||
except OSError:
|
except OSError:
|
||||||
logging.critical((_('spacy_model_not_found')))
|
logging.critical(k.spacy_model_not_found())
|
||||||
spacy.cli.download("en_core_web_sm")
|
spacy.cli.download("en_core_web_sm") # type: ignore
|
||||||
nlp = spacy.load("en_core_web_sm")
|
nlp = spacy.load("en_core_web_sm")
|
||||||
if "spacytextblob" not in nlp.pipe_names:
|
if "spacytextblob" not in nlp.pipe_names:
|
||||||
nlp.add_pipe("spacytextblob")
|
nlp.add_pipe("spacytextblob")
|
||||||
logger.info((_('spacy_initialized')))
|
logger.info(k.spacy_initialized())
|
||||||
|
|
||||||
|
|
||||||
check_resources()
|
check_resources()
|
||||||
|
|
||||||
|
@ -27,52 +31,63 @@ nlp = spacy.load("en_core_web_sm")
|
||||||
nlp.add_pipe("spacytextblob")
|
nlp.add_pipe("spacytextblob")
|
||||||
Doc.set_extension("polarity", getter=lambda doc: doc._.blob.polarity)
|
Doc.set_extension("polarity", getter=lambda doc: doc._.blob.polarity)
|
||||||
|
|
||||||
|
|
||||||
def is_positive(sentence):
|
def is_positive(sentence):
|
||||||
doc = nlp(sentence)
|
doc = nlp(sentence)
|
||||||
sentiment_score = doc._.polarity # from spacytextblob
|
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)
|
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: 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
|
||||||
|
|
||||||
async def send_message(ctx, message=None, embed=None, file=None, edit=False, message_reference=None):
|
|
||||||
if edit and message_reference:
|
if edit and message_reference:
|
||||||
try:
|
try:
|
||||||
await message_reference.edit(content=message, embed=embed)
|
await message_reference.edit(content=message, embed=embed)
|
||||||
|
return message_reference
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await ctx.send(f"{RED}{(_('edit_fail'))} {e}{RESET}")
|
await ctx.send(f"{k.edit_fail()} {e}")
|
||||||
else:
|
return None
|
||||||
if hasattr(ctx, "respond"):
|
|
||||||
sent_message = None
|
|
||||||
if embed:
|
if embed:
|
||||||
sent_message = await ctx.respond(embed=embed, ephemeral=False)
|
sent_message = await ctx.send(embed=embed, content=message)
|
||||||
elif message:
|
elif file:
|
||||||
sent_message = await ctx.respond(message, ephemeral=False)
|
sent_message = await ctx.send(file=file, content=message)
|
||||||
if file:
|
|
||||||
sent_message = await ctx.respond(file=file, ephemeral=False)
|
|
||||||
else:
|
else:
|
||||||
sent_message = None
|
sent_message = await ctx.send(content=message)
|
||||||
if embed:
|
|
||||||
sent_message = await ctx.send(embed=embed)
|
|
||||||
elif message:
|
|
||||||
sent_message = await ctx.send(message)
|
|
||||||
if file:
|
|
||||||
sent_message = await ctx.send(file=file)
|
|
||||||
return sent_message
|
return sent_message
|
||||||
|
|
||||||
|
|
||||||
def append_mentions_to_18digit_integer(message):
|
def append_mentions_to_18digit_integer(message):
|
||||||
pattern = r'\b\d{18}\b'
|
pattern = r"\b\d{18}\b"
|
||||||
return re.sub(pattern, lambda match: "", message)
|
return re.sub(pattern, lambda match: "", message)
|
||||||
|
|
||||||
|
|
||||||
def preprocess_message(message):
|
def preprocess_message(message):
|
||||||
message = append_mentions_to_18digit_integer(message)
|
message = append_mentions_to_18digit_integer(message)
|
||||||
doc = nlp(message)
|
doc = nlp(message)
|
||||||
tokens = [token.text for token in doc if token.is_alpha or token.is_digit]
|
tokens = [token.text for token in doc if token.is_alpha or token.is_digit]
|
||||||
return " ".join(tokens)
|
return " ".join(tokens)
|
||||||
|
|
||||||
|
|
||||||
def improve_sentence_coherence(sentence):
|
def improve_sentence_coherence(sentence):
|
||||||
return re.sub(r'\bi\b', 'I', sentence)
|
return re.sub(r"\bi\b", "I", sentence)
|
||||||
|
|
||||||
|
|
||||||
def rephrase_for_coherence(sentence):
|
def rephrase_for_coherence(sentence):
|
||||||
words = sentence.split()
|
words = sentence.split()
|
||||||
|
|
155
modules/settings.py
Normal file
155
modules/settings.py
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
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
|
||||||
|
activity: Activity
|
||||||
|
positive_gifs: List[str]
|
||||||
|
block_profanity: bool
|
||||||
|
|
||||||
|
|
||||||
|
class BotSettings(TypedDict):
|
||||||
|
prefix: str
|
||||||
|
owner_ids: List[int]
|
||||||
|
blacklisted_users: List[int]
|
||||||
|
user_training: bool
|
||||||
|
allow_show_mem_command: bool
|
||||||
|
react_to_messages: bool
|
||||||
|
misc: MiscBotOptions
|
||||||
|
enabled_cogs: List[str]
|
||||||
|
active_memory: str
|
||||||
|
sync_hub: SyncHub
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsType(TypedDict):
|
||||||
|
bot: BotSettings
|
||||||
|
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:
|
||||||
|
global instance
|
||||||
|
instance = self
|
||||||
|
|
||||||
|
self.path: str = os.path.join(".", "settings", "settings.json")
|
||||||
|
|
||||||
|
if not os.path.exists(self.path):
|
||||||
|
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: dict = json.load(f)
|
||||||
|
|
||||||
|
self.settings = SettingsType(self.__kv_store) # type: ignore
|
||||||
|
self.original_settings = copy.deepcopy(self.settings)
|
||||||
|
|
||||||
|
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, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
self.original_settings = self.settings
|
||||||
|
|
||||||
|
def discard(self) -> None:
|
||||||
|
self.settings = self.original_settings
|
||||||
|
|
||||||
|
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()
|
94
modules/sync_conenctor.py
Normal file
94
modules/sync_conenctor.py
Normal file
|
@ -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"])
|
|
@ -1,25 +1,29 @@
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
import os
|
import os
|
||||||
from modules.globalvars import RED, RESET, splashtext
|
from modules.settings import instance as settings_manager
|
||||||
from modules.volta.main import _
|
import logging
|
||||||
|
from modules.globalvars import RED, RESET
|
||||||
|
import modules.keys as k
|
||||||
|
|
||||||
|
settings = settings_manager.settings
|
||||||
|
logger = logging.getLogger("goober")
|
||||||
|
|
||||||
|
|
||||||
def handle_exception(exc_type, exc_value, exc_traceback, *, context=None):
|
def handle_exception(exc_type, exc_value, exc_traceback, *, context=None):
|
||||||
os.system('cls' if os.name == 'nt' else 'clear')
|
os.system("cls" if os.name == "nt" else "clear")
|
||||||
|
|
||||||
if issubclass(exc_type, KeyboardInterrupt):
|
if issubclass(exc_type, KeyboardInterrupt):
|
||||||
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
||||||
return
|
return
|
||||||
|
|
||||||
print(splashtext)
|
with open(settings["splash_text_loc"], "r") as f:
|
||||||
|
print("".join(f.readlines()))
|
||||||
|
|
||||||
print(f"{RED}=====BEGINNING OF TRACEBACK====={RESET}")
|
print(f"{RED}=====BEGINNING OF TRACEBACK====={RESET}")
|
||||||
traceback.print_exception(exc_type, exc_value, exc_traceback)
|
traceback.print_exception(exc_type, exc_value, exc_traceback)
|
||||||
print(f"{RED}========END OF TRACEBACK========{RESET}")
|
print(f"{RED}========END OF TRACEBACK========{RESET}")
|
||||||
print(f"{RED}{_('unhandled_exception')}{RESET}")
|
print(f"{RED}{k.unhandled_exception()}{RESET}")
|
||||||
|
|
||||||
|
|
||||||
if context:
|
if context:
|
||||||
print(f"{RED}Context: {context}{RESET}")
|
print(f"{RED}Context: {context}{RESET}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
# Automatically update the local repository if the remote is ahead
|
|
||||||
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():
|
|
||||||
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 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
|
|
|
@ -1,207 +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 time
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
env_path = find_dotenv(pathlib.Path(__file__).parent)
|
|
||||||
if env_path:
|
|
||||||
load_dotenv(dotenv_path=env_path)
|
|
||||||
print(f"[VOLTA] {GREEN}Loaded .env from {env_path}{RESET}")
|
|
||||||
else:
|
|
||||||
print(f"[VOLTA] {YELLOW}No .env file found from {__file__} upwards.{RESET}")
|
|
||||||
|
|
||||||
locales_dirs.extend(find_locales_dirs(module_dir))
|
|
||||||
if working_dir != module_dir:
|
|
||||||
locales_dirs.extend(find_locales_dirs(working_dir))
|
|
||||||
|
|
||||||
translations = {}
|
|
||||||
_file_mod_times = {}
|
|
||||||
|
|
||||||
import locale
|
|
||||||
import platform
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def get_system_locale():
|
|
||||||
system = platform.system() # fallback incase locale isnt set
|
|
||||||
if system == "Windows":
|
|
||||||
lang, _ = locale.getdefaultlocale()
|
|
||||||
return lang or os.getenv("LANG")
|
|
||||||
elif system == "Darwin":
|
|
||||||
try:
|
|
||||||
import subprocess
|
|
||||||
result = subprocess.run(
|
|
||||||
["defaults", "read", "-g", "AppleLocale"],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.DEVNULL,
|
|
||||||
text=True
|
|
||||||
)
|
|
||||||
return result.stdout.strip() or locale.getdefaultlocale()[0]
|
|
||||||
except Exception:
|
|
||||||
return locale.getdefaultlocale()[0]
|
|
||||||
elif system == "Linux":
|
|
||||||
return (
|
|
||||||
os.getenv("LC_ALL") or
|
|
||||||
os.getenv("LANG") or
|
|
||||||
locale.getdefaultlocale()[0]
|
|
||||||
)
|
|
||||||
return locale.getdefaultlocale()[0]
|
|
||||||
|
|
||||||
|
|
||||||
def load_translations():
|
|
||||||
global translations, _file_mod_times
|
|
||||||
translations.clear()
|
|
||||||
_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}")
|
|
||||||
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
|
|
||||||
|
|
||||||
def check_missing_translations():
|
|
||||||
global LOCALE, ENGLISH_MISSING
|
|
||||||
load_translations()
|
|
||||||
if FALLBACK_LOCALE not in translations:
|
|
||||||
print(f"[VOLTA] {RED}Fallback translations ({FALLBACK_LOCALE}.json) missing from assets/locales.{RESET}")
|
|
||||||
ENGLISH_MISSING = True
|
|
||||||
return
|
|
||||||
if LOCALE == "en":
|
|
||||||
print("[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
|
|
||||||
|
|
||||||
def get_translation(lang: str, key: str):
|
|
||||||
global printedsystemfallback
|
|
||||||
if ENGLISH_MISSING:
|
|
||||||
return f"[VOLTA] {RED}No fallback available!{RESET}"
|
|
||||||
fallback_translations = translations.get(FALLBACK_LOCALE, {})
|
|
||||||
sys_lang = get_system_locale().split("_")[0] if get_system_locale() else None
|
|
||||||
sys_translations = translations.get(sys_lang, {}) if sys_lang else {}
|
|
||||||
lang_translations = translations.get(lang, {})
|
|
||||||
if key in lang_translations:
|
|
||||||
return lang_translations[key]
|
|
||||||
if sys_lang and sys_lang != lang and key in sys_translations:
|
|
||||||
if not printedsystemfallback:
|
|
||||||
print(f"[VOLTA] {YELLOW}Falling back to system language {sys_lang}!{RESET}")
|
|
||||||
printedsystemfallback = True
|
|
||||||
return sys_translations[key]
|
|
||||||
if key in fallback_translations:
|
|
||||||
print(f"[VOLTA] {YELLOW}Missing key: '{key}' in '{lang}', falling back to fallback locale '{FALLBACK_LOCALE}'{RESET}")
|
|
||||||
return fallback_translations[key]
|
|
||||||
return f"[VOLTA] {YELLOW}Missing key: '{key}' in all locales!{RESET}"
|
|
||||||
|
|
||||||
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__':
|
|
||||||
print("Volta should not be run directly! Please use it as a module..")
|
|
51
replace_volta.py
Normal file
51
replace_volta.py
Normal file
|
@ -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"""
|
||||||
|
(?<!\w) # not part of a variable name
|
||||||
|
\(? # optional opening (
|
||||||
|
_\(\s*'([a-zA-Z0-9_]+)'\s*\) # k.key()
|
||||||
|
\)? # optional closing )
|
||||||
|
(?:\.format\((.*?)\))? # optional .format(...)
|
||||||
|
""",
|
||||||
|
re.VERBOSE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def fix_content(content):
|
||||||
|
def repl(match):
|
||||||
|
key = match.group(1)
|
||||||
|
args = match.group(2)
|
||||||
|
if args:
|
||||||
|
return f"k.{key}({args})"
|
||||||
|
else:
|
||||||
|
return f"k.{key}()"
|
||||||
|
|
||||||
|
return pattern.sub(repl, content)
|
||||||
|
|
||||||
|
|
||||||
|
# File types we sweepin 🧹
|
||||||
|
file_exts = [".py", ".html", ".txt", ".js"]
|
||||||
|
|
||||||
|
for subdir, _, files in os.walk(folder_path):
|
||||||
|
for file in files:
|
||||||
|
if any(file.endswith(ext) for ext in file_exts):
|
||||||
|
path = os.path.join(subdir, file)
|
||||||
|
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
original = f.read()
|
||||||
|
|
||||||
|
updated = fix_content(original)
|
||||||
|
|
||||||
|
if original != updated:
|
||||||
|
print(f"🛠️ Fixed: {path}")
|
||||||
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(updated)
|
||||||
|
|
||||||
|
print(
|
||||||
|
"🚀💥 ALL cleaned. No `_('...')` left on road — now it’s k.dot or nothin fam 😎🔫"
|
||||||
|
)
|
|
@ -8,3 +8,6 @@ better_profanity
|
||||||
python-dotenv
|
python-dotenv
|
||||||
dotenv
|
dotenv
|
||||||
pillow
|
pillow
|
||||||
|
watchdog
|
||||||
|
py-cpuinfo
|
||||||
|
websocket-client
|
44
settings/settings.example.json
Normal file
44
settings/settings.example.json
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"bot": {
|
||||||
|
"prefix": "o.",
|
||||||
|
"owner_ids": [
|
||||||
|
642441889181728810
|
||||||
|
],
|
||||||
|
"blacklisted_users": [
|
||||||
|
1391805740716527666
|
||||||
|
],
|
||||||
|
"user_training": true,
|
||||||
|
"allow_show_mem_command": true,
|
||||||
|
"react_to_messages": true,
|
||||||
|
"misc": {
|
||||||
|
"ping_line": "The Beretta fires fast and won't make you feel any better!",
|
||||||
|
"positive_gifs": [
|
||||||
|
"https://tenor.com/view/i-want-a-divorce-dissolution-annulment-seperation-break-up-gif-25753155"
|
||||||
|
],
|
||||||
|
"block_profanity": false,
|
||||||
|
"activity": {
|
||||||
|
"content": "Rakas - Haloo Helsinki",
|
||||||
|
"type": "listening"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active_memory": "memory.json",
|
||||||
|
"enabled_cogs": [
|
||||||
|
"pulse",
|
||||||
|
"breaking_news"
|
||||||
|
],
|
||||||
|
"sync_hub": {
|
||||||
|
"url": "ws://goober.frii.site",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"locale": "en",
|
||||||
|
"name": "gubert",
|
||||||
|
"auto_update": true,
|
||||||
|
"disable_checks": false,
|
||||||
|
"splash_text_loc": "settings/splash.txt",
|
||||||
|
"cog_settings": {
|
||||||
|
"breaking_news": {
|
||||||
|
"create_from_message_content": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
settings/splash.txt
Normal file
11
settings/splash.txt
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
SS\
|
||||||
|
SS |
|
||||||
|
SSSSSS\ SSSSSS\ SSSSSS\ SSSSSSS\ SSSSSS\ SSSSSS\
|
||||||
|
SS __SS\ SS __SS\ SS __SS\ SS __SS\ SS __SS\ SS __SS\
|
||||||
|
SS / SS |SS / SS |SS / SS |SS | SS |SSSSSSSS |SS | \__|
|
||||||
|
SS | SS |SS | SS |SS | SS |SS | SS |SS ____|SS |
|
||||||
|
\SSSSSSS |\SSSSSS |\SSSSSS |SSSSSSS |\SSSSSSS\ SS |
|
||||||
|
\____SS | \______/ \______/ \_______/ \_______|\__|
|
||||||
|
SS\ SS |
|
||||||
|
\SSSSSS |
|
||||||
|
\______/
|
16
updater.py
Normal file
16
updater.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger("goober")
|
||||||
|
|
||||||
|
def force_update() -> 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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue