mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -05:00
[V3 Audio] Initial V3 addition (#1373)
* Initial audio * Add application data and modify port * Modify codeowners * Need extra newline * add yml to manifest * lock lavalink version
This commit is contained in:
parent
b10b746d9e
commit
f65085946c
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -25,7 +25,7 @@ redbot/core/utils/mod.py @palmtree5
|
||||
# Cogs
|
||||
redbot/cogs/admin/* @tekulvw
|
||||
redbot/cogs/alias/* @tekulvw
|
||||
redbot/cogs/audio/* @tekulvw
|
||||
redbot/cogs/audio/* @aikaterna @atiwiex
|
||||
redbot/cogs/bank/* @tekulvw
|
||||
redbot/cogs/cleanup/* @palmtree5
|
||||
redbot/cogs/customcom/* @palmtree5
|
||||
|
||||
@ -2,3 +2,4 @@ include README.rst
|
||||
include LICENSE
|
||||
include requirements.txt
|
||||
include discord/bin/*.dll
|
||||
include redbot/cogs/audio/application.yml
|
||||
@ -1,5 +1,8 @@
|
||||
from .audio import Audio
|
||||
from discord.ext import commands
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Audio(bot))
|
||||
async def setup(bot: commands.Bot):
|
||||
cog = Audio(bot)
|
||||
await cog.init_config()
|
||||
bot.add_cog(cog)
|
||||
|
||||
20
redbot/cogs/audio/application.yml
Normal file
20
redbot/cogs/audio/application.yml
Normal file
@ -0,0 +1,20 @@
|
||||
server:
|
||||
port: 2333 # REST server
|
||||
lavalink:
|
||||
server:
|
||||
password: "youshallnotpass"
|
||||
ws:
|
||||
host: "localhost"
|
||||
port: 2332
|
||||
sources:
|
||||
youtube: true
|
||||
bandcamp: true
|
||||
soundcloud: true
|
||||
twitch: true
|
||||
vimeo: true
|
||||
mixer: true
|
||||
http: true
|
||||
local: false
|
||||
sentryDsn: ""
|
||||
bufferDurationMs: 400
|
||||
youtubePlaylistLoadLimit: 10000
|
||||
@ -1,116 +1,745 @@
|
||||
from discord.ext import commands
|
||||
from discord import FFmpegPCMAudio, PCMVolumeTransformer
|
||||
import os
|
||||
import youtube_dl
|
||||
# Lavalink.py cog for Red v3 beta 7+
|
||||
# Cog base thanks to Kromatic's example cog.
|
||||
import asyncio
|
||||
import discord
|
||||
import heapq
|
||||
import lavalink
|
||||
import math
|
||||
from discord.ext import commands
|
||||
from redbot.core import Config, checks
|
||||
|
||||
from redbot.core.i18n import CogI18n
|
||||
|
||||
_ = CogI18n("Audio", __file__)
|
||||
__version__ = "2.0.2.9.a"
|
||||
__author__ = ["aikaterna", "billy/bollo/ati"]
|
||||
|
||||
|
||||
# Just a little experimental audio cog not meant for final release
|
||||
LAVALINK_BUILD = 3065
|
||||
|
||||
|
||||
class Audio:
|
||||
"""Audio commands"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.config = Config.get_conf(self, 2711759128, force_registration=True)
|
||||
|
||||
default_global = {
|
||||
"host": 'localhost',
|
||||
"port": '2332',
|
||||
"passw": 'youshallnotpass',
|
||||
"status": False
|
||||
}
|
||||
|
||||
default_guild = {
|
||||
"notify": False,
|
||||
"repeat": False,
|
||||
"shuffle": False,
|
||||
"volume": 100
|
||||
}
|
||||
|
||||
self.config.register_guild(**default_guild)
|
||||
self.config.register_global(**default_global)
|
||||
|
||||
self._lavalink = None
|
||||
self._lavalink_build_url = (
|
||||
"https://ci.fredboat.com/repository/download/"
|
||||
"Lavalink_Build/{}:id/Lavalink.jar"
|
||||
).format(LAVALINK_BUILD)
|
||||
|
||||
async def init_config(self):
|
||||
host = await self.config.host()
|
||||
passw = await self.config.passw()
|
||||
port = await self.config.port()
|
||||
|
||||
try:
|
||||
self._lavalink = lavalink.Client(
|
||||
bot=self.bot, password=passw, host=host, port=port, loop=self.bot.loop
|
||||
)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
self.bot.lavalink.client.register_hook(self.track_hook)
|
||||
|
||||
@property
|
||||
def lavalink(self):
|
||||
return self._lavalink
|
||||
|
||||
async def track_hook(self, player, event):
|
||||
notify = await self.config.guild(self.bot.get_guild(player.fetch('guild'))).notify()
|
||||
status = await self.config.status()
|
||||
playing_servers = await self._get_playing()
|
||||
get_players = [p for p in self.bot.lavalink.players._players.values() if p.is_playing]
|
||||
try:
|
||||
get_single_title = get_players[0].current.title
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if event == 'TrackStartEvent' and notify:
|
||||
c = player.fetch('channel')
|
||||
if c:
|
||||
c = self.bot.get_channel(c)
|
||||
if c:
|
||||
embed = discord.Embed(colour=c.guild.me.top_role.colour, title='Now Playing',
|
||||
description='**[{}]({})**'.format(player.current.title, player.current.uri))
|
||||
await c.send(embed=embed)
|
||||
|
||||
if event == 'TrackStartEvent' and status:
|
||||
if playing_servers > 1:
|
||||
await self.bot.change_presence(game=discord.Game(name='music in {} servers'.format(playing_servers)))
|
||||
else:
|
||||
await self.bot.change_presence(game=discord.Game(name=get_single_title, type=2))
|
||||
|
||||
if event == 'QueueEndEvent' and notify:
|
||||
c = player.fetch('channel')
|
||||
if c:
|
||||
c = self.bot.get_channel(c)
|
||||
if c:
|
||||
embed = discord.Embed(colour=c.guild.me.top_role.colour, title='Queue ended.')
|
||||
await c.send(embed=embed)
|
||||
|
||||
if event == 'QueueEndEvent' and status:
|
||||
await asyncio.sleep(1)
|
||||
if playing_servers == 0:
|
||||
await self.bot.change_presence(game=None)
|
||||
if playing_servers == 1:
|
||||
await self.bot.change_presence(game=discord.Game(name=get_single_title, type=2))
|
||||
if playing_servers > 1:
|
||||
await self.bot.change_presence(game=discord.Game(name='music in {} servers'.format(playing_servers)))
|
||||
|
||||
@commands.group()
|
||||
@checks.is_owner()
|
||||
async def audioset(self, ctx):
|
||||
"""Music configuration options."""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
|
||||
@audioset.command()
|
||||
async def notify(self, ctx):
|
||||
"""Toggle song announcement and other bot messages."""
|
||||
notify = await self.config.guild(ctx.guild).notify()
|
||||
await self.config.guild(ctx.guild).notify.set(not notify)
|
||||
get_notify = await self.config.guild(ctx.guild).notify()
|
||||
await self._embed_msg(ctx, 'Verbose mode on: {}.'.format(get_notify))
|
||||
|
||||
@audioset.command()
|
||||
async def settings(self, ctx):
|
||||
"""Show the current settings."""
|
||||
notify = await self.config.guild(ctx.guild).notify()
|
||||
status = await self.config.status()
|
||||
shuffle = await self.config.guild(ctx.guild).shuffle()
|
||||
repeat = await self.config.guild(ctx.guild).repeat()
|
||||
|
||||
msg = '```ini\n'
|
||||
msg += '----Guild Settings----\n'
|
||||
msg += 'audioset notify: [{}]\n'.format(notify)
|
||||
msg += 'audioset status: [{}]\n'.format(status)
|
||||
msg += 'Repeat: [{}]\n'.format(repeat)
|
||||
msg += 'Shuffle: [{}]\n'.format(shuffle)
|
||||
msg += '---Lavalink Settings---\n'
|
||||
msg += 'Cog version: {}\n'.format(__version__)
|
||||
msg += 'Pip version: {}\n```'.format(lavalink.__version__)
|
||||
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, description=msg)
|
||||
return await ctx.send(embed=embed)
|
||||
|
||||
@audioset.command()
|
||||
@checks.is_owner()
|
||||
async def status(self, ctx):
|
||||
"""Enables/disables songs' titles as status."""
|
||||
status = await self.config.status()
|
||||
await self.config.status.set(not status)
|
||||
get_status = await self.config.status()
|
||||
await self._embed_msg(ctx, 'Song titles as status: {}'.format(get_status))
|
||||
|
||||
@commands.command()
|
||||
async def local(self, ctx, *, filename: str):
|
||||
"""Play mp3"""
|
||||
if ctx.author.voice is None:
|
||||
await ctx.send(_("Join a voice channel first!"))
|
||||
return
|
||||
|
||||
if ctx.voice_client:
|
||||
if ctx.voice_client.channel != ctx.author.voice.channel:
|
||||
await ctx.voice_client.disconnect()
|
||||
path = os.path.join("cogs", "audio", "songs", filename + ".mp3")
|
||||
if not os.path.isfile(path):
|
||||
await ctx.send(_("Let's play a file that exists pls"))
|
||||
return
|
||||
player = PCMVolumeTransformer(FFmpegPCMAudio(path), volume=1)
|
||||
voice = await ctx.author.voice.channel.connect()
|
||||
voice.play(player)
|
||||
await ctx.send(_("{} is playing a song...").format(ctx.author))
|
||||
async def audiostats(self, ctx):
|
||||
"""Audio stats."""
|
||||
server_num = await self._get_playing()
|
||||
server_ids = self.bot.lavalink.players._players
|
||||
server_list = []
|
||||
for k, v in server_ids.items():
|
||||
guild_id = k
|
||||
player = v
|
||||
try:
|
||||
server_list.append(
|
||||
self.bot.get_guild(guild_id).name + ': **[{}]({})**'.format(v.current.title, v.current.uri))
|
||||
except AttributeError:
|
||||
pass
|
||||
servers = '\n'.join(server_list)
|
||||
if server_list == []:
|
||||
servers = 'Not connected anywhere.'
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playing in {} servers:'.format(server_num),
|
||||
description=servers)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command()
|
||||
async def play(self, ctx, url: str):
|
||||
"""Play youtube url"""
|
||||
url = url.strip("<").strip(">")
|
||||
if ctx.author.voice is None:
|
||||
await ctx.send(_("Join a voice channel first!"))
|
||||
return
|
||||
elif "youtube.com" not in url.lower():
|
||||
await ctx.send(_("Youtube links pls"))
|
||||
return
|
||||
async def bump(self, ctx, index: int):
|
||||
"""Bump a song number to the top of the queue."""
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
if ctx.voice_client:
|
||||
if ctx.voice_client.channel != ctx.author.voice.channel:
|
||||
await ctx.voice_client.disconnect()
|
||||
yt = YoutubeSource(url)
|
||||
player = PCMVolumeTransformer(yt, volume=1)
|
||||
voice = await ctx.author.voice.channel.connect()
|
||||
voice.play(player)
|
||||
await ctx.send(_("{} is playing a song...").format(ctx.author))
|
||||
if not player.queue:
|
||||
return await self._embed_msg(ctx, 'Nothing queued.')
|
||||
|
||||
@commands.command()
|
||||
async def stop(self, ctx):
|
||||
"""Stops the music and disconnects"""
|
||||
if ctx.voice_client:
|
||||
ctx.voice_client.source.cleanup()
|
||||
await ctx.voice_client.disconnect()
|
||||
if index > len(player.queue) or index < 1:
|
||||
return await self._embed_msg(ctx, 'Song number must be greater than 1 and within the queue limit.')
|
||||
|
||||
bump_index = index - 1
|
||||
bump_song = self.bot.lavalink.players.get(ctx.guild.id).queue[bump_index]
|
||||
player.queue.insert(0, bump_song)
|
||||
removed = player.queue.pop(index)
|
||||
await self._embed_msg(ctx, 'Moved **' + removed.title + '** to the top of the queue.')
|
||||
|
||||
@commands.command(aliases=['dc'])
|
||||
async def disconnect(self, ctx):
|
||||
"""Disconnect from the voice channel."""
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
await player.disconnect()
|
||||
player.queue.clear()
|
||||
player.store('song', None)
|
||||
player.store('requester', None)
|
||||
await self.bot.lavalink.client._trigger_event("QueueEndEvent", ctx.guild.id)
|
||||
|
||||
@commands.command(aliases=['np', 'n', 'song'])
|
||||
async def now(self, ctx):
|
||||
"""Now playing."""
|
||||
expected = ['⏹', '⏸', '⏭']
|
||||
emoji = {
|
||||
'stop': '⏹',
|
||||
'pause': '⏸',
|
||||
'next': '⏭'
|
||||
}
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
song = 'Nothing'
|
||||
if player.current:
|
||||
arrow = await self._draw_time(ctx)
|
||||
pos = lavalink.Utils.format_time(player.position)
|
||||
if player.current.stream:
|
||||
dur = 'LIVE'
|
||||
else:
|
||||
dur = lavalink.Utils.format_time(player.current.duration)
|
||||
if not player.current:
|
||||
song = 'Nothing.'
|
||||
else:
|
||||
await ctx.send(_("I'm not even connected to a voice channel!"), delete_after=2)
|
||||
await ctx.message.delete()
|
||||
req_user = self.bot.get_user(player.current.requester)
|
||||
song = '**[{}]({})**\nRequested by: **{}**\n\n{}`{}`/`{}`'.format(player.current.title, player.current.uri,
|
||||
req_user, arrow, pos, dur)
|
||||
|
||||
@commands.command()
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Now Playing', description=song)
|
||||
message = await ctx.send(embed=embed)
|
||||
|
||||
def check(r, u):
|
||||
return r.message.id == message.id and u == ctx.message.author
|
||||
|
||||
if player.current:
|
||||
for i in range(3):
|
||||
await message.add_reaction(expected[i])
|
||||
try:
|
||||
(r, u) = await self.bot.wait_for('reaction_add', check=check, timeout=10.0)
|
||||
except asyncio.TimeoutError:
|
||||
return await self._clear_react(message)
|
||||
|
||||
reacts = {v: k for k, v in emoji.items()}
|
||||
react = reacts[r.emoji]
|
||||
|
||||
if react == 'stop':
|
||||
await self._clear_react(message)
|
||||
await ctx.invoke(self.stop)
|
||||
elif react == 'pause':
|
||||
await self._clear_react(message)
|
||||
await ctx.invoke(self.pause)
|
||||
elif react == 'next':
|
||||
await self._clear_react(message)
|
||||
await ctx.invoke(self.skip)
|
||||
|
||||
@commands.command(aliases=['resume'])
|
||||
async def pause(self, ctx):
|
||||
"""Pauses the music"""
|
||||
if ctx.voice_client:
|
||||
ctx.voice_client.pause()
|
||||
await ctx.send("👌", delete_after=2)
|
||||
"""Pause and resume."""
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)):
|
||||
return await self._embed_msg(ctx, 'You must be in the voice channel to pause the music.')
|
||||
|
||||
if not player.is_playing:
|
||||
return
|
||||
|
||||
if player.paused:
|
||||
await player.set_pause(False)
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Resumed',
|
||||
description='**[{}]({})**'.format(player.current.title, player.current.uri))
|
||||
message = await ctx.send(embed=embed)
|
||||
else:
|
||||
await ctx.send(_("I'm not even connected to a voice channel!"), delete_after=2)
|
||||
await ctx.message.delete()
|
||||
await player.set_pause(True)
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Paused',
|
||||
description='**[{}]({})**'.format(player.current.title, player.current.uri))
|
||||
message = await ctx.send(embed=embed)
|
||||
|
||||
@commands.command()
|
||||
async def resume(self, ctx):
|
||||
"""Resumes the music"""
|
||||
if ctx.voice_client:
|
||||
ctx.voice_client.resume()
|
||||
await ctx.send("👌", delete_after=2)
|
||||
else:
|
||||
await ctx.send(_("I'm not even connected to a voice channel!"), delete_after=2)
|
||||
await ctx.message.delete()
|
||||
async def percent(self, ctx):
|
||||
"""Queue percentage."""
|
||||
queue_tracks = self.bot.lavalink.players.get(ctx.guild.id).queue
|
||||
queue_len = len(queue_tracks)
|
||||
requesters = {'total': 0, 'users': {}}
|
||||
|
||||
@commands.command(hidden=True)
|
||||
async def volume(self, ctx, n: float):
|
||||
"""Sets the volume"""
|
||||
if ctx.voice_client:
|
||||
ctx.voice_client.source.volume = n
|
||||
await ctx.send(_("Volume set."), delete_after=2)
|
||||
async def _usercount(req_username):
|
||||
if req_username in requesters['users']:
|
||||
requesters['users'][req_username]['songcount'] += 1
|
||||
requesters['total'] += 1
|
||||
else:
|
||||
requesters['users'][req_username] = {}
|
||||
requesters['users'][req_username]['songcount'] = 1
|
||||
requesters['total'] += 1
|
||||
|
||||
for i in range(queue_len):
|
||||
req_username = self.bot.get_user(queue_tracks[i].requester).name
|
||||
await _usercount(req_username)
|
||||
try:
|
||||
req_username = self.bot.get_user(self.bot.lavalink.players.get(ctx.guild.id).current.requester).name
|
||||
await _usercount(req_username)
|
||||
except AttributeError:
|
||||
return await self._embed_msg(ctx, 'Nothing in the queue.')
|
||||
|
||||
for req_username in requesters['users']:
|
||||
percentage = float(requesters['users'][req_username]['songcount']) / float(requesters['total'])
|
||||
requesters['users'][req_username]['percent'] = round(percentage * 100, 1)
|
||||
|
||||
top_queue_users = heapq.nlargest(20, [(x, requesters['users'][x][y]) for x in requesters['users'] for y in
|
||||
requesters['users'][x] if y == 'percent'], key=lambda x: x[1])
|
||||
queue_user = ["{}: {:g}%".format(x[0], x[1]) for x in top_queue_users]
|
||||
queue_user_list = '\n'.join(queue_user)
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Queued and playing songs:',
|
||||
description=queue_user_list)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(aliases=['p'])
|
||||
async def play(self, ctx, *, query):
|
||||
"""Play a URL or search for a song."""
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
shuffle = await self.config.guild(ctx.guild).shuffle()
|
||||
|
||||
if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)):
|
||||
return await self._embed_msg(ctx, 'You must be in the voice channel to use the play command.')
|
||||
|
||||
player.store('channel', ctx.channel.id)
|
||||
player.store('guild', ctx.guild.id)
|
||||
await self._data_check(ctx)
|
||||
|
||||
if not player.is_connected:
|
||||
await player.connect(ctx.author.voice.channel.id)
|
||||
|
||||
query = query.strip('<>')
|
||||
if not query.startswith('http'):
|
||||
query = 'ytsearch:{}'.format(query)
|
||||
|
||||
tracks = await self.bot.lavalink.client.get_tracks(query)
|
||||
if not tracks:
|
||||
return await self._embed_msg(ctx, 'Nothing found 👀')
|
||||
|
||||
queue_duration = await self._queue_duration(ctx)
|
||||
queue_total_duration = lavalink.Utils.format_time(queue_duration)
|
||||
|
||||
if 'list' in query and 'ytsearch:' not in query:
|
||||
for track in tracks:
|
||||
player.add(requester=ctx.author.id, track=track)
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlist Enqueued',
|
||||
description='Added {} tracks to the queue.'.format(len(tracks)))
|
||||
if not shuffle and queue_duration > 0:
|
||||
embed.set_footer(text='{} until start of playlist playback'.format(queue_total_duration))
|
||||
else:
|
||||
await ctx.send(_("I'm not even connected to a voice channel!"), delete_after=2)
|
||||
await ctx.message.delete()
|
||||
player.add(requester=ctx.author.id, track=tracks[0])
|
||||
track_title = tracks[0]["info"]["title"]
|
||||
track_url = tracks[0]["info"]["uri"]
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Enqueued',
|
||||
description='[**{}**]({})'.format(track_title, track_url))
|
||||
if not shuffle and queue_duration > 0:
|
||||
embed.set_footer(text='{} until track playback'.format(queue_total_duration))
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
if not player.is_playing:
|
||||
await player.play()
|
||||
|
||||
@commands.command(aliases=['q'])
|
||||
async def queue(self, ctx, page: int = 1):
|
||||
"""Lists the queue."""
|
||||
shuffle = await self.config.guild(ctx.guild).shuffle()
|
||||
repeat = await self.config.guild(ctx.guild).repeat()
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
if not player.queue:
|
||||
return await self._embed_msg(ctx, 'There\'s nothing in the queue.')
|
||||
|
||||
if player.current is None:
|
||||
return await self._embed_msg(ctx, 'The player is stopped.')
|
||||
|
||||
items_per_page = 10
|
||||
pages = math.ceil(len(player.queue) / items_per_page)
|
||||
start = (page - 1) * items_per_page
|
||||
end = start + items_per_page
|
||||
|
||||
queue_list = ''
|
||||
arrow = await self._draw_time(ctx)
|
||||
pos = lavalink.Utils.format_time(player.position)
|
||||
|
||||
if player.current.stream:
|
||||
dur = 'LIVE'
|
||||
else:
|
||||
dur = lavalink.Utils.format_time(player.current.duration)
|
||||
|
||||
if player.current.stream:
|
||||
queue_list += '**Currently livestreaming:** [**{}**]({})\nRequested by: **{}**\n\n{}`{}`/`{}`\n\n'.format(
|
||||
player.current.title, player.current.uri, self.bot.get_user(player.current.requester), arrow, pos, dur)
|
||||
else:
|
||||
queue_list += 'Playing: [**{}**]({})\nRequested by: **{}**\n\n{}`{}`/`{}`\n\n'.format(player.current.title,
|
||||
player.current.uri,
|
||||
self.bot.get_user(
|
||||
player.current.requester),
|
||||
arrow, pos, dur)
|
||||
|
||||
for i, track in enumerate(player.queue[start:end], start=start):
|
||||
req_user = self.bot.get_user(track.requester)
|
||||
next = i + 1
|
||||
queue_list += '`{}.` [**{}**]({}), requested by **{}**\n'.format(next, track.title, track.uri, req_user)
|
||||
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Queue for ' + ctx.guild.name,
|
||||
description=queue_list)
|
||||
|
||||
queue_duration = await self._queue_duration(ctx)
|
||||
queue_total_duration = lavalink.Utils.format_time(queue_duration)
|
||||
text = 'Page {}/{} | {} tracks, {} remaining'.format(page, pages, len(player.queue), queue_total_duration)
|
||||
if repeat:
|
||||
text += ' | Repeat: \N{WHITE HEAVY CHECK MARK}'
|
||||
if shuffle:
|
||||
text += ' | Shuffle: \N{WHITE HEAVY CHECK MARK}'
|
||||
embed.set_footer(text=text)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command()
|
||||
async def repeat(self, ctx):
|
||||
"""Toggles repeat."""
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)):
|
||||
return await self._embed_msg(ctx, 'You must be in the voice channel to toggle repeat.')
|
||||
|
||||
if not player.is_playing:
|
||||
return await self._embed_msg(ctx, 'Nothing playing.')
|
||||
|
||||
repeat = await self.config.guild(ctx.guild).repeat()
|
||||
await self.config.guild(ctx.guild).repeat.set(not repeat)
|
||||
get_repeat = await self.config.guild(ctx.guild).repeat()
|
||||
await self._embed_msg(ctx, 'Repeat songs: {}.'.format(get_repeat))
|
||||
|
||||
@commands.command()
|
||||
async def remove(self, ctx, index: int):
|
||||
"""Remove a specific song number from the queue."""
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
if not player.queue:
|
||||
return await self._embed_msg(ctx, 'Nothing queued.')
|
||||
|
||||
if index > len(player.queue) or index < 1:
|
||||
return await self._embed_msg(ctx, 'Song number must be greater than 1 and within the queue limit.')
|
||||
|
||||
index = index - 1
|
||||
removed = player.queue.pop(index)
|
||||
|
||||
await self._embed_msg(ctx, 'Removed **' + removed.title + '** from the queue.')
|
||||
|
||||
@commands.command()
|
||||
async def search(self, ctx, *, query):
|
||||
"""Pick a song with a search.
|
||||
Use [p]search list <search term> to queue all songs.
|
||||
"""
|
||||
expected = ["1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "⏪", "⏩"]
|
||||
emoji = {
|
||||
"one": "1⃣",
|
||||
"two": "2⃣",
|
||||
"three": "3⃣",
|
||||
"four": "4⃣",
|
||||
"five": "5⃣",
|
||||
"back": "⏪",
|
||||
"next": "⏩"
|
||||
}
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
shuffle = await self.config.guild(ctx.guild).shuffle()
|
||||
player.store('channel', ctx.channel.id)
|
||||
player.store('guild', ctx.guild.id)
|
||||
|
||||
if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)):
|
||||
return await self._embed_msg(ctx, 'You must be in the voice channel to enqueue songs.')
|
||||
if not player.is_connected:
|
||||
await player.connect(ctx.author.voice.channel.id)
|
||||
|
||||
query = query.strip('<>')
|
||||
if query.startswith('sc '):
|
||||
query = 'scsearch:{}'.format(query.strip('sc '))
|
||||
elif not query.startswith('http') or query.startswith('sc '):
|
||||
query = 'ytsearch:{}'.format(query)
|
||||
|
||||
tracks = await self.bot.lavalink.client.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, tracks[i]["info"]["title"],
|
||||
tracks[i]["info"]["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)
|
||||
|
||||
def check(r, u):
|
||||
return r.message.id == message.id and u == ctx.message.author
|
||||
|
||||
for i in range(7):
|
||||
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]
|
||||
if react == 'one':
|
||||
await self._search_button(ctx, message, tracks, entry=0)
|
||||
elif react == 'two':
|
||||
await self._search_button(ctx, message, tracks, entry=1)
|
||||
elif react == 'three':
|
||||
await self._search_button(ctx, message, tracks, entry=2)
|
||||
elif react == 'four':
|
||||
await self._search_button(ctx, message, tracks, entry=3)
|
||||
elif react == 'five':
|
||||
await self._search_button(ctx, message, tracks, entry=4)
|
||||
|
||||
elif react == 'back':
|
||||
await self._clear_react(message)
|
||||
return
|
||||
elif react == 'next':
|
||||
await self._clear_react(message)
|
||||
return
|
||||
else:
|
||||
await self._data_check(ctx)
|
||||
songembed = discord.Embed(colour=ctx.guild.me.top_role.colour,
|
||||
title='Queued {} track(s).'.format(len(tracks)))
|
||||
queue_duration = await self._queue_duration(ctx)
|
||||
queue_total_duration = lavalink.Utils.format_time(queue_duration)
|
||||
if not shuffle and queue_duration > 0:
|
||||
songembed.set_footer(text='{} until start of search playback'.format(queue_total_duration))
|
||||
for track in tracks:
|
||||
player.add(requester=ctx.author.id, track=track)
|
||||
if not player.is_playing:
|
||||
await player.play()
|
||||
message = await ctx.send(embed=songembed)
|
||||
|
||||
async def _search_button(self, ctx, message, tracks, entry: int):
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
shuffle = await self.config.guild(ctx.guild).shuffle()
|
||||
await self._clear_react(message)
|
||||
player.add(requester=ctx.author.id, track=tracks[entry])
|
||||
track_title = tracks[entry]["info"]["title"]
|
||||
track_url = tracks[entry]["info"]["uri"]
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Enqueued',
|
||||
description='[**{}**]({})'.format(track_title, track_url))
|
||||
queue_duration = await self._queue_duration(ctx)
|
||||
queue_total_duration = lavalink.Utils.format_time(queue_duration)
|
||||
if not shuffle:
|
||||
embed.set_footer(text='{} until track playback'.format(queue_total_duration))
|
||||
return await ctx.send(embed=embed)
|
||||
|
||||
@commands.command()
|
||||
async def seek(self, ctx, seconds: int = 5):
|
||||
"""Seeks ahead or behind on a track by seconds."""
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)):
|
||||
return await self._embed_msg(ctx, 'You must be in the voice channel to use seek.')
|
||||
if player.is_playing:
|
||||
if player.current.stream:
|
||||
return await self._embed_msg(ctx, 'Can\'t seek on a stream.')
|
||||
else:
|
||||
time_sec = seconds * 1000
|
||||
seek = player.position + time_sec
|
||||
await self._embed_msg(ctx, 'Moved {}s to {}'.format(seconds, lavalink.Utils.format_time(seek)))
|
||||
return await player.seek(seek)
|
||||
|
||||
@commands.command()
|
||||
async def shuffle(self, ctx):
|
||||
"""Toggles shuffle."""
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)):
|
||||
return await self._embed_msg(ctx, 'You must be in the voice channel to toggle shuffle.')
|
||||
|
||||
shuffle = await self.config.guild(ctx.guild).shuffle()
|
||||
await self.config.guild(ctx.guild).shuffle.set(not shuffle)
|
||||
shuffle = await self.config.guild(ctx.guild).shuffle()
|
||||
if player.shuffle != shuffle:
|
||||
player.shuffle = shuffle
|
||||
await self._embed_msg(ctx, 'Shuffle songs: {}.'.format(shuffle))
|
||||
|
||||
@commands.command(aliases=['forceskip', 'fs'])
|
||||
async def skip(self, ctx):
|
||||
"""Skips to the next track."""
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
if player.current is None:
|
||||
return await self._embed_msg(ctx, 'The player is stopped.')
|
||||
|
||||
if not player.queue:
|
||||
pos = player.position
|
||||
dur = player.current.duration
|
||||
remain = dur - pos
|
||||
time_remain = lavalink.Utils.format_time(remain)
|
||||
if player.current.stream:
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='There\'s nothing in the queue.')
|
||||
embed.set_footer(text='Currently livestreaming {}'.format(player.current.title))
|
||||
return await ctx.send(embed=embed)
|
||||
elif player.current.track:
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='There\'s nothing in the queue.')
|
||||
embed.set_footer(text='{} left on {}'.format(time_remain, player.current.title))
|
||||
return await ctx.send(embed=embed)
|
||||
else:
|
||||
return await self._embed_msg(ctx, 'There\'s nothing in the queue.')
|
||||
|
||||
if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)):
|
||||
return await self._embed_msg(ctx, 'You must be in the voice channel to skip the music.')
|
||||
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Skipped',
|
||||
description='**[{}]({})**'.format(player.current.title, player.current.uri))
|
||||
message = await ctx.send(embed=embed)
|
||||
|
||||
await player.skip()
|
||||
|
||||
@commands.command(aliases=['s'])
|
||||
async def stop(self, ctx):
|
||||
"""Stops playback and clears the queue."""
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)):
|
||||
return await self._embed_msg(ctx, 'You must be in the voice channel to stop the music.')
|
||||
if player.is_playing:
|
||||
await self._embed_msg(ctx, 'Stopping...')
|
||||
player.queue.clear()
|
||||
await player.stop()
|
||||
player.store('song', None)
|
||||
player.store('requester', None)
|
||||
await self.bot.lavalink.client._trigger_event("QueueEndEvent", ctx.guild.id)
|
||||
|
||||
@commands.command(aliases=['vol'])
|
||||
async def volume(self, ctx, vol: int = None):
|
||||
"""Sets the volume, 1% - 150%."""
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)):
|
||||
return await self._embed_msg(ctx, 'You must be in the voice channel to change the volume.')
|
||||
if not vol:
|
||||
vol = await self.config.guild(ctx.guild).volume()
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Current Volume:',
|
||||
description=str(vol) + '%')
|
||||
if not player.is_playing:
|
||||
embed.set_footer(text='Nothing playing.')
|
||||
return await ctx.send(embed=embed)
|
||||
if int(vol) > 150:
|
||||
vol = 150
|
||||
await self.config.guild(ctx.guild).volume.set(vol)
|
||||
await player.set_volume(vol)
|
||||
else:
|
||||
await self.config.guild(ctx.guild).volume.set(vol)
|
||||
await player.set_volume(vol)
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Volume:', description=str(vol) + '%')
|
||||
if not player.is_playing:
|
||||
embed.set_footer(text='Nothing playing.')
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.group(aliases=['llset'])
|
||||
@checks.is_owner()
|
||||
async def llsetup(self, ctx):
|
||||
"""Lavalink server configuration options."""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
|
||||
@llsetup.command()
|
||||
async def host(self, ctx, host):
|
||||
"""Set the lavalink server host."""
|
||||
await self.config.host.set(host)
|
||||
get_host = await self.config.host()
|
||||
await self._embed_msg(ctx, 'Host set to {}.'.format(get_host))
|
||||
|
||||
@llsetup.command()
|
||||
async def password(self, ctx, passw):
|
||||
"""Set the lavalink server password."""
|
||||
await self.config.passw.set(str(passw))
|
||||
get_passw = await self.config.passw()
|
||||
await self._embed_msg(ctx, 'Server password set to {}.'.format(get_passw))
|
||||
|
||||
@llsetup.command()
|
||||
async def port(self, ctx, port):
|
||||
"""Set the lavalink server port."""
|
||||
await self.config.port.set(str(port))
|
||||
get_port = await self.config.port()
|
||||
await self._embed_msg(ctx, 'Port set to {}.'.format(get_port))
|
||||
|
||||
async def _clear_react(self, message):
|
||||
try:
|
||||
await message.clear_reactions()
|
||||
except:
|
||||
return
|
||||
|
||||
async def _data_check(self, ctx):
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
shuffle = await self.config.guild(ctx.guild).shuffle()
|
||||
repeat = await self.config.guild(ctx.guild).repeat()
|
||||
volume = await self.config.guild(ctx.guild).volume()
|
||||
if player.repeat != repeat:
|
||||
player.repeat = repeat
|
||||
if player.shuffle != shuffle:
|
||||
player.shuffle = shuffle
|
||||
if player.volume != volume:
|
||||
player.volume = volume
|
||||
|
||||
async def _draw_time(self, ctx):
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
paused = player.paused
|
||||
pos = player.position
|
||||
dur = player.current.duration
|
||||
sections = 12
|
||||
loc_time = round((pos / dur) * sections)
|
||||
bar = '\N{BOX DRAWINGS HEAVY HORIZONTAL}'
|
||||
seek = '\N{RADIO BUTTON}'
|
||||
if paused:
|
||||
msg = '\N{DOUBLE VERTICAL BAR}'
|
||||
else:
|
||||
msg = '\N{BLACK RIGHT-POINTING TRIANGLE}'
|
||||
for i in range(sections):
|
||||
if i == loc_time:
|
||||
msg += seek
|
||||
else:
|
||||
msg += bar
|
||||
return msg
|
||||
|
||||
async def _embed_msg(self, ctx, title):
|
||||
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title=title)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
async def _get_playing(self):
|
||||
return len([p for p in self.bot.lavalink.players._players.values() if p.is_playing])
|
||||
|
||||
async def _queue_duration(self, ctx):
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
duration = []
|
||||
for i in range(len(player.queue)):
|
||||
if not player.queue[i].stream:
|
||||
duration.append(player.queue[i].duration)
|
||||
queue_duration = sum(duration)
|
||||
if player.queue == []:
|
||||
queue_duration = 0
|
||||
try:
|
||||
if not player.current.stream:
|
||||
remain = player.current.duration - player.position
|
||||
else:
|
||||
remain = 0
|
||||
except AttributeError:
|
||||
remain = 0
|
||||
queue_total_duration = remain + queue_duration
|
||||
return queue_total_duration
|
||||
|
||||
def __unload(self):
|
||||
for vc in self.bot.voice_clients:
|
||||
if vc.source:
|
||||
vc.source.cleanup()
|
||||
self.bot.loop.create_task(vc.disconnect())
|
||||
|
||||
|
||||
class YoutubeSource(discord.FFmpegPCMAudio):
|
||||
def __init__(self, url):
|
||||
opts = {
|
||||
'format': 'webm[abr>0]/bestaudio/best',
|
||||
'prefer_ffmpeg': True,
|
||||
'quiet': True
|
||||
}
|
||||
ytdl = youtube_dl.YoutubeDL(opts)
|
||||
self.info = ytdl.extract_info(url, download=False)
|
||||
super().__init__(self.info['url'])
|
||||
self.bot.lavalink.client.destroy()
|
||||
@ -7,3 +7,4 @@ colorama==0.3.9
|
||||
aiohttp-json-rpc==0.8.7
|
||||
pyyaml==3.12
|
||||
Red-Trivia
|
||||
lavalink==2.0.2.9
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user