如何添加比率?

How do add a ratio?

您好,我想为我的选票计数添加一个比率。目前我正在制作一个音乐模块。暂停、跳过、音量等音乐命令基于用户给出的反应(表情符号)。

目前跳过一首歌需要5个反应。提出的问题之一是有时一个语音频道同时听音乐的人数少于五个,因此无法跳过歌曲。

所以我需要实现某种比率,例如,如果 2 个成员在一个语音频道中,则 2 个跳过将跳过该歌曲。我有点不确定如何实施它,但我试了一下。

更具体地说,我正在查看这一行:

if control == 'skip':
        skip = await channel.send(f':poop: **{user.name}** voted to skip **{source.title}**. **{react.count}/5** voted.', delete_after=8)
        if react.count >= 5: # bot counts as 1 reaction.
            vc.stop()
            await channel.send('**Skipping song...**', delete_after=5)

这是我正在使用的完整代码:

if not discord.opus.is_loaded():
    load_opus_lib()


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 -preset ultrafast',
    'options': '-vn -threads 1'
}

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, requester):
    super().__init__(source)
    self.requester = requester

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

    if self.title is None:
        self.title = "No title available"

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

    if self.thumbnail is None:
        self.thumbnail = "http://ppc.tools/wp-content/themes/ppctools/img/no-thumbnail.jpg"

    self.duration = data.get('duration')

    if self.duration is None:
        self.duration = 0

    self.uploader = data.get('uploader')

    if self.uploader is None:
        self.uploader = "Unkown"

    # 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 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:
        # take first item from a playlist
        data = data['entries'][0]

    await ctx.send(f':notes: **{data["title"]} added to the queue.**')

    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):
    """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']), 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', '_ctxs', '_channel', '_cog', 'queue', 'next', 'current', 'np', 'volume', 'buttons', 'music', 'music_controller', 'restmode')

def __init__(self, ctx):

    self.buttons = {'⏯': 'rp',
                    '⏭': 'skip',
                    '➕': 'vol_up',
                    '➖': 'vol_down',
                    '': 'thumbnail',
                    '⏹': 'stop',
                    'ℹ': 'queue',
                    '❔': 'tutorial'}

    self.bot = ctx.bot
    self._guild = ctx.guild
    self._ctxs = ctx
    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
    self.music_controller = None

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

async def buttons_controller(self, guild, current, source, channel, context):
    vc = guild.voice_client
    vctwo = context.voice_client

    for react in self.buttons:
        await current.add_reaction(str(react))

    def check(r, u):
        if not current:
            return False
        elif str(r) not in self.buttons.keys():
            return False
        elif u.id == self.bot.user.id or r.message.id != current.id:
            return False
        elif u not in vc.channel.members:
            return False
        elif u.bot:
            return False
        return True

    while current:
        if vc is None:
            return False

        react, user = await self.bot.wait_for('reaction_add', check=check)
        control = self.buttons.get(str(react))

        if control == 'rp':
            if vc.is_paused():
                vc.resume()
            else:
                vc.pause()
                await current.remove_reaction(react, user)

        if control == 'skip':
            skip = await channel.send(f':poop: **{user.name}** voted to skip **{source.title}**. **{react.count}/5** voted.', delete_after=8)
            if react.count >= 5: # bot counts as 1 reaction.
                vc.stop()
                await channel.send(':track_next: **Skipping...**', delete_after=5)


        if control == 'stop':
            mods = get(guild.roles, name="Mods")
            for member in list(guild.members):
                if mods in member.roles:
                    await context.invoke(self.bot.get_command("stop"))
                    return
            else:
                await channel.send(':raised_hand:  **Only a mod can stop and clear the queue. Try skipping the song instead.**', delete_after=5)
                await current.remove_reaction(react, user)

        if control == 'vol_up':
            player = self._cog.get_player(context)
            vctwo.source.volume += 2.5
            await current.remove_reaction(react, user)

        if control == 'vol_down':
            player = self._cog.get_player(context)
            vctwo.source.volume -= 2.5
            await current.remove_reaction(react, user)

        if control == 'thumbnail':
            await channel.send(embed=discord.Embed(color=0x17FD6E).set_image(url=source.thumbnail).set_footer(text=f"Requested By: {source.requester} | Video Thumbnail: {source.title}", icon_url=source.requester.avatar_url), delete_after=10)
            await current.remove_reaction(react, user)

        if control == 'tutorial':
            await channel.send(embed=discord.Embed(color=0x17FD6E).add_field(name="How to use the music controller?", value="⏯ - Pause\n⏭ - Skip\n➕ - Increase Volume\n➖ - Increase Volume\n - Get Thumbnail\n⏹ - Stop & Leave\nℹ - Queue\n❔ - Display help for music controls"), delete_after=10)
            await current.remove_reaction(react, user)

        if control == 'queue':
            await self._cog.queue_info(context)
            await current.remove_reaction(react, user)


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:
            async with timeout(3500):
                source = await self.queue.get()
        except asyncio.TimeoutError:
            return self.destroy(self._guild)

        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'An error occured!.\n'
                                         f'```css\n[{e}]\n```')
                continue

        source.volume = self.volume
        self.current = source
        try:
            self._guild.voice_client.play(source, after=lambda _: self.bot.loop.call_soon_threadsafe(self.next.set))
        except Exception:
            continue
        embednps = discord.Embed(color=0x17FD6E)
        embednps.add_field(name="Currently Playing:", value=f"```fix\n{source.title}```", inline=False)
        embednps.add_field(name="Requested By:", value=f"**{source.requester}**", inline=True)
        embednps.add_field(name="Source:", value=f"**[URL]({source.web_url})**", inline=True)
        embednps.add_field(name="Uploader:", value=f"**{source.uploader}**", inline=True)
        embednps.add_field(name="Duration:", value=f"**{datetime.timedelta(seconds=source.duration)}**", inline=True)
        embednps.set_thumbnail(url=source.thumbnail)
        self.np = await self._channel.send(embed=embednps)

        self.music_controller = self.bot.loop.create_task(self.buttons_controller(self._guild, self.np, source, self._channel, self._ctxs))
        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()
            self.music_controller.cancel()
        except Exception:
            pass

def destroy(self, guild):
    """Disconnect and cleanup the player."""
    return self.bot.loop.create_task(self._cog.cleanup(guild))

class Music:
"""Music cog for UKGBot."""

__slots__ = ('bot', 'players', 'musictwo', 'music_controller')

def __init__(self, bot):
    self.bot = bot
    self.players = {}

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):
    """A local check which applies to all commands in this cog."""
    if not ctx.guild:
        raise commands.NoPrivateMessage
    return True

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 __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(':notes: Command cannot be used in DM.')
        except discord.HTTPException:
            pass
    elif isinstance(error, InvalidVoiceChannel):
        await ctx.send("Connect to a voice channel first!")

    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='stop', aliases=[ 'l', 'disconnect'])
@checks.is_channel_mod()
async def disconnect_(self, ctx):
    """Stops and leaves the voice channel."""

    try:
        channel = ctx.author.voice.channel
    except AttributeError:
        await ctx.send(":notes: You are not connected to a voice channel.", delete_after=20)

    if not ctx.guild.voice_client:
        return await ctx.send(':notes: I\'m not connected to the voice channel.', delete_after=20)

    await ctx.guild.voice_client.disconnect()
    await ctx.send(':wave: Stopped and left the channel.', delete_after=20)



@commands.command(name='reconnect', aliases=['rc'])
async def reconnect_(self, ctx):
    try:
        channel = ctx.author.voice.channel

    except AttributeError:
        return await ctx.send(":notes: You are not connected to a voice channel.", delete_after=20)

    if ctx.guild.voice_client:
        await ctx.guild.voice_client.disconnect()

    await channel.connect()



@commands.command(name='connect', aliases=['join','summon'])
async def connect_(self, ctx, *, channel: discord.VoiceChannel=None):
    """connectss to a voice channel."""  
    try:
        channel = ctx.author.voice.channel

    except AttributeError:
        return await ctx.send(":notes: You are not connected to a voice channel.", delete_after=20)

    await channel.connect()

@commands.command(name='skip', aliases=['sk'])
@checks.is_channel_mod()
async def skip_(self, ctx, *, channel: discord.VoiceChannel=None):
    """Skips a song (Mods)."""  
    try:
        channel = ctx.author.voice.channel

    except AttributeError:
        return await ctx.send(":notes: You are not connected to a voice channel.", delete_after=20)

    ctx.guild.voice_client.stop()
    await ctx.send(':track_next: **Skipping...**', delete_after=5)




@commands.command(name='play', aliases=['sing', 'p'])
async def play_(self, ctx, *, search: str):
    """searches for and plays a song."""

    await ctx.trigger_typing()

    vc = ctx.voice_client

    try:
        channel = ctx.author.voice.channel
        if not vc:
            await ctx.invoke(self.connect_)

    except AttributeError:
        return await ctx.send(":notes: You are not connected to a voice channel.", delete_after=20)


    player = self.get_player(ctx)

    # 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 player.queue.put(source)



@commands.command(name='playing', aliases=['np', 'current', 'currentsong', 'now_playing'])
async def now_playing_(self, ctx):
    """Shows the current song playing."""

    vc = ctx.voice_client

    if not vc or not vc.is_connected():
        return await ctx.send("I'm not connected to a voice channel..", delete_after=20)

    elif ctx.author not in ctx.guild.voice_client.channel.members:
        return await ctx.send("You need to be in the voice channel first!", delete_after=20)

    player = self.get_player(ctx)
    if not player.current:
        return await ctx.send("There's nothing currently playing.", delete_after=20)

    try:
        # Remove our previous now_playing message.
        await player.np.delete()
    except discord.HTTPException:
        pass

    embednp = discord.Embed(color=0x17FD6E)
    embednp.add_field(name="Currently Playing:", value=f"```fix\n{vc.source.title}```", inline=False)
    embednp.add_field(name="Requested By:", value=f"**{vc.source.requester}**", inline=True)
    embednp.add_field(name="Source:", value=f"**[URL]({vc.source.web_url})**", inline=True)
    embednp.add_field(name="Uploader:", value=f"**{vc.source.uploader}**", inline=True)
    embednp.add_field(name="Duration:", value=f"**{datetime.timedelta(seconds=vc.source.duration)}**", inline=True)
    embednp.set_thumbnail(url=f"{vc.source.thumbnail}")
    player.np = await ctx.send(embed=embednp)
    self.music_controller = self.bot.loop.create_task(MusicPlayer(ctx).buttons_controller(ctx.guild, player.np, vc.source, ctx.channel, ctx))

async def queue_info(self, ctx):
    player = self.get_player(ctx)
    if player.queue.empty():
        return await ctx.send('**:notes: No songs currently queued.**', delete_after=5)

    upcoming = list(itertools.islice(player.queue._queue, 0, 5))

    fmt = '\n'.join(f'**`{_["title"]}`**' for _ in upcoming)
    embed = discord.Embed(title=f'{len(upcoming)} songs queued.', description=fmt, color=0x17FD6E)
    await ctx.send(embed=embed)

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

如果有人能提供帮助那就太好了!

当成员少于五个时允许所有成员一起投票的一种简单方法是使用如下内容:

votes_needed_to_skip = min(current_listener_count, 5)
if react.count >= votes_needed_to_skip: # bot counts as 1 reaction.

如果你想要3/4的人或者5个人先到可以跳过(注意这个方法是6个人的3/4是4个人,所以只有4票即使有五个人以上,也需要六个人):

votes_needed_to_skip = min(current_listener_count*3//4, 5)
if react.count >= votes_needed_to_skip: # bot counts as 1 reaction.

当然,你可以这样做

if control == 'skip':
  skip = await channel.send(f':poop: **{user.name}** voted to skip **{source.title}**. **{react.count}/5** voted.', delete_after=8)
  try:
    ratio = react.count / len(ctx.author.voice.channel.members)
  except ZeroDivisionError:
    pass
  if ratio  >= .5: # bot counts as 1 reaction.
    vc.stop()
    await channel.send('**Skipping song...**', delete_after=5)

如果语音频道中超过或恰好一半的人投票跳过歌曲,它只会跳过这首歌
您可以稍后调整您想要的比例