🛠 fixed queue bugs, added spotify song support 🔊
This commit is contained in:
@@ -5,12 +5,49 @@ import cogs.music.util as util
|
||||
import cogs.music.queue as queue
|
||||
import cogs.music.translate as translate
|
||||
|
||||
import datetime
|
||||
import pytz
|
||||
import asyncio
|
||||
|
||||
from cogs.music.help import music_help
|
||||
import discord
|
||||
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyClientCredentials
|
||||
|
||||
|
||||
# Fix this pls
|
||||
|
||||
import json
|
||||
#from .. import config
|
||||
# Read data from JSON file in ./data/config.json
|
||||
def read_data():
|
||||
with open("./data/config.json", "r") as file:
|
||||
return json.load(file)
|
||||
|
||||
raise Exception("Could not load config data")
|
||||
|
||||
|
||||
def get_spotify_creds():
|
||||
data = read_data()
|
||||
data = data.get("spotify")
|
||||
|
||||
SCID = data.get("SCID")
|
||||
secret = data.get("SECRET")
|
||||
|
||||
return SCID, secret
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 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.create_task(play(self.ctx))
|
||||
|
||||
class music(commands.Cog):
|
||||
def __init__(self, client):
|
||||
@@ -23,21 +60,16 @@ class music(commands.Cog):
|
||||
help_command.cog = self
|
||||
self.help_command = help_command
|
||||
|
||||
SCID, secret = get_spotify_creds()
|
||||
# Authentication - without user
|
||||
client_credentials_manager = SpotifyClientCredentials(client_id=SCID,
|
||||
client_secret=secret)
|
||||
|
||||
self.sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
|
||||
|
||||
queue.initialize_tables()
|
||||
|
||||
|
||||
@commands.command(
|
||||
help="Displays latency from the bot",
|
||||
aliases=['delay'])
|
||||
async def ping(self, ctx: Context):
|
||||
start_time = datetime.datetime.now(pytz.utc)
|
||||
end_time = ctx.message.created_at
|
||||
|
||||
delay = int((end_time - start_time).total_seconds() * 1000)
|
||||
|
||||
await ctx.send(f"Pong! `{delay}MS`")
|
||||
|
||||
|
||||
@commands.command(
|
||||
help="Connects to your current voice channel",
|
||||
aliases=['connect'])
|
||||
@@ -53,7 +85,6 @@ class music(commands.Cog):
|
||||
await util.leave_vc(ctx)
|
||||
await ctx.message.add_reaction('👍')
|
||||
|
||||
|
||||
@commands.command(
|
||||
help="Queues a song into the bot",
|
||||
aliases=['p'])
|
||||
@@ -65,14 +96,14 @@ class music(commands.Cog):
|
||||
|
||||
server = ctx.guild.id
|
||||
|
||||
await ctx.message.add_reaction('👍')
|
||||
await util.join_vc(ctx)
|
||||
await ctx.message.add_reaction('👍')
|
||||
|
||||
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)
|
||||
audio = translate.main(url, self.sp)
|
||||
|
||||
await msg.delete()
|
||||
|
||||
@@ -99,7 +130,6 @@ class music(commands.Cog):
|
||||
await queue.play(ctx)
|
||||
|
||||
|
||||
|
||||
@commands.command(
|
||||
help="Display the current music queue",
|
||||
aliases=['q', 'songs'])
|
||||
@@ -116,7 +146,7 @@ class music(commands.Cog):
|
||||
n, songs = await queue.grab_songs(server.id)
|
||||
|
||||
# Check once more
|
||||
if len(songs) == 0:
|
||||
if len(songs) == 0 and await queue.is_server_playing(ctx.guild.id) == False:
|
||||
await ctx.send("🚫 This server has no queue currently. Start the party by queuing up a song!")
|
||||
return
|
||||
|
||||
@@ -148,4 +178,4 @@ class music(commands.Cog):
|
||||
await queue.pop(server.id)
|
||||
|
||||
# Safe to ignore error for now
|
||||
ctx.voice_client.stop()
|
||||
ctx.voice_client.stop()
|
||||
@@ -1,3 +1,4 @@
|
||||
from http import server
|
||||
import sqlite3
|
||||
|
||||
import discord
|
||||
@@ -22,7 +23,8 @@ def initialize_tables():
|
||||
# Create servers table if it doesn't exist
|
||||
cursor.execute('''CREATE TABLE IF NOT EXISTS servers (
|
||||
server_id TEXT PRIMARY KEY,
|
||||
is_playing INTEGER DEFAULT 0
|
||||
is_playing INTEGER DEFAULT 0,
|
||||
song_name TEXT
|
||||
);''')
|
||||
|
||||
# Set all to not playing
|
||||
@@ -83,6 +85,34 @@ async def add_song(server_id, details, queued_by):
|
||||
return max_order_num
|
||||
|
||||
|
||||
# Pop song from server
|
||||
async def pop(server_id):
|
||||
# Connect to db
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# JUST INCASE!
|
||||
await add_server(server_id, cursor, conn)
|
||||
|
||||
cursor.execute('''SELECT *
|
||||
FROM songs
|
||||
WHERE server_id = ?
|
||||
ORDER BY position
|
||||
LIMIT 1;''', (server_id,))
|
||||
result = cursor.fetchone()
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
if result == None:
|
||||
return None
|
||||
|
||||
await set_current_song(server_id, result[4])
|
||||
await mark_song_as_finished(server_id, result[3])
|
||||
|
||||
return result[1]
|
||||
|
||||
|
||||
# Add server to db if first time queuing
|
||||
async def add_server(server_id, cursor, conn):
|
||||
# Check if the server exists
|
||||
@@ -116,6 +146,42 @@ async def mark_song_as_finished(server_id, order_num):
|
||||
conn.close()
|
||||
|
||||
|
||||
# set the current playing song of the server
|
||||
async def set_current_song(server_id, title):
|
||||
# Connect to the database
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute(''' UPDATE servers
|
||||
SET song_name = ?
|
||||
WHERE server_id = ?''',
|
||||
(title, server_id))
|
||||
|
||||
# Close connection
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
async def get_current_song(server_id):
|
||||
# Connect to the database
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute(''' SELECT song_name
|
||||
FROM servers
|
||||
WHERE server_id = ?
|
||||
LIMIT 1;''',
|
||||
(server_id,))
|
||||
|
||||
result = cursor.fetchone()
|
||||
|
||||
# Close connection
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return result[0]
|
||||
|
||||
|
||||
# Grab max order from server
|
||||
async def get_max(server_id, cursor):
|
||||
cursor.execute(f"""
|
||||
@@ -131,35 +197,6 @@ async def get_max(server_id, cursor):
|
||||
return max_order_num
|
||||
|
||||
|
||||
# Pop song from server
|
||||
async def pop(server_id):
|
||||
# Connect to db
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# JUST INCASE!
|
||||
await add_server(server_id, cursor, conn)
|
||||
|
||||
max_order = await get_max(server_id, cursor)
|
||||
if max_order == -1:
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return None
|
||||
|
||||
cursor.execute('''SELECT song_link
|
||||
FROM songs
|
||||
WHERE server_id = ? AND position = ?
|
||||
''', (server_id, max_order))
|
||||
result = cursor.fetchone()
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
await mark_song_as_finished(server_id, max_order)
|
||||
|
||||
return result[0]
|
||||
|
||||
|
||||
# Sets the playing variable in a server to true or false
|
||||
async def update_server(server_id, playing: bool):
|
||||
# Connect to database
|
||||
@@ -259,7 +296,7 @@ async def play(ctx):
|
||||
return
|
||||
|
||||
# else play next song and call play again
|
||||
ctx.voice_client.play(
|
||||
await ctx.voice_client.play(
|
||||
AstroPlayer(ctx, url, FFMPEG_OPTS))
|
||||
|
||||
# call play on ffmpeg exit
|
||||
@@ -270,4 +307,4 @@ class AstroPlayer(discord.FFmpegPCMAudio):
|
||||
|
||||
def _kill_process(self):
|
||||
super()._kill_process()
|
||||
asyncio.create_task(play(self.ctx))
|
||||
asyncio.run(play(self.ctx))
|
||||
@@ -1,6 +1,7 @@
|
||||
# Handles translating urls and search terms
|
||||
|
||||
import yt_dlp as ytdlp
|
||||
import spotipy
|
||||
|
||||
ydl_opts = {
|
||||
'format': 'bestaudio/best',
|
||||
@@ -9,7 +10,7 @@ ydl_opts = {
|
||||
'ignoreerrors': True,
|
||||
}
|
||||
|
||||
def main(url):
|
||||
def main(url, sp):
|
||||
|
||||
#url = url.lower()
|
||||
|
||||
@@ -20,13 +21,13 @@ def main(url):
|
||||
#TODO add better regex or something
|
||||
if 'spotify' in url:
|
||||
if 'track' in url:
|
||||
return spotify_song(url)
|
||||
return spotify_song(url, sp)
|
||||
elif 'playlist' in url:
|
||||
return spotify_playlist(url)
|
||||
|
||||
soundcloud_song = 'soundcloud' in url and 'sets' not in url
|
||||
# Not implemented yet
|
||||
#soundcloud_playlist = 'soundcloud' in url and 'sets' in url
|
||||
# soundcloud_playlist = 'soundcloud' in url and 'sets' in url
|
||||
|
||||
youtube_song = 'watch?v=' in url or 'youtu.be/' in url
|
||||
youtube_playlist = 'playlist?list=' in url
|
||||
@@ -57,8 +58,21 @@ def search_song(search):
|
||||
return [data]
|
||||
|
||||
|
||||
def spotify_song(url):
|
||||
return []
|
||||
def spotify_song(url, sp):
|
||||
track = sp.track(url.split("/")[-1].split("?")[0])
|
||||
search = ""
|
||||
|
||||
for i in track["artists"]:
|
||||
# grabs all the artists name's if there's more than one
|
||||
search = search + (i['name'] + ", ")
|
||||
|
||||
# remove last comma
|
||||
search = search[:-2]
|
||||
|
||||
# set search to name
|
||||
query = search + " - " + track['name']
|
||||
|
||||
return search_song(query)
|
||||
|
||||
|
||||
def spotify_playlist(url):
|
||||
@@ -74,8 +88,6 @@ def song_download(url):
|
||||
if info is None:
|
||||
return []
|
||||
|
||||
print(info.keys())
|
||||
|
||||
data = {'url': info['url'],
|
||||
'title': info['title'],
|
||||
'thumbnail': info['thumbnail'],
|
||||
|
||||
@@ -2,7 +2,7 @@ import discord
|
||||
from discord.ext.commands.context import Context
|
||||
from discord.ext.commands.converter import CommandError
|
||||
import config
|
||||
|
||||
from . import queue
|
||||
|
||||
# Joining/moving to the user's vc in a guild
|
||||
async def join_vc(ctx: Context):
|
||||
@@ -69,7 +69,7 @@ async def display_server_queue(ctx: Context, songs, n):
|
||||
title=f"{server.name}'s Queue!",
|
||||
color=config.get_color("main"))
|
||||
|
||||
display = ""
|
||||
display = f"🔊 Currently playing: ``{await queue.get_current_song(ctx.guild.id)}``\n"
|
||||
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:",
|
||||
|
||||
10
config.py
10
config.py
@@ -13,6 +13,16 @@ def read_data():
|
||||
raise Exception("Could not load config data")
|
||||
|
||||
|
||||
def get_spotify_creds():
|
||||
data = read_data()
|
||||
data = data.get("spotify")
|
||||
|
||||
SCID = data.get("SCID")
|
||||
secret = data.get("SECRET")
|
||||
|
||||
return SCID, secret
|
||||
|
||||
|
||||
# Reading prefix
|
||||
def get_prefix():
|
||||
data = read_data()
|
||||
|
||||
4
help.py
4
help.py
@@ -32,7 +32,7 @@ class AstroHelp(commands.MinimalHelpCommand):
|
||||
|
||||
# grabs iterable of (Cog, list[Command])
|
||||
for cog, commands in mapping.items():
|
||||
|
||||
|
||||
# Grab commands only the user can access
|
||||
# Safe to ignore warning
|
||||
filtered = await self.filter_commands(commands, sort=True)
|
||||
@@ -78,4 +78,4 @@ class AstroHelp(commands.MinimalHelpCommand):
|
||||
# TODO add error support see
|
||||
# https://gist.github.com/InterStella0/b78488fb28cadf279dfd3164b9f0cf96
|
||||
# and
|
||||
# https://gist.github.com/EvieePy/7822af90858ef65012ea500bcecf1612
|
||||
# https://gist.github.com/EvieePy/7822af90858ef65012ea500bcecf1612
|
||||
2
main.py
2
main.py
@@ -6,4 +6,4 @@ import help
|
||||
client = Astro(command_prefix=config.get_prefix(), intents=discord.Intents.all())
|
||||
client.help_command = help.AstroHelp()
|
||||
|
||||
client.run(config.get_login("live"))
|
||||
client.run(config.get_login("dev"))
|
||||
@@ -24,4 +24,4 @@ spotipy==2.23.0
|
||||
urllib3==2.0.2
|
||||
websockets==11.0.3
|
||||
yarl==1.9.2
|
||||
yt-dlp==2023.3.4
|
||||
yt-dlp
|
||||
|
||||
Reference in New Issue
Block a user