inital release of sfx cog

This commit is contained in:
brandons209 2020-06-26 00:21:16 -04:00
parent 37adcf10ad
commit d81d5e5ef7
6 changed files with 537 additions and 4 deletions

View file

@ -391,8 +391,7 @@ class RoleManagement(
Roles with spaces in the name should be put in quotes
"""
current = await self.config.role(add_role).add_with()
current = [discord.utils.get(ctx.guild.roles, id=r)
for r in current]
current = [discord.utils.get(ctx.guild.roles, id=r) for r in current]
await self.config.role(add_role).add_with.set([r.id for r in roles])
@ -401,8 +400,9 @@ class RoleManagement(
elif not roles and not current:
await ctx.send("No roles originally defined.")
else:
await ctx.send(f"Add with roles set to `{humanize_list([r.name for r in roles])}` from `{humanize_list(current) if current else None}`")
await ctx.send(
f"Add with roles set to `{humanize_list([r.name for r in roles])}` from `{humanize_list(current) if current else None}`"
)
@rgroup.command(name="viewreactions")
async def rg_view_reactions(self, ctx: GuildContext):

5
sfx/__init__.py Normal file
View file

@ -0,0 +1,5 @@
from .sfx import SFX
def setup(bot):
bot.add_cog(SFX(bot))

15
sfx/info.json Normal file
View file

@ -0,0 +1,15 @@
{
"author": [
"Brandons209"
],
"install_msg": "Thanks for install.",
"short": "Play sound effects!",
"description": "Play sound effects, either via links or files. Integrates with economy and cost manager cog.",
"min_bot_version": "3.5.0",
"requirements": ["tabulate"],
"tags": [
"sfx",
"saysound",
"audio"
]
}

210
sfx/injection.py Normal file
View file

@ -0,0 +1,210 @@
#### INJECTED BY SFX COG ####
async def sfx_play(self, ctx: commands.Context, query: str, volume = 100):
### From play command, process query and check if can play in this context ###
query = Query.process_input(query, self.local_folder_current_path)
import datetime
if not self._player_check(ctx):
if self.lavalink_connection_aborted:
msg = _("Connection to Lavalink has failed")
desc = EmptyEmbed
if await self.bot.is_owner(ctx.author):
desc = _("Please check your console or logs for details.")
return await self.send_embed_msg(ctx, title=msg, description=desc)
try:
if (
not ctx.author.voice.channel.permissions_for(ctx.me).connect
or not ctx.author.voice.channel.permissions_for(ctx.me).move_members
and self.is_vc_full(ctx.author.voice.channel)
):
return await self.send_embed_msg(
ctx,
title=_("Unable To Play Tracks"),
description=_("I don't have permission to connect to your channel."),
)
await lavalink.connect(ctx.author.voice.channel)
player = lavalink.get_player(ctx.guild.id)
player.store("connect", datetime.datetime.utcnow())
except AttributeError:
return await self.send_embed_msg(
ctx,
title=_("Unable To Play Tracks"),
description=_("Connect to a voice channel first."),
)
except IndexError:
return await self.send_embed_msg(
ctx,
title=_("Unable To Play Tracks"),
description=_("Connection to Lavalink has not yet been established."),
)
### PLAYER SETTINGS ###
player = lavalink.get_player(ctx.guild.id)
player.store("channel", ctx.channel.id)
player.store("guild", ctx.guild.id)
await self._eq_check(ctx, player)
player.repeat = False
player.shuffle = False
player.shuffle_bumped = True
if player.volume != volume:
await player.set_volume(volume)
if len(player.queue) >= 10000:
return await self.send_embed_msg(
ctx, title=_("Unable To Play Tracks"), description=_("Queue size limit reached.")
)
try:
await self.sfx_enqueue_tracks(ctx, query)
except QueryUnauthorized as err:
return await self.send_embed_msg(
ctx, title=_("Unable To Play Tracks"), description=err.message
)
async def sfx_enqueue_tracks(
self, ctx: commands.Context, query: Query, enqueue: bool = True
) -> Union[discord.Message, List[lavalink.Track], lavalink.Track]:
player = lavalink.get_player(ctx.guild.id)
try:
if self.play_lock[ctx.message.guild.id]:
return await self.send_embed_msg(
ctx,
title=_("Unable To Get Tracks"),
description=_("Wait until the playlist has finished loading."),
)
except KeyError:
self.update_player_lock(ctx, True)
guild_data = await self.config.guild(ctx.guild).all()
first_track_only = False
single_track = None
index = None
playlist_data = None
playlist_url = None
seek = 0
if not await self.is_query_allowed(
self.config, ctx.guild, f"{query}", query_obj=query
):
raise QueryUnauthorized(
_("{query} is not an allowed query.").format(query=query.to_string_user())
)
if query.single_track:
first_track_only = True
index = query.track_index
if query.start_time:
seek = query.start_time
try:
result, called_api = await self.api_interface.fetch_track(ctx, player, query)
except TrackEnqueueError:
self.update_player_lock(ctx, False)
return await self.send_embed_msg(
ctx,
title=_("Unable to Get Track"),
description=_(
"I'm unable get a track from Lavalink at the moment, "
"try again in a few minutes."
),
)
tracks = result.tracks
playlist_data = result.playlist_info
if not enqueue:
return tracks
if not tracks:
self.update_player_lock(ctx, False)
title = _("Nothing found.")
embed = discord.Embed(title=title)
if result.exception_message:
if "Status Code" in result.exception_message:
embed.set_footer(text=result.exception_message[:2000])
else:
embed.set_footer(text=result.exception_message[:2000].replace("\n", ""))
if await self.config.use_external_lavalink() and query.is_local:
embed.description = _(
"Local tracks will not work "
"if the `Lavalink.jar` cannot see the track.\n"
"This may be due to permissions or because Lavalink.jar is being run "
"in a different machine than the local tracks."
)
elif query.is_local and query.suffix in _PARTIALLY_SUPPORTED_MUSIC_EXT:
title = _("Track is not playable.")
embed = discord.Embed(title=title)
embed.description = _(
"**{suffix}** is not a fully supported format and some "
"tracks may not play."
).format(suffix=query.suffix)
return await self.send_embed_msg(ctx, embed=embed)
queue_dur = await self.queue_duration(ctx)
queue_total_duration = self.format_time(queue_dur)
before_queue_length = len(player.queue)
single_track = None
# a ytsearch: prefixed item where we only need the first Track returned
# this is in the case of [p]play <query>, a single Spotify url/code
# or this is a localtrack item
try:
if len(player.queue) >= 10000:
return await self.send_embed_msg(ctx, title=_("Queue size limit reached."))
single_track = (
tracks
if isinstance(tracks, lavalink.rest_api.Track)
else tracks[index]
if index
else tracks[0]
)
if seek and seek > 0:
single_track.start_timestamp = seek * 1000
if not await self.is_query_allowed(
self.config,
ctx.guild,
(
f"{single_track.title} {single_track.author} {single_track.uri} "
f"{str(Query.process_input(single_track, self.local_folder_current_path))}"
),
):
if IS_DEBUG:
log.debug(f"Query is not allowed in {ctx.guild} ({ctx.guild.id})")
self.update_player_lock(ctx, False)
return await self.send_embed_msg(
ctx, title=_("This track is not allowed in this server.")
)
elif guild_data["maxlength"] > 0:
if self.is_track_too_long(single_track, guild_data["maxlength"]):
player.add(ctx.author, single_track)
player.maybe_shuffle()
self.bot.dispatch(
"red_audio_track_enqueue",
player.channel.guild,
single_track,
ctx.author,
)
else:
self.update_player_lock(ctx, False)
return await self.send_embed_msg(
ctx, title=_("Track exceeds maximum length.")
)
else:
player.add(ctx.author, single_track)
player.maybe_shuffle()
self.bot.dispatch(
"red_audio_track_enqueue", player.channel.guild, single_track, ctx.author
)
except IndexError:
self.update_player_lock(ctx, False)
title = _("Nothing found")
desc = EmptyEmbed
if await self.bot.is_owner(ctx.author):
desc = _("Please check your console or logs for details.")
return await self.send_embed_msg(ctx, title=title, description=desc)
if not player.current:
await player.play()
self.update_player_lock(ctx, False)
return single_track or message
### END INJECTION BY SFX ###

290
sfx/sfx.py Normal file
View file

@ -0,0 +1,290 @@
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
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="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()
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
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
async with self.config.guild(ctx.guild).saysounds() as saysounds:
author = f"{ctx.author} id: {ctx.author.id}"
new_sound = saysound(name, author, cost=cost, volume=vol, filepath=save_path)
saysounds[name] = new_sound
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.
"""
async with self.config.guild(ctx.guild).saysounds() as saysounds:
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.
"""
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.py")
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!
"""
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
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

13
sfx/utils.py Normal file
View file

@ -0,0 +1,13 @@
import inspect
from pathlib import Path
# defines a saysound dictionary
def saysound(name: str, added_by: str, cost: int = 0, volume: int = 100, url: str = None, filepath: str = None) -> dict:
saysound = {"name": name, "added_by": added_by, "cost": cost, "volume": volume, "url": url, "filepath": filepath}
return saysound
# gets path to main directory of cog's code
def code_path(cog_instance):
return Path(inspect.getfile(cog_instance.__class__)).parent