add spotify recommendations
This commit is contained in:
parent
0d00fe9f36
commit
ad91a8e2ae
|
|
@ -71,6 +71,16 @@ class InvalidSpotifyClientAuthorization(PomiceException):
|
||||||
"""No Spotify client authorization was provided for track searching."""
|
"""No Spotify client authorization was provided for track searching."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class SpotifyRequestException(PomiceException):
|
||||||
|
"""An error occurred when making a request to the Spotify API"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidSpotifyURL(PomiceException):
|
||||||
|
"""An invalid Spotify URL was passed"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class QueueException(Exception):
|
class QueueException(Exception):
|
||||||
"""Base Pomice queue exception."""
|
"""Base Pomice queue exception."""
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import re
|
import re
|
||||||
from typing import List, Optional
|
from typing import List, Optional, Union
|
||||||
|
from discord import Member, User
|
||||||
|
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
|
|
@ -26,7 +27,8 @@ class Track:
|
||||||
search_type: SearchType = SearchType.ytsearch,
|
search_type: SearchType = SearchType.ytsearch,
|
||||||
spotify_track = None,
|
spotify_track = None,
|
||||||
filters: Optional[List[Filter]] = None,
|
filters: Optional[List[Filter]] = None,
|
||||||
timestamp: Optional[float] = None
|
timestamp: Optional[float] = None,
|
||||||
|
requester: Optional[Union[Member, User]] = None
|
||||||
):
|
):
|
||||||
self.track_id = track_id
|
self.track_id = track_id
|
||||||
self.info = info
|
self.info = info
|
||||||
|
|
@ -56,7 +58,10 @@ class Track:
|
||||||
|
|
||||||
self.length = info.get("length")
|
self.length = info.get("length")
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.requester = self.ctx.author if ctx else None
|
if requester:
|
||||||
|
self.requester = requester
|
||||||
|
else:
|
||||||
|
self.requester = self.ctx.author if ctx else None
|
||||||
self.is_stream = info.get("isStream")
|
self.is_stream = info.get("isStream")
|
||||||
self.is_seekable = info.get("isSeekable")
|
self.is_seekable = info.get("isSeekable")
|
||||||
self.position = info.get("position")
|
self.position = info.get("position")
|
||||||
|
|
|
||||||
|
|
@ -274,6 +274,15 @@ class Player(VoiceProtocol):
|
||||||
"""
|
"""
|
||||||
return await self._node.get_tracks(query, ctx=ctx, search_type=search_type, filters=filters)
|
return await self._node.get_tracks(query, ctx=ctx, search_type=search_type, filters=filters)
|
||||||
|
|
||||||
|
async def get_recommendations(self, *, query: str, ctx: Optional[commands.Context] = None):
|
||||||
|
"""
|
||||||
|
Gets recommendations from Spotify. Query must be a valid Spotify Track URL.
|
||||||
|
|
||||||
|
You can pass in a discord.py Context object to get a
|
||||||
|
Context object on all tracks that get recommended.
|
||||||
|
"""
|
||||||
|
return await self._node.get_recommendations(query=query, ctx=ctx)
|
||||||
|
|
||||||
async def connect(self, *, timeout: float, reconnect: bool, self_deaf: bool = False, self_mute: bool = False):
|
async def connect(self, *, timeout: float, reconnect: bool, self_deaf: bool = False, self_mute: bool = False):
|
||||||
await self.guild.change_voice_state(channel=self.channel, self_deaf=self_deaf, self_mute=self_mute)
|
await self.guild.change_voice_state(channel=self.channel, self_deaf=self_deaf, self_mute=self_mute)
|
||||||
self._node._players[self.guild.id] = self
|
self._node._players[self.guild.id] = self
|
||||||
|
|
@ -456,3 +465,5 @@ class Player(VoiceProtocol):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,10 @@ from .exceptions import (
|
||||||
InvalidSpotifyClientAuthorization,
|
InvalidSpotifyClientAuthorization,
|
||||||
NodeConnectionFailure,
|
NodeConnectionFailure,
|
||||||
NodeCreationError,
|
NodeCreationError,
|
||||||
NodeException,
|
|
||||||
NodeNotAvailable,
|
NodeNotAvailable,
|
||||||
NoNodesAvailable,
|
NoNodesAvailable,
|
||||||
TrackLoadError
|
TrackLoadError,
|
||||||
|
SpotifyTrackLoadFailed
|
||||||
)
|
)
|
||||||
from .filters import Filter
|
from .filters import Filter
|
||||||
from .objects import Playlist, Track
|
from .objects import Playlist, Track
|
||||||
|
|
@ -466,6 +466,38 @@ class Node:
|
||||||
for track in data["tracks"]
|
for track in data["tracks"]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
async def get_recommendations(self, *, query: str, ctx: Optional[commands.Context] = None):
|
||||||
|
"""
|
||||||
|
Gets recommendations from Spotify. Query must be a valid Spotify Track URL.
|
||||||
|
|
||||||
|
You can pass in a discord.py Context object to get a
|
||||||
|
Context object on all tracks that get recommended.
|
||||||
|
"""
|
||||||
|
results = await self._spotify_client.get_recommendations(query=query)
|
||||||
|
tracks = [
|
||||||
|
Track(
|
||||||
|
track_id=track.id,
|
||||||
|
ctx=ctx,
|
||||||
|
spotify=True,
|
||||||
|
spotify_track=track,
|
||||||
|
info={
|
||||||
|
"title": track.name,
|
||||||
|
"author": track.artists,
|
||||||
|
"length": track.length,
|
||||||
|
"identifier": track.id,
|
||||||
|
"uri": track.uri,
|
||||||
|
"isStream": False,
|
||||||
|
"isSeekable": True,
|
||||||
|
"position": 0,
|
||||||
|
"thumbnail": track.image,
|
||||||
|
"isrc": track.isrc
|
||||||
|
},
|
||||||
|
requester=self.bot.user
|
||||||
|
) for track in results
|
||||||
|
]
|
||||||
|
|
||||||
|
return tracks
|
||||||
|
|
||||||
|
|
||||||
class NodePool:
|
class NodePool:
|
||||||
"""The base class for the node pool.
|
"""The base class for the node pool.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
"""Spotify module for Pomice, made possible by cloudwithax 2021"""
|
"""Spotify module for Pomice, made possible by cloudwithax 2021"""
|
||||||
|
|
||||||
from .exceptions import *
|
|
||||||
from .objects import *
|
from .objects import *
|
||||||
from .client import Client
|
from .client import Client
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import aiohttp
|
||||||
import orjson as json
|
import orjson as json
|
||||||
|
|
||||||
|
|
||||||
from .exceptions import InvalidSpotifyURL, SpotifyRequestException
|
from ..exceptions import InvalidSpotifyURL, SpotifyRequestException
|
||||||
from .objects import *
|
from .objects import *
|
||||||
|
|
||||||
GRANT_URL = "https://accounts.spotify.com/api/token"
|
GRANT_URL = "https://accounts.spotify.com/api/token"
|
||||||
|
|
@ -16,6 +16,7 @@ SPOTIFY_URL_REGEX = re.compile(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
"""The base client for the Spotify module of Pomice.
|
"""The base client for the Spotify module of Pomice.
|
||||||
This class will do all the heavy lifting of getting all the metadata
|
This class will do all the heavy lifting of getting all the metadata
|
||||||
|
|
@ -111,3 +112,32 @@ class Client:
|
||||||
next_page_url = next_data["next"]
|
next_page_url = next_data["next"]
|
||||||
|
|
||||||
return Playlist(data, tracks)
|
return Playlist(data, tracks)
|
||||||
|
|
||||||
|
async def get_recommendations(self, *, query: str):
|
||||||
|
if not self._bearer_token or time.time() >= self._expiry:
|
||||||
|
await self._fetch_bearer_token()
|
||||||
|
|
||||||
|
result = SPOTIFY_URL_REGEX.match(query)
|
||||||
|
spotify_type = result.group("type")
|
||||||
|
spotify_id = result.group("id")
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
raise InvalidSpotifyURL("The Spotify link provided is not valid.")
|
||||||
|
|
||||||
|
if not spotify_type == "track":
|
||||||
|
raise InvalidSpotifyURL("The provided query is not a Spotify track.")
|
||||||
|
|
||||||
|
request_url = REQUEST_URL.format(type="recommendation", id=f"?seed_tracks={spotify_id}")
|
||||||
|
|
||||||
|
async with self.session.get(request_url, headers=self._bearer_headers) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
raise SpotifyRequestException(
|
||||||
|
f"Error while fetching results: {resp.status} {resp.reason}"
|
||||||
|
)
|
||||||
|
|
||||||
|
data: dict = await resp.json(loads=json.loads)
|
||||||
|
|
||||||
|
tracks = [Track(track) for track in data["tracks"]]
|
||||||
|
|
||||||
|
return tracks
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ class Track:
|
||||||
"""The base class for a Spotify Track"""
|
"""The base class for a Spotify Track"""
|
||||||
|
|
||||||
def __init__(self, data: dict, image = None) -> None:
|
def __init__(self, data: dict, image = None) -> None:
|
||||||
self.name = data["name"]
|
self.name: str = data["name"]
|
||||||
self.artists = ", ".join(artist["name"] for artist in data["artists"])
|
self.artists = ", ".join(artist["name"] for artist in data["artists"])
|
||||||
self.length = data["duration_ms"]
|
self.length: float = data["duration_ms"]
|
||||||
self.id = data["id"]
|
self.id: str = data["id"]
|
||||||
|
|
||||||
if data.get("external_ids"):
|
if data.get("external_ids"):
|
||||||
self.isrc = data["external_ids"]["isrc"]
|
self.isrc = data["external_ids"]["isrc"]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue