async def on_reaction(self, payload): """When someone reacts to a message in the guild Args: payload (discord.RawReactionActionEvent): The raw reaction data """ if not payload.member.bot: bt.INFO( f'Received reaction {payload.emoji.name} from {payload.member.name}' ) guild = bt.get_guild_by_id(self._bot, payload.guild_id) data_channel_id = list( self._channels.get(str(payload.guild_id)).keys())[0] data = self._channels.get(str(payload.guild_id)) data = data.get(str(data_channel_id)) if str(payload.channel_id) == str(data_channel_id): message = await bt.get_message_by_id( bt.get_channel_by_id(guild, payload.channel_id), payload.message_id) await message.remove_reaction(payload.emoji, payload.member) if payload.emoji.name in data: role = bt.get_role_from_guild( guild, data.get(str(payload.emoji.name))[0]) await payload.member.add_roles(role) bt.INFO( f'Gave {payload.member.name} {role} role in {guild.name}' ) else: bt.INFO('Ignoring Emoji as not in reaction data')
async def find_or_make_category(self, ctx): """Summary Args: ctx: The context of the call Returns: discord.CategoryChannel: Either the category found or the one created by the bot """ bt.INFO(f'Finding category for {ctx.guild.id}') categories = ctx.guild.categories found = [] for category in categories: if category.name == category_name or 'bot' in category.name: found.append(category) category = None if len(found) < 1: bt.INFO(f'Found no categories in {ctx.guild.name}, making one called {category_name}') category = await ctx.guild.create_category_channel(category_name) elif len(found) == 1: category = found[0] else: for category in found: if category.name == category_name: return category category = found[0] return category
async def play_next(self, player, timer): """Summary Args: player (discord.Client.voice_client): The voice client to get the queue of timer (str): The time in seconds to wait for """ try: time = int(timer) await asyncio.sleep(time + 3) bt.INFO(f'Sleeping for {timer} seconds for player {player}') if len(self.players[player]) == 1: bt.INFO(f'No more songs to play for {player}') self.players[player] = [] await self.edit_song_message(player) asyncio.create_task(self.empty(player, datetime.datetime.now())) elif len(self.players[player]) > 1: self.players[player].pop(0) next_song = self.players[player][0] bt.INFO( f'Playing next song in queue for {player} : {next_song["title"]}' ) await self.play_song(player, next_song) except BaseException as e: ex_type, ex_value, ex_traceback = sys.exc_info() bt.ERROR( f'Unable to play the next song due to an error: {ex_value}')
def keyboardInterruptHandler(signal, frame): """Handles when the program is shutdown with a keyboard interrupt Args: signal: The signal interrupt that is recieved frame: The current execution frame """ bt.INFO('Doing cleanup') bot.gather_data() bt.INFO('Cleanup done!') exit(0)
async def find_channel(self, ctx, find): bt.INFO(f'Finding {find} channel in {ctx.guild.id}') channel = bt.get_channel_by_name(ctx.guild, find) if channel is None: bt.INFO(f'Unable to find {find} channel in {ctx.guild.id}, making one') channel = await ctx.guild.create_text_channel(find, category=bt.get_channel_by_id(ctx.guild, self._categories.get( f'{ctx.guild.id}')), overwrites=self.gen_overwrites(ctx)) else: await self.clear(channel)
def teardown(bot): """Unloads the Music extension Args: bot commands.Bot: The bot unloading the Music extension """ bt.INFO('Unloading Music.py')
def teardown(bot): """Unloads the Roles extension from the bot Args: bot (commands.Bot): The bot to be unloaded from """ bt.INFO('Unloading Roles.py')
def teardown(bot): """Summary Args: bot (commands.Bot): The bot that is unloading this extension """ bt.INFO('Unloading Misc.py')
def setup(bot): """Loads the Misc extensions into the bot Args: bot (commands.Bot): The bot to be added as a cog to """ bt.INFO('Loading Misc.py') bot.add_cog(Misc(bot))
def setup(bot): """Loads the Roles extension into the bot given Args: bot (commands.Bot): The bot to be loaded into """ bt.INFO('Loading Roles.py') bot.add_cog(Roles(bot))
def setup(bot): """Loads the Music extension into bot Args: bot commands.Bot: The bot to be loaded into """ bt.INFO('Loading Music.py') bot.add_cog(Music(bot))
def __init__(self, bot): """Initialises the Music extension Args: bot (commands.Bot): The bot initialising the Music extension """ self._bot = bot bt.INFO('Initialised Music Cog')
def __init__(self, bot): """Initialises the Roles extension Args: bot (commands.Bot): The bot initialising the extension """ self._bot = bot bt.INFO('Initialised Roles Bot')
def find_url(self, string): """Determines if the string parsed through is a url Args: string (str): The string to be checked Returns: bool: True if the string is a url, False if not """ url = re.findall( 'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+] |[!*\(\), ]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', string) if len(url) != 0: bt.INFO('Song was a url') return True else: bt.INFO('Song was not a url') return False
async def roles(self, ctx): """Performs the setup for just the roles channel, Usage: !setup roles Args: ctx: The context of the call """ bt.INFO(f'Setting up roles for {ctx.guild.id}') await self.setup(ctx) await self.find_or_make_roles(ctx)
async def music(self, ctx): """Performs the setup for just the music channel, Usage: !setup music Args: ctx: The context of the call """ bt.INFO(f'Setting up music for {ctx.guild.id}') await self.setup(ctx) await self.find_or_make_music(ctx)
async def do_setup(self, ctx): """Adds the required channels for the bot to function in, Usage: !setup [optional] <roles|music> Args: ctx: The context of the call """ bt.INFO(f'Performing complete Setup for {ctx.guild.id}') await self.setup(ctx) await self.roles(ctx) await self.music(ctx)
async def play_command(self, ctx, *args): """Adds the song to the queue, if the queue is empty it will play it. Usage: !play <url|song name> Args: ctx: The context of the call *args: The name of the song to search for or the url of the song """ await ctx.message.delete() if self._channels.get(str(ctx.guild.id)) is not None: if ctx.channel.id == self._channels.get(str(ctx.guild.id)): channel = self.get_channel(ctx) client = self.get_voice_client(channel) if client is None: client = await self.do_join(ctx) song = "" for arg in args: song += " " + arg song = song.strip() if client is not None: if not self.find_url(song): data = await self.find_song(song) song = 'https://youtube.com' + data.get('link') if len(self.players[client]) == 0: file_data = self.get_song(song) if file_data is not None: self.players[client].append(file_data) bt.INFO(f'Playing song: {file_data["title"]}') await self.play_song(client, file_data) else: file_data = self.get_song(song) if file_data is not None: self.players[client].append(file_data) await self.edit_song_message(client) else: music_channel = bt.get_channel_by_id( ctx.guild, self._channels.get(str(ctx.guild.id))) await ctx.send(embed=bt.embed_message( "Error!", description=f"That command is only available " f"in the {music_channel} channel", colour='red')) else: await ctx.send(embed=bt.embed_message( f'Setup is not complete!', description="Run !setup for full setup or " "'!setup music' for just music", colour='red'))
async def setup(self, ctx): """Summary Args: ctx: The context of the call """ if self._categories.get(str(ctx.guild.id)) is None: category = await self.find_or_make_category(ctx) self._categories[str(ctx.guild.id)] = category.id else: bt.INFO(f'Category already identified for guild {ctx.guild.id}')
async def do_role(self, ctx, role, emoji, name): """Performs the role command Args: ctx: The context of the call role (str): The role being added emoji (str): The emoji being associated with the role name (str): The description of the role """ bt.INFO(f'Adding role {role} with emoji {emoji}') await self.add(ctx, role, emoji, name)
async def clear_command(self, ctx): """Clears the current channel of the last 100 messages, Usage: !clear Args: ctx: The context of the call """ bt.INFO(f'Clearing channel {ctx.channel} in {ctx.guild.name} (id: {ctx.guild.id})') channel = ctx.message.channel counter = await self.clear(channel) result = await ctx.send( embed=bt.embed_message("Cleared channel", description=f"Deleted {counter} messages from this channel")) await result.delete(delay=5)
def get_song(self, url, retries=0): """Prunes the song's data that was retrieved Args: url (str): The url of the song to be downloaded retries (int, optional): The number of retries performed if the fetch has failed Returns: dict: The file data of the song """ try: info = self.download_song(url) file_data = { 'file': 'songs/' + info['title'] + '-' + info['id'] + '.mp3', 'title': info['title'], 'artist': info['artist'], 'duration': info['duration'], 'track': info['track'], 'id': info['id'], 'link': url } bt.INFO(f'Title: {file_data["title"]}') bt.INFO(f'Artist: {file_data["artist"]}') bt.INFO(f'Track: {file_data["track"]}') bt.INFO(f'Duration: {file_data["duration"]}') bt.INFO(f'File: {file_data["file"]}') bt.INFO(f'ID: {file_data["id"]}') return file_data except Exception as e: bt.ERROR(f'Unable to find the requested song: {url}') if retries < 3: return self.get_song(url, retries=retries + 1) else: return None
async def reload(ctx): """Reloads the bots commands, Usage !reload Args: ctx: The context of the call """ bt.WARN('Reloading Extensions!') reload_extensions() bot.load_data() message = await ctx.send( embed=bt.embed_message('Done Reloading!', colour='green')) await message.delete(delay=3.0) bt.INFO('Done Reloading Extensions!')
async def play_song(self, client, file_data): """Plays the song in file_data for the voice client client Args: client (discord.Client.voice_client): The voice client instance to play music to file_data (dict): A dictionary of the file data about the song """ client.play(discord.FFmpegPCMAudio(file_data['file'])) client.volume = 100 bt.INFO(f'{client} Playing status: {client.is_playing()}') await self.edit_preview(client) await self.edit_song_message(client) asyncio.create_task(self.play_next(client, file_data['duration']))
async def pause(self, ctx): """Pauses the current song Args: ctx: The context of the call """ await ctx.message.delete() channel = self.get_channel(ctx) client = self.get_voice_client(channel) bt.INFO(f'Paused playback for {client}') if client is not None: client.pause() await self.edit_preview(client, default=True)
async def skip(self, ctx): """Skips the current song and plays the next one Args: ctx: The context of the call """ await ctx.message.delete() channel = self.get_channel(ctx) client = self.get_voice_client(channel) if client is not None: bt.INFO(f'Skipping song for client {client}') client.pause() asyncio.create_task(self.play_next(client, -3))
async def stop(self, ctx): """Stops playback and clears the queue Args: ctx: The context of the call """ await ctx.message.delete() channel = self.get_channel(ctx) client = self.get_voice_client(channel) bt.INFO(f'Stopped playback for {client}') if client is not None: client.stop() self.players[client] = []
async def resume(self, ctx): """Resumes the current playback Args: ctx: The context of the call """ await ctx.message.delete() channel = self.get_channel(ctx) client = self.get_voice_client(channel) bt.INFO(f'Resumed playback for {client}') if client is not None: if client.is_paused(): client.resume() await self.edit_preview(client)
async def remove(self, ctx, emoji): """Performs the role remove command Args: ctx: The context of the call emoji (str): The emoji association to be removed """ await ctx.message.delete() result, channel = self.check_and_get_channel_id(ctx) if result: guild_data = self._channels.get(f'{ctx.guild.id}').get(channel) if emoji not in guild_data: message = await ctx.send(embed=bt.embed_message( 'Error!', description=f'Unable to remove {emoji} as ' f'it does not exist!')) await message.delete(delay=4.0) else: bt.INFO( f'Removing {emoji} from {ctx.guild.name}\'s reaction roles' ) keys = list(guild_data.keys()) index = keys.index(str(emoji)) bt.INFO('Editing roles message') message = await self.get_reaction_message(ctx) content = message.content split_content = content.split('\n') split_content.pop(index + 1) content = '' for line in split_content: content += line + '\n' await message.edit(content=content) await message.clear_reaction(emoji) bt.INFO('Edit complete!') del guild_data[str(emoji)] bt.INFO('Removed data entry')
def load(): """Loads the token from the file Returns: str: The string of the token """ cipher = AESCipher(calculate_hash()) with open('token.key', 'rb') as f: data = f.read() decrypted = cipher.decrypt(data) decrypted = decrypted.replace('\t', '\n') dictionary = json.loads(decrypted) token = dictionary.get('token') bt.INFO(f'Got bot token: {token}') return token