mirror of
https://github.com/brandons209/Red-bot-Cogs.git
synced 2024-06-18 10:24:36 +12:00
inital release of sfx cog
This commit is contained in:
parent
37adcf10ad
commit
d81d5e5ef7
|
@ -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
5
sfx/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from .sfx import SFX
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(SFX(bot))
|
15
sfx/info.json
Normal file
15
sfx/info.json
Normal 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
210
sfx/injection.py
Normal 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
290
sfx/sfx.py
Normal 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
13
sfx/utils.py
Normal 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
|
Loading…
Reference in a new issue