From b889beaa39fb60adb81c422fc9ccc30b3ebd8293 Mon Sep 17 00:00:00 2001 From: AnInternetTroll Date: Thu, 11 Jun 2020 21:22:49 +0200 Subject: minor fixes and !serverinfo --- cogs/player.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 cogs/player.py (limited to 'cogs/player.py') diff --git a/cogs/player.py b/cogs/player.py old mode 100755 new mode 100644 -- cgit v1.2.3 From 452cc8c997a47e95415a54b7130e70b37ee23266 Mon Sep 17 00:00:00 2001 From: AnInternetTroll Date: Sun, 21 Jun 2020 13:05:46 +0100 Subject: modified: cogs/logs.py --- .gitignore | 0 .vscode/settings.json | 0 bot.py | 0 cogs/admin.py | 0 cogs/general.py | 0 cogs/help.py | 0 cogs/logs.py | 0 cogs/player.py | 0 cogs/src.py | 0 cogs/trans.py | 0 cogs/utils.py | 0 custom_commands.json | 0 main.py | 0 palette.png | Bin 14 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 .vscode/settings.json mode change 100644 => 100755 bot.py mode change 100644 => 100755 cogs/admin.py mode change 100644 => 100755 cogs/general.py mode change 100644 => 100755 cogs/help.py mode change 100644 => 100755 cogs/logs.py mode change 100644 => 100755 cogs/player.py mode change 100644 => 100755 cogs/src.py mode change 100644 => 100755 cogs/trans.py mode change 100644 => 100755 cogs/utils.py mode change 100644 => 100755 custom_commands.json mode change 100644 => 100755 main.py mode change 100644 => 100755 palette.png (limited to 'cogs/player.py') diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/.vscode/settings.json b/.vscode/settings.json old mode 100644 new mode 100755 diff --git a/bot.py b/bot.py old mode 100644 new mode 100755 diff --git a/cogs/admin.py b/cogs/admin.py old mode 100644 new mode 100755 diff --git a/cogs/general.py b/cogs/general.py old mode 100644 new mode 100755 diff --git a/cogs/help.py b/cogs/help.py old mode 100644 new mode 100755 diff --git a/cogs/logs.py b/cogs/logs.py old mode 100644 new mode 100755 diff --git a/cogs/player.py b/cogs/player.py old mode 100644 new mode 100755 diff --git a/cogs/src.py b/cogs/src.py old mode 100644 new mode 100755 diff --git a/cogs/trans.py b/cogs/trans.py old mode 100644 new mode 100755 diff --git a/cogs/utils.py b/cogs/utils.py old mode 100644 new mode 100755 diff --git a/custom_commands.json b/custom_commands.json old mode 100644 new mode 100755 diff --git a/main.py b/main.py old mode 100644 new mode 100755 diff --git a/palette.png b/palette.png old mode 100644 new mode 100755 -- cgit v1.2.3 From dcfc891b717abd0cbb0e8b1348ec7459c7ff150b Mon Sep 17 00:00:00 2001 From: AnInternetTroll Date: Fri, 26 Jun 2020 20:54:42 +0200 Subject: Added basic compiler --- .gitignore | 0 .vscode/settings.json | 0 bot.py | 3 +- cogs/compiler.py | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++ cogs/general.py | 0 cogs/help.py | 0 cogs/logs.py | 0 cogs/player.py | 0 cogs/src.py | 0 cogs/trans.py | 0 cogs/utils.py | 0 custom_commands.json | 0 main.py | 0 palette.png | Bin 14 files changed, 80 insertions(+), 1 deletion(-) mode change 100755 => 100644 .gitignore mode change 100755 => 100644 .vscode/settings.json mode change 100755 => 100644 bot.py create mode 100644 cogs/compiler.py mode change 100755 => 100644 cogs/general.py mode change 100755 => 100644 cogs/help.py mode change 100755 => 100644 cogs/logs.py mode change 100755 => 100644 cogs/player.py mode change 100755 => 100644 cogs/src.py mode change 100755 => 100644 cogs/trans.py mode change 100755 => 100644 cogs/utils.py mode change 100755 => 100644 custom_commands.json mode change 100755 => 100644 main.py mode change 100755 => 100644 palette.png (limited to 'cogs/player.py') diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 diff --git a/.vscode/settings.json b/.vscode/settings.json old mode 100755 new mode 100644 diff --git a/bot.py b/bot.py old mode 100755 new mode 100644 index d67dc3c..6c0720d --- a/bot.py +++ b/bot.py @@ -13,7 +13,8 @@ extensions = [ "cogs.trans", "cogs.player", "cogs.general", - "cogs.logs" + "cogs.logs", + "cogs.compiler" ] diff --git a/cogs/compiler.py b/cogs/compiler.py new file mode 100644 index 0000000..3046349 --- /dev/null +++ b/cogs/compiler.py @@ -0,0 +1,78 @@ +from discord.ext import commands +import discord +import requests +import json + +class Compiler(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.command() + async def compile(self, ctx, language=None, *, code=None): + """ + listRequest = requests.get("https://wandbox.org/api/list.json") + compilerList = json.loads(listRequest.text) + + for i in compilerList: + if i["language"] == language: + compiler = i["name"] + print(compiler) + """ + compilers = { + "python":"cpython-3.8.0", + "c":"gcc-head-c", + "javascript":"nodejs-head", + "typescript":"typescript-3.5.1", + "c#":"dotnetcore-head", + "bash": "bash", + "vim-script": "vim-head", + "cpp": "gcc-head", + "swift": "swift-5.0.1", + "php": "php-head", + "coffeescript": "coffeescript-head", + "go": "go-head", + "java": "openjdk-head", + "ruby": "ruby-head", + "rust": "rust-head", + "sql": "sqlite-head", + "perl": "perl-head", + "lua": "lua-5.3.4" + } + if not language: + await ctx.send(f"```json\n{json.dumps(compilers, indent=4)}```") + if not code: + await ctx.send("No code found") + return + try: + compiler = compilers[language] + except KeyError: + await ctx.send("Language not found") + return + body = { + "compiler": compiler, + "code": code, + "save": True + } + head = { + "Content-Type":"application/json" + } + async with ctx.typing(): + r = requests.post("https://wandbox.org/api/compile.json", headers=head, data=json.dumps(body)) + try: + response = json.loads(r.text) + #await ctx.send(f"```json\n{json.dumps(response, indent=4)}```") + print(f"```json\n{json.dumps(response, indent=4)}```") + except json.decoder.JSONDecodeError: + await ctx.send(f"```json\n{r.text}```") + + try: + embed=discord.Embed(title="Compiled code") + embed.add_field(name="Output", value=f'```{response["program_message"]}```', inline=False) + embed.add_field(name="Exit code", value=response["status"], inline=True) + embed.add_field(name="Link", value=f"[Permalink]({response['url']})", inline=True) + await ctx.send(embed=embed) + except KeyError: + await ctx.send(f"```json\n{json.dumps(response, indent=4)}```") + +def setup(bot): + bot.add_cog(Compiler(bot)) \ No newline at end of file diff --git a/cogs/general.py b/cogs/general.py old mode 100755 new mode 100644 diff --git a/cogs/help.py b/cogs/help.py old mode 100755 new mode 100644 diff --git a/cogs/logs.py b/cogs/logs.py old mode 100755 new mode 100644 diff --git a/cogs/player.py b/cogs/player.py old mode 100755 new mode 100644 diff --git a/cogs/src.py b/cogs/src.py old mode 100755 new mode 100644 diff --git a/cogs/trans.py b/cogs/trans.py old mode 100755 new mode 100644 diff --git a/cogs/utils.py b/cogs/utils.py old mode 100755 new mode 100644 diff --git a/custom_commands.json b/custom_commands.json old mode 100755 new mode 100644 diff --git a/main.py b/main.py old mode 100755 new mode 100644 diff --git a/palette.png b/palette.png old mode 100755 new mode 100644 -- cgit v1.2.3 From 5eb37689ae2aac9d0a6aa10d72f14609d12dc985 Mon Sep 17 00:00:00 2001 From: AnInternetTroll Date: Sat, 27 Jun 2020 14:16:19 +0100 Subject: modified: cogs/admin.py modified: cogs/compiler.py --- .gitignore | 0 .vscode/settings.json | 0 bot.py | 0 cogs/admin.py | 5 ++++- cogs/compiler.py | 0 cogs/general.py | 0 cogs/help.py | 0 cogs/logs.py | 0 cogs/player.py | 0 cogs/src.py | 0 cogs/trans.py | 0 cogs/utils.py | 0 custom_commands.json | 0 main.py | 0 palette.png | Bin 15 files changed, 4 insertions(+), 1 deletion(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 .vscode/settings.json mode change 100644 => 100755 bot.py mode change 100644 => 100755 cogs/compiler.py mode change 100644 => 100755 cogs/general.py mode change 100644 => 100755 cogs/help.py mode change 100644 => 100755 cogs/logs.py mode change 100644 => 100755 cogs/player.py mode change 100644 => 100755 cogs/src.py mode change 100644 => 100755 cogs/trans.py mode change 100644 => 100755 cogs/utils.py mode change 100644 => 100755 custom_commands.json mode change 100644 => 100755 main.py mode change 100644 => 100755 palette.png (limited to 'cogs/player.py') diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/.vscode/settings.json b/.vscode/settings.json old mode 100644 new mode 100755 diff --git a/bot.py b/bot.py old mode 100644 new mode 100755 diff --git a/cogs/admin.py b/cogs/admin.py index 67ffa4c..6c15e5e 100644 --- a/cogs/admin.py +++ b/cogs/admin.py @@ -32,7 +32,10 @@ class Admin(commands.Cog): @commands.check(is_mod) async def pull(self, ctx): g = git.cmd.Git(os.getcwd()) - await ctx.send(f"Probably pulled.\n```bash\n{g.pull()}```") + try: + await ctx.send(f"Probably pulled.\n```bash\n{g.pull()}```") + except git.exc.GitCommandError as e: + await ctx.send(f"An error has occured when pulling```bash\n{e}```") @commands.command(aliases=['addcommand', 'newcommand']) @commands.check(is_mod) diff --git a/cogs/compiler.py b/cogs/compiler.py old mode 100644 new mode 100755 diff --git a/cogs/general.py b/cogs/general.py old mode 100644 new mode 100755 diff --git a/cogs/help.py b/cogs/help.py old mode 100644 new mode 100755 diff --git a/cogs/logs.py b/cogs/logs.py old mode 100644 new mode 100755 diff --git a/cogs/player.py b/cogs/player.py old mode 100644 new mode 100755 diff --git a/cogs/src.py b/cogs/src.py old mode 100644 new mode 100755 diff --git a/cogs/trans.py b/cogs/trans.py old mode 100644 new mode 100755 diff --git a/cogs/utils.py b/cogs/utils.py old mode 100644 new mode 100755 diff --git a/custom_commands.json b/custom_commands.json old mode 100644 new mode 100755 diff --git a/main.py b/main.py old mode 100644 new mode 100755 diff --git a/palette.png b/palette.png old mode 100644 new mode 100755 -- cgit v1.2.3 From e593aa26927db20ce21b7dcf6ec499dad232f01d Mon Sep 17 00:00:00 2001 From: Luca Matei Pintilie Date: Thu, 2 Jul 2020 23:09:10 +0200 Subject: New player.py cog --- cogs/player.py | 455 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 377 insertions(+), 78 deletions(-) (limited to 'cogs/player.py') diff --git a/cogs/player.py b/cogs/player.py index 5c046a7..7013e6d 100755 --- a/cogs/player.py +++ b/cogs/player.py @@ -1,18 +1,38 @@ -import asyncio +""" +Please understand Music bots are complex, and that even this basic example can be daunting to a beginner. + +For this reason it's highly advised you familiarize yourself with discord.py, python and asyncio, BEFORE +you attempt to write a music bot. + +This example makes use of: Python 3.6 + +For a more basic voice example please read: + https://github.com/Rapptz/discord.py/blob/rewrite/examples/basic_voice.py + +This is a very basic playlist example, which allows per guild playback of unique queues. +The commands implement very basic logic for basic usage. But allow for expansion. It would be advisable to implement +your own permissions and usage logic for commands. + +e.g You might like to implement a vote before skipping the song or only allow admins to stop the player. + +Music bots require lots of work, and tuning. Goodluck. +If you find any bugs feel free to ping me on discord. @Eviee#0666 +""" import discord -import youtube_dl from discord.ext import commands -from discord.ext import tasks -from pathlib import Path - -# Straight up copied from -# https://github.com/Rapptz/discord.py/blob/master/examples/basic_voice.py -# Suppress noise about console usage from errors -youtube_dl.utils.bug_reports_message = lambda: '' +import asyncio +import itertools +import sys +import traceback +from async_timeout import timeout +from functools import partial +from youtube_dl import YoutubeDL +from pathlib import Path +from discord.ext import tasks -ytdl_format_options = { +ytdlopts = { 'format': 'bestaudio/best', 'outtmpl': 'downloads/%(extractor)s-%(id)s-%(title)s.%(ext)s', 'restrictfilenames': True, @@ -23,117 +43,396 @@ ytdl_format_options = { 'quiet': True, 'no_warnings': True, 'default_search': 'auto', - 'source_address': '0.0.0.0' # bind to ipv4 since ipv6 addresses cause issues sometimes + 'source_address': '0.0.0.0' # ipv6 addresses cause issues sometimes } -ffmpeg_options = { - 'options': '-vn' +ffmpegopts = { + 'before_options': '-nostdin', + 'options': '-vn -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5' } -ytdl = youtube_dl.YoutubeDL(ytdl_format_options) +ytdl = YoutubeDL(ytdlopts) + + +class VoiceConnectionError(commands.CommandError): + """Custom Exception class for connection errors.""" + + +class InvalidVoiceChannel(VoiceConnectionError): + """Exception for cases of invalid Voice Channels.""" class YTDLSource(discord.PCMVolumeTransformer): - def __init__(self, source, *, data, volume=0.5): - super().__init__(source, volume) - self.data = data + def __init__(self, source, *, data, requester): + super().__init__(source) + self.requester = requester self.title = data.get('title') - self.url = data.get('url') + self.web_url = data.get('webpage_url') + + # YTDL info dicts (data) have other useful information you might want + # https://github.com/rg3/youtube-dl/blob/master/README.md + + def __getitem__(self, item: str): + """Allows us to access attributes similar to a dict. + + This is only useful when you are NOT downloading. + """ + return self.__getattribute__(item) @classmethod - async def from_url(cls, url, *, loop=None, stream=False): + async def create_source(cls, ctx, search: str, *, loop, download=True): loop = loop or asyncio.get_event_loop() - data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream)) + + to_run = partial(ytdl.extract_info, url=search, download=download) + data = await loop.run_in_executor(None, to_run) if 'entries' in data: # take first item from a playlist data = data['entries'][0] - filename = data['url'] if stream else ytdl.prepare_filename(data) - return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data) + await ctx.send(f'```ini\n[Added {data["title"]} to the Queue.]\n```', delete_after=15) + + if download: + source = ytdl.prepare_filename(data) + else: + return {'webpage_url': data['webpage_url'], 'requester': ctx.author, 'title': data['title']} + + return cls(discord.FFmpegPCMAudio(source, **ffmpegopts), data=data, requester=ctx.author) + + @classmethod + async def regather_stream(cls, data, *, loop): + """Used for preparing a stream, instead of downloading. + + Since Youtube Streaming links expire.""" + loop = loop or asyncio.get_event_loop() + requester = data['requester'] + + to_run = partial(ytdl.extract_info, url=data['webpage_url'], download=False) + data = await loop.run_in_executor(None, to_run) + + return cls(discord.FFmpegPCMAudio(data['url'], **ffmpegopts), data=data, requester=requester) + + +class MusicPlayer: + """A class which is assigned to each guild using the bot for Music. + + This class implements a queue and loop, which allows for different guilds to listen to different playlists + simultaneously. + + When the bot disconnects from the Voice it's instance will be destroyed. + """ + + __slots__ = ('bot', '_guild', '_channel', '_cog', 'queue', 'next', 'current', 'np', 'volume') + + def __init__(self, ctx): + self.bot = ctx.bot + self._guild = ctx.guild + self._channel = ctx.channel + self._cog = ctx.cog + + self.queue = asyncio.Queue() + self.next = asyncio.Event() + + self.np = None # Now playing message + self.volume = .5 + self.current = None + + ctx.bot.loop.create_task(self.player_loop()) + + async def player_loop(self): + """Our main player loop.""" + await self.bot.wait_until_ready() + + while not self.bot.is_closed(): + self.next.clear() + + try: + # Wait for the next song. If we timeout cancel the player and disconnect... + async with timeout(300): # 5 minutes... + source = await self.queue.get() + except asyncio.TimeoutError: + if self in self._cog.players.values(): + return self.destroy(self._guild) + return + + if not isinstance(source, YTDLSource): + # Source was probably a stream (not downloaded) + # So we should regather to prevent stream expiration + try: + source = await YTDLSource.regather_stream(source, loop=self.bot.loop) + except Exception as e: + await self._channel.send(f'There was an error processing your song.\n' + f'```css\n[{e}]\n```') + continue + + source.volume = self.volume + self.current = source + + self._guild.voice_client.play(source, after=lambda _: self.bot.loop.call_soon_threadsafe(self.next.set)) + self.np = await self._channel.send(f'**Now Playing:** `{source.title}` requested by ' + f'`{source.requester}`') + await self.next.wait() + + # Make sure the FFmpeg process is cleaned up. + source.cleanup() + self.current = None + + try: + # We are no longer playing this song... + await self.np.delete() + except discord.HTTPException: + pass + + def destroy(self, guild): + """Disconnect and cleanup the player.""" + return self.bot.loop.create_task(self._cog.cleanup(guild)) class Player(commands.Cog): + """Music related commands.""" + + __slots__ = ('bot', 'players') + def __init__(self, bot): self.bot = bot self.cleanup.start() + self.players = {} - async def is_in_vc(ctx): + async def cleanup(self, guild): try: - return ctx.author.voice.channel + await guild.voice_client.disconnect() except AttributeError: - return False + pass + + try: + for entry in self.players[guild.id].queue._queue: + if isinstance(entry, YTDLSource): + entry.cleanup() + self.players[guild.id].queue._queue.clear() + except KeyError: + pass + + try: + del self.players[guild.id] + except KeyError: + pass + + async def __local_check(self, ctx): + """A local check which applies to all commands in this cog.""" + if not ctx.guild: + raise commands.NoPrivateMessage + return True + + async def __error(self, ctx, error): + """A local error handler for all errors arising from commands in this cog.""" + if isinstance(error, commands.NoPrivateMessage): + try: + return await ctx.send('This command can not be used in Private Messages.') + except discord.HTTPException: + pass + elif isinstance(error, InvalidVoiceChannel): + await ctx.send('Error connecting to Voice Channel. ' + 'Please make sure you are in a valid channel or provide me with one') + + print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr) + traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr) + + def get_player(self, ctx): + """Retrieve the guild player, or generate one.""" + try: + player = self.players[ctx.guild.id] + except KeyError: + player = MusicPlayer(ctx) + self.players[ctx.guild.id] = player + + return player + + @commands.command(name='connectvc', aliases=['join']) + async def connect_(self, ctx, *, channel: discord.VoiceChannel=None): + """Connect to voice. + + Parameters + ------------ + channel: discord.VoiceChannel [Optional] + The channel to connect to. If a channel is not specified, an attempt to join the voice channel you are in + will be made. + + This command also handles moving the bot to different channels. + """ + if not channel: + try: + channel = ctx.author.voice.channel + except AttributeError: + raise InvalidVoiceChannel('No channel to join. Please either specify a valid channel or join one.') + + vc = ctx.voice_client + + if vc: + if vc.channel.id == channel.id: + return + try: + await vc.move_to(channel) + except asyncio.TimeoutError: + raise VoiceConnectionError(f'Moving to channel: <{channel}> timed out.') + else: + try: + await channel.connect() + except asyncio.TimeoutError: + raise VoiceConnectionError(f'Connecting to channel: <{channel}> timed out.') + + await ctx.send(f'Connected to: **{channel}**', delete_after=20) + + @commands.command(name='play', aliases=['sing']) + async def play_(self, ctx, *, search: str): + """Request a song and add it to the queue. + + This command attempts to join a valid voice channel if the bot is not already in one. + Uses YTDL to automatically search and retrieve a song. + + Parameters + ------------ + search: str [Required] + The song to search and retrieve using YTDL. This could be a simple search, an ID or URL. + """ + await ctx.trigger_typing() + + vc = ctx.voice_client + + if not vc: + await ctx.invoke(self.connect_) - @commands.check(is_in_vc) - @commands.command() - async def join(self, ctx, *, channel: discord.VoiceChannel): - """Joins a voice channel""" + player = self.get_player(ctx) - if ctx.voice_client is not None: - return await ctx.voice_client.move_to(channel) + # If download is False, source will be a dict which will be used later to regather the stream. + # If download is True, source will be a discord.FFmpegPCMAudio with a VolumeTransformer. + source = await YTDLSource.create_source(ctx, search, loop=self.bot.loop, download=False) - await channel.connect() - @commands.command() - async def play(self, ctx, *, query): - """Plays a file from the local filesystem""" + await player.queue.put(source) - source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(query)) - ctx.voice_client.play(source, after=lambda e: print('Player error: %s' % e) if e else None) + @commands.command(name='pause') + async def pause_(self, ctx): + """Pause the currently playing song.""" + vc = ctx.voice_client - await ctx.send('Now playing: {}'.format(query)) - @commands.command() - async def yt(self, ctx, *, url): - """Plays from a url (almost anything youtube_dl supports)""" + if not vc or not vc.is_playing(): + return await ctx.send('I am not currently playing anything!', delete_after=20) + elif vc.is_paused(): + return + + vc.pause() + await ctx.send(f'**`{ctx.author}`**: Paused the song!') + + @commands.command(name='resume') + async def resume_(self, ctx): + """Resume the currently paused song.""" + vc = ctx.voice_client + + if not vc or not vc.is_connected(): + return await ctx.send('I am not currently playing anything!', delete_after=20) + elif not vc.is_paused(): + return + + vc.resume() + await ctx.send(f'**`{ctx.author}`**: Resumed the song!') + + @commands.command(name='skip') + async def skip_(self, ctx): + """Skip the song.""" + vc = ctx.voice_client + + if not vc or not vc.is_connected(): + return await ctx.send('I am not currently playing anything!', delete_after=20) + + if vc.is_paused(): + pass + elif not vc.is_playing(): + return + + vc.stop() + await ctx.send(f'**`{ctx.author}`**: Skipped the song!') + + @commands.command(name='queue', aliases=['q', 'playlist']) + async def queue_info(self, ctx): + """Retrieve a basic queue of upcoming songs.""" + vc = ctx.voice_client + + if not vc or not vc.is_connected(): + return await ctx.send('I am not currently connected to voice!', delete_after=20) + + player = self.get_player(ctx) + if player.queue.empty(): + return await ctx.send('There are currently no more queued songs.') + + # Grab up to 5 entries from the queue... + upcoming = list(itertools.islice(player.queue._queue, 0, 5)) + + fmt = '\n'.join(f'**`{_["title"]}`**' for _ in upcoming) + embed = discord.Embed(title=f'Upcoming - Next {len(upcoming)}', description=fmt) + + await ctx.send(embed=embed) + + @commands.command(name='now_playing', aliases=['np', 'current', 'currentsong', 'playing']) + async def now_playing_(self, ctx): + """Display information about the currently playing song.""" + vc = ctx.voice_client + + if not vc or not vc.is_connected(): + return await ctx.send('I am not currently connected to voice!', delete_after=20) + + player = self.get_player(ctx) + if not player.current: + return await ctx.send('I am not currently playing anything!') + + try: + # Remove our previous now_playing message. + await player.np.delete() + except discord.HTTPException: + pass - async with ctx.typing(): - player = await YTDLSource.from_url(url, loop=self.bot.loop) - ctx.voice_client.play(player, after=lambda e: print('Player error: %s' % e) if e else None) + player.np = await ctx.send(f'**Now Playing:** `{vc.source.title}` ' + f'requested by `{vc.source.requester}`') - await ctx.send('Now playing: {}'.format(player.title)) + @commands.command(name='volume', aliases=['vol']) + async def change_volume(self, ctx, *, vol: float): + """Change the player volume. - @commands.command() - async def stream(self, ctx, *, url): - """Streams from a url (same as yt, but doesn't predownload)""" + Parameters + ------------ + volume: float or int [Required] + The volume to set the player to in percentage. This must be between 1 and 100. + """ + vc = ctx.voice_client - async with ctx.typing(): - player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True) - ctx.voice_client.play(player, after=lambda e: print('Player error: %s' % e) if e else None) + if not vc or not vc.is_connected(): + return await ctx.send('I am not currently connected to voice!', delete_after=20) - await ctx.send('Now playing: {}'.format(player.title)) + if not 0 < vol < 101: + return await ctx.send('Please enter a value between 1 and 100.') - @commands.check(is_in_vc) - @commands.command() - async def volume(self, ctx, volume: int): - """Changes the player's volume""" + player = self.get_player(ctx) - if ctx.voice_client is None: - return await ctx.send("Not connected to a voice channel.") + if vc.source: + vc.source.volume = vol / 100 - ctx.voice_client.source.volume = volume / 100 - await ctx.send("Changed volume to {}%".format(volume)) + player.volume = vol / 100 + await ctx.send(f'**`{ctx.author}`**: Set the volume to **{vol}%**') - @commands.check(is_in_vc) - @commands.command() - async def stop(self, ctx): - """Stops and disconnects the bot from voice""" + @commands.command(name='stop') + async def stop_(self, ctx): + """Stop the currently playing song and destroy the player. - await ctx.voice_client.disconnect() + !Warning! + This will destroy the player assigned to your guild, also deleting any queued songs and settings. + """ + vc = ctx.voice_client - @play.before_invoke - @yt.before_invoke - @stream.before_invoke - async def ensure_voice(self, ctx): - if ctx.voice_client is None: - if ctx.author.voice: - await ctx.author.voice.channel.connect() - else: - await ctx.send("You are not connected to a voice channel.") - raise commands.CommandError("Author not connected to a voice channel.") - elif ctx.voice_client.is_playing(): - ctx.voice_client.stop() + if not vc or not vc.is_connected(): + return await ctx.send('I am not currently playing anything!', delete_after=20) + await self.cleanup(ctx.guild) + @tasks.loop(hours=10.0) async def cleanup(self): for p in Path("./downloads/").glob("*"): @@ -141,4 +440,4 @@ class Player(commands.Cog): def setup(bot): - bot.add_cog(Player(bot)) \ No newline at end of file + bot.add_cog(Player(bot)) -- cgit v1.2.3