Advertisement
Guest User

Untitled

a guest
Feb 3rd, 2021
82
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.31 KB | None | 0 0
  1. 1#! /usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import discord
  4. from discord.ext import commands
  5.  
  6. import asyncio
  7. import itertools
  8. import sys
  9. import traceback
  10. from async_timeout import timeout
  11. from functools import partial
  12. from youtube_dl import YoutubeDL
  13.  
  14.  
  15. ytdlopts = {
  16.     'format': 'bestaudio/best',
  17.     'outtmpl': 'downloads/%(extractor)s-%(id)s-%(title)s.%(ext)s',
  18.     'restrictfilenames': True,
  19.     'noplaylist': True,
  20.     'nocheckcertificate': True,
  21.     'ignoreerrors': False,
  22.     'logtostderr': False,
  23.     'quiet': True,
  24.     'no_warnings': True,
  25.     'default_search': 'auto',
  26.     'source_address': '0.0.0.0'  # ipv6 addresses cause issues sometimes
  27. }
  28.  
  29. ffmpegopts = {
  30.     'before_options': '-nostdin',
  31.     'options': '-vn'
  32. }
  33.  
  34. ytdl = YoutubeDL(ytdlopts)
  35.  
  36.  
  37. class VoiceConnectionError(commands.CommandError):
  38.     """Custom Exception class for connection errors."""
  39.  
  40.  
  41. class InvalidVoiceChannel(VoiceConnectionError):
  42.     """Exception for cases of invalid Voice Channels."""
  43.  
  44.  
  45. class YTDLSource(discord.PCMVolumeTransformer):
  46.  
  47.     def __init__(self, source, *, data, requester):
  48.         super().__init__(source)
  49.         self.requester = requester
  50.  
  51.         self.title = data.get('title')
  52.         self.web_url = data.get('webpage_url')
  53.  
  54.         # YTDL info dicts (data) have other useful information you might want
  55.         # https://github.com/rg3/youtube-dl/blob/master/README.md
  56.  
  57.     def __getitem__(self, item: str):
  58.         """Allows us to access attributes similar to a dict.
  59.        This is only useful when you are NOT downloading.
  60.        """
  61.         return self.__getattribute__(item)
  62.  
  63.     @classmethod
  64.     async def create_source(cls, ctx, search: str, *, loop, download=False):
  65.         loop = loop or asyncio.get_event_loop()
  66.         if ctx.channel.id != 804421163568070666:
  67.             return
  68.  
  69.         to_run = partial(ytdl.extract_info, url=search, download=download)
  70.         data = await loop.run_in_executor(None, to_run)
  71.  
  72.         if 'entries' in data:
  73.             # take first item from a playlist
  74.             data = data['entries'][0]
  75.             succes = discord.Embed(title='Добавление в очередь', description = f'Песня {data["title"]} Была успешно добавлена {ctx.author.name}', colour = 0x9900FF)
  76.             await ctx.send(embed=succes)
  77.  
  78.         if download:
  79.             source = ytdl.prepare_filename(data)
  80.         else:
  81.             return {'webpage_url': data['webpage_url'], 'requester': ctx.author, 'title': data['title']}
  82.  
  83.         return cls(discord.FFmpegPCMAudio(source), data=data, requester=ctx.author)
  84.  
  85.     @classmethod
  86.     async def regather_stream(cls, data, *, loop):
  87.         """Used for preparing a stream, instead of downloading.
  88.        Since Youtube Streaming links expire."""
  89.         loop = loop or asyncio.get_event_loop()
  90.         requester = data['requester']
  91.  
  92.         to_run = partial(ytdl.extract_info, url=data['webpage_url'], download=False)
  93.         data = await loop.run_in_executor(None, to_run)
  94.  
  95.         return cls(discord.FFmpegPCMAudio(data['url']), data=data, requester=requester)
  96.  
  97.  
  98. class MusicPlayer(commands.Cog):
  99.     """A class which is assigned to each guild using the bot for Music.
  100.    This class implements a queue and loop, which allows for different guilds to listen to different playlists
  101.    simultaneously.
  102.    When the bot disconnects from the Voice it's instance will be destroyed.
  103.    """
  104.  
  105.     __slots__ = ('bot', '_guild', '_channel', '_cog', 'queue', 'next', 'current', 'np', 'volume')
  106.  
  107.     def __init__(self, ctx):
  108.         self.bot = ctx.bot
  109.         self._guild = ctx.guild
  110.         self._channel = ctx.channel
  111.         self._cog = ctx.cog
  112.  
  113.         self.queue = asyncio.Queue()
  114.         self.next = asyncio.Event()
  115.  
  116.         self.np = None  # Now playing message
  117.         self.volume = .5
  118.         self.current = None
  119.  
  120.         ctx.bot.loop.create_task(self.player_loop())
  121.  
  122.     async def player_loop(self):
  123.         """Our main player loop."""
  124.         await self.bot.wait_until_ready()
  125.  
  126.         while not self.bot.is_closed():
  127.             self.next.clear()
  128.  
  129.             try:
  130.                 # Wait for the next song. If we timeout cancel the player and disconnect...
  131.                 async with timeout(300):  # 5 minutes...
  132.                     source = await self.queue.get()
  133.             except asyncio.TimeoutError:
  134.                 return self.destroy(self._guild)
  135.  
  136.             if not isinstance(source, YTDLSource):
  137.                 # Source was probably a stream (not downloaded)
  138.                 # So we should regather to prevent stream expiration
  139.                 try:
  140.                     source = await YTDLSource.regather_stream(source, loop=self.bot.loop)
  141.                 except Exception as e:
  142.                     await self._channel.send(f'Ошибка при проигрывании песни.\n'
  143.                                              f'```css\n[{e}]\n```')
  144.                     continue
  145.  
  146.             source.volume = self.volume
  147.             self.current = source
  148.  
  149.             self._guild.voice_client.play(source, after=lambda _: self.bot.loop.call_soon_threadsafe(self.next.set))
  150.             self.np = await self._channel.send(f'**Сейчас играет:** `{source.title}` запустил '
  151.                                                f'`{source.requester}`')
  152.             await self.next.wait()
  153.  
  154.             # Make sure the FFmpeg process is cleaned up.
  155.             source.cleanup()
  156.             self.current = None
  157.  
  158.             try:
  159.                 # We are no longer playing this song...
  160.                 await self.np.delete()
  161.             except discord.HTTPException:
  162.                 pass
  163.  
  164.     def destroy(self, guild):
  165.         """Disconnect and cleanup the player."""
  166.         return self.bot.loop.create_task(self._cog.cleanup(guild))
  167.  
  168.  
  169. class Music(commands.Cog):
  170.     """Music related commands."""
  171.  
  172.     __slots__ = ('bot', 'players')
  173.  
  174.     def __init__(self, bot):
  175.         self.bot = bot
  176.         self.players = {}
  177.  
  178.     async def cleanup(self, guild):
  179.  
  180.         try:
  181.             await guild.voice_client.disconnect()
  182.         except AttributeError:
  183.             pass
  184.  
  185.         try:
  186.             del self.players[guild.id]
  187.         except KeyError:
  188.             pass
  189.  
  190.     async def __local_check(self, ctx):
  191.         if ctx.channel.id != 804421163568070666:
  192.             return
  193.         """A local check which applies to all commands in this cog."""
  194.         if not ctx.guild:
  195.             raise commands.NoPrivateMessage
  196.         return True
  197.  
  198.     async def __error(self, ctx, error):
  199.         """A local error handler for all errors arising from commands in this cog."""
  200.         if isinstance(error, commands.NoPrivateMessage):
  201.             if ctx.channel.id != 804421163568070666:
  202.                 return
  203.             try:
  204.                 return await ctx.send('Эта команда не отправляет личные сообщения.')
  205.             except discord.HTTPException:
  206.                 pass
  207.         elif isinstance(error, InvalidVoiceChannel):
  208.             await ctx.send('Ошибка подключения к каналу. '
  209.                            'Удостоверься что ты в канале музыка')
  210.  
  211.         print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr)
  212.         traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr)
  213.  
  214.     def get_player(self, ctx):
  215.         """Retrieve the guild player, or generate one."""
  216.         try:
  217.             player = self.players[ctx.guild.id]
  218.         except KeyError:
  219.             player = MusicPlayer(ctx)
  220.             self.players[ctx.guild.id] = player
  221.  
  222.         return player
  223.  
  224.     @commands.command(name='connect', aliases=['join'])
  225.     @commands.has_permissions(administrator=True)
  226.     async def connect_(self, ctx):
  227.         if ctx.channel.id != 804421163568070666:
  228.             return
  229.         try:
  230.             channel = ctx.author.voice.channel
  231.         except AttributeError:
  232.             raise InvalidVoiceChannel('Нет канала для входа.')
  233.  
  234.         vc = ctx.voice_client
  235.  
  236.         if vc:
  237.             if vc.channel.id == channel.id:
  238.                 return
  239.             try:
  240.                 await vc.move_to(channel)
  241.             except asyncio.TimeoutError:
  242.                 raise VoiceConnectionError(f'Переход на: <{channel}> Время вышло.')
  243.         else:
  244.             try:
  245.                 await channel.connect()
  246.             except asyncio.TimeoutError:
  247.                 raise VoiceConnectionError(f'Подключение к: <{channel}> время вышло.')
  248.  
  249.     @commands.command(name='play', aliases=['sing'])
  250.     async def play_(self, ctx, *, search: str):
  251.         if ctx.channel.id != 804421163568070666:
  252.             return
  253.         await ctx.trigger_typing()
  254.  
  255.         vc = ctx.voice_client
  256.  
  257.         if not vc:
  258.             await ctx.invoke(self.connect_)
  259.  
  260.         player = self.get_player(ctx)
  261.  
  262.         # If download is False, source will be a dict which will be used later to regather the stream.
  263.         # If download is True, source will be a discord.FFmpegPCMAudio with a VolumeTransformer.
  264.         source = await YTDLSource.create_source(ctx, search, loop=self.bot.loop, download=False)
  265.  
  266.         await player.queue.put(source)
  267.  
  268.     @commands.command(name='pause')
  269.     @commands.has_permissions(administrator=True)
  270.     async def pause_(self, ctx):
  271.         if ctx.channel.id != 804421163568070666:
  272.             return
  273.         """Pause the currently playing song."""
  274.         vc = ctx.voice_client
  275.  
  276.         if not vc or not vc.is_playing():
  277.             return await ctx.send('Сейчас ничего не проигрывается!')
  278.         elif vc.is_paused():
  279.             return
  280.  
  281.         vc.pause()
  282.         await ctx.send(f'**`{ctx.author}`**: Остановил песню!')
  283.  
  284.     @commands.command(name='resume')
  285.     @commands.has_permissions(administrator=True)
  286.     async def resume_(self, ctx):
  287.         if ctx.channel.id != 804421163568070666:
  288.             return
  289.         """Resume the currently paused song."""
  290.         vc = ctx.voice_client
  291.  
  292.         if not vc or not vc.is_connected():
  293.             return await ctx.send('Я ничего сейчас не играю!', )
  294.         elif not vc.is_paused():
  295.             return
  296.  
  297.         vc.resume()
  298.         await ctx.send(f'**`{ctx.author}`**: Вернул песню!')
  299.  
  300.     @commands.command(name='skip')
  301.     @commands.has_permissions(administrator=True)
  302.     async def skip_(self, ctx):
  303.         if ctx.channel.id != 804421163568070666:
  304.             return
  305.         """Skip the song."""
  306.         vc = ctx.voice_client
  307.  
  308.         if not vc or not vc.is_connected():
  309.             return await ctx.send('Сейчас я ничего не играю!')
  310.  
  311.         if vc.is_paused():
  312.             pass
  313.         elif not vc.is_playing():
  314.             return
  315.  
  316.         vc.stop()
  317.         await ctx.send(f'**`{ctx.author}`**: Пропустил песни!')
  318.  
  319.     @commands.command(name='queue', aliases=['q', 'playlist'])
  320.     async def queue_info(self, ctx):
  321.         if ctx.channel.id != 804421163568070666:
  322.             return
  323.         """Retrieve a basic queue of upcoming songs."""
  324.         vc = ctx.voice_client
  325.  
  326.         if not vc or not vc.is_connected():
  327.             return await ctx.send('Я не подключен к музыке!')
  328.  
  329.         player = self.get_player(ctx)
  330.         if player.queue.empty():
  331.             return await ctx.send('Нет песен в очереди.')
  332.  
  333.         # Grab up to 5 entries from the queue...
  334.         upcoming = list(itertools.islice(player.queue._queue, 0, 5))
  335.  
  336.         fmt = '\n'.join(f'**`{_["title"]}`**' for _ in upcoming)
  337.         embed = discord.Embed(title=f'Следующие {len(upcoming)}', description=fmt, colour=0x9900FF)
  338.  
  339.         await ctx.send(embed=embed)
  340.  
  341.     @commands.command(name='сейчас играет', aliases=['np', 'current', 'currentsong', 'playing'])
  342.     async def now_playing_(self, ctx):
  343.         if ctx.channel.id != 804421163568070666:
  344.             return
  345.         """Display information about the currently playing song."""
  346.         vc = ctx.voice_client
  347.  
  348.         if not vc or not vc.is_connected():
  349.             return await ctx.send('Я не подключен к музыке!', )
  350.  
  351.         player = self.get_player(ctx)
  352.         if not player.current:
  353.             return await ctx.send('Я ничего не играю!')
  354.  
  355.         try:
  356.             # Remove our previous now_playing message.
  357.             await player.np.delete()
  358.         except discord.HTTPException:
  359.             pass
  360.  
  361.         player.np = await ctx.send(f'**Сейчас Играет:** `{vc.source.title}` '
  362.                                    f'Включил `{vc.source.requester}`')
  363.  
  364.     @commands.command(name='volume', aliases=['vol'])
  365.     @commands.has_permissions(administrator=True)
  366.     async def change_volume(self, ctx, *, vol: float):
  367.         if ctx.channel.id != 804421163568070666:
  368.             return
  369.         """Change the player volume.
  370.        Parameters
  371.        ------------
  372.        volume: float or int [Required]
  373.            The volume to set the player to in percentage. This must be between 1 and 100.
  374.        """
  375.         vc = ctx.voice_client
  376.  
  377.         if not vc or not vc.is_connected():
  378.             return await ctx.send('Я не подключен к каналу!', )
  379.  
  380.         if not 0 < vol < 101:
  381.             return await ctx.send('Введите от 1 до 100.')
  382.  
  383.         player = self.get_player(ctx)
  384.  
  385.         if vc.source:
  386.             vc.source.volume = vol / 100
  387.  
  388.         player.volume = vol / 100
  389.         await ctx.send(f'**`{ctx.author}`**: Установил звук на **{vol}%**')
  390.  
  391.     @commands.command(name='stop', aliases=['leave'])
  392.     @commands.has_permissions(administrator=True)
  393.     async def stop_(self, ctx):
  394.         if ctx.channel.id != 804421163568070666:
  395.             return
  396.         """Stop the currently playing song and destroy the player.
  397.        !Warning!
  398.            This will destroy the player assigned to your guild, also deleting any queued songs and settings.
  399.        """
  400.         vc = ctx.voice_client
  401.  
  402.         if not vc or not vc.is_connected():
  403.             return await ctx.send('Я сейчас не играю ничего!')
  404.  
  405.         await self.cleanup(ctx.guild)
  406.  
  407.  
  408. def setup(client):
  409.     client.add_cog(Music(client))
  410.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement