can queue playlists, databse stores more song info, display queue command works, skip needs looking at
This commit is contained in:
@@ -7,6 +7,7 @@ import cogs.music.translate as translate
|
||||
|
||||
import datetime
|
||||
import pytz
|
||||
import asyncio
|
||||
|
||||
from cogs.music.help import music_help
|
||||
|
||||
@@ -55,27 +56,96 @@ class music(commands.Cog):
|
||||
|
||||
@commands.command(
|
||||
help="Queues a song into the bot",
|
||||
aliases=['p', 'qeue', 'q'])
|
||||
@commands.check(util.in_server)
|
||||
aliases=['p'])
|
||||
async def play(self, ctx: Context, *, url=None):
|
||||
if url is None:
|
||||
raise commands.CommandError("Must provide a link or search query")
|
||||
elif ctx.guild is None:
|
||||
raise commands.CommandError("Command must be issued in a server")
|
||||
|
||||
server = ctx.guild.id
|
||||
|
||||
#TODO potentially save requests before getting stream link
|
||||
audio = translate.main(url)
|
||||
await ctx.message.add_reaction('👍')
|
||||
await util.join_vc(ctx)
|
||||
|
||||
msg = await ctx.send("Fetching song(s)...")
|
||||
async with ctx.typing():
|
||||
#TODO potentially save requests before getting stream link
|
||||
# Grab video details such as title thumbnail duration
|
||||
audio = translate.main(url)
|
||||
|
||||
await msg.delete()
|
||||
|
||||
if len(audio) == 0:
|
||||
await ctx.message.add_reaction('🚫')
|
||||
await ctx.send("Failed to find song!")
|
||||
return
|
||||
|
||||
|
||||
#TODO make sure user isn't queuing in dm for some stupid reason
|
||||
for song in audio:
|
||||
await queue.add_song(server, song, ctx.author.id)
|
||||
song['position'] = await queue.add_song(
|
||||
server,
|
||||
song,
|
||||
ctx.author.display_name)
|
||||
|
||||
await ctx.message.add_reaction('👍')
|
||||
await util.queue_message(ctx, audio[0])
|
||||
|
||||
await util.join_vc(ctx)
|
||||
|
||||
if await queue.is_server_playing(ctx.guild.id):
|
||||
if await queue.is_server_playing(server):
|
||||
return
|
||||
|
||||
await queue.update_server(server, True)
|
||||
await queue.play(ctx)
|
||||
|
||||
|
||||
|
||||
@commands.command(
|
||||
help="Display the current music queue",
|
||||
aliases=['q', 'songs'])
|
||||
async def queue(self, ctx: Context):
|
||||
|
||||
server = ctx.guild
|
||||
|
||||
# Perform usual checks
|
||||
if server is None:
|
||||
raise commands.CommandError("Command must be issued in a server")
|
||||
|
||||
|
||||
# Grab all songs from this server
|
||||
n, songs = await queue.grab_songs(server.id)
|
||||
|
||||
# Check once more
|
||||
if len(songs) == 0:
|
||||
await ctx.send("🚫 This server has no queue currently. Start the party by queuing up a song!")
|
||||
return
|
||||
|
||||
# Display songs
|
||||
await util.display_server_queue(ctx, songs, n)
|
||||
|
||||
|
||||
@commands.command(
|
||||
help="Skips the current song that is playing, can include number to skip more songs",
|
||||
aliases=['s'])
|
||||
async def skip(self, ctx: Context, n='1'):
|
||||
server = ctx.guild
|
||||
|
||||
if server is None:
|
||||
raise commands.CommandError("Command must be issued in a server")
|
||||
|
||||
if ctx.voice_client is None:
|
||||
raise commands.CommandError("I'm not in a voice channel")
|
||||
|
||||
if not n.isdigit():
|
||||
raise commands.CommandError("Please enter a number to skip")
|
||||
n = int(n)
|
||||
|
||||
if n <= 0:
|
||||
raise commands.CommandError("Please enter a positive number")
|
||||
|
||||
# Skip specificed number of songs
|
||||
for _ in range(n-1):
|
||||
await queue.pop(server.id)
|
||||
|
||||
# Safe to ignore error for now
|
||||
ctx.voice_client.stop()
|
||||
|
||||
@@ -29,17 +29,21 @@ def initialize_tables():
|
||||
cursor.execute("UPDATE servers SET is_playing = 0;")
|
||||
|
||||
# Create queue table if it doesn't exist
|
||||
cursor.execute('''CREATE TABLE IF NOT EXISTS queue (
|
||||
cursor.execute('''CREATE TABLE IF NOT EXISTS songs (
|
||||
server_id TEXT NOT NULL,
|
||||
song_link TEXT,
|
||||
queued_by TEXT,
|
||||
position INTEGER NOT NULL,
|
||||
has_played INTEGER DEFAULT 0,
|
||||
|
||||
title TEXT,
|
||||
thumbnail TEXT,
|
||||
duration INTEGER,
|
||||
|
||||
PRIMARY KEY (position),
|
||||
FOREIGN KEY (server_id) REFERENCES servers(server_id)
|
||||
);''')
|
||||
# Clear all entries
|
||||
cursor.execute("DELETE FROM queue;")
|
||||
cursor.execute("DELETE FROM songs;")
|
||||
|
||||
# Commit the changes and close the connection
|
||||
conn.commit()
|
||||
@@ -47,7 +51,7 @@ def initialize_tables():
|
||||
|
||||
|
||||
# Queue a song in the db
|
||||
async def add_song(server_id, song_link, queued_by):
|
||||
async def add_song(server_id, details, queued_by):
|
||||
# Connect to db
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
@@ -57,13 +61,27 @@ async def add_song(server_id, song_link, queued_by):
|
||||
max_order_num = await get_max(server_id, cursor) + 1
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO queue (server_id, song_link, queued_by, position)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""", (server_id, song_link, queued_by, max_order_num))
|
||||
INSERT INTO songs (server_id,
|
||||
song_link,
|
||||
queued_by,
|
||||
position,
|
||||
title,
|
||||
thumbnail,
|
||||
duration)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""", (server_id,
|
||||
details['url'],
|
||||
queued_by,
|
||||
max_order_num,
|
||||
details['title'],
|
||||
details['thumbnail'],
|
||||
details['duration']))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return max_order_num
|
||||
|
||||
|
||||
# Add server to db if first time queuing
|
||||
async def add_server(server_id, cursor, conn):
|
||||
@@ -89,7 +107,7 @@ async def mark_song_as_finished(server_id, order_num):
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Update the song as finished
|
||||
cursor.execute('''DELETE FROM queue
|
||||
cursor.execute('''DELETE FROM songs
|
||||
WHERE server_id = ? AND position = ?''',
|
||||
(server_id, order_num))
|
||||
|
||||
@@ -102,7 +120,7 @@ async def mark_song_as_finished(server_id, order_num):
|
||||
async def get_max(server_id, cursor):
|
||||
cursor.execute(f"""
|
||||
SELECT MAX(position)
|
||||
FROM queue
|
||||
FROM songs
|
||||
WHERE server_id = ?
|
||||
""", (server_id,))
|
||||
result = cursor.fetchone()
|
||||
@@ -129,7 +147,7 @@ async def pop(server_id):
|
||||
return None
|
||||
|
||||
cursor.execute('''SELECT song_link
|
||||
FROM queue
|
||||
FROM songs
|
||||
WHERE server_id = ? AND position = ?
|
||||
''', (server_id, max_order))
|
||||
result = cursor.fetchone()
|
||||
@@ -178,7 +196,6 @@ async def is_server_playing(server_id):
|
||||
(server_id,))
|
||||
|
||||
result = cursor.fetchone()
|
||||
print(result)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@@ -196,19 +213,42 @@ async def clear(server_id):
|
||||
await update_server(server_id, False)
|
||||
|
||||
# Delete all songs from the server
|
||||
cursor.execute('''DELETE FROM queue WHERE server_id = ?''', (server_id,))
|
||||
cursor.execute('''DELETE FROM songs WHERE server_id = ?''', (server_id,))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
# Grabs all songs from a server for display purposes
|
||||
async def grab_songs(server_id):
|
||||
# Connect to db
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
await add_server(server_id, cursor, conn)
|
||||
|
||||
# Grabs all songs from the server
|
||||
cursor.execute('''SELECT title, duration, queued_by
|
||||
FROM songs
|
||||
WHERE server_id = ?
|
||||
ORDER BY position
|
||||
LIMIT 10''', (server_id,))
|
||||
songs = cursor.fetchall()
|
||||
max = await get_max(server_id, cursor)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return max, songs
|
||||
|
||||
|
||||
# Play and loop songs in server
|
||||
async def play(ctx):
|
||||
server_id = ctx.guild.id
|
||||
|
||||
# Wait until song is stopped playing
|
||||
#while ctx.voice_client.is_playing():
|
||||
#await asyncio.sleep(1)
|
||||
# Wait until song is stopped playing fully
|
||||
while ctx.voice_client.is_playing():
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# check next song
|
||||
url = await pop(server_id)
|
||||
@@ -222,11 +262,12 @@ async def play(ctx):
|
||||
ctx.voice_client.play(
|
||||
AstroPlayer(ctx, url, FFMPEG_OPTS))
|
||||
|
||||
# call play on ffmpeg exit
|
||||
class AstroPlayer(discord.FFmpegPCMAudio):
|
||||
def __init__(self, ctx, source, options) -> None:
|
||||
self.ctx = ctx
|
||||
super().__init__(source, **options)
|
||||
|
||||
def _kill_process(self):
|
||||
super()._kill_process
|
||||
asyncio.run(play(self.ctx))
|
||||
super()._kill_process()
|
||||
asyncio.create_task(play(self.ctx))
|
||||
|
||||
@@ -6,6 +6,7 @@ ydl_opts = {
|
||||
'format': 'bestaudio/best',
|
||||
'quiet': True,
|
||||
'default_search': 'ytsearch',
|
||||
'ignoreerrors': True,
|
||||
}
|
||||
|
||||
def main(url):
|
||||
@@ -41,9 +42,19 @@ def main(url):
|
||||
|
||||
def search_song(search):
|
||||
with ytdlp.YoutubeDL(ydl_opts) as ydl:
|
||||
info = ydl.extract_info(f"ytsearch1:{search}", download=False)
|
||||
audio_url = info['entries'][0]['url'] # Get audio stream URL
|
||||
return [audio_url]
|
||||
try:
|
||||
info = ydl.extract_info(f"ytsearch1:{search}", download=False)
|
||||
except:
|
||||
return []
|
||||
if info is None:
|
||||
return []
|
||||
|
||||
info = info['entries'][0] # Get audio stream URL
|
||||
data = {'url': info['url'],
|
||||
'title': info['title'],
|
||||
'thumbnail': info['thumbnail'],
|
||||
'duration': info['duration']} # Grab data
|
||||
return [data]
|
||||
|
||||
|
||||
def spotify_song(url):
|
||||
@@ -56,10 +67,39 @@ def spotify_playlist(url):
|
||||
|
||||
def song_download(url):
|
||||
with ytdlp.YoutubeDL(ydl_opts) as ydl:
|
||||
info = ydl.extract_info(url, download=False)
|
||||
audio_url = info['url'] # Get audio stream URL
|
||||
return [audio_url]
|
||||
try:
|
||||
info = ydl.extract_info(url, download=False)
|
||||
except:
|
||||
return []
|
||||
if info is None:
|
||||
return []
|
||||
|
||||
print(info.keys())
|
||||
|
||||
data = {'url': info['url'],
|
||||
'title': info['title'],
|
||||
'thumbnail': info['thumbnail'],
|
||||
'duration': info['duration']} # Grab data
|
||||
return [data]
|
||||
|
||||
|
||||
def playlist_download(url):
|
||||
return []
|
||||
with ytdlp.YoutubeDL(ydl_opts) as ydl:
|
||||
try:
|
||||
info = ydl.extract_info(url, download=False)
|
||||
except:
|
||||
return []
|
||||
if info is None:
|
||||
return []
|
||||
|
||||
info = info['entries'] # Grabbing all songs in playlist
|
||||
urls = []
|
||||
|
||||
for song in info:
|
||||
data = {'url': song['url'],
|
||||
'title': song['title'],
|
||||
'thumbnail': song['thumbnail'],
|
||||
'duration': song['duration']} # Grab data
|
||||
urls.append(data)
|
||||
|
||||
return urls
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import discord
|
||||
from discord.ext.commands.context import Context
|
||||
from discord.ext.commands.converter import CommandError
|
||||
import config
|
||||
|
||||
|
||||
# Joining/moving to the user's vc in a guild
|
||||
@@ -45,6 +47,48 @@ async def leave_vc(ctx: Context):
|
||||
await ctx.voice_client.disconnect(force=False)
|
||||
|
||||
|
||||
# Check if command was entered in a server
|
||||
async def in_server(ctx: Context):
|
||||
return ctx.guild != None
|
||||
# Build a display message for queuing a new song
|
||||
async def queue_message(ctx: Context, data: dict):
|
||||
msg = discord.Embed(
|
||||
title=f"{ctx.author.display_name} queued a song!",
|
||||
color=config.get_color("main"))
|
||||
|
||||
msg.set_thumbnail(url=data['thumbnail'])
|
||||
msg.add_field(name=data['title'],
|
||||
value=f"Duration: {format_time(data['duration'])}" + '\n'
|
||||
+ f"Position: {data['position']}")
|
||||
|
||||
await ctx.send(embed=msg)
|
||||
|
||||
|
||||
# Build an embed message that shows the queue
|
||||
async def display_server_queue(ctx: Context, songs, n):
|
||||
server = ctx.guild
|
||||
|
||||
msg = discord.Embed(
|
||||
title=f"{server.name}'s Queue!",
|
||||
color=config.get_color("main"))
|
||||
|
||||
display = ""
|
||||
for i, song in enumerate(songs):
|
||||
display += f"``{i + 1}.`` {song[0]} - {format_time(song[1])} Queued by {song[2]}\n"
|
||||
msg.add_field(name="Songs:",
|
||||
value=display,
|
||||
inline=True)
|
||||
if n > 10:
|
||||
msg.set_footer(text=f"and {n - 10} more!..")
|
||||
|
||||
await ctx.send(embed=msg)
|
||||
|
||||
|
||||
# Converts seconds into more readable format
|
||||
def format_time(seconds):
|
||||
minutes, seconds = divmod(seconds, 60)
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
|
||||
if hours > 0:
|
||||
return f"{hours}:{minutes:02d}:{seconds:02d}"
|
||||
elif minutes > 0:
|
||||
return f"{minutes}:{seconds:02d}"
|
||||
else:
|
||||
return f"{seconds} seconds"
|
||||
|
||||
Reference in New Issue
Block a user