mirror of
https://github.com/UiO-Gaming/ShiteMusicBot.git
synced 2026-11-06 06:14:31 +01:00
442 lines
16 KiB
Python
442 lines
16 KiB
Python
"""
|
|
Commands that interact with just discord and lavalink and only present the basic commands needed for a music bot
|
|
"""
|
|
|
|
# Discord Packages
|
|
import discord
|
|
from discord.ext import commands
|
|
|
|
import asyncio
|
|
import re
|
|
|
|
from ..utils import checks, timeformatter
|
|
from ..utils.paginator import QueuePaginator, Scroller
|
|
from ..utils.selector import Selector
|
|
from .decorators import require_playing, require_queue, require_voice_connection, voteable
|
|
|
|
time_rx = re.compile('[0-9]+')
|
|
url_rx = re.compile('https?:\\/\\/(?:www\\.)?.+')
|
|
|
|
|
|
@commands.command(name='play')
|
|
@require_voice_connection(should_connect=True)
|
|
async def _play(self, ctx, *, query: str):
|
|
""" Searches and plays a song from a given query. """
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
query = query.strip('<>')
|
|
|
|
if not url_rx.match(query):
|
|
query = f'ytsearch:{query}'
|
|
|
|
results = await player.node.get_tracks(query)
|
|
|
|
if not results or not results['tracks']:
|
|
return await ctx.send(ctx.localizer.format_str("{nothing_found}"))
|
|
|
|
embed = discord.Embed(color=ctx.me.color)
|
|
|
|
if results['loadType'] == 'PLAYLIST_LOADED':
|
|
numtracks = 0
|
|
for track in results['tracks']:
|
|
_, track_added = await self.enqueue(ctx, track, embed, silent=True)
|
|
if track_added:
|
|
numtracks += 1
|
|
|
|
embed.title = '{playlist_enqued}'
|
|
embed.description = f'{results["playlistInfo"]["name"]} - {numtracks} {{tracks}}'
|
|
else:
|
|
track = results['tracks'][0]
|
|
await self.enqueue(ctx, track, embed)
|
|
|
|
embed = ctx.localizer.format_embed(embed)
|
|
await ctx.send(embed=embed)
|
|
|
|
if not player.is_playing:
|
|
await player.play()
|
|
|
|
|
|
@commands.command(name='seek')
|
|
@checks.dj_or(alone=True, track_requester=True)
|
|
@require_voice_connection()
|
|
@require_playing(require_user_listening=True)
|
|
async def _seek(self, ctx, *, time: str):
|
|
""" Seeks to a given position in a track. """
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
if seconds := time_rx.search(time):
|
|
# Convert to milliseconds, include sign
|
|
milliseconds = int(seconds.group())*1000 * (-1 if time.startswith('-1') else 1)
|
|
|
|
track_time = player.position + milliseconds
|
|
await player.seek(track_time)
|
|
msg = ctx.localizer.format_str("{seek.track_moved}", _position=timeformatter.format_ms(track_time))
|
|
await ctx.send(msg)
|
|
else:
|
|
await ctx.send(ctx.localizer.format_str("{seek.missing_amount}"))
|
|
|
|
|
|
@commands.command(name='skip')
|
|
@require_voice_connection()
|
|
@require_playing()
|
|
@voteable(requester_override=True, react_to_vote=True)
|
|
async def _skip(self, ctx):
|
|
""" Skips the current track. """
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
|
|
await player.skip()
|
|
if player.current:
|
|
embed = self.get_current_song_embed(ctx)
|
|
await ctx.send(ctx.localizer.format_str("{skip.skipped}"), embed=embed)
|
|
else:
|
|
await ctx.send(ctx.localizer.format_str("{skip.skipped}"))
|
|
|
|
|
|
@commands.command(name='skipto')
|
|
@checks.dj_or(alone=True)
|
|
@require_playing(require_user_listening=True)
|
|
async def _skip_to(self, ctx, pos: int = 1):
|
|
""" Plays the queue from a specific point. Disregards tracks before the pos. """
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
|
|
# TODO: Do all queue out of range messages the same way
|
|
if pos < 1:
|
|
return await ctx.send(ctx.localizer.format_str("{skip_to.invalid_pos}"))
|
|
if len(player.queue) < pos:
|
|
return await ctx.send(ctx.localizer.format_str("{skip_to.exceeds_queue}"))
|
|
|
|
await player.skip(pos - 1)
|
|
|
|
msg = ctx.localizer.format_str("{skip_to.skipped_to}", _title=player.current.title, _pos=pos)
|
|
await ctx.send(msg)
|
|
|
|
|
|
@commands.command(name='stop')
|
|
@require_voice_connection()
|
|
@require_playing(require_user_listening=True)
|
|
@voteable(DJ_override=True, react_to_vote=True)
|
|
async def _stop(self, ctx):
|
|
""" Stops the player and clears its queue. """
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
player.queue.clear()
|
|
await player.stop()
|
|
|
|
embed = discord.Embed(color=ctx.me.color, title='{stop}')
|
|
await ctx.send(embed=ctx.localizer.format_embed(embed))
|
|
|
|
|
|
@commands.command(name='now')
|
|
@require_playing()
|
|
async def _now(self, ctx):
|
|
embed = self.get_current_song_embed(ctx, include_time=True)
|
|
embed = ctx.localizer.format_embed(embed)
|
|
await ctx.send(embed=embed)
|
|
|
|
|
|
@commands.command(name='queue')
|
|
@require_queue(require_member_queue=True)
|
|
async def _queue(self, ctx, *, member: discord.Member = None):
|
|
""" Shows the player's queue. """
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
pagified_queue = QueuePaginator(ctx.localizer, player, color=ctx.me.color, member=member)
|
|
scroller = Scroller(ctx, pagified_queue)
|
|
await scroller.start_scrolling()
|
|
|
|
|
|
@commands.command(name='myqueue')
|
|
@require_queue(require_author_queue=True)
|
|
async def _myqueue(self, ctx):
|
|
""" Shows your queue. """
|
|
await self._queue(ctx, member=ctx.author)
|
|
|
|
|
|
@commands.command(name='pause')
|
|
@checks.dj_or(alone=True)
|
|
@require_voice_connection()
|
|
@require_playing()
|
|
async def _pause(self, ctx):
|
|
""" Pauses/Resumes the current track. """
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
await player.set_pause(not player.paused)
|
|
if player.paused:
|
|
await ctx.send(ctx.localizer.format_str("{resume.paused}"))
|
|
else:
|
|
await ctx.send(ctx.localizer.format_str("{resume.resumed}"))
|
|
|
|
|
|
@commands.command(name='shuffle')
|
|
@require_voice_connection()
|
|
@require_queue(require_author_queue=True)
|
|
async def _shuffle(self, ctx):
|
|
""" Shuffles your queue. """
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
player.shuffle_user_queue(ctx.author.id)
|
|
await ctx.send(ctx.localizer.format_str("{shuffle}"))
|
|
|
|
|
|
@commands.command(name='move')
|
|
@require_voice_connection()
|
|
@require_queue(require_author_queue=True)
|
|
async def _move(self, ctx):
|
|
""" Moves a song in your queue. """
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
|
|
user_queue = player.user_queue(ctx.author.id, dual=True)
|
|
|
|
# Setup for selector
|
|
identifiers = []
|
|
for index, temp in enumerate(user_queue):
|
|
track, globpos = temp
|
|
identifiers.append(ctx.localizer.format_str("{queue.usertrack}", _index=index+1,
|
|
_globalindex=globpos+1, _title=track.title,
|
|
_uri=track.uri))
|
|
|
|
def ret_first_arg(*args):
|
|
return args[0]
|
|
|
|
functions = [ret_first_arg]*len(user_queue)
|
|
arguments = [(i,) for i in range(len(user_queue))]
|
|
|
|
# Set the different titles for the different selections
|
|
round_titles = ["{moved.choose_song}", "{moved.choose_pos}"]
|
|
round_titles = [ctx.localizer.format_str(i) for i in round_titles]
|
|
|
|
selector = Selector(ctx, identifiers, functions, arguments, num_selections=5, round_titles=round_titles,
|
|
color=ctx.me.color, title=ctx.localizer.format_str("{moved.choose}"))
|
|
|
|
# Get the initial and final position
|
|
message, page, selections = await selector.start_scrolling()
|
|
|
|
try:
|
|
pos_initial, pos_final = selections[0], selections[1]
|
|
except (IndexError, TypeError):
|
|
return await message.delete()
|
|
|
|
# Move the track
|
|
moved = player.move_user_track(ctx.author.id, pos_initial, pos_final)
|
|
|
|
# Create a nice embed explaining what happened
|
|
song = f'**[{moved.title}]({moved.uri})**'
|
|
embed = discord.Embed(color=ctx.me.color, description=song, title='{moved.moved}')
|
|
thumbnail_url = moved.extra["thumbnail_url"]
|
|
member = ctx.guild.get_member(moved.requester)
|
|
if thumbnail_url:
|
|
|
|
embed.set_thumbnail(url=thumbnail_url)
|
|
|
|
embed.add_field(name="{moved.local}", value=f"`{pos_initial + 1} → {pos_final + 1}`", inline=True)
|
|
embed.add_field(name="{moved.global}", value=f"`{player.queue._loc_to_glob(ctx.author.id, pos_initial) + 1}\
|
|
→ {player.queue._loc_to_glob(ctx.author.id, pos_final) + 1}`", inline=True)
|
|
embed.set_footer(text=f'{{requested_by}} {member.display_name}', icon_url=member.avatar_url)
|
|
embed = ctx.localizer.format_embed(embed)
|
|
|
|
await message.edit(embed=embed)
|
|
|
|
|
|
@commands.command(name='remove')
|
|
@require_voice_connection()
|
|
@require_queue(require_author_queue=True)
|
|
async def _remove(self, ctx):
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
|
|
user_queue = player.user_queue(ctx.author.id, dual=True)
|
|
functions = [player.remove_user_track]*len(user_queue)
|
|
arguments = [(ctx.author.id, i) for i in range(len(user_queue))]
|
|
identifiers = []
|
|
|
|
for index, temp in enumerate(user_queue):
|
|
track, globpos = temp
|
|
identifiers.append(ctx.localizer.format_str("{queue.usertrack}", _index=index+1,
|
|
_globalindex=globpos+1, _title=track.title,
|
|
_uri=track.uri))
|
|
|
|
selector = Selector(ctx, identifiers, functions, arguments, num_selections=5, color=0xFF0000,
|
|
title='Remove song plz')
|
|
message, _, removed = await selector.start_scrolling()
|
|
if removed:
|
|
await message.edit(content=ctx.localizer.format_str("{remove}", _title=removed.title), embed=None)
|
|
else:
|
|
await message.delete()
|
|
|
|
|
|
@commands.command(name="DJremove")
|
|
@checks.dj_or()
|
|
@require_voice_connection()
|
|
@require_queue()
|
|
async def _djremove(self, ctx, pos: int, member: discord.Member = None):
|
|
""" Remove a song from either the global queue or a users queue"""
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
|
|
# TODO: add way to allow "member" as regular arg in require_queue() instead of only kwargs
|
|
queue = player.user_queue(member.id) if member else player.queue
|
|
|
|
# TODO: Do all queue out of range messages the same way
|
|
if pos <= len(queue) and pos >= 1:
|
|
if member is None:
|
|
removed = player.remove_global_track(pos - 1)
|
|
else:
|
|
removed = player.remove_user_track(member.id, pos - 1)
|
|
|
|
requester = self.bot.get_user(removed.requester)
|
|
await ctx.send(ctx.localizer.format_str("{dj_removed}", _title=removed.title, _user=requester.display_name))
|
|
else:
|
|
await ctx.send(ctx.localizer.format_str("{out_of_range}", _len=len(queue)))
|
|
|
|
|
|
@commands.command(name='removeuser')
|
|
@checks.dj_or(alone=True)
|
|
@require_voice_connection()
|
|
@require_queue(require_member_queue=True)
|
|
async def _user_queue_remove(self, ctx, *, member: discord.Member):
|
|
""" Remove a song from either the global queue or a users queue"""
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
player.remove_user_queue(member.id)
|
|
|
|
embed = discord.Embed(description="{dj_remove_user}", color=ctx.me.color)
|
|
embed = ctx.localizer.format_embed(embed, _id=member.id)
|
|
|
|
await ctx.send(embed=embed)
|
|
|
|
|
|
@commands.command(name='search')
|
|
@require_voice_connection(should_connect=True)
|
|
async def _search(self, ctx, *, query):
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
|
|
if not query.startswith('ytsearch:') and not query.startswith('scsearch:'):
|
|
query = 'ytsearch:' + query
|
|
|
|
results = await player.node.get_tracks(query)
|
|
|
|
embed = discord.Embed(description='{nothing_found}', color=0x36393F)
|
|
if not results or not results['tracks']:
|
|
embed = ctx.localizer.format_embed(embed)
|
|
return await ctx.send(embed=embed)
|
|
|
|
tracks = results['tracks']
|
|
result_count = min(len(tracks), 15)
|
|
|
|
# Setup for the selector
|
|
identifiers = []
|
|
functions = [self.enqueue]*result_count
|
|
arguments = [(ctx, tracks[i], discord.Embed(color=ctx.me.color)) for i in range(result_count)]
|
|
|
|
for index, track in enumerate(tracks, start=1):
|
|
track = track['info']
|
|
duration = timeformatter.format_ms(int(track['length']))
|
|
identifiers.append(f'`{index}.` [{track["title"]}]({track["uri"]}) `{duration}`')
|
|
if index == result_count:
|
|
break
|
|
|
|
search_selector = Selector(ctx, identifiers, functions, arguments, num_selections=5,
|
|
color=ctx.me.color, title=ctx.localizer.format_str('{results}'))
|
|
# Let the user scroll through results
|
|
message, current_page, result = await search_selector.start_scrolling()
|
|
|
|
if result:
|
|
(embed, added) = result
|
|
embed = ctx.localizer.format_embed(embed)
|
|
await message.edit(embed=embed)
|
|
|
|
if not player.is_playing:
|
|
await player.play()
|
|
else:
|
|
await message.delete()
|
|
|
|
|
|
@commands.command(name='disconnect')
|
|
@require_voice_connection()
|
|
@voteable(DJ_override=True, react_to_vote=True)
|
|
async def _disconnect(self, ctx):
|
|
""" Disconnects the player from the voice channel and clears its queue. """
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
|
|
player.queue.clear()
|
|
await player.stop()
|
|
await self.connect_to(ctx.guild.id, None)
|
|
embed = discord.Embed(description='{disconnect.disconnected}', color=ctx.me.color)
|
|
embed = ctx.localizer.format_embed(embed)
|
|
await ctx.send(embed=embed)
|
|
|
|
|
|
@commands.command(name='reconnect')
|
|
@require_voice_connection()
|
|
@voteable(DJ_override=True, react_to_vote=True)
|
|
async def _reconnect(self, ctx):
|
|
""" Tries to disconnect then reconnect the player in case the bot gets stuck on a song """
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
current_channel = player.channel_id
|
|
|
|
async def recon():
|
|
await player.stop()
|
|
await self.connect_to(ctx.guild.id, None)
|
|
await asyncio.sleep(1) # Pretend stuff is happening/give everything some time to reset.
|
|
await self.connect_to(ctx.guild.id, current_channel)
|
|
|
|
if player.current:
|
|
track = player.current
|
|
start_time = player.position
|
|
await recon()
|
|
if track:
|
|
await player.play(track, start_time=start_time)
|
|
else:
|
|
await recon()
|
|
|
|
|
|
@commands.command(name='volume')
|
|
@checks.dj_or(alone=True, track_requester=True)
|
|
@require_playing(require_user_listening=True)
|
|
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.player_manager.get(ctx.guild.id)
|
|
|
|
if not volume:
|
|
return await ctx.send(f'🔈 | {player.volume}%')
|
|
|
|
is_dj = checks.is_dj(ctx)
|
|
is_admin = getattr(ctx.author.guild_permissions, 'administrator', None) is True
|
|
if player.current and (int(player.current.requester) == ctx.author.id and not is_dj and not is_admin):
|
|
if not 50 <= volume <= 125:
|
|
return await ctx.send(ctx.localizer.format_str("{volume.out_of_range}"))
|
|
await player.set_volume(volume)
|
|
|
|
embed = discord.Embed(description="{volume.set_to}", color=ctx.me.color)
|
|
embed = ctx.localizer.format_embed(embed, _volume=player.volume)
|
|
await ctx.send(embed=embed)
|
|
|
|
|
|
@commands.command(name='forceplay')
|
|
@require_voice_connection(should_connect=True)
|
|
@voteable(DJ_override=True, react_to_vote=True)
|
|
async def _forceplay(self, ctx, *, query: str):
|
|
""" Searches and plays a song from a given query. """
|
|
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
|
|
query = query.strip('<>')
|
|
|
|
if not url_rx.match(query):
|
|
query = f'ytsearch:{query}'
|
|
|
|
results = await player.node.get_tracks(query)
|
|
|
|
if not results or not results['tracks']:
|
|
return await ctx.send(ctx.localizer.format_str("{nothing_found}"))
|
|
|
|
embed = discord.Embed(color=ctx.me.color)
|
|
|
|
if results['loadType'] == 'PLAYLIST_LOADED':
|
|
numtracks = 0
|
|
for track in results['tracks']:
|
|
_, track_added = await self.enqueue(ctx, track, embed, silent=True, check_max_length=False)
|
|
if track_added:
|
|
numtracks += 1
|
|
|
|
embed.title = '{playlist_enqued}'
|
|
embed.description = f'{results["playlistInfo"]["name"]} - {numtracks} {{tracks}}'
|
|
else:
|
|
track = results['tracks'][0]
|
|
await self.enqueue(ctx, track, embed, check_max_length=False)
|
|
|
|
embed = ctx.localizer.format_embed(embed)
|
|
await ctx.send(embed=embed)
|
|
|
|
if not player.is_playing:
|
|
await player.play()
|