can queue playlists, databse stores more song info, display queue command works, skip needs looking at

This commit is contained in:
2023-05-29 01:15:54 +01:00
parent 10907570a2
commit 59d7af67bb
4 changed files with 230 additions and 35 deletions

View File

@@ -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
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()

View File

@@ -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))

View File

@@ -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:
try:
info = ydl.extract_info(f"ytsearch1:{search}", download=False)
audio_url = info['entries'][0]['url'] # Get audio stream URL
return [audio_url]
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:
try:
info = ydl.extract_info(url, download=False)
audio_url = info['url'] # Get audio stream URL
return [audio_url]
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):
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

View File

@@ -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"