Как мне подключиться к ВК в Discord? - PullRequest
0 голосов
/ 07 ноября 2019

Я пытаюсь создать музыкального бота для Discord. Единственная часть, над которой у меня возникают проблемы, - это заставить бота играть музыку.

Я запускаю своего бота на Macbook с PyCharm. Я не пробовал так много только потому, что понятия не имею, что делать. Вы можете увидеть код, который я использовал:

import discord
from discord.ext import commands

import asyncio
import itertools
import sys
import traceback
from async_timeout import timeout
from functools import partial
from youtube_dl import YoutubeDL


ytdlopts = {
    'format': 'bestaudio/best',
    'outtmpl': 'downloads/%(extractor)s-%(id)s-%(title)s.%(ext)s',
    'restrictfilenames': True,
    'noplaylist': True,
    'nocheckcertificate': True,
    'ignoreerrors': False,
    'logtostderr': False,
    'quiet': True,
    'no_warnings': True,
    'default_search': 'auto',
    'source_address': '0.0.0.0'
}

ffmpegopts = {
    'before_options': '-nostdin',
    'options': '-vn'
}

ytdl = YoutubeDL(ytdlopts)


class VoiceConnectionError(commands.CommandError):
    pass

class InvalidVoiceChannel(VoiceConnectionError):
    pass

class YTDLSource(discord.PCMVolumeTransformer):

    def __init__(self, source, *, data, requester):
        super().__init__(source)
        self.requester = requester

        self.title = data.get('title')
        self.web_url = data.get('webpage_url')

    def __getitem__(self, item: str):
        return self.__getattribute__(item)

    @classmethod
    async def create_source(cls, ctx, search: str, *, loop, download=False):
        loop = loop or asyncio.get_event_loop()

        to_run = partial(ytdl.extract_info, url=search, download=download)
        data = await loop.run_in_executor(None, to_run)

        if 'entries' in data:
            data = data['entries'][0]

        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), data=data, requester=ctx.author)

    @classmethod
    async def regather_stream(cls, data, *, loop):
        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']), data=data, requester=requester)


class MusicPlayer:
    __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
        self.volume = .5
        self.current = None

        ctx.bot.loop.create_task(self.player_loop())

    async def player_loop(self):
        await self.bot.wait_until_ready()

        while not self.bot.is_closed():
            self.next.clear()

            try:
                async with timeout(300):
                    source = await self.queue.get()
            except asyncio.TimeoutError:
                return self.destroy(self._guild)

            if not isinstance(source, YTDLSource):
                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()

            source.cleanup()
            self.current = None

            try:
                await self.np.delete()
            except discord.HTTPException:
                pass

    def destroy(self, guild):
        return self.bot.loop.create_task(self._cog.cleanup(guild))


class Music(commands.Cog):
    __slots__ = ('bot', 'players')

    def __init__(self, bot):
        self.bot = bot
        self.players = {}
        if not discord.opus.is_loaded():
            discord.opus.load_opus('opus')

    async def cleanup(self, guild):
        try:
            await guild.voice_client.disconnect()
        except AttributeError:
            pass

        try:
            del self.players[guild.id]
        except KeyError:
            pass

    async def __local_check(self, ctx):
        if not ctx.guild:
            raise commands.NoPrivateMessage
        return True

    async def __error(self, ctx, error):
        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):
        try:
            player = self.players[ctx.guild.id]
        except KeyError:
            player = MusicPlayer(ctx)
            self.players[ctx.guild.id] = player

        return player

    @commands.command(name='connect', aliases=['join'])
    async def connect_(self, ctx, *, channel: discord.VoiceChannel=None):
        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):
        await ctx.trigger_typing()

        vc = ctx.voice_client

        if not vc:
            await ctx.invoke(self.connect_)

        player = self.get_player(ctx)

        source = await YTDLSource.create_source(ctx, search, loop=self.bot.loop, download=False)

        await player.queue.put(source)

    @commands.command(name='pause')
    async def pause_(self, ctx):
        vc = ctx.voice_client

        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):
        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):
        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):
        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.')

        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):
        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:
            await player.np.delete()
        except discord.HTTPException:
            pass

        player.np = await ctx.send(f'**Now Playing:** `{vc.source.title}` '
                                   f'requested by `{vc.source.requester}`')

    @commands.command(name='volume', aliases=['vol'])
    async def change_volume(self, ctx, *, vol: float):
        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)

        if not 0 < vol < 101:
            return await ctx.send('Please enter a value between 1 and 100.')

        player = self.get_player(ctx)

        if vc.source:
            vc.source.volume = vol / 100

        player.volume = vol / 100
        await ctx.send(f'**`{ctx.author}`**: Set the volume to **{vol}%**')

    @commands.command(name='stop')
    async def stop_(self, ctx):
        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)

        await self.cleanup(ctx.guild)


def setup(bot):
    bot.add_cog(Music(bot))

Это был мой оригинальный код:

if vc:
  if vc.channel.id == channel.id:
    return
  await vc.move_to(channel)
else:
  await channel.connect()

await ctx.send(f'Connected to: **{channel}**')

Я использовал этот код, чтобы попытаться отладить проблему:

vc = ctx.voice_client

