async def night_core(self, ctx: custom.Context) -> None: """ Sets a nightcore audio filter on the player. """ if enums.Filters.NIGHTCORE in ctx.voice_client.enabled_filters: await ctx.voice_client.set_filter( slate.obsidian.Filter(ctx.voice_client.filter, timescale=slate.obsidian.Timescale())) ctx.voice_client.enabled_filters.remove(enums.Filters.NIGHTCORE) embed = utils.embed( colour=colours.GREEN, description="**Nightcore** audio effect is now **inactive**.") else: await ctx.voice_client.set_filter( slate.obsidian.Filter(ctx.voice_client.filter, timescale=slate.obsidian.Timescale( speed=1.12, pitch=1.12))) ctx.voice_client.enabled_filters.add(enums.Filters.NIGHTCORE) embed = utils.embed( colour=colours.GREEN, description="**Nightcore** audio effect is now **active**.") await ctx.reply(embed=embed)
async def _8d(self, ctx: custom.Context) -> None: """ Sets an 8D audio filter on the player. """ if enums.Filters.ROTATION in ctx.voice_client.enabled_filters: await ctx.voice_client.set_filter( slate.obsidian.Filter(ctx.voice_client.filter, rotation=slate.obsidian.Rotation())) ctx.voice_client.enabled_filters.remove(enums.Filters.ROTATION) embed = utils.embed( colour=colours.GREEN, description="**8D** audio effect is now **inactive**.") else: await ctx.voice_client.set_filter( slate.obsidian.Filter( ctx.voice_client.filter, rotation=slate.obsidian.Rotation(rotation_hertz=0.5))) ctx.voice_client.enabled_filters.add(enums.Filters.ROTATION) embed = utils.embed( colour=colours.GREEN, description="**8D** audio effect is now **active**.") await ctx.reply(embed=embed)
async def save(self, ctx: custom.Context) -> None: """ Saves the current track to our DM's. """ try: embed = utils.embed( title=f"{ctx.voice_client.current.title}", url=f"{ctx.voice_client.current.uri}", image=ctx.voice_client.current.thumbnail, description=f"**Author:** {ctx.voice_client.current.author}\n" f"**Source:** {ctx.voice_client.current.source.name.title()}\n" f"**Length:** {utils.format_seconds(ctx.voice_client.current.length // 1000, friendly=True)}\n" f"**Is stream:** {ctx.voice_client.current.is_stream()}\n" f"**Is seekable:** {ctx.voice_client.current.is_seekable()}\n" f"**Requester:** {ctx.voice_client.current.requester} `{ctx.voice_client.current.requester.id}`" ) await ctx.author.send(embed=embed) await ctx.reply(embed=utils.embed( colour=colours.GREEN, description="Saved the current track to our DM's.")) except discord.Forbidden: raise exceptions.EmbedError(colour=colours.RED, description="I am unable to DM you.")
async def todo_add(self, ctx: custom.Context, *, content: converters.TodoContentConverter) -> None: """ Creates a todo. **content**: The content of your todo. Must be under 150 characters. **Usage:** `l-todo Finish documentation` """ user_config = await self.bot.user_manager.get_config(ctx.author.id) if len(user_config.todos) > 100: raise exceptions.EmbedError( colour=colours.RED, description= "You have 100 todos, try finishing some before adding any more.", ) todo = await user_config.create_todo(content=str(content), jump_url=ctx.message.jump_url) await ctx.reply(embed=utils.embed( colour=colours.GREEN, description=f"Todo **{todo.id}** created."))
async def source(self, ctx: custom.Context, *, command: Optional[str]) -> None: if not command: await ctx.reply( embed=utils.embed( emoji="\U0001f4da", description=f"My source code can be viewed here: **{values.GITHUB_LINK}**" ) ) return if command == "help": source = type(self.bot.help_command) module = source.__module__ filename: str = str(inspect.getsourcefile(source)) else: if (obj := self.bot.get_command(command.replace(".", ""))) is None: raise exceptions.EmbedError( colour=colours.RED, emoji=emojis.CROSS, description="I couldn't find that command." ) source = obj.callback.__code__ module = obj.callback.__module__ filename = source.co_filename
async def sort(self, ctx: custom.Context, method: Literal["title", "length", "author"], reverse: bool = False) -> None: """ Sorts the queue. **method**: The method to sort the queue with. Can be **title**, **length** or **author**. **reverse**: Whether to reverse the sort, as in **5, 3, 2, 4, 1** -> **5, 4, 3, 2, 1** instead of **5, 3, 2, 4, 1** -> **1, 2, 3, 4, 5**. Defaults to False. **Usage:** `l-sort title True` `l-sort author` `l-sort length True` """ if method == "title": ctx.voice_client.queue._queue.sort(key=lambda track: track.title, reverse=reverse) elif method == "author": ctx.voice_client.queue._queue.sort(key=lambda track: track.author, reverse=reverse) elif method == "length": ctx.voice_client.queue._queue.sort(key=lambda track: track.length, reverse=reverse) await ctx.reply(embed=utils.embed( colour=colours.GREEN, description=f"The queue has been sorted by**{method}**."))
async def tag_transfer(self, ctx: custom.Context, tag: objects.Tag, *, member: discord.Member) -> None: """ Transfers a tag to another member. **name**: The name of the tag to transfer. **member**: The member to transfer the tag too. Can be their ID, Username, Nickname or @Mention. """ if member.bot: raise exceptions.EmbedError( colour=colours.RED, description="You can not transfer tags to bots.") if tag.user_id != ctx.author.id: raise exceptions.EmbedError(colour=colours.RED, description="You do not own that tag.") if tag.user_id == member.id: raise exceptions.EmbedError( colour=colours.RED, description="You can not transfer tags to yourself.") await tag.change_owner(user_id=member.id) await ctx.reply(embed=utils.embed( colour=colours.GREEN, emoji=emojis.TICK, description= f"Transferred tag **{tag.name}** from **{ctx.author}** to **{ctx.guild.get_member(tag.user_id)}**.", ))
def __init__( self, footer_url: str | None = None, footer: str | None = None, image: str | None = None, thumbnail: str | None = None, author: str | None = None, author_url: str | None = None, author_icon_url: str | None = None, title: str | None = None, description: str | None = None, url: str | None = None, colour: discord.Colour = colours.MAIN, emoji: str | None = None, view: discord.ui.View | None = None, ) -> None: self.embed: discord.Embed = utils.embed( footer_url=footer_url, footer=footer, image=image, thumbnail=thumbnail, author=author, author_url=author_url, author_icon_url=author_icon_url, title=title, description=description, url=url, colour=colour, emoji=emoji, ) self.view: discord.ui.View | None = view
async def dev_cleanup(self, ctx: custom.Context, limit: int = 100) -> None: """ Deletes the bots messages. **limit**: The amount of messages to check back through to delete. """ if ctx.channel.permissions_for(ctx.me).manage_messages: messages = await ctx.channel.purge( check=lambda message: message.author == ctx.me or message. content.startswith(config.PREFIX), limit=limit) else: messages = await ctx.channel.purge( check=lambda message: message.author == ctx.me, bulk=False, limit=limit) s = "s" if len(messages) > 1 else "" s1 = "s" if limit > 1 else "" # TODO: Pluraliser or something?? embed = utils.embed( colour=colours.GREEN, emoji=emojis.TICK, description= f"Found and deleted **{len(messages)}** message{s} out of the last **{limit}** message{s1}.", ) await ctx.reply(embed=embed, delete_after=10)
async def _birthday_set(self, ctx: custom.Context, *, date: objects.PastPhrasedDatetimeSearch) -> None: """ Sets your birthday. **date**: Your birthday. This should include some form of date such as **tomorrow**, **in 3 weeks** or **1st january 2020**. """ entries = {index: (phrase, datetime) for index, (phrase, datetime) in enumerate(date.datetimes.items())} choice = await ctx.choice( entries=[f"`{index + 1}:` {phrase}\n{utils.format_date(datetime)}" for index, (phrase, datetime) in entries.items()], per_page=5, splitter="\n\n", title="Type the number of the date you want to set your birthday too:", ) _, birthday = entries[choice] if birthday > pendulum.now(tz="UTC").subtract(years=13): raise exceptions.EmbedError( colour=colours.RED, emoji=emojis.CROSS, description="Your birthday must allow you to be more than 13 years old.", ) user_config = await self.bot.user_manager.get_config(ctx.author.id) await user_config.set_birthday(birthday) embed = utils.embed( colour=colours.GREEN, emoji=emojis.TICK, description=f"Birthday set to **{utils.format_date(user_config.birthday)}**", ) await ctx.reply(embed=embed)
async def join(self, ctx: custom.Context) -> None: """ Joins the bot to your voice channel. """ if ctx.voice_client and ctx.voice_client.is_connected(): raise exceptions.EmbedError( colour=colours.RED, emoji=emojis.CROSS, description= f"I am already connected to {ctx.voice_client.voice_channel.mention}.", ) if not ctx.author.voice or not ctx.author.voice.channel: raise exceptions.EmbedError( colour=colours.RED, description= "You must be connected to a voice channel to use this command.", ) # noinspection PyTypeChecker await ctx.author.voice.channel.connect(cls=custom.Player) ctx.voice_client._text_channel = ctx.channel await ctx.send(embed=utils.embed( colour=colours.GREEN, emoji=emojis.TICK, description=f"Joined {ctx.voice_client.voice_channel.mention}."))
async def _reminders_delete(self, ctx: custom.Context, reminders: commands.Greedy[objects.Reminder]) -> None: """ Deletes reminders with the given id's. **reminders**: A list of reminders id's to delete, separated by spaces. """ if not reminders: raise exceptions.EmbedError( colour=colours.RED, emoji=emojis.CROSS, description="One or more of the reminder id's provided were invalid." ) for reminder in reminders: await reminder.delete() s = "s" if len(reminders) > 1 else "" embed = utils.embed( colour=colours.GREEN, emoji=emojis.TICK, description=f"Deleted **{len(reminders)}** reminder{s} with id{s} {', '.join(f'**{reminder.id}**' for reminder in reminders)}.", ) await ctx.reply(embed=embed)
async def move(self, ctx: custom.Context, entry_1: int = 0, entry_2: int = 0) -> None: """ Move a track in the queue to a different position. **entry_1**: The position of the track you want to move from. **entry_2**: The position of the track you want to move too. """ if entry_1 <= 0 or entry_1 > len(ctx.voice_client.queue): raise exceptions.EmbedError( colour=colours.RED, description= f"That was not a valid track entry to move from, try again with a number between **1** and **{len(ctx.voice_client.queue)}**.", ) if entry_2 <= 0 or entry_2 > len(ctx.voice_client.queue): raise exceptions.EmbedError( colour=colours.RED, description= f"That was not a valid track entry to move too, try again with a number between **1** and **{len(ctx.voice_client.queue)}**.", ) track = ctx.voice_client.queue.get(entry_1 - 1, put_history=False) ctx.voice_client.queue.put(track, position=entry_2 - 1) embed = utils.embed( colour=colours.GREEN, description= f"Moved **[{track.title}]({track.uri})** from position **{entry_1}** to position **{entry_2}**.", ) await ctx.reply(embed=embed)
async def reverse(self, ctx: custom.Context) -> None: """ Reverses the queue. """ ctx.voice_client.queue.reverse() await ctx.reply(embed=utils.embed( colour=colours.GREEN, description="The queue has been reversed."))
async def clear(self, ctx: custom.Context) -> None: """ Clears the queue. """ ctx.voice_client.queue.clear() await ctx.reply(embed=utils.embed( colour=colours.GREEN, description="The queue has been cleared."))
async def handle_track_error(self) -> None: await self.send(embed=utils.embed( colour=colours.RED, description="Something went wrong while playing a track.", ), view=views.SupportButton()) await self.handle_track_over()
async def invoke_controller( self, channel: discord.TextChannel | None = None ) -> discord.Message | None: if (channel is None and self.text_channel is None) or self.current is None: return text_channel = channel or self.text_channel if text_channel is None: return guild_config = await self.client.guild_manager.get_config( text_channel.guild.id) embed = utils.embed( title="Now playing:", description= f"**[{self.current.title}]({self.current.uri})**\nBy **{self.current.author}**", thumbnail=self.current.thumbnail) if guild_config.embed_size.value >= enums.EmbedSize.MEDIUM.value: embed.add_field( name="__Player info:__", value=f"**Paused:** {self.paused}\n" f"**Loop mode:** {self.queue.loop_mode.name.title()}\n" f"**Queue length:** {len(self.queue)}\n" f"**Queue time:** {utils.format_seconds(sum(track.length for track in self.queue) // 1000, friendly=True)}\n", ) embed.add_field( name="__Track info:__", value= f"**Time:** {utils.format_seconds(self.position // 1000)} / {utils.format_seconds(self.current.length // 1000)}\n" f"**Is Stream:** {self.current.is_stream()}\n" f"**Source:** {self.current.source.value.title()}\n" f"**Requester:** {self.current.requester.mention if self.current.requester else 'N/A'}\n" ) if guild_config.embed_size is enums.EmbedSize.LARGE and not self.queue.is_empty( ): entries = [ f"**{index + 1}.** [{entry.title}]({entry.uri})" for index, entry in enumerate(list(self.queue)[:3]) ] if len(self.queue) > 3: entries.append( f"**...**\n**{len(self.queue)}.** [{self.queue[-1].title}]({self.queue[-1].uri})" ) embed.add_field(name="__Up next:__", value="\n".join(entries), inline=False) return await text_channel.send(embed=embed)
async def disconnect(self, ctx: custom.Context) -> None: """ Disconnects the bot from its voice channel. """ await ctx.send(embed=utils.embed( colour=colours.GREEN, emoji=emojis.TICK, description=f"Left {ctx.voice_client.voice_channel.mention}.")) await ctx.voice_client.disconnect()
async def notifications(self, ctx: custom.Context) -> None: """ Shows your current notification settings. """ user_config = await self.bot.user_manager.get_config(ctx.author.id) await ctx.send(embed=utils.embed( title=f"Notification settings for **{ctx.author}**:", description= f"**Levels up:** {utils.readable_bool(user_config.notifications.level_ups)}" ))
async def resume(self, ctx: custom.Context) -> None: """ Resumes the current track. """ if ctx.voice_client.paused is False: raise exceptions.EmbedError( colour=colours.RED, description="The player is not paused.") await ctx.voice_client.set_pause(False) await ctx.reply(embed=utils.embed( colour=colours.GREEN, description="The player is now **resumed**.") )
async def pause(self, ctx: custom.Context) -> None: """ Pauses the current track. """ if ctx.voice_client.paused is True: raise exceptions.EmbedError( colour=colours.RED, description="The played is already paused.") await ctx.voice_client.set_pause(True) await ctx.reply(embed=utils.embed( colour=colours.GREEN, description="The player is now **paused**."))
async def replay(self, ctx: custom.Context) -> None: """ Replays the current track. """ await ctx.voice_client.set_position(0) embed = utils.embed( colour=colours.GREEN, description= f"Replaying [{ctx.voice_client.current.title}]({ctx.voice_client.current.uri}) by **{ctx.voice_client.current.author}**." ) await ctx.reply(embed=embed)
async def edit_image(ctx: custom.Context, edit_function: Callable[..., Any], image: objects.Image, **kwargs) -> None: embed = utils.embed(colour=colours.GREEN, emoji=emojis.LOADING, description="Processing image") message = await ctx.reply(embed=embed) image_bytes = await request_image_bytes(session=ctx.bot.session, url=image.url) receiving_pipe, sending_pipe = multiprocessing.Pipe(duplex=False) process = multiprocessing.Process(target=do_edit_image, daemon=True, args=(edit_function, image_bytes, sending_pipe), kwargs=kwargs) process.start() data = await ctx.bot.loop.run_in_executor(None, receiving_pipe.recv) process.join() receiving_pipe.close() sending_pipe.close() process.terminate() process.close() if data is ValueError or data is EOFError: try: await message.delete() except Exception: pass raise exceptions.EmbedError( colour=colours.RED, description="Something went wrong while editing that image.") url = await utils.upload_file(ctx.bot.session, file_bytes=data[0], file_format=data[1]) try: await message.delete() except Exception: pass await ctx.reply(url) del data
async def fast_forward(self, ctx: custom.Context, *, time: objects.Time) -> None: """ Seeks the player forward. **time**: The amount of time to seek forward. Valid time formats include: - 01:10:20 (hh:mm:ss) - 01:20 (mm:ss) - 20 (ss) - (h:m:s) - (hh:m:s) - (h:mm:s) - etc - 1 hour 20 minutes 30 seconds - 1 hour 20 minutes - 1 hour 30 seconds - 20 minutes 30 seconds - 20 minutes - 30 seconds - 1 hour 20 minutes and 30 seconds - 1h20m30s - 20m and 30s - 20s - etc """ milliseconds = time.seconds * 1000 position = ctx.voice_client.position remaining = ctx.voice_client.current.length - position if milliseconds >= remaining: raise exceptions.EmbedError( colour=colours.RED, description= f"That was too much time to seek forward, try seeking forward an amount less than " f"**{utils.format_seconds(remaining // 1000, friendly=True)}**.", ) await ctx.voice_client.set_position(position + milliseconds) embed = utils.embed( colour=colours.GREEN, description= f"Seeking forward **{utils.format_seconds(time.seconds, friendly=True)}**, the players position is now " f"**{utils.format_seconds(ctx.voice_client.position // 1000, friendly=True)}**." ) await ctx.reply(embed=embed)
async def _timezone_reset(self, ctx: custom.Context) -> None: """ Resets your timezone. """ user_config = await self.bot.user_manager.get_config(ctx.author.id) await user_config.set_timezone() embed = utils.embed( colour=colours.GREEN, emoji=emojis.TICK, description=f"Your timezone has been reset back to **{None}**.", ) await ctx.reply(embed=embed)
async def _birthday_reset(self, ctx: custom.Context) -> None: """ Resets your birthday. """ user_config = await self.bot.user_manager.get_config(ctx.author.id) await user_config.set_birthday() await ctx.reply( embed=utils.embed( colour=colours.GREEN, emoji=emojis.TICK, description="Birthday reset." ) )
async def loop_queue(self, ctx: custom.Context) -> None: """ Loops the queue. """ if ctx.voice_client.queue.loop_mode != slate.QueueLoopMode.QUEUE: ctx.voice_client.queue.set_loop_mode(slate.QueueLoopMode.QUEUE) else: ctx.voice_client.queue.set_loop_mode(slate.QueueLoopMode.OFF) embed = utils.embed( colour=colours.GREEN, description= f"The queue looping mode is now **{ctx.voice_client.queue.loop_mode.name.title()}**.", ) await ctx.reply(embed=embed)
async def seek(self, ctx: custom.Context, *, time: objects.Time) -> None: """ Seeks to a position in the current track. **time**: The position to seek too. Valid time formats include: - 01:10:20 (hh:mm:ss) - 01:20 (mm:ss) - 20 (ss) - (h:m:s) - (hh:m:s) - (h:mm:s) - etc - 1 hour 20 minutes 30 seconds - 1 hour 20 minutes - 1 hour 30 seconds - 20 minutes 30 seconds - 20 minutes - 30 seconds - 1 hour 20 minutes and 30 seconds - 1h20m30s - 20m and 30s - 20s - etc """ milliseconds = time.seconds * 1000 if 0 < milliseconds > ctx.voice_client.current.length: raise exceptions.EmbedError( colour=colours.RED, description= f"That is not a valid amount of time, please choose a time between " f"**0s** and **{utils.format_seconds(ctx.voice_client.current.length // 1000, friendly=True)}**.", ) await ctx.voice_client.set_position(milliseconds) embed = utils.embed( colour=colours.GREEN, description= f"The players position is now **{utils.format_seconds(ctx.voice_client.position // 1000, friendly=True)}**." ) await ctx.reply(embed=embed)
async def tag_delete(self, ctx: custom.Context, *, tag: objects.Tag) -> None: """ Deletes a tag. **name**: The name of the tag to delete. """ if tag.user_id != ctx.author.id: raise exceptions.EmbedError(colour=colours.RED, description="You do not own that tag.") await tag.delete() await ctx.reply(embed=utils.embed( colour=colours.GREEN, description=f"Deleted tag with name **{tag.name}**."))
async def todo_edit(self, ctx: custom.Context, todo: objects.Todo, *, content: converters.TodoContentConverter) -> None: """ Edits a todo. **todo_id**: The id of the todo to edit. **content**: The content of the todo. **Usage:** `l-todo edit 1 new content here` """ await todo.change_content(content=str(content), jump_url=ctx.message.jump_url) await ctx.reply(embed=utils.embed( colour=colours.GREEN, description=f"Edited content of todo with id **{todo.id}**."))