Beeeg refactor
This commit is contained in:
1 parent
bd305ca1ca
commit
926fc4b96a
15 files changed
+281
-664
No files matched your search
@@ -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)
|
||||
@@ -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))
|
||||
@@ -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")
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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))
|
||||
Reference in new issue
Block a user