cleared out the last of the image command
Before Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 295 KiB |
Before Width: | Height: | Size: 155 KiB |
Before Width: | Height: | Size: 746 KiB |
Before Width: | Height: | Size: 355 KiB |
Before Width: | Height: | Size: 275 KiB |
Before Width: | Height: | Size: 1.7 MiB |
88
main.py
|
@ -49,7 +49,6 @@ from modules.markovmemory import *
|
||||||
from modules.version 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
|
|
||||||
sys.excepthook = handle_exception
|
sys.excepthook = handle_exception
|
||||||
check_for_update()
|
check_for_update()
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
@ -216,59 +215,6 @@ async def ramusage(ctx):
|
||||||
mem = process.memory_info().rss
|
mem = process.memory_info().rss
|
||||||
await send_message(ctx, f"{mem / 1024 / 1024:.2f} MB")
|
await send_message(ctx, f"{mem / 1024 / 1024:.2f} MB")
|
||||||
|
|
||||||
@bot.hybrid_command(description=f"{(_('command_desc_help'))}")
|
|
||||||
async def impact(ctx: commands.Context, text: Optional[str] = None) -> None:
|
|
||||||
assets_folder = "assets/images"
|
|
||||||
|
|
||||||
def get_random_asset_image() -> Optional[str]:
|
|
||||||
images = [f for f in os.listdir(assets_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp'))]
|
|
||||||
if not images:
|
|
||||||
return None
|
|
||||||
return os.path.join(assets_folder, random.choice(images))
|
|
||||||
|
|
||||||
temp_input = None
|
|
||||||
input_path = None
|
|
||||||
|
|
||||||
# Determine input image path
|
|
||||||
if ctx.message.attachments:
|
|
||||||
attachment = ctx.message.attachments[0]
|
|
||||||
if attachment.content_type and attachment.content_type.startswith("image/"):
|
|
||||||
ext = os.path.splitext(attachment.filename)[1]
|
|
||||||
temp_input = f"tempy{ext}"
|
|
||||||
await attachment.save(temp_input)
|
|
||||||
input_path = temp_input
|
|
||||||
else:
|
|
||||||
fallback_image = get_random_asset_image()
|
|
||||||
if fallback_image is None:
|
|
||||||
await ctx.reply(_('no_image_available'))
|
|
||||||
return
|
|
||||||
temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1])
|
|
||||||
shutil.copy(fallback_image, temp_input)
|
|
||||||
input_path = temp_input
|
|
||||||
else:
|
|
||||||
fallback_image = get_random_asset_image()
|
|
||||||
if fallback_image is None:
|
|
||||||
await ctx.reply(_('no_image_available'))
|
|
||||||
return
|
|
||||||
temp_input = tempfile.mktemp(suffix=os.path.splitext(fallback_image)[1])
|
|
||||||
shutil.copy(fallback_image, temp_input)
|
|
||||||
input_path = temp_input
|
|
||||||
|
|
||||||
# Generate meme image with one-shot text generation
|
|
||||||
output_path = await gen_meme(input_path, custom_text=text)
|
|
||||||
|
|
||||||
if output_path is None or not os.path.isfile(output_path):
|
|
||||||
if temp_input and os.path.exists(temp_input):
|
|
||||||
os.remove(temp_input)
|
|
||||||
await ctx.reply(_('failed_generate_image'))
|
|
||||||
return
|
|
||||||
|
|
||||||
await ctx.send(file=discord.File(output_path))
|
|
||||||
|
|
||||||
if temp_input and os.path.exists(temp_input):
|
|
||||||
os.remove(temp_input)
|
|
||||||
|
|
||||||
|
|
||||||
bot.remove_command('help')
|
bot.remove_command('help')
|
||||||
# Command: Show help information
|
# Command: Show help information
|
||||||
@bot.hybrid_command(description=f"{(_('command_desc_help'))}")
|
@bot.hybrid_command(description=f"{(_('command_desc_help'))}")
|
||||||
|
@ -370,12 +316,10 @@ async def ping(ctx: commands.Context) -> None:
|
||||||
LOLembed.set_footer(text=f"{(_('command_ping_footer'))} {ctx.author.name}", icon_url=ctx.author.avatar.url)
|
LOLembed.set_footer(text=f"{(_('command_ping_footer'))} {ctx.author.name}", icon_url=ctx.author.avatar.url)
|
||||||
|
|
||||||
await ctx.send(embed=LOLembed)
|
await ctx.send(embed=LOLembed)
|
||||||
# Command: Show about information
|
|
||||||
@bot.hybrid_command(description=f"{(_('command_about_desc'))}")
|
@bot.hybrid_command(description=f"{(_('command_about_desc'))}")
|
||||||
async def about(ctx: commands.Context) -> None:
|
async def about(ctx: commands.Context) -> None:
|
||||||
print("-----------------------------------\n\n")
|
latest_version: str = check_for_update(slient=True)
|
||||||
latest_version: str = check_for_update()
|
|
||||||
print("-----------------------------------")
|
|
||||||
embed: discord.Embed = discord.Embed(title=f"{(_('command_about_embed_title'))}", description="", color=Colour(0x000000))
|
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_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"{(_('command_about_embed_field2name'))}", value=f"{(_('command_about_embed_field2value')).format(local_version=local_version, latest_version=latest_version)}", inline=False)
|
||||||
|
@ -384,14 +328,11 @@ async def about(ctx: commands.Context) -> None:
|
||||||
|
|
||||||
await send_message(ctx, embed=embed)
|
await send_message(ctx, embed=embed)
|
||||||
|
|
||||||
# Command: Show bot statistics (admin only)
|
|
||||||
@bot.hybrid_command(description="stats")
|
@bot.hybrid_command(description="stats")
|
||||||
async def stats(ctx: commands.Context) -> None:
|
async def stats(ctx: commands.Context) -> None:
|
||||||
if ctx.author.id != ownerid:
|
if ctx.author.id != ownerid:
|
||||||
return
|
return
|
||||||
print("-----------------------------------\n\n")
|
|
||||||
latest_version: str = check_for_update()
|
latest_version: str = check_for_update()
|
||||||
print("-----------------------------------")
|
|
||||||
memory_file: str = 'memory.json'
|
memory_file: str = 'memory.json'
|
||||||
file_size: int = os.path.getsize(memory_file)
|
file_size: int = os.path.getsize(memory_file)
|
||||||
|
|
||||||
|
@ -406,20 +347,29 @@ async def stats(ctx: commands.Context) -> None:
|
||||||
embed.add_field(name="Python Version", value=platform.python_version())
|
embed.add_field(name="Python Version", value=platform.python_version())
|
||||||
await send_message(ctx, embed=embed)
|
await send_message(ctx, embed=embed)
|
||||||
|
|
||||||
# Command: Upload memory.json to litterbox.catbox.moe and return the link
|
|
||||||
@bot.hybrid_command()
|
@bot.hybrid_command()
|
||||||
async def mem(ctx: commands.Context) -> None:
|
async def mem(ctx: commands.Context) -> None:
|
||||||
if showmemenabled != "true":
|
if showmemenabled != "true":
|
||||||
return
|
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)
|
with open("memory.json", "rb") as file:
|
||||||
|
files = {
|
||||||
|
"fileToUpload": file
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"reqtype": "fileupload",
|
||||||
|
"time": "1h"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post("https://litterbox.catbox.moe/resources/internals/api.php", files=files, data=data)
|
||||||
|
response.raise_for_status()
|
||||||
|
await send_message(ctx, response.text.strip())
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Upload failed: {e}")
|
||||||
|
await send_message(ctx, "Upload failed.")
|
||||||
|
|
||||||
def improve_sentence_coherence(sentence: str) -> str:
|
def improve_sentence_coherence(sentence: str) -> str:
|
||||||
# Capitalizes "i" to "I" in the sentence
|
|
||||||
sentence = sentence.replace(" i ", " I ")
|
|
||||||
return sentence
|
return sentence
|
||||||
|
|
||||||
# Start the bot
|
# Start the bot
|
||||||
|
|
|
@ -1,26 +1,19 @@
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
from dotenv import load_dotenv
|
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
import discord
|
import discord
|
||||||
|
from discord import Colour, Embed, File, Interaction, Message
|
||||||
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.abc import Messageable
|
from discord.abc import Messageable
|
||||||
from discord.ext import commands
|
|
||||||
import subprocess
|
|
||||||
def get_git_branch():
|
|
||||||
try:
|
|
||||||
branch = subprocess.check_output(
|
|
||||||
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
||||||
stderr=subprocess.DEVNULL
|
|
||||||
).decode('utf-8').strip()
|
|
||||||
return branch
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
env_path = pathlib.Path(__file__).parent.parent / '.env'
|
env_path = pathlib.Path(__file__).parent.parent / '.env'
|
||||||
load_dotenv(dotenv_path=env_path)
|
load_dotenv(dotenv_path=env_path)
|
||||||
|
|
||||||
|
# ANSI colors
|
||||||
ANSI = "\033["
|
ANSI = "\033["
|
||||||
RED = f"{ANSI}31m"
|
RED = f"{ANSI}31m"
|
||||||
GREEN = f"{ANSI}32m"
|
GREEN = f"{ANSI}32m"
|
||||||
|
@ -28,10 +21,30 @@ YELLOW = f"{ANSI}33m"
|
||||||
PURPLE = f"{ANSI}35m"
|
PURPLE = f"{ANSI}35m"
|
||||||
DEBUG = f"{ANSI}1;30m"
|
DEBUG = f"{ANSI}1;30m"
|
||||||
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 = f"{VERSION_URL}/latest_version.json"
|
||||||
print(UPDATE_URL)
|
print(UPDATE_URL)
|
||||||
|
|
||||||
LOCAL_VERSION_FILE = "current_version.txt"
|
LOCAL_VERSION_FILE = "current_version.txt"
|
||||||
|
MEMORY_FILE = "memory.json"
|
||||||
|
MEMORY_LOADED_FILE = "MEMORY_LOADED" # used in markov module
|
||||||
|
local_version = "3.0.0"
|
||||||
|
latest_version = "0.0.0"
|
||||||
|
os.environ['gooberlocal_version'] = local_version
|
||||||
|
|
||||||
|
def get_git_branch() -> str | None:
|
||||||
|
try:
|
||||||
|
return subprocess.check_output(
|
||||||
|
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
||||||
|
stderr=subprocess.DEVNULL
|
||||||
|
).decode().strip()
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
branch = get_git_branch()
|
||||||
|
beta = branch != "main" if branch else True
|
||||||
|
|
||||||
TOKEN = os.getenv("DISCORDBOTTOKEN", "0")
|
TOKEN = os.getenv("DISCORDBOTTOKEN", "0")
|
||||||
PREFIX = os.getenv("BOTPREFIX", "g.")
|
PREFIX = os.getenv("BOTPREFIX", "g.")
|
||||||
PING_LINE = os.getenv("PINGLINE")
|
PING_LINE = os.getenv("PINGLINE")
|
||||||
|
@ -45,27 +58,23 @@ showmemenabled = os.getenv("SHOWMEMENABLED")
|
||||||
BLACKLISTED_USERS = os.getenv("BLACKLISTEDUSERS", "").split(",")
|
BLACKLISTED_USERS = os.getenv("BLACKLISTEDUSERS", "").split(",")
|
||||||
USERTRAIN_ENABLED = os.getenv("USERTRAINENABLED", "true").lower() == "true"
|
USERTRAIN_ENABLED = os.getenv("USERTRAINENABLED", "true").lower() == "true"
|
||||||
NAME = os.getenv("NAME")
|
NAME = os.getenv("NAME")
|
||||||
MEMORY_FILE = "memory.json"
|
|
||||||
MEMORY_LOADED_FILE = "MEMORY_LOADED" # is this still even used?? okay just checked its used in the markov module
|
|
||||||
ALIVEPING = os.getenv("ALIVEPING")
|
ALIVEPING = os.getenv("ALIVEPING")
|
||||||
AUTOUPDATE = os.getenv("AUTOUPDATE")
|
AUTOUPDATE = os.getenv("AUTOUPDATE")
|
||||||
song = os.getenv("SONG")
|
song = os.getenv("SONG")
|
||||||
launched = False
|
|
||||||
latest_version = "0.0.0"
|
|
||||||
local_version = "3.0.0"
|
|
||||||
os.environ['gooberlocal_version'] = local_version
|
|
||||||
REACT = os.getenv("REACT")
|
REACT = os.getenv("REACT")
|
||||||
if get_git_branch() != "main":
|
|
||||||
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
|
|
||||||
|
|
||||||
|
intents = discord.Intents.default()
|
||||||
# Set up Discord bot intents and create bot instance
|
|
||||||
intents: discord.Intents = discord.Intents.default()
|
|
||||||
intents.messages = True
|
intents.messages = True
|
||||||
intents.presences = True
|
intents.presences = True
|
||||||
intents.members = True
|
intents.members = True
|
||||||
intents.message_content = True
|
intents.message_content = True
|
||||||
bot: commands.Bot = commands.Bot(command_prefix=PREFIX, intents=intents, allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False, replied_user=True))
|
|
||||||
|
bot = commands.Bot(
|
||||||
|
command_prefix=PREFIX,
|
||||||
|
intents=intents,
|
||||||
|
allowed_mentions=discord.AllowedMentions(
|
||||||
|
everyone=False, roles=False, users=False, replied_user=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
launched = False
|
||||||
|
|
111
modules/image.py
|
@ -1,111 +0,0 @@
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import random
|
|
||||||
import shutil
|
|
||||||
import tempfile
|
|
||||||
from typing import Optional, List
|
|
||||||
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
|
||||||
from modules.markovmemory import load_markov_model
|
|
||||||
from modules.sentenceprocessing import improve_sentence_coherence, rephrase_for_coherence
|
|
||||||
|
|
||||||
generated_sentences = set()
|
|
||||||
|
|
||||||
def load_font(size):
|
|
||||||
return ImageFont.truetype("assets/fonts/Impact.ttf", size=size)
|
|
||||||
def draw_text_with_outline(draw, text, x, y, font):
|
|
||||||
outline_offsets = [(-2, -2), (-2, 2), (2, -2), (2, 2), (0, -2), (0, 2), (-2, 0), (2, 0)]
|
|
||||||
for ox, oy in outline_offsets:
|
|
||||||
draw.text((x + ox, y + oy), text, font=font, fill="black")
|
|
||||||
draw.text((x, y), text, font=font, fill="white")
|
|
||||||
|
|
||||||
def fits_in_width(text, font, max_width, draw):
|
|
||||||
bbox = draw.textbbox((0, 0), text, font=font)
|
|
||||||
text_width = bbox[2] - bbox[0]
|
|
||||||
return text_width <= max_width
|
|
||||||
|
|
||||||
def split_text_to_fit(text, font, max_width, draw):
|
|
||||||
words = text.split()
|
|
||||||
for i in range(len(words), 0, -1):
|
|
||||||
top_text = " ".join(words[:i])
|
|
||||||
bottom_text = " ".join(words[i:])
|
|
||||||
if fits_in_width(top_text, font, max_width, draw) and fits_in_width(bottom_text, font, max_width, draw):
|
|
||||||
return top_text, bottom_text
|
|
||||||
midpoint = len(words) // 2
|
|
||||||
return " ".join(words[:midpoint]), " ".join(words[midpoint:])
|
|
||||||
|
|
||||||
async def gen_meme(input_image_path, sentence_size=5, max_attempts=10, custom_text=None):
|
|
||||||
markov_model = load_markov_model()
|
|
||||||
if not markov_model or not os.path.isfile(input_image_path):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def generate_text():
|
|
||||||
if custom_text:
|
|
||||||
return custom_text
|
|
||||||
|
|
||||||
if sentence_size == 1:
|
|
||||||
candidate = markov_model.make_short_sentence(max_chars=100, tries=100)
|
|
||||||
if candidate:
|
|
||||||
candidate = candidate.split()[0]
|
|
||||||
return candidate
|
|
||||||
else:
|
|
||||||
candidate = markov_model.make_sentence(tries=100, max_words=sentence_size)
|
|
||||||
if candidate:
|
|
||||||
return improve_sentence_coherence(candidate)
|
|
||||||
print(candidate)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def draw_centered_text(img, text):
|
|
||||||
draw = ImageDraw.Draw(img)
|
|
||||||
width, height = img.size
|
|
||||||
font_size = int(height / 10)
|
|
||||||
font = load_font(font_size)
|
|
||||||
|
|
||||||
cleaned = re.sub(r'[^\w\s]', '', text).lower()
|
|
||||||
coherent = rephrase_for_coherence(cleaned).upper()
|
|
||||||
|
|
||||||
bbox = draw.textbbox((0, 0), coherent, font=font)
|
|
||||||
text_width, text_height_px = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
|
||||||
max_text_height = height // 4
|
|
||||||
|
|
||||||
if text_width <= width and text_height_px <= max_text_height:
|
|
||||||
draw_text_with_outline(draw, coherent, (width - text_width) / 2, 0, font)
|
|
||||||
img.save(input_image_path)
|
|
||||||
return True
|
|
||||||
|
|
||||||
top_text, bottom_text = split_text_to_fit(coherent, font, width, draw)
|
|
||||||
top_bbox = draw.textbbox((0, 0), top_text, font=font)
|
|
||||||
bottom_bbox = draw.textbbox((0, 0), bottom_text, font=font)
|
|
||||||
|
|
||||||
top_height = top_bbox[3] - top_bbox[1]
|
|
||||||
bottom_height = bottom_bbox[3] - bottom_bbox[1]
|
|
||||||
|
|
||||||
if top_height <= max_text_height and bottom_height <= max_text_height:
|
|
||||||
draw_text_with_outline(draw, top_text, (width - (top_bbox[2] - top_bbox[0])) / 2, 0, font)
|
|
||||||
y_bottom = height - bottom_height - int(height * 0.04)
|
|
||||||
draw_text_with_outline(draw, bottom_text, (width - (bottom_bbox[2] - bottom_bbox[0])) / 2, y_bottom, font)
|
|
||||||
img.save(input_image_path)
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
attempt = 0
|
|
||||||
while attempt < max_attempts:
|
|
||||||
response = generate_text() or "NO TEXT GENERATED"
|
|
||||||
with Image.open(input_image_path).convert("RGBA") as img:
|
|
||||||
if draw_centered_text(img, response):
|
|
||||||
return input_image_path
|
|
||||||
attempt += 1
|
|
||||||
with Image.open(input_image_path).convert("RGBA") as img:
|
|
||||||
draw = ImageDraw.Draw(img)
|
|
||||||
width, height = img.size
|
|
||||||
font_size = int(height / 10)
|
|
||||||
font = load_font(font_size)
|
|
||||||
|
|
||||||
truncated = (rephrase_for_coherence(re.sub(r'[^\w\s]', '', "NO TEXT GENERATED").lower()).upper())[:100]
|
|
||||||
bbox = draw.textbbox((0, 0), truncated, font=font)
|
|
||||||
text_width = bbox[2] - bbox[0]
|
|
||||||
|
|
||||||
draw_text_with_outline(draw, truncated, (width - text_width) / 2, 0, font)
|
|
||||||
img.save(input_image_path)
|
|
||||||
return input_image_path
|
|
|
@ -69,7 +69,7 @@ def get_latest_version_info():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Check if an update is available and perform update if needed
|
# Check if an update is available and perform update if needed
|
||||||
def check_for_update():
|
def check_for_update(slient=False):
|
||||||
global latest_version, local_version, launched
|
global latest_version, local_version, launched
|
||||||
|
|
||||||
latest_version_info = get_latest_version_info()
|
latest_version_info = get_latest_version_info()
|
||||||
|
@ -89,6 +89,7 @@ def check_for_update():
|
||||||
logger.error(f"{RED}{_('cant_find_local_version')}{RESET}")
|
logger.error(f"{RED}{_('cant_find_local_version')}{RESET}")
|
||||||
return
|
return
|
||||||
# Compare local and latest versions
|
# Compare local and latest versions
|
||||||
|
if slient != True:
|
||||||
if local_version < latest_version:
|
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}{_('new_version').format(latest_version=latest_version, local_version=local_version)}{RESET}")
|
||||||
logger.info(f"{YELLOW}{_('changelog').format(VERSION_URL=VERSION_URL)}{RESET}")
|
logger.info(f"{YELLOW}{_('changelog').format(VERSION_URL=VERSION_URL)}{RESET}")
|
||||||
|
|