mirror of
https://github.com/UiO-Gaming/ShiteMusicBot.git
synced 2026-11-05 13:56:51 +01:00
Merge branch 'lavalink-dev' into 'master'
Now using the dev branch of lavalink See merge request Ev-1/shite-music-bot!6
This commit is contained in:
commit
20e0ca8958
6 files changed
+293
-460
No files matched your search
@@ -12,8 +12,7 @@ from cogs.utils.settings import Settings
|
||||
initial_extension = [
|
||||
'cogs.cogs',
|
||||
'cogs.botsettings',
|
||||
'cogs.misc',
|
||||
'cogs.music'
|
||||
'cogs.misc'
|
||||
]
|
||||
|
||||
|
||||
@@ -84,6 +83,8 @@ class Bot(commands.Bot):
|
||||
f' in {len(self.guilds)} servers.')
|
||||
print(f'Version: {discord.__version__}\n')
|
||||
|
||||
self.load_extension('cogs.music')
|
||||
|
||||
await self.change_presence(activity=discord.Game(type=0,
|
||||
name=config["playing"]),
|
||||
status=discord.Status.online)
|
||||
|
||||
+160
-218
@@ -1,75 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
This is an example code that shows how you would setup a simple music bot for Lavalink v3.
|
||||
This example is only compatible with the discord.py rewrite branch.
|
||||
Because of the F-Strings, you also must have Python 3.6 or higher installed.
|
||||
This is an example cog that shows how you would make use of Lavalink.py.
|
||||
This example cog requires that you have python 3.6 or higher due to the f-strings.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
import typing
|
||||
import asyncio
|
||||
|
||||
import discord
|
||||
import lavalink
|
||||
from discord.ext import commands
|
||||
import time
|
||||
|
||||
import asyncio
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
import lavalink
|
||||
|
||||
from .utils import checks, RoxUtils
|
||||
from .utils.mixplayer.mixplayer import MixPlayer
|
||||
from .utils.mixplayer import MixPlayer
|
||||
from typing import Optional
|
||||
|
||||
from .utils.embedscroller import QueueScroller
|
||||
from lavasettings import *
|
||||
|
||||
time_rx = re.compile('[0-9]+')
|
||||
url_rx = re.compile('https?:\/\/(?:www\.)?.+')
|
||||
url_rx = re.compile('https?:\\/\\/(?:www\\.)?.+')
|
||||
|
||||
|
||||
class Music:
|
||||
def __init__(self, bot):
|
||||
|
||||
self.bot = bot
|
||||
|
||||
if not hasattr(bot, 'lavalink'):
|
||||
lavalink.Client(bot=bot, password='youshallnotpass',
|
||||
loop=bot.loop, log_level=logging.INFO,
|
||||
host=host, ws_port=ws_port,
|
||||
rest_port=rest_port,
|
||||
player=MixPlayer)
|
||||
self.bot.lavalink.register_hook(self._track_hook)
|
||||
if not hasattr(bot, 'lavalink'): # This ensures the client isn't overwritten during cog reloads.
|
||||
bot.lavalink = lavalink.Client(bot.user.id, player=MixPlayer)
|
||||
bot.lavalink.add_node(host, port, password, region, 'default-node') # Host, Port, Password, Region, Name
|
||||
bot.add_listener(bot.lavalink.voice_update_handler, 'on_socket_response')
|
||||
|
||||
bot.lavalink.add_event_hook(self.track_hook)
|
||||
|
||||
def __unload(self):
|
||||
for guild_id, player in self.bot.lavalink.players:
|
||||
self.bot.loop.create_task(player.disconnect())
|
||||
player.cleanup()
|
||||
# Clear the players from Lavalink's internal cache
|
||||
self.bot.lavalink.players.clear()
|
||||
self.bot.lavalink.unregister_hook(self._track_hook)
|
||||
self.bot.lavalink._event_hooks.clear()
|
||||
|
||||
async def __local_check(self, ctx):
|
||||
is_guild = ctx.guild is not None
|
||||
return is_guild
|
||||
|
||||
async def _track_hook(self, event):
|
||||
if isinstance(event, lavalink.Events.StatsUpdateEvent):
|
||||
return
|
||||
channel = self.bot.get_channel(event.player.fetch('channel'))
|
||||
if not channel:
|
||||
return
|
||||
|
||||
if isinstance(event, lavalink.Events.TrackStartEvent):
|
||||
embed = discord.Embed(title='Now playing:', description=event.track.title, color=0xEFD26C)
|
||||
thumbnail_url = await RoxUtils.ThumbNailer.identify(self, event.player.current.identifier, event.player.current.uri)
|
||||
if thumbnail_url:
|
||||
embed.set_thumbnail(url=thumbnail_url)
|
||||
await channel.send(embed=embed)
|
||||
elif isinstance(event, lavalink.Events.QueueEndEvent):
|
||||
await channel.send('Queue ended.')
|
||||
async def track_hook(self, event):
|
||||
if isinstance(event, lavalink.events.TrackEndEvent):
|
||||
pass # Send track ended message to channel.
|
||||
if isinstance(event, lavalink.events.QueueEndEvent):
|
||||
channel = self.bot.get_channel(event.player.fetch('channel'))
|
||||
await self.check_leave_voice(channel.guild)
|
||||
|
||||
@commands.command(name='play', aliases=['p'])
|
||||
async def _play(self, ctx, *, query: str):
|
||||
async def __before_invoke(self, ctx):
|
||||
guild_check = ctx.guild is not None
|
||||
# This is essentially the same as `@commands.guild_only()`
|
||||
# except it saves us repeating ourselves (and also a few lines).
|
||||
|
||||
if guild_check:
|
||||
await self.ensure_voice(ctx)
|
||||
# Ensure that the bot and command author share a mutual voicechannel.
|
||||
|
||||
return guild_check
|
||||
|
||||
async def connect_to(self, guild_id: int, channel_id: str):
|
||||
""" Connects to the given voicechannel ID. A channel_id of `None` means disconnect. """
|
||||
ws = self.bot._connection._get_websocket(guild_id)
|
||||
await ws.voice_state(str(guild_id), channel_id)
|
||||
|
||||
@commands.command(aliases=['p'])
|
||||
async def play(self, ctx, *, query: str):
|
||||
""" Searches and plays a song from a given query. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
@@ -78,7 +69,7 @@ class Music:
|
||||
if not url_rx.match(query):
|
||||
query = f'ytsearch:{query}'
|
||||
|
||||
results = await self.bot.lavalink.get_tracks(query)
|
||||
results = await player.node.get_tracks(query)
|
||||
|
||||
if not results or not results['tracks']:
|
||||
return await ctx.send('Nothing found!')
|
||||
@@ -97,6 +88,7 @@ class Music:
|
||||
else:
|
||||
track = results['tracks'][0]
|
||||
embed.title = 'Track Enqueued'
|
||||
# Rox
|
||||
thumbnail_url = await RoxUtils.ThumbNailer.identify(self, track['info']['identifier'], track['info']['uri'])
|
||||
if thumbnail_url:
|
||||
embed.set_thumbnail(url=thumbnail_url)
|
||||
@@ -108,51 +100,9 @@ class Music:
|
||||
if not player.is_playing:
|
||||
await player.play()
|
||||
|
||||
@commands.command(name='playnow', aliases=['pn'])
|
||||
async def _playnow(self, ctx, *, query: str):
|
||||
""" Plays immediately a song. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
if not player.queue and not player.is_playing:
|
||||
return await ctx.invoke(self._play, query=query)
|
||||
|
||||
query = query.strip('<>')
|
||||
|
||||
if not url_rx.match(query):
|
||||
query = f'ytsearch:{query}'
|
||||
|
||||
results = await self.bot.lavalink.get_tracks(query)
|
||||
|
||||
if not results or not results['tracks']:
|
||||
return await ctx.send('Nothing found!')
|
||||
|
||||
tracks = results['tracks']
|
||||
track = tracks.pop(0)
|
||||
|
||||
if results['loadType'] == 'PLAYLIST_LOADED':
|
||||
for _track in tracks:
|
||||
player.add(requester=ctx.author.id, track=_track)
|
||||
|
||||
await player.play_now(requester=ctx.author.id, track=track)
|
||||
|
||||
@commands.command(name='skipto', aliases=['st'])
|
||||
@commands.command()
|
||||
@checks.is_DJ()
|
||||
async def _skip_to(self, ctx, index: int):
|
||||
""" Plays the queue from a specific point. Disregards tracks before the index. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
if ctx.author not in player.listeners:
|
||||
return await ctx.send('You have to be listening to the bot')
|
||||
|
||||
if index < 1:
|
||||
return await ctx.send('Invalid specified index.')
|
||||
if len(player.queue) < index:
|
||||
return await ctx.send('This index exceeds the queue\'s length.')
|
||||
await player.skip_to(index - 1)
|
||||
|
||||
@commands.command(name='seek')
|
||||
@checks.is_DJ()
|
||||
async def _seek(self, ctx, *, time: str):
|
||||
async def seek(self, ctx, *, time: str):
|
||||
""" Seeks to a given position in a track. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
@@ -173,10 +123,10 @@ class Music:
|
||||
track_time = player.position + seconds
|
||||
await player.seek(track_time)
|
||||
|
||||
await ctx.send(f'Moved track to **{lavalink.Utils.format_time(track_time)}**')
|
||||
await ctx.send(f'Moved track to **{lavalink.utils.format_time(track_time)}**')
|
||||
|
||||
@commands.command(name='skip', aliases=['forceskip', 'fs'])
|
||||
async def _skip(self, ctx):
|
||||
@commands.command(aliases=['forceskip'])
|
||||
async def skip(self, ctx):
|
||||
""" Skips the current track. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
@@ -193,9 +143,24 @@ class Music:
|
||||
if skips != 0:
|
||||
await ctx.send(f'{skips} out of {math.ceil(total/2)} required haters have voted to skip.')
|
||||
|
||||
@commands.command(name='stop')
|
||||
@commands.command(name='skipto', aliases=['st','skip_to'])
|
||||
@checks.is_DJ()
|
||||
async def _stop(self, ctx):
|
||||
async def skip_to(self, ctx, pos: int):
|
||||
""" Plays the queue from a specific point. Disregards tracks before the pos. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
if ctx.author not in player.listeners:
|
||||
return await ctx.send('You have to be listening to the bot')
|
||||
|
||||
if pos < 1:
|
||||
return await ctx.send('Invalid specified position.')
|
||||
if len(player.queue) < pos:
|
||||
return await ctx.send('The position exceeds the queue\'s length.')
|
||||
await player.skip(pos - 1)
|
||||
|
||||
@commands.command()
|
||||
@checks.is_DJ()
|
||||
async def stop(self, ctx):
|
||||
""" Stops the player and clears its queue. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
@@ -209,36 +174,34 @@ class Music:
|
||||
await player.stop()
|
||||
await ctx.send('⏹ | Stopped.')
|
||||
|
||||
@commands.command(name='now', aliases=['np', 'n', 'playing'])
|
||||
async def _now(self, ctx):
|
||||
@commands.command(aliases=['np', 'n', 'playing'])
|
||||
async def now(self, ctx):
|
||||
""" Shows some stats about the currently playing song. """
|
||||
|
||||
#def scrubber(song, length):
|
||||
|
||||
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
song = 'Nothing'
|
||||
|
||||
if player.current:
|
||||
position = lavalink.Utils.format_time(player.position)
|
||||
if player.current.stream:
|
||||
duration = '🔴 LIVE'
|
||||
else:
|
||||
duration = lavalink.Utils.format_time(player.current.duration)
|
||||
song = f'**[{player.current.title}]({player.current.uri})**\n({position}/{duration})'
|
||||
if not player.current:
|
||||
return await ctx.send('Nothing playing.')
|
||||
|
||||
embed = discord.Embed(color=0xEFD26C, title='Now Playing', description=song)
|
||||
position = lavalink.utils.format_time(player.position)
|
||||
if player.current.stream:
|
||||
duration = '🔴 LIVE'
|
||||
else:
|
||||
duration = lavalink.utils.format_time(player.current.duration)
|
||||
song = f'**[{player.current.title}]({player.current.uri})**\n({position}/{duration})'
|
||||
|
||||
embed = discord.Embed(color=discord.Color.blurple(),
|
||||
title='Now Playing', description=song)
|
||||
|
||||
thumbnail_url = await RoxUtils.ThumbNailer.identify(self, player.current.identifier, player.current.uri)
|
||||
if thumbnail_url:
|
||||
embed.set_thumbnail(url=thumbnail_url)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(name='queue', aliases=['q'])
|
||||
async def _queue(self, ctx, user: discord.Member=None):
|
||||
""" Shows the global queue or another users queue. """
|
||||
@commands.command(aliases=['q'])
|
||||
async def queue(self, ctx, user: discord.Member=None):
|
||||
""" Shows the player's queue. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
if player.queue.empty:
|
||||
return await ctx.send('The queue is empty')
|
||||
|
||||
@@ -267,8 +230,9 @@ class Music:
|
||||
scroller = QueueScroller(ctx, user_queue, lines_per_page=10, user_name=ctx.author.name)
|
||||
await scroller.start_scrolling()
|
||||
|
||||
@commands.command(name='pause', aliases=['resume'])
|
||||
async def _pause(self, ctx):
|
||||
@commands.command(aliases=['resume'])
|
||||
@checks.is_DJ()
|
||||
async def pause(self, ctx):
|
||||
""" Pauses/Resumes the current track. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
@@ -282,9 +246,9 @@ class Music:
|
||||
await player.set_pause(True)
|
||||
await ctx.send('⏯ | Paused')
|
||||
|
||||
@commands.command(name='volume', aliases=['vol'])
|
||||
@commands.command(aliases=['vol'])
|
||||
@checks.is_DJ()
|
||||
async def _volume(self, ctx, volume: int = None):
|
||||
async def volume(self, ctx, volume: int = None):
|
||||
""" Changes the player's volume. Must be between 0 and 1000. Error Handling for that is done by Lavalink. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
@@ -294,22 +258,6 @@ class Music:
|
||||
await player.set_volume(volume)
|
||||
await ctx.send(f'🔈 | Set to {player.volume}%')
|
||||
|
||||
@commands.command(name='boost', aliases=['boo'])
|
||||
async def _boost(self, ctx, boost: bool=None):
|
||||
""" Changes the player's volume. Must be between 0 and 1000. Error Handling for that is done by Lavalink. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
if boost is None:
|
||||
if player.boosted:
|
||||
await ctx.send('Bass boost is on')
|
||||
else:
|
||||
await ctx.send('Bass boost is off')
|
||||
else:
|
||||
await player.bassboost(boost)
|
||||
if boost:
|
||||
await ctx.send('Bass boost turned on')
|
||||
else:
|
||||
await ctx.send('Bass boost turned off')
|
||||
|
||||
@commands.command(name='shuffle')
|
||||
async def _shuffle(self, ctx):
|
||||
""" Shuffles your queue. """
|
||||
@@ -340,9 +288,9 @@ class Music:
|
||||
|
||||
await ctx.send(f'{moved.title} moved from position {from_pos} to {to_pos} in your queue')
|
||||
|
||||
@commands.command(name='remove')
|
||||
async def _remove(self, ctx, pos: int):
|
||||
""" Removes the song at pos from your queue. """
|
||||
@commands.command()
|
||||
async def remove(self, ctx, pos: int):
|
||||
""" Removes an item from the player's queue with the given pos. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
if player.queue.empty:
|
||||
@@ -390,89 +338,79 @@ class Music:
|
||||
removed = player.remove_user_track(user.id, pos - 1)
|
||||
await ctx.send(f'**{removed.title}** queued by {user.name} removed.')
|
||||
|
||||
|
||||
@commands.command(name='find')
|
||||
async def _find(self, ctx, *, query):
|
||||
@commands.command()
|
||||
async def find(self, ctx, *, query):
|
||||
""" Lists the first 10 search results from a given query. """
|
||||
await ctx.trigger_typing()
|
||||
not_reacted = True
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
# Rox
|
||||
limit_results = 5 # Var to set max results
|
||||
react_emoji = {1: "\u0030\u20E3", 2: "\u0031\u20E3", 3: "\u0032\u20E3", 4: "\u0033\u20E3", 5: "\u0034\u20E3",
|
||||
6: "\u0035\u20E3", 7: "\u0036\u20E3", 8: "\u0037\u20E3", 9: "\u0038\u20E3", 10: "\u0039\u20E3"}
|
||||
# Rox
|
||||
|
||||
if not query.startswith('ytsearch:') and not query.startswith('scsearch:'):
|
||||
query = 'ytsearch:' + query
|
||||
|
||||
results = await self.bot.lavalink.get_tracks(query)
|
||||
results = await player.node.get_tracks(query)
|
||||
|
||||
if not results or not results['tracks']:
|
||||
return await ctx.send('Nothing found')
|
||||
|
||||
tracks = results['tracks'][:10] # First 10 results
|
||||
tracks = results['tracks'][:limit_results] # First 10 results
|
||||
|
||||
o = ''
|
||||
for index, track in enumerate(tracks, start=1):
|
||||
track_title = track["info"]["title"]
|
||||
track_uri = track["info"]["uri"]
|
||||
track_title = track['info']['title']
|
||||
track_uri = track['info']['uri']
|
||||
o += f'`{index}.` [{track_title}]({track_uri})\n'
|
||||
|
||||
o += f'{react_emoji[index]} [{track_title}]({track_uri})\n'
|
||||
embed = discord.Embed(color=0xEFD26C, description=o)
|
||||
start_msg = await ctx.send(embed=embed) # Sending a message, so we can delete it later
|
||||
|
||||
embed_start = discord.Embed(color=0xEFD26C, description=o)
|
||||
start_msg = await ctx.send(embed=embed_start)
|
||||
await ctx.trigger_typing()
|
||||
for num_index in range(min(len(tracks), 10)):
|
||||
# Adding reactions to messages, doing this outside previous loop to give the user some
|
||||
# "thinkingtime while reacting
|
||||
for num_index in range(min(len(tracks), limit_results)):
|
||||
index = num_index + 1
|
||||
await start_msg.add_reaction(react_emoji[index])
|
||||
await start_msg.add_reaction("\u274C")
|
||||
# React does shit
|
||||
time_start = time.time()
|
||||
|
||||
while not_reacted:
|
||||
await ctx.trigger_typing()
|
||||
embed = discord.Embed(color=0xEFD26C)
|
||||
# Loop to detect reactions on the message
|
||||
time_start = time.time()
|
||||
while True:
|
||||
timer = time.time() - time_start
|
||||
msg_id = await ctx.get_message(start_msg.id)
|
||||
if int(timer) >= 10:
|
||||
if int(timer) >= 10: # Checks timer. ends loop after 10 seconds
|
||||
await start_msg.clear_reactions()
|
||||
embed.title = "Sorry"
|
||||
embed.description = "Timer expired"
|
||||
embed = discord.Embed(color=0xEFD26C, title="Sorry", description="Timer expired")
|
||||
await start_msg.edit(embed=embed)
|
||||
ping = await ctx.send(content=f"{ctx.author.mention}")
|
||||
await asyncio.sleep(10)
|
||||
await ctx.message.delete()
|
||||
await ping.delete()
|
||||
await msg_id.delete()
|
||||
break
|
||||
return
|
||||
|
||||
for react in msg_id.reactions:
|
||||
async for user in react.users():
|
||||
if user is ctx.author:
|
||||
if react.emoji[:-1].isdigit():
|
||||
not_reacted = False
|
||||
track_num = int(react.emoji[:-1])
|
||||
track_ = tracks[track_num]
|
||||
await self.send_to_play(ctx, track_)
|
||||
embed.title = "Song sent to queue"
|
||||
thumb_url = await RoxUtils.ThumbNailer.identify(self, track_['info']['identifier'],track_['info']['uri'])
|
||||
track_ = tracks[int(react.emoji[:-1])]
|
||||
info = track_['info']
|
||||
embed = discord.Embed(color=0xEFD26C, title="Song sent to queue")
|
||||
thumb_url = await RoxUtils.ThumbNailer.identify(self, info['identifier'], info['uri'])
|
||||
if thumb_url:
|
||||
embed.set_thumbnail(url=thumb_url)
|
||||
embed.description = f'[{track_["info"]["title"]}]({track_["info"]["uri"]})'
|
||||
embed.description = f'[{info["title"]}]({info["uri"]})'
|
||||
await start_msg.edit(embed=embed)
|
||||
await start_msg.clear_reactions()
|
||||
break
|
||||
# await self.ensure_voice(ctx, True)
|
||||
player.add(requester=ctx.author.id, track=track_)
|
||||
if not player.is_playing:
|
||||
await player.play()
|
||||
return
|
||||
if react.emoji == "\u274C":
|
||||
not_reacted = False
|
||||
await start_msg.clear_reactions()
|
||||
embed.title = "Sorry"
|
||||
embed.description = "Search cancelled by user"
|
||||
embed = discord.Embed(color=0xEFD26C, title="Sorry", description="Search cancelled by user")
|
||||
await start_msg.edit(embed=embed)
|
||||
ping = await ctx.send(content=f"{user.mention}")
|
||||
await asyncio.sleep(10)
|
||||
await ctx.message.delete()
|
||||
await ping.delete()
|
||||
await msg_id.delete()
|
||||
break
|
||||
return
|
||||
|
||||
@commands.command(name='disconnect', aliases=['dc'])
|
||||
async def _disconnect(self, ctx):
|
||||
@commands.command(aliases=['dc'])
|
||||
async def disconnect(self, ctx):
|
||||
""" Disconnects the player from the voice channel and clears its queue. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
@@ -483,40 +421,44 @@ class Music:
|
||||
return await ctx.send('You\'re not in my voicechannel!')
|
||||
|
||||
player.queue.clear()
|
||||
await player.disconnect()
|
||||
await player.stop()
|
||||
await self.connect_to(ctx.guild.id, None)
|
||||
await ctx.send('*⃣ | Disconnected.')
|
||||
|
||||
async def send_to_play(self, ctx, track):
|
||||
await self.ensure_voice_real(ctx)
|
||||
@commands.command(name='boost', aliases=['boo'])
|
||||
async def _boost(self, ctx, boost: bool=None):
|
||||
""" Changes the player's volume. Must be between 0 and 1000. Error Handling for that is done by Lavalink. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
player.add(requester=ctx.author.id, track=track)
|
||||
if not player.is_playing:
|
||||
await player.play()
|
||||
|
||||
if boost is not None:
|
||||
await player.bassboost(boost)
|
||||
|
||||
@_playnow.before_invoke
|
||||
@_play.before_invoke
|
||||
async def ensure_voice(self, ctx):
|
||||
await self.ensure_voice_real(ctx)
|
||||
if player.boosted:
|
||||
await ctx.send('Bass boost is on')
|
||||
else:
|
||||
await ctx.send('Bass boost is off')
|
||||
|
||||
async def ensure_voice_real(self, ctx):
|
||||
""" A few checks to make sure the bot can join a voice channel. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
async def ensure_voice(self, ctx, do_connect: Optional[bool] = None):
|
||||
""" This check ensures that the bot and command author are in the same voicechannel. """
|
||||
player = self.bot.lavalink.players.create(ctx.guild.id, endpoint=ctx.guild.region.value)
|
||||
# Create returns a player if one exists, otherwise creates.
|
||||
|
||||
should_connect = ctx.command.name in ('play', "find") # Add commands that require joining voice to work.
|
||||
|
||||
if not ctx.author.voice or not ctx.author.voice.channel:
|
||||
raise commands.CommandInvokeError('Join a voicechannel first.')
|
||||
|
||||
if not player.is_connected:
|
||||
if not ctx.author.voice or not ctx.author.voice.channel:
|
||||
await ctx.send('You aren\'t connected to any voice channel.')
|
||||
raise commands.CommandInvokeError(
|
||||
'Author not connected to voice channel.')
|
||||
if not should_connect:
|
||||
raise commands.CommandInvokeError('Not connected.')
|
||||
|
||||
permissions = ctx.author.voice.channel.permissions_for(ctx.me)
|
||||
|
||||
if not permissions.connect or not permissions.speak:
|
||||
await ctx.send('Missing permissions `CONNECT` and/or `SPEAK`.')
|
||||
raise commands.CommandInvokeError(
|
||||
'Bot has no permissions CONNECT and/or SPEAK')
|
||||
if not permissions.connect or not permissions.speak: # Check user limit too?
|
||||
raise commands.CommandInvokeError('I need the `CONNECT` and `SPEAK` permissions.')
|
||||
|
||||
player.store('channel', ctx.channel.id)
|
||||
await player.connect(ctx.author.voice.channel.id)
|
||||
await self.connect_to(ctx.guild.id, str(ctx.author.voice.channel.id))
|
||||
|
||||
# Get listeners
|
||||
voice_channel = ctx.author.voice.channel
|
||||
@@ -527,23 +469,23 @@ class Music:
|
||||
if not member.bot:
|
||||
player.update_listeners(member, member.voice)
|
||||
|
||||
|
||||
else:
|
||||
if player.connected_channel.id != ctx.author.voice.channel.id:
|
||||
return await ctx.send('Join my voice channel!')
|
||||
|
||||
if int(player.channel_id) != ctx.author.voice.channel.id:
|
||||
raise commands.CommandInvokeError('You need to be in my voicechannel.')
|
||||
|
||||
async def on_voice_state_update(self, member, before, after):
|
||||
if not member.bot:
|
||||
player = self.bot.lavalink.players.get(member.guild.id)
|
||||
player.update_listeners(member, after)
|
||||
await self.check_leave_voice(member.guild)
|
||||
if player is not None:
|
||||
player.update_listeners(member, after)
|
||||
await self.check_leave_voice(member.guild)
|
||||
|
||||
async def check_leave_voice(self, guild):
|
||||
player = self.bot.lavalink.players.get(guild.id)
|
||||
if len(player.listeners) == 0 and player.is_connected:
|
||||
if player.queue.empty and player.current is None:
|
||||
await player.disconnect()
|
||||
await player.stop()
|
||||
await self.connect_to(guild.id, None)
|
||||
|
||||
|
||||
def setup(bot):
|
||||
|
||||
@@ -1,7 +1,130 @@
|
||||
import time
|
||||
import lavalink
|
||||
|
||||
from collections import OrderedDict
|
||||
from itertools import cycle, islice, chain
|
||||
from random import shuffle
|
||||
from lavalink import AudioTrack
|
||||
|
||||
from lavalink.events import (TrackStartEvent, TrackStuckEvent,
|
||||
TrackExceptionEvent, TrackEndEvent,
|
||||
QueueEndEvent, PlayerUpdateEvent,
|
||||
NodeChangedEvent)
|
||||
from lavalink import Node, DefaultPlayer, AudioTrack
|
||||
|
||||
|
||||
class MixPlayer(DefaultPlayer):
|
||||
def __init__(self, guild_id: int, node: Node):
|
||||
super().__init__(guild_id, node)
|
||||
|
||||
self.queue = MixQueue()
|
||||
|
||||
self.listeners = set()
|
||||
self.skip_voters = set()
|
||||
self.boosted = False
|
||||
|
||||
def add(self, requester: int, track: dict, pos: int=None):
|
||||
""" Adds a track to the queue. """
|
||||
return self.queue.add_track(requester,
|
||||
AudioTrack.build(track, requester),
|
||||
pos)
|
||||
|
||||
def add_next(self, requester: int, track: dict):
|
||||
""" Adds a track to beginning of the queue """
|
||||
self.queue.add_next_track(AudioTrack.build(track, requester))
|
||||
|
||||
def move_user_track(self, requester: int, initial: int, final: int):
|
||||
""" Moves a track in a users queue"""
|
||||
return self.queue.move_user_track(requester, initial, final)
|
||||
|
||||
def remove_user_track(self, requester: int, pos: int):
|
||||
""" Removes the song at <pos> from the queue of requester """
|
||||
return self.queue.remove_user_track(requester, pos)
|
||||
|
||||
def remove_global_track(self, pos: int):
|
||||
""" Removes the song at <pos> in the global queue """
|
||||
return self.queue.remove_global_track(pos)
|
||||
|
||||
def shuffle_user_queue(self, requester: int):
|
||||
""" Randomly reorders the queue of requester """
|
||||
self.queue.shuffle_user_queue(requester)
|
||||
|
||||
def user_queue(self, user: int, dual: bool=False):
|
||||
return self.queue.get_user_queue(user, dual)
|
||||
|
||||
def global_queue(self):
|
||||
return self.queue.get_queue()
|
||||
|
||||
def queue_duration(self, include_current: bool=True):
|
||||
duration = 0
|
||||
for track in self.queue:
|
||||
duration += track.duration
|
||||
remaining = self.current.duration - self.position
|
||||
if include_current:
|
||||
return lavalink.Utils.format_time(duration + remaining)
|
||||
return lavalink.Utils.format_time(duration)
|
||||
|
||||
async def play(self, track: AudioTrack=None, start_time: int=0):
|
||||
|
||||
self.current = None
|
||||
self.last_update = 0
|
||||
self.last_position = 0
|
||||
self.position_timestamp = 0
|
||||
self.paused = False
|
||||
|
||||
if not track:
|
||||
if self.queue.empty:
|
||||
self.boosted = False
|
||||
await self.stop()
|
||||
await self.node._dispatch_event(QueueEndEvent(self))
|
||||
return
|
||||
else:
|
||||
track = self.queue.pop_first()
|
||||
|
||||
self.current = track
|
||||
await self.node._send(op='play', guildId=self.guild_id,
|
||||
track=track.track, startTime=start_time)
|
||||
await self.node._dispatch_event(TrackStartEvent(self, track))
|
||||
|
||||
async def skip(self, pos: int=0):
|
||||
""" Plays the next track in the queue, if any. """
|
||||
for i in range(pos):
|
||||
_ = self.queue.pop_first()
|
||||
self.skip_voters.clear()
|
||||
await self.play()
|
||||
|
||||
def update_listeners(self, member, voice_state):
|
||||
if self.is_connected:
|
||||
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)
|
||||
else:
|
||||
if voice_state.deaf or voice_state.self_deaf:
|
||||
self.listeners.discard(member)
|
||||
self.skip_voters.discard(member)
|
||||
else:
|
||||
self.listeners.add(member)
|
||||
|
||||
def add_skipper(self, member):
|
||||
if member in self.listeners:
|
||||
self.skip_voters.add(member)
|
||||
|
||||
async def bassboost(self, boost: bool=False):
|
||||
if boost:
|
||||
gains = [
|
||||
(0, 0.15),
|
||||
(1, 0.15),
|
||||
(2, 0.25),
|
||||
(3, 0.15),
|
||||
(4, -0.15),
|
||||
(5, -0.1),
|
||||
(6, -0.05)
|
||||
]
|
||||
self.boosted = True
|
||||
await self.set_gains(*gains)
|
||||
else:
|
||||
self.boosted = False
|
||||
await self.reset_equalizer()
|
||||
|
||||
|
||||
def roundrobin(*iterables):
|
||||
@@ -35,7 +158,7 @@ class MixQueue:
|
||||
|
||||
def __iter__(self):
|
||||
global_queue = roundrobin(*[x for x in self.queues.values()])
|
||||
priority_queue = self.priority_queue
|
||||
priority_queue = self.priority_queue
|
||||
out = chain(priority_queue, global_queue)
|
||||
return out
|
||||
|
||||
@@ -167,4 +290,3 @@ class MixQueue:
|
||||
@property
|
||||
def empty(self):
|
||||
return len(self) == 0
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
from .mixqueue import MixQueue
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from random import randrange
|
||||
|
||||
import lavalink
|
||||
from lavalink import BasePlayer
|
||||
from lavalink.AudioTrack import AudioTrack
|
||||
from lavalink.Events import QueueEndEvent, TrackExceptionEvent, TrackEndEvent, TrackStartEvent, TrackStuckEvent
|
||||
|
||||
|
||||
'''
|
||||
|
||||
Version of lavalink.py's DefaultPlayer with a custom queue(mixqueue).
|
||||
|
||||
'''
|
||||
|
||||
class MixPlayer(BasePlayer):
|
||||
def __init__(self, lavalink, guild_id: int):
|
||||
super().__init__(lavalink, guild_id)
|
||||
|
||||
self._user_data = {}
|
||||
self.channel_id = None
|
||||
|
||||
self.paused = False
|
||||
self.position = 0
|
||||
self.position_timestamp = 0
|
||||
self.volume = 100
|
||||
|
||||
self.queue = MixQueue()
|
||||
self.current = None
|
||||
|
||||
self.listeners = set()
|
||||
self.skip_voters = set()
|
||||
self.boosted = False
|
||||
|
||||
@property
|
||||
def is_playing(self):
|
||||
""" Returns the player's track state. """
|
||||
return self.connected_channel is not None and self.current is not None
|
||||
|
||||
@property
|
||||
def is_connected(self):
|
||||
""" Returns the player's connection state. """
|
||||
return self.connected_channel is not None
|
||||
|
||||
@property
|
||||
def connected_channel(self):
|
||||
""" Returns the voice channel the player is connected to. """
|
||||
if not self.channel_id:
|
||||
return None
|
||||
|
||||
return self._lavalink.bot.get_channel(int(self.channel_id))
|
||||
|
||||
async def connect(self, channel_id: int):
|
||||
""" Connects to a voice channel. """
|
||||
ws = self._lavalink.bot._connection._get_websocket(int(self.guild_id))
|
||||
await ws.voice_state(self.guild_id, str(channel_id))
|
||||
|
||||
async def disconnect(self):
|
||||
""" Disconnects from the voice channel, if any. """
|
||||
if not self.is_connected:
|
||||
return
|
||||
|
||||
await self.stop()
|
||||
|
||||
ws = self._lavalink.bot._connection._get_websocket(int(self.guild_id))
|
||||
await ws.voice_state(self.guild_id, None)
|
||||
|
||||
def store(self, key: object, value: object):
|
||||
""" Stores custom user data. """
|
||||
self._user_data.update({key: value})
|
||||
|
||||
def fetch(self, key: object, default=None):
|
||||
""" Retrieves the related value from the stored user data. """
|
||||
return self._user_data.get(key, default)
|
||||
|
||||
def delete(self, key: object):
|
||||
""" Removes an item from the the stored user data. """
|
||||
try:
|
||||
del self._user_data[key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def add(self, requester: int, track: dict):
|
||||
""" Adds a track to the queue. """
|
||||
return self.queue.add_track(requester, AudioTrack().build(track, requester))
|
||||
|
||||
def add_next(self, requester: int, track: dict):
|
||||
""" Adds a track to beginning of the queue """
|
||||
self.queue.add_next_track(AudioTrack().build(track, requester))
|
||||
|
||||
def add_at(self, index: int, requester: int, track: dict):
|
||||
""" Adds a track at a specific index in the queue. """
|
||||
return self.queue.add_track(requester, AudioTrack().build(track, requester), index)
|
||||
|
||||
def move_user_track(self, requester: int, initial: int, final: int):
|
||||
""" Moves a track in a users queue"""
|
||||
return self.queue.move_user_track(requester, initial, final)
|
||||
|
||||
def remove_user_track(self, requester: int, pos: int):
|
||||
""" Removes the song at <pos> from the queue of requester """
|
||||
return self.queue.remove_user_track(requester, pos)
|
||||
|
||||
def remove_global_track(self, pos: int):
|
||||
""" Removes the song at <pos> in the global queue """
|
||||
return self.queue.remove_global_track(pos)
|
||||
|
||||
def shuffle_user_queue(self, requester: int):
|
||||
""" Randomly reorders the queue of requester """
|
||||
self.queue.shuffle_user_queue(requester)
|
||||
|
||||
def user_queue(self, user: int, dual: bool=False):
|
||||
return self.queue.get_user_queue(user, dual)
|
||||
|
||||
def global_queue(self):
|
||||
return self.queue.get_queue()
|
||||
|
||||
def queue_duration(self, include_current: bool=True):
|
||||
duration = 0
|
||||
for track in self.queue:
|
||||
duration += track.duration
|
||||
remaining = self.current.duration - self.position
|
||||
if include_current:
|
||||
return lavalink.Utils.format_time(duration + remaining)
|
||||
return lavalink.Utils.format_time(duration)
|
||||
|
||||
def current_pos(self):
|
||||
return lavalink.Utils.format_time(self.position)
|
||||
|
||||
async def play(self, track_index: int = 0):
|
||||
""" Plays the first track in the queue, if any or plays a track from the specified index in the queue. """
|
||||
|
||||
self.current = None
|
||||
self.position = 0
|
||||
self.paused = False
|
||||
|
||||
if self.queue.empty:
|
||||
await self.stop()
|
||||
await self._lavalink.dispatch_event(QueueEndEvent(self))
|
||||
else:
|
||||
track = self.queue.pop_first()
|
||||
|
||||
self.current = track
|
||||
await self._lavalink.ws.send(op='play', guildId=self.guild_id, track=track.track)
|
||||
await self._lavalink.dispatch_event(TrackStartEvent(self, track))
|
||||
|
||||
async def play_now(self, requester: int, track: dict):
|
||||
""" Add track and play it. """
|
||||
self.add_next(requester, track)
|
||||
await self.play()
|
||||
|
||||
async def skip_to(self, index: int):
|
||||
""" Play the queue from a specific point. Disregards tracks before the index. """
|
||||
for i in range(index):
|
||||
_ = self.queue.pop_first()
|
||||
await self.play()
|
||||
|
||||
async def stop(self):
|
||||
""" Stops the player, if playing. """
|
||||
await self._lavalink.ws.send(op='stop', guildId=self.guild_id)
|
||||
self.current = None
|
||||
|
||||
async def skip(self):
|
||||
""" Plays the next track in the queue, if any. """
|
||||
self.skip_voters.clear()
|
||||
await self.play()
|
||||
|
||||
async def set_pause(self, pause: bool):
|
||||
""" Sets the player's paused state. """
|
||||
await self._lavalink.ws.send(op='pause', guildId=self.guild_id, pause=pause)
|
||||
self.paused = pause
|
||||
|
||||
async def set_volume(self, vol: int):
|
||||
""" Sets the player's volume (150% or 1000% limit imposed by lavalink depending on the version). """
|
||||
if self._lavalink._server_version <= 2:
|
||||
self.volume = max(min(vol, 150), 0)
|
||||
else:
|
||||
self.volume = max(min(vol, 1000), 0)
|
||||
await self._lavalink.ws.send(op='volume', guildId=self.guild_id, volume=self.volume)
|
||||
|
||||
async def seek(self, pos: int):
|
||||
""" Seeks to a given position in the track. """
|
||||
await self._lavalink.ws.send(op='seek', guildId=self.guild_id, position=pos)
|
||||
|
||||
async def handle_event(self, event):
|
||||
""" Makes the player play the next song from the queue if a song has finished or an issue occurred. """
|
||||
if isinstance(event, (TrackStuckEvent, TrackExceptionEvent)) or \
|
||||
isinstance(event, TrackEndEvent) and event.reason == 'FINISHED':
|
||||
await self.play()
|
||||
|
||||
def update_listeners(self, member, voice_state):
|
||||
if self.is_connected:
|
||||
vc = self.connected_channel
|
||||
if voice_state.channel is None or voice_state.channel != vc:
|
||||
self.listeners.discard(member)
|
||||
self.skip_voters.discard(member)
|
||||
else:
|
||||
if voice_state.deaf or voice_state.self_deaf:
|
||||
self.listeners.discard(member)
|
||||
self.skip_voters.discard(member)
|
||||
else:
|
||||
self.listeners.add(member)
|
||||
|
||||
|
||||
def add_skipper(self, member):
|
||||
if member in self.listeners:
|
||||
self.skip_voters.add(member)
|
||||
|
||||
|
||||
async def bassboost(self, boost: bool=False):
|
||||
if boost:
|
||||
boostval = 1
|
||||
else:
|
||||
boostval = 0
|
||||
|
||||
bands = [
|
||||
{"band": 0, "gain": boostval * 0.15}, # 25Hz
|
||||
{"band": 1, "gain": boostval * 0.15}, # 40Hz
|
||||
{"band": 2, "gain": boostval * 0.25}, # 63Hz
|
||||
{"band": 3, "gain": boostval * 0.15}, #100Hz
|
||||
{"band": 4, "gain": boostval * -0.15}, #160Hz
|
||||
{"band": 5, "gain": boostval * -0.1}, #250Hz
|
||||
{"band": 6, "gain": boostval * -0.05}, #400Hz
|
||||
]
|
||||
|
||||
if boost:
|
||||
await self._lavalink.ws.send(op="equalizer", guildId=self.guild_id, bands=bands)
|
||||
self.boosted = True
|
||||
else:
|
||||
await self._lavalink.ws.send(op="equalizer", guildId=self.guild_id, bands=bands)
|
||||
self.boosted = False
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
host = 'localhost'
|
||||
rest_port = 2333
|
||||
ws_port = 2333
|
||||
port = 2333
|
||||
password = 'youshallnotpass'
|
||||
region = 'eu'
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
git+https://github.com/Rapptz/discord.py@rewrite
|
||||
git+https://github.com/Devoxin/Lavalink.py@dev
|
||||
asyncio
|
||||
lavalink
|
||||
bs4
|
||||
aiohttp
|
||||
aiohttp<3.5.0,>=3.3.0
|
||||
Reference in new issue
Block a user