if vc:
  if vc.channel.id == channel.id:
    return
  await vc.move_to(channel)
else:
  await channel.connect()
  while not ctx.voice_client:
    await channel.connect()
await ctx.send(f'Connected to: **{channel}**')

Я ожидал, что бот присоединится к каналу, а затем «подключится» к нему, что позволит ему воспроизводить музыку через него. Однако, с моим исходным кодом, он выдал ошибку discord.errors.ClientException: Not connected to voice., когда я попытался воспроизвести музыку через него. Тем не менее, он отправил сообщение через чат. Во время второй итерации кода сообщение так и не было отправлено, что заставило меня поверить, что бот никогда не подключался к каналу.

Изображение, поскольку у меня недостаточно репутации

Как вы могли видеть, бот появляется в голосовом канале, но на самом деле не "подключен".

Ответы [ 3 ]

1 голос
/ 08 ноября 2019

Читали ли вы информацию в верхней части файла, откуда вы получили код, или просто копировали и вставляли его?

"Пожалуйста, поймите, музыкальные боты сложны, и что даже этот основнойпример может быть пугающим для новичка. По этой причине настоятельно рекомендуем вам ознакомиться с discord.py, python и asyncio, ПРЕЖДЕ ЧЕМ вы пытаетесь написать музыкального бота. Этот пример использует: Python 3.6 Для более простого примера голоса, пожалуйста,читайте: https://github.com/Rapptz/discord.py/blob/rewrite/examples/basic_voice.py Это очень простой пример списка воспроизведения, который позволяет каждой гильдии воспроизводить уникальные очереди. Команды реализуют базовую логику для базового использования. Но допускают расширение. Было бы целесообразно реализовать свои собственные разрешения ииспользование логики для команд. Например, вы можете выполнить голосование, прежде чем пропустить песню, или позволить только администраторам остановить плеер. Музыкальные боты требуют много работы и настройки. Goodluck. Если вы обнаружите какие-либо ошибки, не стесняйтесь пинговать меня по поводу раздора. @ Eviee # 0666 "

https://github.com/Rapptz/discord.py#installing

Или инстаllation information for discord.py?

Требуется установка Python 3.5.3 или выше

Чтобы установить библиотеку без полной голосовой поддержки , вы можете просто запуститьследующая команда:

Linux / OS X python3 -m pip install -U discord.py

Windows

py -3 -m pip install -U discord.py

В противном случае, чтобы получить голосовую поддержку , вам нужно выполнить следующую команду:

Linux / OS X python3 -m pip install -U discord.py [voice]

Windows py -3 -m pip install -U discord.py [голос]

1 голос
/ 08 ноября 2019

Используете ли вы команду «play» или «connect»?

Если посмотреть на это, команда «connect» просто подключится к голосовому каналу, а команда play подключится к голосовому каналу. если он не подключен, а затем попытайтесь воспроизвести музыку.

Я вижу, что он также использует discord.FFmpegPCMAudio Это непосредственно из документации на discord.py.

Предупреждение: Чтобы это работало, вы должны иметь исполняемый файл ffmpeg или avconv в переменной окружения path.

https://discordpy.readthedocs.io/en/latest/api.html?highlight=ffmpeg#discord.FFmpegPCMAudio

Скорее всего, проблема связана с подключением вашего бота к этому каналу. Я только что проверил этот код и заставил его работать нормально. Тот факт, что сообщение никогда не отображалось, когда вы использовали второй бит кода, говорит мне о том, что бот застрял в цикле while, потому что не может подключиться.

Вы должны попытаться сделать общедоступный голосовой канал ипопробуйте это сделать или убедитесь, что у вашего бота достаточно прав для используемого вами частного голосового канала.

Вы также можете попробовать это, чтобы выяснить, почему он не работает:

        try:
            voice_client = await channel.connect()
            # Code below this e.g.
            if not voice_client.is_connected():
                print(f"Not connected to {channel.name}")
        except asyncio.TimeoutError:
            raise VoiceConnectionError(f'Connecting to channel: <{channel}> timed out.')
        except discord.ClientException:
            print("You are already connected to a voice channel.")
        except discord.opus.OpusNotLoaded:
            print("The opus library has not been loaded.")
        except discord.DiscordException as ex:
            print(ex)

connect() Вызывает 3 исключения, а также возвращает голосового клиента, я также включил исключение базовых разногласий, чтобы помочь выяснить, в чем проблема. См. Ссылки ниже для документации.

Вот также предупреждение от discord.VoiceClient документации.

Предупреждение: Для использования аудиоисточников на основе PCM,у вас должна быть установлена ​​библиотека opus в вашей системе и загружена через opus.load_opus (). В противном случае ваши аудиоисточники должны быть закодированы опусом (например, с использованием FFmpegOpusAudio), иначе библиотека не сможет передавать аудио.

https://discordpy.readthedocs.io/en/latest/api.html?highlight=connect#discord.VoiceChannel.connect https://discordpy.readthedocs.io/en/latest/api.html?highlight=connect#discord.VoiceClient

0 голосов
/ 08 ноября 2019

Код работает нормально. Установленная библиотека просто не была голосовой версией. Отлично работает с голосовой версией библиотеки.

...