wrap up apple music, v2 is done
This commit is contained in:
parent
c1a9d7603f
commit
14c82c1b56
|
|
@ -345,9 +345,6 @@ class Music(commands.Cog):
|
||||||
await player.set_volume(vol)
|
await player.set_volume(vol)
|
||||||
await ctx.send(f'Set the volume to **{vol}**%', delete_after=7)
|
await ctx.send(f'Set the volume to **{vol}**%', delete_after=7)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot: commands.Bot):
|
async def setup(bot: commands.Bot):
|
||||||
await bot.add_cog(Music(bot))
|
await bot.add_cog(Music(bot))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
import re
|
import re
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import json
|
import orjson as json
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from .objects import *
|
from .objects import *
|
||||||
|
from .exceptions import *
|
||||||
|
|
||||||
AM_URL_REGEX = re.compile(r"https?://music.apple.com/(?P<country>[a-zA-Z]{2})/(?P<type>album|playlist|song|artist)/(?P<name>.+)/(?P<id>[^?]+)")
|
AM_URL_REGEX = re.compile(r"https?://music.apple.com/(?P<country>[a-zA-Z]{2})/(?P<type>album|playlist|song|artist)/(?P<name>.+)/(?P<id>[^?]+)")
|
||||||
AM_SINGLE_IN_ALBUM_REGEX = re.compile(r"https?://music.apple.com/(?P<country>[a-zA-Z]{2})/(?P<type>album|playlist|song|artist)/(?P<name>.+)/(?P<id>.+)(\?i=)(?P<id2>.+)")
|
AM_SINGLE_IN_ALBUM_REGEX = re.compile(r"https?://music.apple.com/(?P<country>[a-zA-Z]{2})/(?P<type>album|playlist|song|artist)/(?P<name>.+)/(?P<id>.+)(\?i=)(?P<id2>.+)")
|
||||||
AM_REQ_URL = "https://api.music.apple.com/v1/catalog/{country}/{type}s/{id}"
|
AM_REQ_URL = "https://api.music.apple.com/v1/catalog/{country}/{type}s/{id}"
|
||||||
|
AM_BASE_URL = "https://api.music.apple.com"
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
"""The base Apple Music client for Pomice.
|
"""The base Apple Music client for Pomice.
|
||||||
|
|
@ -16,14 +20,17 @@ class Client:
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.token: str = None
|
self.token: str = None
|
||||||
self.origin: str = None
|
self.expiry: datetime = None
|
||||||
self.session: aiohttp.ClientSession = None
|
self.session: aiohttp.ClientSession = aiohttp.ClientSession()
|
||||||
self.headers = None
|
self.headers = None
|
||||||
|
|
||||||
|
|
||||||
async def request_token(self):
|
async def request_token(self):
|
||||||
self.session = aiohttp.ClientSession()
|
|
||||||
async with self.session.get("https://music.apple.com/assets/index.919fe17f.js") as resp:
|
async with self.session.get("https://music.apple.com/assets/index.919fe17f.js") as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
raise AppleMusicRequestException(
|
||||||
|
f"Error while fetching results: {resp.status} {resp.reason}"
|
||||||
|
)
|
||||||
text = await resp.text()
|
text = await resp.text()
|
||||||
result = re.search("\"(eyJ.+?)\"", text).group(1)
|
result = re.search("\"(eyJ.+?)\"", text).group(1)
|
||||||
self.token = result
|
self.token = result
|
||||||
|
|
@ -31,10 +38,14 @@ class Client:
|
||||||
'Authorization': f"Bearer {result}",
|
'Authorization': f"Bearer {result}",
|
||||||
'Origin': 'https://apple.com',
|
'Origin': 'https://apple.com',
|
||||||
}
|
}
|
||||||
|
token_split = self.token.split(".")[1]
|
||||||
|
token_json = base64.b64decode(token_split + '=' * (-len(token_split) % 4)).decode()
|
||||||
|
token_data = json.loads(token_json)
|
||||||
|
self.expiry = datetime.fromtimestamp(token_data["exp"])
|
||||||
|
|
||||||
|
|
||||||
async def search(self, query: str):
|
async def search(self, query: str):
|
||||||
if not self.token:
|
if not self.token or datetime.utcnow() > self.expiry:
|
||||||
await self.request_token()
|
await self.request_token()
|
||||||
|
|
||||||
result = AM_URL_REGEX.match(query)
|
result = AM_URL_REGEX.match(query)
|
||||||
|
|
@ -47,39 +58,66 @@ class Client:
|
||||||
# apple music likes to generate links for singles off an album
|
# apple music likes to generate links for singles off an album
|
||||||
# by adding a param at the end of the url
|
# by adding a param at the end of the url
|
||||||
# so we're gonna scan for that and correct it
|
# so we're gonna scan for that and correct it
|
||||||
|
|
||||||
id = sia_result.group("id2")
|
id = sia_result.group("id2")
|
||||||
type = "song"
|
type = "song"
|
||||||
request_url = AM_REQ_URL.format(country=country, type=type, id=id)
|
request_url = AM_REQ_URL.format(country=country, type=type, id=id)
|
||||||
else:
|
else:
|
||||||
request_url = AM_REQ_URL.format(country=country, type=type, id=id)
|
request_url = AM_REQ_URL.format(country=country, type=type, id=id)
|
||||||
|
|
||||||
print(request_url)
|
|
||||||
|
|
||||||
print(self.token)
|
|
||||||
|
|
||||||
async with self.session.get(request_url, headers=self.headers) as resp:
|
async with self.session.get(request_url, headers=self.headers) as resp:
|
||||||
print(resp.status)
|
if resp.status != 200:
|
||||||
data = await resp.json()
|
raise AppleMusicRequestException(
|
||||||
|
f"Error while fetching results: {resp.status} {resp.reason}"
|
||||||
|
)
|
||||||
|
data: dict = await resp.json(loads=json.loads)
|
||||||
|
|
||||||
with open('yes.txt', 'w') as file:
|
data = data["data"][0]
|
||||||
file.write(json.dumps(data))
|
|
||||||
|
|
||||||
if type == "playlist":
|
|
||||||
return Playlist(data)
|
if type == "song":
|
||||||
|
return Song(data)
|
||||||
|
|
||||||
elif type == "album":
|
elif type == "album":
|
||||||
return Album(data)
|
return Album(data)
|
||||||
|
|
||||||
elif type == "song":
|
|
||||||
return Song(data)
|
|
||||||
|
|
||||||
elif type == "artist":
|
elif type == "artist":
|
||||||
return Artist(data)
|
async with self.session.get(f"{request_url}/view/top-songs", headers=self.headers) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
raise AppleMusicRequestException(
|
||||||
|
f"Error while fetching results: {resp.status} {resp.reason}"
|
||||||
|
)
|
||||||
|
top_tracks: dict = await resp.json(loads=json.loads)
|
||||||
|
tracks: dict = top_tracks["data"]
|
||||||
|
|
||||||
|
return Artist(data, tracks=tracks)
|
||||||
|
|
||||||
|
else:
|
||||||
|
tracks = [Song(track) for track in data["relationships"]["tracks"]["data"]]
|
||||||
|
|
||||||
|
if not len(tracks):
|
||||||
|
raise AppleMusicRequestException("This playlist is empty and therefore cannot be queued.")
|
||||||
|
|
||||||
|
if data["relationships"]["tracks"]["next"]:
|
||||||
|
next_page_url = AM_BASE_URL + data["relationships"]["tracks"]["next"]
|
||||||
|
|
||||||
|
while next_page_url is not None:
|
||||||
|
async with self.session.get(next_page_url, headers=self.headers) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
raise AppleMusicRequestException(
|
||||||
|
f"Error while fetching results: {resp.status} {resp.reason}"
|
||||||
|
)
|
||||||
|
|
||||||
|
next_data: dict = await resp.json(loads=json.loads)
|
||||||
|
|
||||||
|
tracks += [Song(track) for track in next_data["data"]]
|
||||||
|
if next_data.get("next"):
|
||||||
|
next_page_url = AM_BASE_URL + next_data["next"]
|
||||||
|
else:
|
||||||
|
next_page_url = None
|
||||||
|
|
||||||
|
return Playlist(data, tracks)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
await self.session.close()
|
|
||||||
|
|
@ -1,24 +1,41 @@
|
||||||
|
"""Module for managing Apple Music objects"""
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
class Song:
|
class Song:
|
||||||
def __init__(self, data: dict) -> None:
|
"""The base class for an Apple Music song"""
|
||||||
self.track_data = ["data"][0]
|
def __init__(self, data: dict) -> None:
|
||||||
self.name = self.track_data["attributes"]["name"]
|
self.name: str = data["attributes"]["name"]
|
||||||
self.url = self.track_data["atrributes"]["url"]
|
self.url: str = data["attributes"]["url"]
|
||||||
self.isrc = self.track_data["atrributes"]["isrc"]
|
self.isrc: str = data["attributes"]["isrc"]
|
||||||
self.length = self.track_data["atrributes"]["durationInMillis"]
|
self.length: float = data["attributes"]["durationInMillis"]
|
||||||
self.id = self.track_data["id"]
|
self.id: str = data["id"]
|
||||||
self.artists = self.track_data["atrributes"]["artistName"]
|
self.artists: str = data["attributes"]["artistName"]
|
||||||
self.image = self.track_data["atrributes"]["artwork"]["url"].replace("{w}x{h}", f'{self.track_data["atrributes"]["artwork"]["width"]}x{self.track_data["atrributes"]["artwork"]["height"]}')
|
self.image: str = data["attributes"]["artwork"]["url"].replace(
|
||||||
|
"{w}x{h}",
|
||||||
|
f'{data["attributes"]["artwork"]["width"]}x{data["attributes"]["artwork"]["height"]}'
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"<Pomice.applemusic.Track name={self.name} artists={self.artists} "
|
f"<Pomice.applemusic.Song name={self.name} artists={self.artists} "
|
||||||
f"length={self.length} id={self.id} isrc={self.isrc}>"
|
f"length={self.length} id={self.id} isrc={self.isrc}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Playlist:
|
class Playlist:
|
||||||
def __init__(self, data: dict) -> None:
|
"""The base class for an Apple Music playlist"""
|
||||||
pass
|
def __init__(self, data: dict, tracks: List[Song]) -> None:
|
||||||
|
self.name: str = data["attributes"]["name"]
|
||||||
|
self.owner: str = data["attributes"]["curatorName"]
|
||||||
|
self.id: str = data["id"]
|
||||||
|
self.tracks: List[Song] = tracks
|
||||||
|
self.total_tracks: int = len(tracks)
|
||||||
|
self.url: str = data["attributes"]["url"]
|
||||||
|
# we'll use the first song's image as the image for the playlist
|
||||||
|
# because apple dynamically generates playlist covers client-side
|
||||||
|
self.image = self.tracks[0].image
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
|
|
@ -28,8 +45,18 @@ class Playlist:
|
||||||
|
|
||||||
|
|
||||||
class Album:
|
class Album:
|
||||||
|
"""The base class for an Apple Music album"""
|
||||||
def __init__(self, data: dict) -> None:
|
def __init__(self, data: dict) -> None:
|
||||||
pass
|
self.name: str = data["attributes"]["name"]
|
||||||
|
self.url: str = data["attributes"]["url"]
|
||||||
|
self.id: str = data["id"]
|
||||||
|
self.artists: str = data["attributes"]["artistName"]
|
||||||
|
self.total_tracks: int = data["attributes"]["trackCount"]
|
||||||
|
self.tracks: List[Song] = [Song(track) for track in data["relationships"]["tracks"]["data"]]
|
||||||
|
self.image: str = data["attributes"]["artwork"]["url"].replace(
|
||||||
|
"{w}x{h}",
|
||||||
|
f'{data["attributes"]["artwork"]["width"]}x{data["attributes"]["artwork"]["height"]}'
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
|
|
@ -40,8 +67,17 @@ class Album:
|
||||||
|
|
||||||
|
|
||||||
class Artist:
|
class Artist:
|
||||||
def __init__(self, data: dict) -> None:
|
"""The base class for an Apple Music artist"""
|
||||||
pass
|
def __init__(self, data: dict, tracks: dict) -> None:
|
||||||
|
self.name: str = f'Top tracks for {data["attributes"]["name"]}'
|
||||||
|
self.url: str = data["attributes"]["url"]
|
||||||
|
self.id: str = data["id"]
|
||||||
|
self.genres: str = ", ".join(genre for genre in data["attributes"]["genreNames"])
|
||||||
|
self.tracks: List[Song] = [Song(track) for track in tracks]
|
||||||
|
self.image: str = data["attributes"]["artwork"]["url"].replace(
|
||||||
|
"{w}x{h}",
|
||||||
|
f'{data["attributes"]["artwork"]["width"]}x{data["attributes"]["artwork"]["height"]}'
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,11 @@ from discord.ext import commands
|
||||||
from .enums import SearchType
|
from .enums import SearchType
|
||||||
from .filters import Filter
|
from .filters import Filter
|
||||||
|
|
||||||
|
from . import (
|
||||||
|
spotify,
|
||||||
|
applemusic
|
||||||
|
)
|
||||||
|
|
||||||
SOUNDCLOUD_URL_REGEX = re.compile(
|
SOUNDCLOUD_URL_REGEX = re.compile(
|
||||||
r"^(https?:\/\/)?(www.)?(m\.)?soundcloud\.com\/[\w\-\.]+(\/)+[\w\-\.]+/?$"
|
r"^(https?:\/\/)?(www.)?(m\.)?soundcloud\.com\/[\w\-\.]+(\/)+[\w\-\.]+/?$"
|
||||||
)
|
)
|
||||||
|
|
@ -24,8 +29,10 @@ class Track:
|
||||||
info: dict,
|
info: dict,
|
||||||
ctx: Optional[commands.Context] = None,
|
ctx: Optional[commands.Context] = None,
|
||||||
spotify: bool = False,
|
spotify: bool = False,
|
||||||
|
apple_music: bool = False,
|
||||||
|
am_track: applemusic.Song = None,
|
||||||
search_type: SearchType = SearchType.ytsearch,
|
search_type: SearchType = SearchType.ytsearch,
|
||||||
spotify_track = None,
|
spotify_track: 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
|
requester: Optional[Union[Member, User]] = None
|
||||||
|
|
@ -33,12 +40,17 @@ class Track:
|
||||||
self.track_id = track_id
|
self.track_id = track_id
|
||||||
self.info = info
|
self.info = info
|
||||||
self.spotify = spotify
|
self.spotify = spotify
|
||||||
|
self.apple_music = apple_music
|
||||||
self.filters: List[Filter] = filters
|
self.filters: List[Filter] = filters
|
||||||
self.timestamp: Optional[float] = timestamp
|
self.timestamp: Optional[float] = timestamp
|
||||||
|
|
||||||
self.original: Optional[Track] = None if spotify else self
|
if spotify or apple_music:
|
||||||
|
self.original: Optional[Track] = None
|
||||||
|
else:
|
||||||
|
self.original = self
|
||||||
self._search_type = search_type
|
self._search_type = search_type
|
||||||
self.spotify_track = spotify_track
|
self.spotify_track = spotify_track
|
||||||
|
self.am_track = am_track
|
||||||
|
|
||||||
self.title = info.get("title")
|
self.title = info.get("title")
|
||||||
self.author = info.get("author")
|
self.author = info.get("author")
|
||||||
|
|
@ -95,13 +107,17 @@ class Playlist:
|
||||||
tracks: list,
|
tracks: list,
|
||||||
ctx: Optional[commands.Context] = None,
|
ctx: Optional[commands.Context] = None,
|
||||||
spotify: bool = False,
|
spotify: bool = False,
|
||||||
spotify_playlist = None
|
spotify_playlist: spotify.Playlist = None,
|
||||||
|
apple_music: bool = False,
|
||||||
|
am_playlist: applemusic.Playlist = None
|
||||||
):
|
):
|
||||||
self.playlist_info = playlist_info
|
self.playlist_info = playlist_info
|
||||||
self.tracks_raw = tracks
|
self.tracks_raw = tracks
|
||||||
self.spotify = spotify
|
self.spotify = spotify
|
||||||
self.name = playlist_info.get("name")
|
self.name = playlist_info.get("name")
|
||||||
self.spotify_playlist = spotify_playlist
|
self.spotify_playlist = spotify_playlist
|
||||||
|
self.apple_music = apple_music
|
||||||
|
self.am_playlist = am_playlist
|
||||||
|
|
||||||
self._thumbnail = None
|
self._thumbnail = None
|
||||||
self._uri = None
|
self._uri = None
|
||||||
|
|
@ -110,6 +126,12 @@ class Playlist:
|
||||||
self.tracks = tracks
|
self.tracks = tracks
|
||||||
self._thumbnail = self.spotify_playlist.image
|
self._thumbnail = self.spotify_playlist.image
|
||||||
self._uri = self.spotify_playlist.uri
|
self._uri = self.spotify_playlist.uri
|
||||||
|
|
||||||
|
elif self.apple_music:
|
||||||
|
self.tracks = tracks
|
||||||
|
self._thumbnail = self.am_playlist.image
|
||||||
|
self._uri = self.am_playlist.url
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.tracks = [
|
self.tracks = [
|
||||||
Track(track_id=track["track"], info=track["info"], ctx=ctx)
|
Track(track_id=track["track"], info=track["info"], ctx=ctx)
|
||||||
|
|
@ -133,10 +155,10 @@ class Playlist:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def uri(self) -> Optional[str]:
|
def uri(self) -> Optional[str]:
|
||||||
"""Spotify album/playlist URI, or None if not a Spotify object."""
|
"""Returns either an Apple Music/Spotify URL/URI, or None if its neither of those."""
|
||||||
return self._uri
|
return self._uri
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def thumbnail(self) -> Optional[str]:
|
def thumbnail(self) -> Optional[str]:
|
||||||
"""Spotify album/playlist thumbnail, or None if not a Spotify object."""
|
"""Returns either an Apple Music/Spotify album/playlist thumbnail, or None if its neither of those."""
|
||||||
return self._thumbnail
|
return self._thumbnail
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,11 @@ class Filters:
|
||||||
"""Property which checks if any applied filters were preloaded"""
|
"""Property which checks if any applied filters were preloaded"""
|
||||||
return any(f for f in self._filters if f.preload == True)
|
return any(f for f in self._filters if f.preload == True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_global(self):
|
||||||
|
"""Property which checks if any applied filters are global"""
|
||||||
|
return any(f for f in self._filters if f.preload == False)
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def empty(self):
|
def empty(self):
|
||||||
|
|
|
||||||
|
|
@ -380,7 +380,61 @@ class Node:
|
||||||
"Please set apple_music to True in your Node class."
|
"Please set apple_music to True in your Node class."
|
||||||
)
|
)
|
||||||
|
|
||||||
await self._apple_music_client.search(query=query)
|
apple_music_results = await self._apple_music_client.search(query=query)
|
||||||
|
if isinstance(apple_music_results, applemusic.Song):
|
||||||
|
return [
|
||||||
|
Track(
|
||||||
|
track_id=apple_music_results.id,
|
||||||
|
ctx=ctx,
|
||||||
|
search_type=search_type,
|
||||||
|
apple_music=True,
|
||||||
|
am_track=apple_music_results,
|
||||||
|
filters=filters,
|
||||||
|
info={
|
||||||
|
"title": apple_music_results.name,
|
||||||
|
"author": apple_music_results.artists,
|
||||||
|
"length": apple_music_results.length,
|
||||||
|
"identifier": apple_music_results.id,
|
||||||
|
"uri": apple_music_results.url,
|
||||||
|
"isStream": False,
|
||||||
|
"isSeekable": True,
|
||||||
|
"position": 0,
|
||||||
|
"thumbnail": apple_music_results.image,
|
||||||
|
"isrc": apple_music_results.isrc
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
tracks = [
|
||||||
|
Track(
|
||||||
|
track_id=track.id,
|
||||||
|
ctx=ctx,
|
||||||
|
search_type=search_type,
|
||||||
|
apple_music=True,
|
||||||
|
am_track=track,
|
||||||
|
filters=filters,
|
||||||
|
info={
|
||||||
|
"title": track.name,
|
||||||
|
"author": track.artists,
|
||||||
|
"length": track.length,
|
||||||
|
"identifier": track.id,
|
||||||
|
"uri": track.url,
|
||||||
|
"isStream": False,
|
||||||
|
"isSeekable": True,
|
||||||
|
"position": 0,
|
||||||
|
"thumbnail": track.image,
|
||||||
|
"isrc": track.isrc
|
||||||
|
}
|
||||||
|
) for track in apple_music_results.tracks
|
||||||
|
]
|
||||||
|
|
||||||
|
return Playlist(
|
||||||
|
playlist_info={"name": apple_music_results.name, "selectedTrack": 0},
|
||||||
|
tracks=tracks,
|
||||||
|
ctx=ctx,
|
||||||
|
apple_music=True,
|
||||||
|
am_playlist=apple_music_results
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
elif SPOTIFY_URL_REGEX.match(query):
|
elif SPOTIFY_URL_REGEX.match(query):
|
||||||
|
|
|
||||||
|
|
@ -6,24 +6,24 @@ class Track:
|
||||||
|
|
||||||
def __init__(self, data: dict, image = None) -> None:
|
def __init__(self, data: dict, image = None) -> None:
|
||||||
self.name: str = data["name"]
|
self.name: str = data["name"]
|
||||||
self.artists = ", ".join(artist["name"] for artist in data["artists"])
|
self.artists: str = ", ".join(artist["name"] for artist in data["artists"])
|
||||||
self.length: float = data["duration_ms"]
|
self.length: float = data["duration_ms"]
|
||||||
self.id: str = 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: str = data["external_ids"]["isrc"]
|
||||||
else:
|
else:
|
||||||
self.isrc = None
|
self.isrc = None
|
||||||
|
|
||||||
if data.get("album") and data["album"].get("images"):
|
if data.get("album") and data["album"].get("images"):
|
||||||
self.image = data["album"]["images"][0]["url"]
|
self.image: str = data["album"]["images"][0]["url"]
|
||||||
else:
|
else:
|
||||||
self.image = image
|
self.image: str = image
|
||||||
|
|
||||||
if data["is_local"]:
|
if data["is_local"]:
|
||||||
self.uri = None
|
self.uri = None
|
||||||
else:
|
else:
|
||||||
self.uri = data["external_urls"]["spotify"]
|
self.uri: str = data["external_urls"]["spotify"]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
|
|
@ -35,13 +35,13 @@ class Playlist:
|
||||||
"""The base class for a Spotify playlist"""
|
"""The base class for a Spotify playlist"""
|
||||||
|
|
||||||
def __init__(self, data: dict, tracks: List[Track]) -> None:
|
def __init__(self, data: dict, tracks: List[Track]) -> None:
|
||||||
self.name = data["name"]
|
self.name: str = data["name"]
|
||||||
self.tracks = tracks
|
self.tracks = tracks
|
||||||
self.owner = data["owner"]["display_name"]
|
self.owner: str = data["owner"]["display_name"]
|
||||||
self.total_tracks = data["tracks"]["total"]
|
self.total_tracks: int = data["tracks"]["total"]
|
||||||
self.id = data["id"]
|
self.id: str = data["id"]
|
||||||
if data.get("images") and len(data["images"]):
|
if data.get("images") and len(data["images"]):
|
||||||
self.image = data["images"][0]["url"]
|
self.image: str = data["images"][0]["url"]
|
||||||
else:
|
else:
|
||||||
self.image = None
|
self.image = None
|
||||||
self.uri = data["external_urls"]["spotify"]
|
self.uri = data["external_urls"]["spotify"]
|
||||||
|
|
@ -56,13 +56,13 @@ class Album:
|
||||||
"""The base class for a Spotify album"""
|
"""The base class for a Spotify album"""
|
||||||
|
|
||||||
def __init__(self, data: dict) -> None:
|
def __init__(self, data: dict) -> None:
|
||||||
self.name = data["name"]
|
self.name: str = data["name"]
|
||||||
self.artists = ", ".join(artist["name"] for artist in data["artists"])
|
self.artists: str = ", ".join(artist["name"] for artist in data["artists"])
|
||||||
self.image = data["images"][0]["url"]
|
self.image: str = data["images"][0]["url"]
|
||||||
self.tracks = [Track(track, image=self.image) for track in data["tracks"]["items"]]
|
self.tracks = [Track(track, image=self.image) for track in data["tracks"]["items"]]
|
||||||
self.total_tracks = data["total_tracks"]
|
self.total_tracks: int = data["total_tracks"]
|
||||||
self.id = data["id"]
|
self.id: str = data["id"]
|
||||||
self.uri = data["external_urls"]["spotify"]
|
self.uri: str = data["external_urls"]["spotify"]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
|
|
@ -74,13 +74,13 @@ class Artist:
|
||||||
"""The base class for a Spotify artist"""
|
"""The base class for a Spotify artist"""
|
||||||
|
|
||||||
def __init__(self, data: dict, tracks: dict) -> None:
|
def __init__(self, data: dict, tracks: dict) -> None:
|
||||||
self.name = f"Top tracks for {data['name']}" # Setting that because its only playing top tracks
|
self.name: str = f"Top tracks for {data['name']}" # Setting that because its only playing top tracks
|
||||||
self.genres = ", ".join(genre for genre in data["genres"])
|
self.genres: str = ", ".join(genre for genre in data["genres"])
|
||||||
self.followers = data["followers"]["total"]
|
self.followers: int = data["followers"]["total"]
|
||||||
self.image = data["images"][0]["url"]
|
self.image: str = data["images"][0]["url"]
|
||||||
self.tracks = [Track(track, image=self.image) for track in tracks]
|
self.tracks = [Track(track, image=self.image) for track in tracks]
|
||||||
self.id = data["id"]
|
self.id: str = data["id"]
|
||||||
self.uri = data["external_urls"]["spotify"]
|
self.uri: str = data["external_urls"]["spotify"]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue