Added votable decorator, reduced code duplication

Votable decorator added, makes it possible to make a command accessible through voting. Can be overwridden.
This commit is contained in:
Ev-1 committed 2020-09-14 17:05:07 +02:00
1 parent 7a32e04820
commit 28208da262
5 files changed
+95 -60

No files matched your search

+26
View File
@@ -1,4 +1,5 @@
# Discord Packages
import discord
import lavalink
from discord.ext import commands
@@ -70,6 +71,31 @@ class Music(commands.Cog):
return embed, True
def get_current_song_embed(self, ctx, include_time=False):
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
if include_time:
position = timeformatter.format_ms(player.position)
if player.current.stream:
duration = '{live}'
else:
duration = timeformatter.format_ms(player.current.duration)
song = f'**[{player.current.title}]({player.current.uri})**\n({position}/{duration})'
else:
song = f'**[{player.current.title}]({player.current.uri})**'
embed = discord.Embed(color=ctx.me.color, description=song, title='{now}')
thumbnail_url = player.current.extra["thumbnail_url"]
member = ctx.guild.get_member(player.current.requester)
if thumbnail_url:
embed.set_thumbnail(url=thumbnail_url)
embed.set_footer(text=f'{{requested_by}} {member.display_name}', icon_url=member.avatar_url)
embed = ctx.localizer.format_embed(embed)
return embed
def max_track_length(self, guild, player):
if maxlength := self.bot.settings.get(guild, 'duration.max', None):
is_dynamic = self.bot.settings.get(guild, 'duration.is_dynamic', 'default_duration_type')
View File
Whitespace-only changes.
+14 -55
View File
@@ -7,13 +7,12 @@ import discord
from discord.ext import commands
import asyncio
import math
import re
from ..utils import checks, timeformatter
from ..utils.paginator import QueuePaginator, Scroller
from ..utils.selector import Selector
from .decorators import require_playing, require_queue, require_voice_connection
from .decorators import require_playing, require_queue, require_voice_connection, voteable
time_rx = re.compile('[0-9]+')
url_rx = re.compile('https?:\\/\\/(?:www\\.)?.+')
@@ -78,38 +77,17 @@ async def _seek(self, ctx, *, time: str):
@commands.command(name='skip')
@require_voice_connection()
@require_playing()
@voteable(requester_override=True)
async def _skip(self, ctx):
# TODO: rework
""" Skips the current track. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player.add_skipper(ctx.author)
total = len(player.listeners)
skips = len(player.skip_voters)
threshold = self.bot.settings.get(ctx.guild, 'vote_threshold', 'default_threshold')
if skips/total >= threshold/100 or player.current.requester == ctx.author.id:
await player.skip()
if player.current:
song = f'**[{player.current.title}]({player.current.uri})**'
embed = discord.Embed(color=ctx.me.color, description=song, title='{now}')
thumbnail_url = player.current.extra["thumbnail_url"]
member = ctx.guild.get_member(player.current.requester)
if thumbnail_url:
embed.set_thumbnail(url=thumbnail_url)
embed.set_footer(text=f'{{requested_by}} {member.display_name}', icon_url=member.avatar_url)
embed = ctx.localizer.format_embed(embed)
await ctx.send(ctx.localizer.format_str("{skip.skipped}"), embed=embed)
else:
await ctx.send(ctx.localizer.format_str("{skip.skipped}"))
await player.skip()
if player.current:
embed = self.get_current_song_embed(ctx)
await ctx.send(ctx.localizer.format_str("{skip.skipped}"), embed=embed)
else:
if skips != 0:
needed = math.ceil(total*threshold/100)
msg = ctx.localizer.format_str("{skip.require_vote}", _skips=skips, _total=needed)
await ctx.send(msg)
await ctx.send(ctx.localizer.format_str("{skip.skipped}"))
@commands.command(name='skipto')
@@ -132,9 +110,9 @@ async def _skip_to(self, ctx, pos: int = 1):
@commands.command(name='stop')
@checks.dj_or(alone=True)
@require_voice_connection()
@require_playing(require_user_listening=True)
@voteable(DJ_override=True)
async def _stop(self, ctx):
""" Stops the player and clears its queue. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
@@ -148,25 +126,7 @@ async def _stop(self, ctx):
@commands.command(name='now')
@require_playing()
async def _now(self, ctx):
# TODO: rework
""" Shows some stats about the currently playing song. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
position = timeformatter.format_ms(player.position)
if player.current.stream:
duration = '{live}'
else:
duration = timeformatter.format_ms(player.current.duration)
song = f'**[{player.current.title}]({player.current.uri})**\n({position}/{duration})'
member = ctx.guild.get_member(player.current.requester)
embed = discord.Embed(color=ctx.me.color, description=song, title='{now}')
thumbnail_url = player.current.extra["thumbnail_url"]
if thumbnail_url:
embed.set_thumbnail(url=thumbnail_url)
embed.set_footer(text=f'{{requested_by}} {member.display_name}', icon_url=member.avatar_url)
embed = self.get_current_song_embed(ctx, include_time=True)
embed = ctx.localizer.format_embed(embed)
await ctx.send(embed=embed)
@@ -195,12 +155,11 @@ async def _myqueue(self, ctx):
async def _pause(self, ctx):
""" Pauses/Resumes the current track. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
await player.set_pause(not player.paused)
if player.paused:
await player.set_pause(False)
await ctx.send(ctx.localizer.format_str("{resume.resumed}"))
else:
await player.set_pause(True)
await ctx.send(ctx.localizer.format_str("{resume.paused}"))
else:
await ctx.send(ctx.localizer.format_str("{resume.resumed}"))
@commands.command(name='shuffle')
@@ -373,8 +332,8 @@ async def _search(self, ctx, *, query):
@commands.command(name='disconnect')
@checks.dj_or(alone=True)
@require_voice_connection()
@voteable(DJ_override=True)
async def _disconnect(self, ctx):
""" Disconnects the player from the voice channel and clears its queue. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
@@ -388,8 +347,8 @@ async def _disconnect(self, ctx):
@commands.command(name='reconnect')
@checks.dj_or(alone=True)
@require_voice_connection()
@voteable(DJ_override=True)
async def _reconnect(self, ctx):
""" Tries to disconnect then reconnect the player in case the bot gets stuck on a song """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
+32
View File
@@ -4,9 +4,12 @@ from discord.ext import commands
import functools
import inspect
import math
from cogs.music.music_errors import WrongVoiceChannelError
from ..utils.checks import is_dj
def require_voice_connection(should_connect=False):
"""
@@ -122,3 +125,32 @@ def require_queue(require_member_queue=False, require_author_queue=False):
await func(self, ctx, *command_args, **kwargs)
return ensure_queue_inner
return ensure_queue
def voteable(requester_override=False, DJ_override=False):
def make_voteable(func):
@functools.wraps(func)
async def voteable_inner(self, ctx, *command_args, **kwargs):
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player.add_vote(func.__name__, ctx.author)
total = len(player.listeners)
votes = len(player.get_voters(func.__name__))
threshold = self.bot.settings.get(ctx.guild, 'vote_threshold', 'default_threshold')
enough_votes = votes/total >= threshold/100
DJ = DJ_override and is_dj(ctx)
requester = player.current.requester == ctx.author.id and requester_override
if enough_votes or requester or DJ:
await func(self, ctx, *command_args, **kwargs)
player.clear_votes()
else:
if votes != 0:
needed = math.ceil(total*threshold/100)
# TODO: redo this message to allow for other vote types
msg = ctx.localizer.format_str("{skip.require_vote}", _skips=votes, _total=needed)
await ctx.send(msg)
return voteable_inner
return make_voteable
+23 -5
View File
@@ -17,6 +17,7 @@ class MixPlayer(DefaultPlayer):
self.queue = MixQueue()
self.listeners = set()
self.voteables = {}
self.skip_voters = set()
self.boosted = False
@@ -98,7 +99,7 @@ class MixPlayer(DefaultPlayer):
""" Plays the next track in the queue, if any. """
for i in range(pos):
_ = self.queue.pop_first()
self.skip_voters.clear()
self.clear_votes()
await self.play()
def update_listeners(self, member, voice_state):
@@ -106,26 +107,43 @@ class MixPlayer(DefaultPlayer):
vc = int(self.channel_id)
if voice_state.channel is None or voice_state.channel.id != vc:
self.listeners.discard(member)
self.skip_voters.discard(member)
self.remove_member_votes(member)
else:
if voice_state.deaf or voice_state.self_deaf:
self.listeners.discard(member)
self.skip_voters.discard(member)
self.remove_member_votes(member)
else:
self.listeners.add(member)
def clear_listeners(self):
self.listeners.clear()
def add_skipper(self, member):
def add_vote(self, category, member):
if member in self.listeners:
self.skip_voters.add(member)
if category not in self.voteables:
self.voteables[category] = set()
self.voteables[category].add(member)
def remove_member_votes(self, member):
for category, votes in self.voteables.items():
votes.discard(member)
def get_voters(self, category):
if category not in self.voteables:
self.voteables[category] = set()
return self.voteables[category]
def clear_votes(self):
for category, votes in self.voteables.items():
votes.clear()
async def handle_event(self, event):
""" Handles the given event as necessary. """
if isinstance(event, (TrackStuckEvent, TrackExceptionEvent)) or \
isinstance(event, TrackEndEvent) and event.reason == 'FINISHED':
self.skip_voters.clear()
for category, votes in self.voteables.items():
votes.clear()
await self.play()
async def bassboost(self, boost: bool = False):