Beeeg refactor

This commit is contained in:
Benjamyn committed 2026-08-30 16:54:49 +10:00
1 parent bd305ca1ca
commit 926fc4b96a
15 files changed
+281 -664

No files matched your search

-124
View File
@@ -1,124 +0,0 @@
"""GIF emote handler — parses emoji strings, fetches from CDN, sends reactions."""
import re
import logging
from typing import Optional
import discord
from discord.ext import commands
import aiohttp
import discordemojiparser as edp
logger = logging.getLogger(__name__)
# ─── Configuration ───────────────────────────────────────────────────────────
GIF_EMOTE_CDN = "https://media.discordapp.net/gifs/"
REACTION_CHANCE = 0.05 # 5% chance to trigger on GIF emote mention
REACTION_COOLDOWN = 60 # seconds per channel
# ─── Emoji Parser ─────────────────────────────────────────────────────────────
def parse_emoji(emoji_str: str) -> Optional[discord.Emoji]:
"""Parse an emoji string (e.g., :cat:) into a discord.Emoji object."""
emoji_str = emoji_str.strip()
if not emoji_str.startswith(":") or not emoji_str.endswith(":"):
return None
emoji_name = emoji_str[1:-1]
try:
emoji_obj = edp.parse(emoji_name)
except Exception as e:
logger.debug(f"Failed to parse emoji '{emoji_name}': {e}")
return None
if not emoji_obj:
return None
guild_id = emoji_obj.get("guild_id")
emoji_id = emoji_obj.get("id")
if guild_id is None or emoji_id is None:
return None
try:
guild = bot.get_guild(guild_id)
if guild is None:
return None
return guild.get_emoji(emoji_id)
except (discord.NotFound, AttributeError) as e:
logger.debug(f"Emoji not found: guild={guild_id}, emoji={emoji_id}")
return None
# ─── Reaction Handler ────────────────────────────────────────────────────────
class GIFReactionHandler:
"""Handles GIF emote reactions on messages."""
def __init__(self, bot: commands.Bot) -> None:
self.bot = bot
self._cooldowns: dict[int, float] = {} # channel_id -> next_available
async def handle_reaction(self, message: discord.Message) -> None:
"""Check if a message mentions a GIF emote and react accordingly."""
channel_id = message.channel.id
# Enforce cooldown
now = discord.utils.utcnow().timestamp()
if channel_id in self._cooldowns:
if now < self._cooldowns[channel_id]:
return
# Check if message contains a GIF emote
if await self._check_and_react(message):
self._cooldown(channel_id)
async def _check_and_react(self, message: discord.Message) -> bool:
"""Check if message mentions a GIF emote and add reaction."""
# Look for emoji mentions in the message
emoji_mentions = re.findall(r":(\w+):", message.content)
for name in emoji_mentions:
emoji_obj = parse_emoji(f":{name}:")
if emoji_obj is None:
continue
# Determine the GIF URL
gif_url = f"{GIF_EMOTE_CDN}{emoji_obj.id}.gif"
# Add reaction
try:
await message.add_reaction(emoji_obj)
return True
except discord.Forbidden:
pass
return False
def _cooldown(self, channel_id: int) -> None:
"""Set cooldown for a channel."""
self._cooldowns[channel_id] = discord.utils.utcnow().timestamp() + REACTION_COOLDOWN
# ─── Bot Integration ─────────────────────────────────────────────────────────
async def setup(bot: commands.Bot) -> None:
"""Register the GIF reaction handler with the bot."""
handler = GIFReactionHandler(bot)
@bot.event
async def on_message(message: discord.Message) -> None:
"""Check for GIF emote mentions in messages."""
if message.author == bot.user:
return
if message.author.bot:
return
await handler.handle_reaction(message)
+59
View File
@@ -0,0 +1,59 @@
import discord
import logging
import aiohttp
import asyncio
import io
import threading
from pathlib import Path
import glob
from discord.ext import commands
logger = logging.getLogger(__name__)
class LoveEmote():
def __init__(self, name, path):
self.name = name
self.path = path
class LoveEmoteCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.emotes = list()
async def cog_load(self):
PROJ_DIR = Path(__file__).resolve().parent.parent
emotes = []
types = ["*.webp", "*.gif"]
for t in types:
emotes = glob.glob(f"{PROJ_DIR}/emotes/{t}")
for e in emotes:
p = Path(e)
self.emotes.append(LoveEmote(p.stem, p))
logger.debug(self.emotes)
logger.debug(f"{PROJ_DIR}/{t}")
# self.emotes = emotes
@commands.command(name="emote_list", aliases=['el'], description="get list of gif emotes")
async def emote_list(self, ctx: commands.Context):
"""[emote_list | el] List emotes available to the bot"""
data = "\n".join([f" - {e.name}" for e in self.emotes])
await ctx.send(data)
@commands.command(name="emote_send", aliases=["e"], description="Send an emote to the channel")
async def send_emote(self, ctx: commands.Context, emote_name: str):
"""[emote_send | e] Send an emote to the channel"""
for e in self.emotes:
if e.name == emote_name:
with open(e.path, 'rb') as f:
await ctx.message.delete()
data = io.BytesIO(f.read())
file_data = discord.File(data, filename=f"{e.name}.{e.path.suffix}")
await ctx.send(f"{ctx.author.display_name} sent {e.name}", file=file_data)
return
await ctx.send("Emote not found, contact aram to create one")
async def setup(bot: commands.Bot) -> None:
"""Register the voice_gen cog with the bot."""
await bot.add_cog(LoveEmoteCog(bot))
+14
View File
@@ -0,0 +1,14 @@
async def process_meme(message):
memes = {"software": ["mercury", "smws", "cpanel", "whm"],
"perks": ["office", "fishbowl"],
"insults_to_us": []}
for meme in memes:
for token in message.content.lower().split():
if token in memes[meme]:
match meme:
case "software":
await message.channel.send("That sounds like software to me")
case "perks":
await message.channel.send("Something snarky about dinner")
case "insults_to_us":
await message.channel.send("The \"wealth\" of knowledge")
+34
View File
@@ -0,0 +1,34 @@
import logging
from discord.ext import commands
logger = logging.getLogger(__name__)
class UtilCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.command(name="ping", description="Check bot latency.")
async def ping_command(self, ctx: commands.Context) -> None:
"""Check the bot's latency."""
latency = round(self.bot.latency * 1000)
await ctx.send(f"Pong! Latency: {latency}ms")
@commands.command(name="info", description="Show bot information.")
async def info_command(self, ctx: commands.Context) -> None:
"""Show bot information."""
await ctx.send(
f"**{ctx.guild.name if ctx.guild else 'DM'}**\n"
f"Bot: `{self.bot.user.name}` (ID: {self.bot.user.id})\n"
f"Latency: `{self.bot.latency:.3f}s`"
)
@commands.command(name="hello", description="Say hello to someone.")
async def hello_command(self, ctx: commands.Context, name: str = "World"):
"""Say hello to someone."""
await ctx.send(f"Hello, {name}!")
async def setup(bot: commands.Bot) -> None:
"""Register the voice_gen cog with the bot."""
await bot.add_cog(UtilCog(bot))
+34 -10
View File
@@ -6,7 +6,7 @@ from typing import Optional
import discord
from discord.ext import commands
from discord.ext.commands import Cog, Context, has_permissions
from discord.ext.commands import Cog, Context
logger = logging.getLogger(__name__)
@@ -57,18 +57,42 @@ class MusicCog(Cog):
self._is_playing = False
self._stop_requested = False
@commands.command()
async def join(self, ctx: Context, c=None):
if c is None:
# Check if the user is in a voice channel
if ctx.author.voice:
channel = ctx.author.voice.channel
self._voice_channel = await channel.connect()
await ctx.send(f"Joined {channel.name}!")
else:
await ctx.send("You must be in a voice channel first!")
channel = discord.utils.get(ctx.guild.voice_channels, name=c)
print(channel)
await channel.connect()
@commands.command()
async def leave(self, ctx):
# Check if the bot is in a voice channel in this server
if ctx.voice_client:
await ctx.voice_client.disconnect()
self._voice_channel = None
await ctx.send("Disconnected from the voice channel.")
else:
await ctx.send("I am not in a voice channel.")
@commands.command()
async def play(self, ctx: Context, source: str) -> None:
"""Play a track from a source (URL or file)."""
if not self._is_playing:
await self._ensure_voice(ctx)
try:
track = await YTDLSource.from_url(source, source_type="youtube", loop=False)
self._current_queue.append(track)
await self._play_next()
except Exception as e:
logger.error(f"Error playing source {source}: {e}")
await ctx.send(f"Error playing: {e}")
try:
track = await YTDLSource.from_url(source, source_type="youtube", loop=False)
self._current_queue.append(track)
await self._play_next()
except Exception as e:
logger.error(f"Error playing source {source}: {e}")
await ctx.send(f"Error playing: {e}")
async def _play_next(self) -> None:
"""Play the next track in the queue."""
@@ -167,7 +191,7 @@ class YTDLSource(discord.PCMVolumeTransformer):
@classmethod
async def from_url(cls, url, source_type="youtube", loop=False):
"""Create a source from a URL."""
import youtube_dl
import yt_dlp as youtube_dl
ydl_opts = {
"format": "bestaudio/best",
+87
View File
@@ -0,0 +1,87 @@
import discord
import logging
import aiohttp
import io
from discord.ext import commands
logger = logging.getLogger(__name__)
class VoiceGeneratorProfile():
def __init__(self, name, uuid, model="audio-cpp-chatterbox"):
self.name = name
self.model = model
self.uuid = uuid
class VoiceCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self._running = False
self._current_voice = "Pricey"
self.api_url = "http://10.6.9.4:8080"
async def cog_load(self):
data = await self._get_voices()
self.voices = [VoiceGeneratorProfile(d['name'], d['id']) for d in data.get('data')]
self._current_voice = self.voices[0]
async def _get_voices(self):
async with aiohttp.ClientSession() as session:
data = await session.get(f"{self.api_url}/api/voice-profiles")
data = await data.json()
return data
@commands.command(name="voice_set", description="Change the active voice by name")
async def voice_set(self, ctx, voice_name):
"""Set the voice for the generator to use"""
for v in self.voices:
if v.name.lower() == voice_name.lower():
self._current_voice = v
await ctx.send(f"Swapped voice to {v.name}")
return
await ctx.send("Voice not found asshole")
@commands.command(name="voice_list", description="List available voices")
async def voice_list(self, ctx: commands.Context):
"""List available voices"""
await ctx.channel.send(f"{"\n".join([f" - {v.name}" for v in self.voices])}")
@commands.command(name="voice_info", description="See the status of the voice generator :tm:")
async def voice_info(self, ctx: commands.Context):
"""View status of the voice generator"""
data = f"""
- Running: {self._running}
- Current Voice: {self._current_voice.name}
"""
await ctx.channel.send(data)
@commands.command(name="voice_gen", description="Generate an audio clip using the currently configured voice")
async def voice_gen(self, ctx: commands.Context, prompt: str):
"""Generate some voices"""
if self._running:
await ctx.send("All lines are currently busy, please try again later")
return
data = {"model": self._current_voice.model,
"voice": f"localai://voice-profiles/{self._current_voice.uuid}",
"input": prompt,
"stream": False}
await ctx.channel.send("Working on it!")
self._running = True
try:
headers = {"Content-Type": "application/json"}
async with aiohttp.ClientSession(headers=headers) as session:
resp = await session.post(f"{self.api_url}/tts", json=data)
d = await resp.read()
# logging.debug(d)
voice_data = io.BytesIO(d)
self._running = False
filedata = discord.File(voice_data, filename=f"{self._current_voice.name.replace(" ", "_").lower()}-{prompt[:10].replace(" ", "_").lower()}.wav")
await ctx.channel.send(file=filedata)
except Exception as e:
self._running = False
logging.error(e)
async def setup(bot: commands.Bot) -> None:
"""Register the voice_gen cog with the bot."""
await bot.add_cog(VoiceCog(bot))