Brandon209-Red-bot-Cogs/sfx/sfx.py

375 lines
12 KiB
Python

from redbot.core.utils.chat_formatting import *
from redbot.core import Config, checks, commands, bank
from redbot.core.data_manager import cog_data_path
import discord
import os
import glob
import asyncio
from difflib import get_close_matches
from typing import Literal
import tabulate
from .utils import saysound, code_path
EXT = ("mp3", "flac", "ogg", "wav")
class SFX(commands.Cog):
"""Play saysounds in VC's in your guild
Supports costs, files, and links.
"""
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=8495126065166516132, force_registration=True)
# saysounds maps saysound name (str) -> saysound data (dict)
default_guild = {"saysounds": {}, "FREE_ROLES": []}
default_global = {"attachments": True}
self.config.register_guild(**default_guild)
self.config.register_global(**default_global)
global PATH
global AUDIO_CODE_PATH
PATH = str(cog_data_path(cog_instance=self))
audio_cog = bot.get_cog("Audio")
if audio_cog:
AUDIO_CODE_PATH = str(code_path(cog_instance=audio_cog))
else:
AUDIO_CODE_PATH = None
# integrates cost manager free roles!
# if cost_manager isn't loaded, use this cog's free roles
async def get_cost(self, member: discord.Member, sound: dict):
cost = sound["cost"]
cost_manager = self.bot.get_cog("CostManager")
if cost_manager:
free_roles = await cost_manager.config.guild(member.guild).FREE_ROLES()
else:
free_roles = await self.config.guild(member.guild).FREE_ROLES()
member_roles = {r.id for r in member.roles if r.name != "@everyone"}
found_roles = set(free_roles) & member_roles
if found_roles:
cost = 0
return cost
# plays sound in vc of author
async def play(self, ctx, sound: dict):
audio_cog = self.bot.get_cog("Audio")
if not audio_cog:
await ctx.send(error("Unable to load audio cog, please contact bot owner!"))
return
try:
query = sound["url"] if sound["url"] else sound["filepath"]
await audio_cog.sfx_play(ctx, query, volume=sound["volume"])
except Exception as e:
await ctx.send(error("Unable to play sound, please contact bot owner!"))
print(e) # TODO add logging properly
@commands.group()
@checks.admin_or_permissions(administrator=True)
async def sfxset(self, ctx):
"""
Manage settings for saysounds
"""
pass
@sfxset.command(name="vol")
@commands.guild_only()
async def sfxset_vol(self, ctx, vol: int, *, name: str):
"""
Change volume of sfx sound
"""
async with self.config.guild(ctx.guild).saysounds() as saysounds:
try:
saysounds[name]["volume"] = vol
except:
await ctx.send(error("Sound not found, try again!"))
return
await ctx.tick()
@sfxset.command(name="cost")
@commands.guild_only()
async def sfxset_cost(self, ctx, cost: int, *, name: str):
"""
Change cost of sfx sound
"""
async with self.config.guild(ctx.guild).saysounds() as saysounds:
try:
saysounds[name]["cost"] = cost
except:
await ctx.send(error("Sound not found, try again!"))
return
await ctx.tick()
@sfxset.command(name="name")
@commands.guild_only()
async def sfxset_name(self, ctx, old_name: str, *, new_name: str):
"""
Change name of sfx sound
**OLD_NAME MUST be in quotes if it has spaces!**
"""
async with self.config.guild(ctx.guild).saysounds() as saysounds:
try:
temp = saysounds[old_name]
saysounds[new_name] = temp
saysounds[new_name]["name"] = new_name
del saysounds[old_name]
except:
await ctx.send(error("Sound not found, try again!"))
return
await ctx.tick()
@sfxset.command(name="add")
@commands.guild_only()
async def sfxset_add(self, ctx, cost: int, vol: int, *, name: str):
"""
Add an sfx sound for the guild.
Attach the audio file to the message.
"""
can_use = await self.config.attachments()
name = name.lower()
if not can_use:
await ctx.send(error("Sorry, I only allow adding say sounds using URLs."))
return
if len(ctx.message.attachments) < 1:
await ctx.send(error("Please provide an attachment."))
return
sounds = await self.config.guild(ctx.guild).saysounds()
try:
x = sounds[name]
await ctx.send(error("Sound already exists by that name, please delete it first!"))
return
except:
pass
file = ctx.message.attachments[0]
ext = file.filename.split(".")[-1]
if ext not in EXT:
await ctx.send(error("Audio file must one of `mp3, wav, flac, or ogg` formats."))
return
save_path = os.path.join(PATH, str(ctx.guild.id))
if not os.path.exists(save_path):
os.makedirs(save_path)
save_path = os.path.join(save_path, file.filename)
try:
await file.save(save_path)
except:
await ctx.send(error("Error saving file, please try again."))
return
author = f"{ctx.author} id: {ctx.author.id}"
new_sound = saysound(name, author, cost=cost, volume=vol, filepath=save_path)
sounds[name] = new_sound
await self.config.guild(ctx.guild).saysounds.set(sounds)
await ctx.tick()
@sfxset.command(name="addurl")
@commands.guild_only()
async def sfxset_addurl(self, ctx, cost: int, vol: int, url: str, *, name: str):
"""
Add an sfx sound for the guild.
URL must be a direct link to the audio file, a youtube link, spotify link,
soundcloud link etc.
You can test if it will work using the `play` command on the bot.
"""
name = name.lower()
async with self.config.guild(ctx.guild).saysounds() as saysounds:
try:
x = saysounds[name]
await ctx.send(error("Sound already exists by that name, please delete it first!"))
return
except:
pass
author = f"{ctx.author} id: {ctx.author.id}"
new_sound = saysound(name, author, cost=cost, volume=vol, url=url)
saysounds[name] = new_sound
await ctx.tick()
@sfxset.command(name="del")
@commands.guild_only()
async def sfxset_del(self, ctx, *, name: str):
"""
Delete an sfx sound for the guild.
"""
name = name.lower()
async with self.config.guild(ctx.guild).saysounds() as saysounds:
try:
del saysounds[name]
await ctx.tick()
except KeyError:
await ctx.send(error("That say sound does not exist!"))
@sfxset.command(name="file")
@checks.is_owner()
async def sfxset_file(self, ctx, *, on_off: bool = None):
"""
Enable or disable allowing to save audio files directly for playback
"""
curr = await self.config.attachments()
if on_off is None:
msg = "on" if curr else "off"
await ctx.send(f"Allowing saving of audio files is currently {msg}.")
return
await self.config.attachments.set(on_off)
await ctx.tick()
@sfxset.command(name="setup")
@checks.is_owner()
async def sfxset_setup(self, ctx):
"""
Run this first to inject code into audio cog for proper sfx usage.
After injection, reload audio cog and run this again to confirm injection
was successful.
"""
audio_cog = self.bot.get_cog("Audio")
if not audio_cog:
await ctx.send(error("Audio cog not loaded, load it first before running this."))
return
try:
if callable(audio_cog.sfx_play):
await ctx.send("Injection successful, SFX is ready to use!")
else:
await ctx.send("Function found but not callable, unknown error.")
return
except:
pass
if not AUDIO_CODE_PATH:
await ctx.send(error("Please reload the cog after the Audio cog has been loaded!"))
return
inject_path = str(code_path(cog_instance=self) / "injection.txt")
with open(inject_path, "r") as f:
injection = f.read()
inject_path = os.path.join(AUDIO_CODE_PATH, "utilities", "player.py")
with open(inject_path, "a") as f:
f.write(injection)
await ctx.send("Injection complete, reload audio cog then run this command again to make sure it worked.")
@commands.command(name="sfx")
@commands.guild_only()
@commands.cooldown(rate=1, per=10, type=commands.BucketType.user)
async def sfx(self, ctx, *, name: str):
"""
Play a say sound!
"""
# TODO: cost manager receipt integration
if not ctx.author.voice or ctx.author.voice.channel is None:
await ctx.send(error("Connect to a voice channel to use this command."))
return
# TODO: create my own queue to fix this issue
if ctx.guild.me.voice and ctx.guild.me.voice.channel != ctx.author.voice.channel:
await ctx.send(error("Please wait for the bot to disconnect from it's VC before using the command."))
return
name = name.lower()
saysounds = await self.config.guild(ctx.guild).saysounds()
audio_cog = self.bot.get_cog("Audio")
play = self.bot.get_command("play")
volume = self.bot.get_command("volume")
if not play:
await ctx.send(error("Audio cog not loaded! Please contact bot owner."))
return
try:
sound = saysounds[name]
except KeyError:
# name doesn't have to be full name, will find closest match
matches = get_close_matches(name, list(saysounds.keys()), n=1, cutoff=0.7)
if matches:
sound = saysounds[matches[0]]
else:
await ctx.send(error("Say sound could not be found!"))
return
# found saysound
# charge user
cost = await self.get_cost(ctx.author, sound)
msg = None
if cost > 0:
currency_name = await bank.get_currency_name(ctx.guild)
try:
await bank.withdraw_credits(ctx.author, cost)
balance = await bank.get_balance(ctx.author)
msg = await ctx.send(f"Charged: {cost}, Balance: {balance}")
except ValueError:
balance = await bank.get_balance(ctx.author)
msg = await ctx.send(
error(
f"Sorry {ctx.author.name}, you do not have enough {currency_name} to use that say sound. (Cost: {cost}, Balance: {balance})"
)
)
await asyncio.sleep(10)
await msg.delete()
return
await self.play(ctx, sound)
if msg:
await asyncio.sleep(5)
await msg.delete()
@commands.command(name="sfxlist")
async def sfx_list(self, ctx):
"""
List all say sounds for guild.
"""
saysounds = await self.config.guild(ctx.guild).saysounds()
msg = []
keys = sorted(list(saysounds.keys()))
for sound_name in keys:
msg.append((sound_name, saysounds[sound_name]["cost"]))
msg = tabulate.tabulate(msg, ["Sound", "Cost"], tablefmt="github")
pages = pagify(msg)
for page in pages:
try:
await ctx.author.send(box(page))
except:
await ctx.send("Please allow DMs from server members so I can DM you the list!")
return
async def red_delete_data_for_user(
self,
*,
requester: Literal["discord_deleted_user", "owner", "user", "user_strict"],
user_id: int,
):
pass