mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-07 11:48:55 -05:00
[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:
parent
8f74e4dd31
commit
4a8358ecb4
@ -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):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user