[V3 Audio] Update queue and search to use menus (#1633)

* [V3 Audio] Update queue and search to use menus

* [V3 Audio] Fix for playlist upload saving

* [V3 Audio] Add position in queue to enqueued songs

Also a bit of cleanup.

* [V3 Audio] Improvements for mobile formatting
This commit is contained in:
aikaterna 2018-05-13 17:01:46 -07:00 committed by Tobotimus
parent 8f74e4dd31
commit 4a8358ecb4

View File

@ -7,16 +7,15 @@ import lavalink
import math import math
import re import re
import redbot.core import redbot.core
from redbot.core import Config, checks, bank from redbot.core import Config, commands, checks, bank
from redbot.core import commands from redbot.core.utils.menus import menu, DEFAULT_CONTROLS, prev_page, next_page, close_menu
from redbot.core.i18n import Translator, cog_i18n from redbot.core.i18n import Translator, cog_i18n
_ = Translator("Audio", __file__)
from .manager import shutdown_lavalink_server from .manager import shutdown_lavalink_server
__version__ = "0.0.5a" _ = Translator("Audio", __file__)
__author__ = ["aikaterna", "billy/bollo/ati"]
__version__ = "0.0.6"
__author__ = ["aikaterna", "billy/bollo/ati"]
@cog_i18n(_) @cog_i18n(_)
@ -152,8 +151,10 @@ class Audio:
dj_role_id = await self.config.guild(ctx.guild).dj_role() dj_role_id = await self.config.guild(ctx.guild).dj_role()
if dj_role_id is None: if dj_role_id is None:
await self._embed_msg(ctx, 'Please set a role to use with DJ mode. Enter the role name now.') await self._embed_msg(ctx, 'Please set a role to use with DJ mode. Enter the role name now.')
def check(m): def check(m):
return m.author == ctx.author return m.author == ctx.author
try: try:
dj_role = await ctx.bot.wait_for('message', timeout=15.0, check=check) dj_role = await ctx.bot.wait_for('message', timeout=15.0, check=check)
dj_role_obj = discord.utils.get(ctx.guild.roles, name=dj_role.content) dj_role_obj = discord.utils.get(ctx.guild.roles, name=dj_role.content)
@ -304,7 +305,7 @@ class Audio:
bump_song = player.queue[bump_index] bump_song = player.queue[bump_index]
player.queue.insert(0, bump_song) player.queue.insert(0, bump_song)
removed = player.queue.pop(index) removed = player.queue.pop(index)
await self._embed_msg(ctx, 'Moved **' + removed.title + '** to the top of the queue.') await self._embed_msg(ctx, 'Moved {} to the top of the queue.'.format(removed.title))
@commands.command(aliases=['dc']) @commands.command(aliases=['dc'])
async def disconnect(self, ctx): async def disconnect(self, ctx):
@ -531,6 +532,8 @@ class Audio:
if not shuffle and queue_duration > 0: if not shuffle and queue_duration > 0:
embed.set_footer(text='{} until track playback: #{} in queue'.format( embed.set_footer(text='{} until track playback: #{} in queue'.format(
queue_total_duration, before_queue_length)) queue_total_duration, before_queue_length))
elif queue_duration > 0:
embed.set_footer(text='#{} in queue'.format(len(player.queue) + 1))
if not player.current: if not player.current:
await player.play() await player.play()
await ctx.send(embed=embed) await ctx.send(embed=embed)
@ -745,8 +748,10 @@ class Audio:
return return
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
await self._embed_msg(ctx, 'Please upload the playlist file. Any other message will cancel this operation.') await self._embed_msg(ctx, 'Please upload the playlist file. Any other message will cancel this operation.')
def check(m): def check(m):
return m.author == ctx.author return m.author == ctx.author
try: try:
file_message = await ctx.bot.wait_for('message', timeout=30.0, check=check) file_message = await ctx.bot.wait_for('message', timeout=30.0, check=check)
except asyncio.TimeoutError: except asyncio.TimeoutError:
@ -792,7 +797,8 @@ class Audio:
if not track_list: if not track_list:
return await self._embed_msg(ctx, 'No tracks found.') return await self._embed_msg(ctx, 'No tracks found.')
playlist_list = self._to_json(ctx, v2_playlist_url, track_list) playlist_list = self._to_json(ctx, v2_playlist_url, track_list)
v3_playlists[v2_playlist_name] = playlist_list async with self.config.guild(ctx.guild).playlists() as v3_playlists:
v3_playlists[v2_playlist_name] = playlist_list
if len(v2_playlist["playlist"]) != track_count: if len(v2_playlist["playlist"]) != track_count:
bad_tracks = len(v2_playlist["playlist"]) - track_count bad_tracks = len(v2_playlist["playlist"]) - track_count
msg = ('Added {} tracks from the {} playlist. {} track(s) could not ' msg = ('Added {} tracks from the {} playlist. {} track(s) could not '
@ -891,17 +897,24 @@ class Audio:
"""Lists the queue.""" """Lists the queue."""
if not self._player_check(ctx): if not self._player_check(ctx):
return await self._embed_msg(ctx, 'There\'s nothing in the queue.') return await self._embed_msg(ctx, 'There\'s nothing in the queue.')
shuffle = await self.config.guild(ctx.guild).shuffle()
repeat = await self.config.guild(ctx.guild).repeat()
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
if not player.queue: if not player.queue:
return await self._embed_msg(ctx, 'There\'s nothing in the queue.') return await self._embed_msg(ctx, 'There\'s nothing in the queue.')
len_queue_pages = math.ceil(len(player.queue) / 10)
queue_page_list = []
for page_num in range(1, len_queue_pages + 1):
embed = await self._build_queue_page(ctx, player, page_num)
queue_page_list.append(embed)
if page > len_queue_pages:
page = len_queue_pages
await menu(ctx, queue_page_list, DEFAULT_CONTROLS, page=(page - 1))
items_per_page = 10 async def _build_queue_page(self, ctx, player, page_num):
pages = math.ceil(len(player.queue) / items_per_page) shuffle = await self.config.guild(ctx.guild).shuffle()
start = (page - 1) * items_per_page repeat = await self.config.guild(ctx.guild).repeat()
end = start + items_per_page queue_num_pages = math.ceil(len(player.queue) / 10)
queue_idx_start = (page_num - 1) * 10
queue_idx_end = queue_idx_start + 10
queue_list = '' queue_list = ''
try: try:
arrow = await self._draw_time(ctx) arrow = await self._draw_time(ctx)
@ -929,22 +942,27 @@ class Audio:
arrow, pos, dur arrow, pos, dur
) )
for i, track in enumerate(player.queue[start:end], start=start): for i, track in enumerate(player.queue[queue_idx_start:queue_idx_end], start=queue_idx_start):
if len(track.title) > 40:
track_title = str(track.title).replace('[', '')
track_title = '{}...'.format((track_title[:40]).rstrip(' '))
else:
track_title = track.title
req_user = track.requester req_user = track.requester
_next = i + 1 track_idx = i + 1
queue_list += '`{}.` **[{}]({})**, requested by **{}**\n'.format(_next, track.title, track.uri, req_user) queue_list += '`{}.` **[{}]({})**, requested by **{}**\n'.format(track_idx, track_title, track.uri, req_user)
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Queue for ' + ctx.guild.name, embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Queue for ' + ctx.guild.name,
description=queue_list) description=queue_list)
queue_duration = await self._queue_duration(ctx) queue_duration = await self._queue_duration(ctx)
queue_total_duration = lavalink.utils.format_time(queue_duration) queue_total_duration = lavalink.utils.format_time(queue_duration)
text = 'Page {}/{} | {} tracks, {} remaining'.format(page, pages, len(player.queue) + 1, queue_total_duration) text = 'Page {}/{} | {} tracks, {} remaining'.format(page_num, queue_num_pages, len(player.queue) + 1, queue_total_duration)
if repeat: if repeat:
text += ' | Repeat: \N{WHITE HEAVY CHECK MARK}' text += ' | Repeat: \N{WHITE HEAVY CHECK MARK}'
if shuffle: if shuffle:
text += ' | Shuffle: \N{WHITE HEAVY CHECK MARK}' text += ' | Shuffle: \N{WHITE HEAVY CHECK MARK}'
embed.set_footer(text=text) embed.set_footer(text=text)
await ctx.send(embed=embed) return embed
@commands.command() @commands.command()
async def repeat(self, ctx): async def repeat(self, ctx):
@ -983,21 +1001,14 @@ class Audio:
return await self._embed_msg(ctx, 'Song number must be greater than 1 and within the queue limit.') return await self._embed_msg(ctx, 'Song number must be greater than 1 and within the queue limit.')
index -= 1 index -= 1
removed = player.queue.pop(index) removed = player.queue.pop(index)
await self._embed_msg(ctx, 'Removed **' + removed.title + '** from the queue.') await self._embed_msg(ctx, 'Removed {} from the queue.'.format(removed.title))
@commands.command() @commands.command()
async def search(self, ctx, *, query): async def search(self, ctx, *, query):
"""Pick a song with a search. """Pick a song with a search.
Use [p]search list <search term> to queue all songs. Use [p]search list <search term> to queue all songs found on YouTube.
[p]search sc <search term> will search SoundCloud instead of YouTube.
""" """
expected = ("1⃣", "2⃣", "3⃣", "4⃣", "5⃣")
emoji = {
"one": "1⃣",
"two": "2⃣",
"three": "3⃣",
"four": "4⃣",
"five": "5⃣"
}
if not self._player_check(ctx): if not self._player_check(ctx):
try: try:
await lavalink.connect(ctx.author.voice.channel) await lavalink.connect(ctx.author.voice.channel)
@ -1012,53 +1023,14 @@ class Audio:
if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not
await self._can_instaskip(ctx, ctx.author)): await self._can_instaskip(ctx, ctx.author)):
return await self._embed_msg(ctx, 'You must be in the voice channel to enqueue songs.') return await self._embed_msg(ctx, 'You must be in the voice channel to enqueue songs.')
await self._data_check(ctx)
query = query.strip('<>') query = query.strip('<>')
if query.startswith('sc '): if query.startswith('list '):
query = 'scsearch:{}'.format(query.strip('sc ')) query = 'ytsearch:{}'.format(query.lstrip('list '))
elif not query.startswith('http') or query.startswith('sc '): tracks = await player.get_tracks(query)
query = 'ytsearch:{}'.format(query) if not tracks:
return await self._embed_msg(ctx, 'Nothing found 👀')
tracks = await player.get_tracks(query)
if not tracks:
return await self._embed_msg(ctx, 'Nothing found 👀')
if 'list' not in query and 'ytsearch:' or 'scsearch:' in query:
page = 1
items_per_page = 5
pages = math.ceil(len(tracks) / items_per_page)
start = (page - 1) * items_per_page
end = start + items_per_page
search_list = ''
for i, track in enumerate(tracks[start:end], start=start):
_next = i + 1
search_list += '`{0}.` [**{1}**]({2})\n'.format(_next, track.title,
track.uri)
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Tracks Found:', description=search_list)
embed.set_footer(text='Page {}/{} | {} search results'.format(page, pages, len(tracks)))
message = await ctx.send(embed=embed)
dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
if dj_enabled:
if not await self._can_instaskip(ctx, ctx.author):
return
def check(r, u):
return r.message.id == message.id and u == ctx.message.author
for i in range(5):
await message.add_reaction(expected[i])
try:
(r, u) = await self.bot.wait_for('reaction_add', check=check, timeout=30.0)
except asyncio.TimeoutError:
await self._clear_react(message)
return
reacts = {v: k for k, v in emoji.items()}
react = reacts[r.emoji]
choice = {'one': 0, 'two': 1, 'three': 2, 'four': 3, 'five': 4}
await self._search_button(ctx, message, tracks, entry=choice[react])
else:
await self._data_check(ctx)
songembed = discord.Embed(colour=ctx.guild.me.top_role.colour, songembed = discord.Embed(colour=ctx.guild.me.top_role.colour,
title='Queued {} track(s).'.format(len(tracks))) title='Queued {} track(s).'.format(len(tracks)))
queue_duration = await self._queue_duration(ctx) queue_duration = await self._queue_duration(ctx)
@ -1070,27 +1042,97 @@ class Audio:
player.add(ctx.author, track) player.add(ctx.author, track)
if not player.current: if not player.current:
await player.play() await player.play()
message = await ctx.send(embed=songembed) return await ctx.send(embed=songembed)
if query.startswith('sc '):
query = 'scsearch:{}'.format(query.lstrip('sc '))
elif not query.startswith('http'):
query = 'ytsearch:{}'.format(query)
tracks = await player.get_tracks(query)
if not tracks:
return await self._embed_msg(ctx, 'Nothing found 👀')
async def _search_button(self, ctx, message, tracks, entry: int): len_search_pages = math.ceil(len(tracks) / 5)
player = lavalink.get_player(ctx.guild.id) search_page_list = []
jukebox_price = await self.config.guild(ctx.guild).jukebox_price() for page_num in range(1, len_search_pages + 1):
shuffle = await self.config.guild(ctx.guild).shuffle() embed = await self._build_search_page(ctx, tracks, page_num)
await self._clear_react(message) search_page_list.append(embed)
if not await self._currency_check(ctx, jukebox_price):
return dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
search_choice = tracks[entry] if dj_enabled:
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Enqueued', if not await self._can_instaskip(ctx, ctx.author):
description='**[{}]({})**'.format(search_choice.title, search_choice.uri)) return await menu(ctx, search_page_list, DEFAULT_CONTROLS)
queue_duration = await self._queue_duration(ctx)
queue_total_duration = lavalink.utils.format_time(queue_duration) async def _search_menu(ctx: commands.Context, pages: list,
if not shuffle and queue_duration > 0: controls: dict, message: discord.Message, page: int,
embed.set_footer(text='{} until track playback: #{} in queue'.format(queue_total_duration, ( timeout: float, emoji: str):
len(player.queue) + 1))) if message:
player.add(ctx.author, search_choice) await _search_button_action(ctx, tracks, emoji, page)
if not player.current: await message.delete()
await player.play() return None
return await ctx.send(embed=embed)
SEARCH_CONTROLS = {
"1⃣": _search_menu,
"2⃣": _search_menu,
"3⃣": _search_menu,
"4⃣": _search_menu,
"5⃣": _search_menu,
"": prev_page,
"": close_menu,
"": next_page
}
async def _search_button_action(ctx, tracks, emoji, page):
player = lavalink.get_player(ctx.guild.id)
jukebox_price = await self.config.guild(ctx.guild).jukebox_price()
shuffle = await self.config.guild(ctx.guild).shuffle()
if not await self._currency_check(ctx, jukebox_price):
return
try:
if emoji == "1⃣":
search_choice = tracks[0 + (page * 5)]
if emoji == "2⃣":
search_choice = tracks[1 + (page * 5)]
if emoji == "3⃣":
search_choice = tracks[2 + (page * 5)]
if emoji == "4⃣":
search_choice = tracks[3 + (page * 5)]
if emoji == "5⃣":
search_choice = tracks[4 + (page * 5)]
except IndexError:
search_choice = tracks[-1]
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Enqueued',
description='**[{}]({})**'.format(search_choice.title, search_choice.uri))
queue_duration = await self._queue_duration(ctx)
queue_total_duration = lavalink.utils.format_time(queue_duration)
if not shuffle and queue_duration > 0:
embed.set_footer(text='{} until track playback: #{} in queue'.format(queue_total_duration, (
len(player.queue) + 1)))
elif queue_duration > 0:
embed.set_footer(text='#{} in queue'.format(len(player.queue) + 1))
player.add(ctx.author, search_choice)
if not player.current:
await player.play()
await ctx.send(embed=embed)
await menu(ctx, search_page_list, SEARCH_CONTROLS)
async def _build_search_page(self, ctx, tracks, page_num):
search_num_pages = math.ceil(len(tracks) / 5)
search_idx_start = (page_num - 1) * 5
search_idx_end = search_idx_start + 5
search_list = ''
for i, track in enumerate(tracks[search_idx_start:search_idx_end], start=search_idx_start):
search_track_num = i + 1
if search_track_num > 5:
search_track_num = search_track_num % 5
if search_track_num == 0:
search_track_num = 5
search_list += '`{0}.` **[{1}]({2})**\n'.format(search_track_num, track.title, track.uri)
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Tracks Found:', description=search_list)
embed.set_footer(text='Page {}/{} | {} search results'.format(page_num, search_num_pages, len(tracks)))
return embed
@commands.command() @commands.command()
async def seek(self, ctx, seconds: int=30): async def seek(self, ctx, seconds: int=30):