fully objectified model
This commit is contained in:
parent
2f840f879b
commit
0bd9fac8f4
@ -1,21 +0,0 @@
|
||||
# This file specifies files that are *not* uploaded to Google Cloud Platform
|
||||
# using gcloud. It follows the same syntax as .gitignore, with the addition of
|
||||
# "#!include" directives (which insert the entries of the given .gitignore-style
|
||||
# file at that point).
|
||||
#
|
||||
# For more information, run:
|
||||
# $ gcloud topic gcloudignore
|
||||
#
|
||||
.gcloudignore
|
||||
# If you would like to upload your .git directory, .gitignore file or files
|
||||
# from your .gitignore file, remove the corresponding line
|
||||
# below:
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
env
|
||||
.idea
|
||||
.spot
|
||||
|
||||
node_modules
|
||||
#!include:.gitignore
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,3 +3,5 @@ __pycache__
|
||||
*.csv
|
||||
.idea
|
||||
.spot
|
||||
|
||||
scratch.py
|
10
alarm.py
10
alarm.py
@ -1,4 +1,4 @@
|
||||
from spotframework.net.user import User
|
||||
from spotframework.net.user import NetworkUser
|
||||
from spotframework.net.network import Network
|
||||
import spotframework.net.const as const
|
||||
import spotframework.io.json as json
|
||||
@ -33,10 +33,10 @@ if __name__ == '__main__':
|
||||
|
||||
try:
|
||||
|
||||
network = Network(User(os.environ['SPOTCLIENT'],
|
||||
os.environ['SPOTSECRET'],
|
||||
os.environ['SPOTACCESS'],
|
||||
os.environ['SPOTREFRESH']))
|
||||
network = Network(NetworkUser(os.environ['SPOTCLIENT'],
|
||||
os.environ['SPOTSECRET'],
|
||||
os.environ['SPOTACCESS'],
|
||||
os.environ['SPOTREFRESH']))
|
||||
|
||||
found = False
|
||||
|
||||
|
12
backup.py
12
backup.py
@ -1,4 +1,4 @@
|
||||
from spotframework.net.user import User
|
||||
from spotframework.net.user import NetworkUser
|
||||
from spotframework.net.network import Network
|
||||
import spotframework.io.csv as csvwrite
|
||||
|
||||
@ -21,14 +21,14 @@ if __name__ == '__main__':
|
||||
|
||||
try:
|
||||
|
||||
network = Network(User(os.environ['SPOTCLIENT'],
|
||||
os.environ['SPOTSECRET'],
|
||||
os.environ['SPOTACCESS'],
|
||||
os.environ['SPOTREFRESH']))
|
||||
network = Network(NetworkUser(os.environ['SPOTCLIENT'],
|
||||
os.environ['SPOTSECRET'],
|
||||
os.environ['SPOTACCESS'],
|
||||
os.environ['SPOTREFRESH']))
|
||||
playlists = network.get_user_playlists()
|
||||
|
||||
for playlist in playlists:
|
||||
playlist.tracks = network.get_playlist_tracks(playlist.playlistid)
|
||||
playlist.tracks = network.get_playlist_tracks(playlist.playlist_id)
|
||||
|
||||
path = sys.argv[1]
|
||||
|
||||
|
@ -1,13 +1,12 @@
|
||||
import spotframework.net.const as const
|
||||
from spotframework.net.network import Network
|
||||
from spotframework.net.user import User
|
||||
from spotframework.net.user import NetworkUser
|
||||
import spotframework.io.json as json
|
||||
import spotframework.util.monthstrings as monthstrings
|
||||
from spotframework.engine.playlistengine import PlaylistEngine
|
||||
from spotframework.engine.filter.shuffle import Shuffle
|
||||
from spotframework.engine.filter.sortreversereleasedate import SortReverseReleaseDate
|
||||
from spotframework.engine.filter.deduplicatebyid import DeduplicateByID
|
||||
from spotframework.engine.filter.deduplicatebyname import DeduplicateByName
|
||||
from spotframework.engine.filter.sort import SortReverseReleaseDate
|
||||
from spotframework.engine.filter.deduplicate import DeduplicateByID, DeduplicateByName
|
||||
|
||||
import os
|
||||
import datetime
|
||||
@ -115,10 +114,10 @@ def go():
|
||||
logger.critical('none to execute, terminating')
|
||||
return
|
||||
|
||||
net = Network(User(os.environ['SPOTCLIENT'],
|
||||
os.environ['SPOTSECRET'],
|
||||
os.environ['SPOTACCESS'],
|
||||
os.environ['SPOTREFRESH']))
|
||||
net = Network(NetworkUser(os.environ['SPOTCLIENT'],
|
||||
os.environ['SPOTSECRET'],
|
||||
os.environ['SPOTACCESS'],
|
||||
os.environ['SPOTREFRESH']))
|
||||
|
||||
engine = PlaylistEngine(net)
|
||||
engine.load_user_playlists()
|
||||
|
@ -1,13 +1,13 @@
|
||||
from spotframework.net.user import User
|
||||
from spotframework.net.user import NetworkUser
|
||||
from spotframework.net.network import Network
|
||||
|
||||
import os
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
network = Network(User(os.environ['SPOTCLIENT'],
|
||||
os.environ['SPOTSECRET'],
|
||||
os.environ['SPOTACCESS'],
|
||||
os.environ['SPOTREFRESH']))
|
||||
network = Network(NetworkUser(os.environ['SPOTCLIENT'],
|
||||
os.environ['SPOTSECRET'],
|
||||
os.environ['SPOTACCESS'],
|
||||
os.environ['SPOTREFRESH']))
|
||||
|
||||
print(network.user.access_token)
|
||||
|
47
spotframework/engine/filter/abstract.py
Normal file
47
spotframework/engine/filter/abstract.py
Normal file
@ -0,0 +1,47 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
from spotframework.model.track import Track
|
||||
|
||||
|
||||
class AbstractProcessor(ABC):
|
||||
|
||||
def __init__(self, names: List[str] = None):
|
||||
self.playlist_names = names
|
||||
|
||||
def has_targets(self):
|
||||
if self.playlist_names:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
def process(self, tracks: List[Track]):
|
||||
pass
|
||||
|
||||
|
||||
class AbstractTestFilter(AbstractProcessor, ABC):
|
||||
|
||||
def __init__(self,
|
||||
names: List[str] = None,
|
||||
keep_failed: bool = True):
|
||||
super().__init__(names)
|
||||
self.keep_failed = keep_failed
|
||||
|
||||
@abstractmethod
|
||||
def logic_test(self, track: Track):
|
||||
pass
|
||||
|
||||
def process(self, tracks: List[Track]):
|
||||
|
||||
return_tracks = []
|
||||
malformed_tracks = []
|
||||
|
||||
for track in tracks:
|
||||
if self.logic_test(track):
|
||||
return_tracks.append(track)
|
||||
else:
|
||||
malformed_tracks.append(track)
|
||||
if self.keep_failed:
|
||||
return_tracks += malformed_tracks
|
||||
|
||||
return return_tracks
|
@ -1,11 +0,0 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class AbstractProcessor(ABC):
|
||||
|
||||
def __init__(self, names=[]):
|
||||
self.playlist_names = names
|
||||
|
||||
@abstractmethod
|
||||
def process(self, tracks):
|
||||
pass
|
47
spotframework/engine/filter/added.py
Normal file
47
spotframework/engine/filter/added.py
Normal file
@ -0,0 +1,47 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from .abstract import AbstractProcessor
|
||||
import datetime
|
||||
from typing import List
|
||||
from spotframework.model.track import Track, PlaylistTrack
|
||||
|
||||
|
||||
class Added(AbstractProcessor, ABC):
|
||||
|
||||
def __init__(self,
|
||||
boundary: datetime.datetime,
|
||||
names: List[str] = None,
|
||||
keep_malformed_type: bool = True):
|
||||
super().__init__(names)
|
||||
self.boundary = boundary
|
||||
self.keep_malformed_type = keep_malformed_type
|
||||
|
||||
@abstractmethod
|
||||
def check_date(self, track: PlaylistTrack):
|
||||
pass
|
||||
|
||||
def process(self, tracks: List[Track]):
|
||||
|
||||
return_tracks = []
|
||||
malformed_tracks = []
|
||||
|
||||
for track in tracks:
|
||||
if isinstance(track, PlaylistTrack):
|
||||
if self.check_date(track):
|
||||
return_tracks.append(track)
|
||||
else:
|
||||
malformed_tracks.append(track)
|
||||
|
||||
if self.keep_malformed_type:
|
||||
return_tracks += malformed_tracks
|
||||
|
||||
return return_tracks
|
||||
|
||||
|
||||
class AddedBefore(Added):
|
||||
def check_date(self, track: PlaylistTrack):
|
||||
return track.added_at < self.boundary
|
||||
|
||||
|
||||
class AddedSince(Added):
|
||||
def check_date(self, track: PlaylistTrack):
|
||||
return track.added_at > self.boundary
|
@ -1,17 +0,0 @@
|
||||
from .abstractprocessor import AbstractProcessor
|
||||
import datetime
|
||||
|
||||
|
||||
class AddedBefore(AbstractProcessor):
|
||||
|
||||
def __init__(self, boundary, names=[]):
|
||||
super().__init__(names)
|
||||
self.boundary = boundary
|
||||
|
||||
def check_date(self, track):
|
||||
added_at = datetime.datetime.fromisoformat(track['added_at'].replace('T', ' ').replace('Z', ''))
|
||||
|
||||
return added_at < self.boundary
|
||||
|
||||
def process(self, tracks):
|
||||
return [i for i in tracks if self.check_date(i)]
|
@ -1,17 +0,0 @@
|
||||
from .abstractprocessor import AbstractProcessor
|
||||
import datetime
|
||||
|
||||
|
||||
class AddedSince(AbstractProcessor):
|
||||
|
||||
def __init__(self, boundary, names=[]):
|
||||
super().__init__(names)
|
||||
self.boundary = boundary
|
||||
|
||||
def check_date(self, track):
|
||||
added_at = datetime.datetime.fromisoformat(track['added_at'].replace('T', ' ').replace('Z', ''))
|
||||
|
||||
return added_at > self.boundary
|
||||
|
||||
def process(self, tracks):
|
||||
return [i for i in tracks if self.check_date(i)]
|
45
spotframework/engine/filter/deduplicate.py
Normal file
45
spotframework/engine/filter/deduplicate.py
Normal file
@ -0,0 +1,45 @@
|
||||
from spotframework.engine.filter.abstract import AbstractProcessor
|
||||
from typing import List
|
||||
from spotframework.model.track import Track, SpotifyTrack
|
||||
|
||||
|
||||
class DeduplicateByID(AbstractProcessor):
|
||||
|
||||
def __init__(self,
|
||||
names: List[str] = None,
|
||||
keep_malformed_type: bool = True):
|
||||
super().__init__(names)
|
||||
self.keep_malformed_type = keep_malformed_type
|
||||
|
||||
def process(self, tracks: List[Track]):
|
||||
return_tracks = []
|
||||
malformed_tracks = []
|
||||
|
||||
for track in tracks:
|
||||
if isinstance(track, SpotifyTrack):
|
||||
if track.uri not in [i.uri for i in return_tracks]:
|
||||
return_tracks.append(track)
|
||||
else:
|
||||
malformed_tracks.append(track)
|
||||
|
||||
if self.keep_malformed_type:
|
||||
return_tracks += malformed_tracks
|
||||
|
||||
return return_tracks
|
||||
|
||||
|
||||
class DeduplicateByName(AbstractProcessor):
|
||||
|
||||
def process(self, tracks: List[Track]):
|
||||
return_tracks = []
|
||||
|
||||
for to_check in tracks:
|
||||
|
||||
for cache_track in return_tracks:
|
||||
if to_check.name.lower() == cache_track.name.lower():
|
||||
if to_check.artists[0].name.lower() == cache_track.artists[0].name.lower():
|
||||
break
|
||||
else:
|
||||
return_tracks.append(to_check)
|
||||
|
||||
return return_tracks
|
@ -1,13 +0,0 @@
|
||||
from .abstractprocessor import AbstractProcessor
|
||||
|
||||
|
||||
class DeduplicateByID(AbstractProcessor):
|
||||
|
||||
def process(self, tracks):
|
||||
return_tracks = []
|
||||
|
||||
for track in tracks:
|
||||
if track['track']['uri'] not in [i['track']['uri'] for i in return_tracks]:
|
||||
return_tracks.append(track)
|
||||
|
||||
return return_tracks
|
@ -1,19 +0,0 @@
|
||||
from .abstractprocessor import AbstractProcessor
|
||||
|
||||
|
||||
class DeduplicateByName(AbstractProcessor):
|
||||
|
||||
def process(self, tracks):
|
||||
return_tracks = []
|
||||
|
||||
for to_check in tracks:
|
||||
|
||||
for cache_track in return_tracks:
|
||||
if to_check['track']['name'].lower() == cache_track['track']['name'].lower():
|
||||
if to_check['track']['artists'][0]['name'].lower() \
|
||||
== cache_track['track']['artists'][0]['name'].lower():
|
||||
break
|
||||
else:
|
||||
return_tracks.append(to_check)
|
||||
|
||||
return return_tracks
|
@ -1,17 +0,0 @@
|
||||
from .abstractprocessor import AbstractProcessor
|
||||
|
||||
import random
|
||||
|
||||
|
||||
class RandomSample(AbstractProcessor):
|
||||
|
||||
def __init__(self, sample_size, names=[]):
|
||||
super().__init__(names)
|
||||
self.sample_size = sample_size
|
||||
|
||||
def process(self, tracks):
|
||||
|
||||
return_tracks = list(tracks)
|
||||
random.shuffle(return_tracks)
|
||||
|
||||
return return_tracks[:self.sample_size]
|
@ -1,9 +1,23 @@
|
||||
from .abstractprocessor import AbstractProcessor
|
||||
from .abstract import AbstractProcessor
|
||||
import random
|
||||
from typing import List
|
||||
from spotframework.model.track import Track
|
||||
|
||||
|
||||
class Shuffle(AbstractProcessor):
|
||||
|
||||
def process(self, tracks):
|
||||
def process(self, tracks: List[Track]):
|
||||
random.shuffle(tracks)
|
||||
return tracks
|
||||
|
||||
|
||||
class RandomSample(Shuffle):
|
||||
|
||||
def __init__(self,
|
||||
sample_size: int,
|
||||
names: List[str] = None):
|
||||
super().__init__(names)
|
||||
self.sample_size = sample_size
|
||||
|
||||
def process(self, tracks: List[Track]):
|
||||
return super().process(tracks)[:self.sample_size]
|
||||
|
31
spotframework/engine/filter/sort.py
Normal file
31
spotframework/engine/filter/sort.py
Normal file
@ -0,0 +1,31 @@
|
||||
from .abstract import AbstractProcessor
|
||||
from typing import List
|
||||
from spotframework.model.track import Track
|
||||
|
||||
|
||||
class SortReverseReleaseDate(AbstractProcessor):
|
||||
|
||||
def process(self, tracks: List[Track]):
|
||||
tracks.sort(key=lambda x: x.album.release_date, reverse=True)
|
||||
return tracks
|
||||
|
||||
|
||||
class SortReleaseDate(AbstractProcessor):
|
||||
|
||||
def process(self, tracks: List[Track]):
|
||||
tracks.sort(key=lambda x: x.album.release_date, reverse=False)
|
||||
return tracks
|
||||
|
||||
|
||||
class SortArtistName(AbstractProcessor):
|
||||
|
||||
def process(self, tracks: List[Track]):
|
||||
tracks.sort(key=lambda x: x.artists[0].name, reverse=False)
|
||||
return tracks
|
||||
|
||||
|
||||
class SortReverseArtistName(AbstractProcessor):
|
||||
|
||||
def process(self, tracks: List[Track]):
|
||||
tracks.sort(key=lambda x: x.artists[0].name, reverse=True)
|
||||
return tracks
|
@ -1,8 +0,0 @@
|
||||
from .abstractprocessor import AbstractProcessor
|
||||
|
||||
|
||||
class SortReverseReleaseDate(AbstractProcessor):
|
||||
|
||||
def process(self, tracks):
|
||||
tracks.sort(key=lambda x: x['track']['album']['release_date'], reverse=True)
|
||||
return tracks
|
@ -3,14 +3,21 @@ import os
|
||||
import logging
|
||||
|
||||
import spotframework.util.monthstrings as monthstrings
|
||||
from spotframework.engine.filter.addedsince import AddedSince
|
||||
from spotframework.engine.filter.added import AddedSince
|
||||
|
||||
from typing import List
|
||||
from spotframework.model.track import SpotifyTrack
|
||||
from spotframework.model.playlist import Playlist
|
||||
from spotframework.net.network import Network
|
||||
from spotframework.engine.filter.abstract import AbstractProcessor
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PlaylistEngine:
|
||||
|
||||
def __init__(self, net):
|
||||
def __init__(self, net: Network):
|
||||
self.playlists = []
|
||||
self.net = net
|
||||
|
||||
@ -32,16 +39,24 @@ class PlaylistEngine:
|
||||
else:
|
||||
logger.error('error getting playlists')
|
||||
|
||||
def get_playlist_tracks(self, playlist):
|
||||
def get_playlist_tracks(self,
|
||||
playlist: Playlist):
|
||||
logger.info(f"pulling tracks for {playlist.name}")
|
||||
|
||||
tracks = self.net.get_playlist_tracks(playlist.playlistid)
|
||||
tracks = self.net.get_playlist_tracks(playlist.playlist_id)
|
||||
if tracks and len(tracks) > 0:
|
||||
playlist.tracks = tracks
|
||||
else:
|
||||
logger.error('error getting tracks')
|
||||
|
||||
def make_playlist(self, playlist_parts, processors=[], include_recommendations=False, recommendation_limit=10):
|
||||
def make_playlist(self,
|
||||
playlist_parts: List[str],
|
||||
processors: List[AbstractProcessor] = None,
|
||||
include_recommendations: bool = False,
|
||||
recommendation_limit: int = 10):
|
||||
|
||||
if processors is None:
|
||||
processors = []
|
||||
|
||||
tracks = []
|
||||
|
||||
@ -56,39 +71,41 @@ class PlaylistEngine:
|
||||
|
||||
playlist_tracks = list(play.tracks)
|
||||
|
||||
for processor in [i for i in processors if play.name in [j for j in i.playlist_names]]:
|
||||
playlist_tracks = processor.process(playlist_tracks)
|
||||
for processor in [i for i in processors if i.has_targets()]:
|
||||
if play.name in [i for i in processor.playlist_names]:
|
||||
playlist_tracks = processor.process(playlist_tracks)
|
||||
|
||||
tracks += [i for i in playlist_tracks if i['is_local'] is False]
|
||||
tracks += [i for i in playlist_tracks if i.is_local is False]
|
||||
|
||||
else:
|
||||
logger.warning(f"requested playlist {part} not found")
|
||||
if 'SLACKHOOK' in os.environ:
|
||||
requests.post(os.environ['SLACKHOOK'], json={"text": f"spot playlists: {part} not found"})
|
||||
|
||||
for processor in [i for i in processors if len(i.playlist_names) <= 0]:
|
||||
for processor in [i for i in processors if i.has_targets() is False]:
|
||||
tracks = processor.process(tracks)
|
||||
|
||||
tracks = [i['track'] for i in tracks]
|
||||
|
||||
if include_recommendations:
|
||||
recommendations = self.net.get_recommendations(tracks=[i['id'] for i in tracks],
|
||||
recommendations = self.net.get_recommendations(tracks=[i.spotify_id for i in tracks],
|
||||
response_limit=recommendation_limit)
|
||||
if recommendations and len(recommendations) > 0:
|
||||
tracks += recommendations['tracks']
|
||||
tracks += recommendations
|
||||
else:
|
||||
logger.error('error getting recommendations')
|
||||
|
||||
return tracks
|
||||
|
||||
def get_recent_playlist(self,
|
||||
boundary_date,
|
||||
recent_playlist_parts,
|
||||
processors=[],
|
||||
include_recommendations=False,
|
||||
recommendation_limit=10,
|
||||
add_this_month=False,
|
||||
add_last_month=False):
|
||||
boundary_date: datetime,
|
||||
recent_playlist_parts: List[str],
|
||||
processors: List[AbstractProcessor] = None,
|
||||
include_recommendations: bool = False,
|
||||
recommendation_limit: int = 10,
|
||||
add_this_month: bool = False,
|
||||
add_last_month: bool = False):
|
||||
|
||||
if processors is None:
|
||||
processors = []
|
||||
|
||||
this_month = monthstrings.get_this_month()
|
||||
last_month = monthstrings.get_last_month()
|
||||
@ -110,16 +127,22 @@ class PlaylistEngine:
|
||||
include_recommendations=include_recommendations,
|
||||
recommendation_limit=recommendation_limit)
|
||||
|
||||
def execute_playlist(self, tracks, playlist_id):
|
||||
def execute_playlist(self,
|
||||
tracks: List[SpotifyTrack],
|
||||
playlist_id: str):
|
||||
|
||||
resp = self.net.replace_playlist_tracks(playlist_id, [i['uri'] for i in tracks])
|
||||
resp = self.net.replace_playlist_tracks(playlist_id, [i.uri for i in tracks])
|
||||
if resp:
|
||||
return resp
|
||||
else:
|
||||
logger.error('error executing')
|
||||
return None
|
||||
|
||||
def change_description(self, playlistparts, playlist_id, overwrite=None, suffix=None):
|
||||
def change_description(self,
|
||||
playlistparts: List[str],
|
||||
playlist_id: str,
|
||||
overwrite: bool = None,
|
||||
suffix: str = None):
|
||||
|
||||
if overwrite:
|
||||
string = overwrite
|
||||
|
@ -24,15 +24,15 @@ def export_playlist(playlist, path, name=None):
|
||||
for track in playlist.tracks:
|
||||
|
||||
trackdict = {
|
||||
'name':track['track']['name'],
|
||||
'album':track['track']['album']['name'],
|
||||
'added':track['added_at'],
|
||||
'track id':track['track']['id'],
|
||||
'album id':track['track']['album']['id'],
|
||||
'added by':track['added_by']['id']}
|
||||
'name':track.name,
|
||||
'album':track.album.name,
|
||||
'added':track.added_at,
|
||||
'track id':track.spotify_id,
|
||||
'album id':track.album.spotify_id,
|
||||
'added by':track.added_by.username}
|
||||
|
||||
trackdict['album artist'] = ', '.join(x['name'] for x in track['track']['album']['artists'])
|
||||
trackdict['album artist'] = ', '.join(x.name for x in track.album.artists)
|
||||
|
||||
trackdict['artist'] = ', '.join(x['name'] for x in track['track']['artists'])
|
||||
trackdict['artist'] = ', '.join(x.name for x in track.artists)
|
||||
|
||||
writer.writerow(trackdict)
|
||||
|
50
spotframework/model/album.py
Normal file
50
spotframework/model/album.py
Normal file
@ -0,0 +1,50 @@
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import List
|
||||
if TYPE_CHECKING:
|
||||
from spotframework.model.artist import Artist
|
||||
|
||||
|
||||
class Album:
|
||||
def __init__(self, name: str, artists: List[Artist]):
|
||||
self.name = name
|
||||
self.artists = artists
|
||||
|
||||
def __str__(self):
|
||||
artists = ' , '.join([i.name for i in self.artists]) if self.artists else 'n/a'
|
||||
|
||||
return f'{self.name} / {artists}'
|
||||
|
||||
|
||||
class SpotifyAlbum(Album):
|
||||
def __init__(self,
|
||||
name: str,
|
||||
artists: List[Artist],
|
||||
|
||||
href: str = None,
|
||||
spotify_id: str = None,
|
||||
uri: str = None,
|
||||
|
||||
genres: List[str] = None,
|
||||
tracks: List = None,
|
||||
|
||||
release_date: str = None,
|
||||
release_date_precision: str = None,
|
||||
|
||||
label: str = None,
|
||||
popularity: int = None
|
||||
):
|
||||
super().__init__(name, artists)
|
||||
|
||||
self.href = href
|
||||
self.spotify_id = spotify_id
|
||||
self.uri = uri
|
||||
|
||||
self.genres = genres
|
||||
self.tracks = tracks
|
||||
|
||||
self.release_date = release_date
|
||||
self.release_date_precision = release_date_precision
|
||||
|
||||
self.label = label
|
||||
self.popularity = popularity
|
32
spotframework/model/artist.py
Normal file
32
spotframework/model/artist.py
Normal file
@ -0,0 +1,32 @@
|
||||
from typing import List
|
||||
|
||||
|
||||
class Artist:
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name}'
|
||||
|
||||
|
||||
class SpotifyArtist(Artist):
|
||||
def __init__(self,
|
||||
name: str,
|
||||
|
||||
href: str = None,
|
||||
spotify_id: str = None,
|
||||
uri: str = None,
|
||||
|
||||
genres: List[str] = None,
|
||||
|
||||
popularity: int = None
|
||||
):
|
||||
super().__init__(name)
|
||||
|
||||
self.href = href
|
||||
self.spotify_id = spotify_id
|
||||
self.uri = uri
|
||||
|
||||
self.genres = genres
|
||||
|
||||
self.popularity = popularity
|
@ -1,14 +1,35 @@
|
||||
from spotframework.model.user import User
|
||||
|
||||
|
||||
class Playlist:
|
||||
|
||||
def __init__(self, playlistid, uri=None, name=None, userid=None):
|
||||
def __init__(self,
|
||||
playlistid: str,
|
||||
|
||||
name: str = None,
|
||||
owner: User = None,
|
||||
description: str = None,
|
||||
|
||||
href: str = None,
|
||||
uri: str = None,
|
||||
|
||||
collaborative: bool = None,
|
||||
public: bool = None,
|
||||
ext_spotify: str = None):
|
||||
self.tracks = []
|
||||
self.name = name
|
||||
self.playlistid = playlistid
|
||||
self.userid = userid
|
||||
|
||||
self.playlist_id = playlistid
|
||||
self.owner = owner
|
||||
self.description = description
|
||||
|
||||
self.href = href
|
||||
self.uri = uri
|
||||
|
||||
self.collaborative = collaborative
|
||||
self.public = public
|
||||
self.ext_spotify = ext_spotify
|
||||
|
||||
def has_tracks(self):
|
||||
if len(self.tracks) > 0:
|
||||
return True
|
||||
|
100
spotframework/model/track.py
Normal file
100
spotframework/model/track.py
Normal file
@ -0,0 +1,100 @@
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import List
|
||||
from datetime import datetime
|
||||
if TYPE_CHECKING:
|
||||
from spotframework.model.album import Album
|
||||
from spotframework.model.artist import Artist
|
||||
from spotframework.model.user import User
|
||||
|
||||
|
||||
class Track:
|
||||
def __init__(self,
|
||||
name: str,
|
||||
album: Album,
|
||||
artists: List[Artist],
|
||||
|
||||
disc_number: int = None,
|
||||
duration_ms: int = None,
|
||||
excplicit: bool = None
|
||||
):
|
||||
self.name = name
|
||||
self.album = album
|
||||
self.artists = artists
|
||||
|
||||
self.disc_number = disc_number
|
||||
self.duration_ms = duration_ms
|
||||
self.explicit = excplicit
|
||||
|
||||
def __str__(self):
|
||||
album = self.album.name if self.album else 'n/a'
|
||||
artists = ' , '.join([i.name for i in self.artists]) if self.artists else 'n/a'
|
||||
|
||||
return f'{self.name} / {album} / {artists}'
|
||||
|
||||
|
||||
class SpotifyTrack(Track):
|
||||
def __init__(self,
|
||||
name: str,
|
||||
album: Album,
|
||||
artists: List[Artist],
|
||||
|
||||
href: str = None,
|
||||
spotify_id: str = None,
|
||||
uri: str = None,
|
||||
|
||||
disc_number: int = None,
|
||||
duration_ms: int = None,
|
||||
explicit: bool = None,
|
||||
is_playable: bool = None,
|
||||
|
||||
popularity: int = None
|
||||
):
|
||||
super().__init__(name=name, album=album, artists=artists,
|
||||
disc_number=disc_number,
|
||||
duration_ms=duration_ms,
|
||||
excplicit=explicit)
|
||||
|
||||
self.href = href
|
||||
self.spotify_id = spotify_id
|
||||
self.uri = uri
|
||||
self.is_playable = is_playable
|
||||
|
||||
self.popularity = popularity
|
||||
|
||||
|
||||
class PlaylistTrack(SpotifyTrack):
|
||||
def __init__(self,
|
||||
name: str,
|
||||
album: Album,
|
||||
artists: List[Artist],
|
||||
|
||||
added_at: str,
|
||||
added_by: User,
|
||||
is_local: bool,
|
||||
|
||||
href: str = None,
|
||||
spotify_id: str = None,
|
||||
uri: str = None,
|
||||
|
||||
disc_number: int = None,
|
||||
duration_ms: int = None,
|
||||
explicit: bool = None,
|
||||
is_playable: bool = None,
|
||||
|
||||
popularity: int = None
|
||||
):
|
||||
super().__init__(name=name, album=album, artists=artists,
|
||||
href=href,
|
||||
spotify_id=spotify_id,
|
||||
uri=uri,
|
||||
|
||||
disc_number=disc_number,
|
||||
duration_ms=duration_ms,
|
||||
explicit=explicit,
|
||||
is_playable=is_playable,
|
||||
popularity=popularity)
|
||||
|
||||
self.added_at = datetime.fromisoformat(added_at.replace('T', ' ').replace('Z', ''))
|
||||
self.added_by = added_by
|
||||
self.is_local = is_local
|
21
spotframework/model/user.py
Normal file
21
spotframework/model/user.py
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
|
||||
class User:
|
||||
def __init__(self,
|
||||
username: str,
|
||||
|
||||
href: str = None,
|
||||
uri: str = None,
|
||||
|
||||
display_name: str = None,
|
||||
ext_spotify: str = None):
|
||||
self.username = username
|
||||
|
||||
self.href = href
|
||||
self.uri = uri
|
||||
|
||||
self.display_name = display_name
|
||||
self.ext_spotify = ext_spotify
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.username}'
|
@ -2,7 +2,9 @@ import requests
|
||||
import random
|
||||
import logging
|
||||
import time
|
||||
from typing import List
|
||||
from . import const
|
||||
from spotframework.net.parse import parse
|
||||
from spotframework.model.playlist import Playlist
|
||||
|
||||
limit = 50
|
||||
@ -96,7 +98,7 @@ class Network:
|
||||
|
||||
return None
|
||||
|
||||
def get_playlist(self, playlistid):
|
||||
def get_playlist(self, playlistid: str):
|
||||
|
||||
logger.info(f"{playlistid}")
|
||||
|
||||
@ -140,14 +142,7 @@ class Network:
|
||||
if resp:
|
||||
|
||||
for responseplaylist in resp['items']:
|
||||
|
||||
playlist = Playlist(responseplaylist['id'], responseplaylist['uri'])
|
||||
playlist.name = responseplaylist['name']
|
||||
playlist.userid = responseplaylist['owner']['id']
|
||||
|
||||
playlists.append(playlist)
|
||||
|
||||
# playlists = playlists + resp['items']
|
||||
playlists.append(parse.parse_playlist(responseplaylist))
|
||||
|
||||
if resp.get('next', None):
|
||||
more_playlists = self.get_playlists(offset + limit)
|
||||
@ -167,7 +162,7 @@ class Network:
|
||||
playlists = self.get_playlists()
|
||||
|
||||
if playlists:
|
||||
return list(filter(lambda x: x.userid == self.user.username, playlists))
|
||||
return list(filter(lambda x: x.owner.username == self.user.username, playlists))
|
||||
else:
|
||||
logger.error('no playlists returned to filter')
|
||||
return None
|
||||
@ -184,7 +179,7 @@ class Network:
|
||||
|
||||
if resp:
|
||||
if resp.get('items', None):
|
||||
tracks += resp['items']
|
||||
tracks += [parse.parse_track(i) for i in resp.get('items', None)]
|
||||
else:
|
||||
logger.warning(f'{playlistid} no items returned')
|
||||
|
||||
@ -366,7 +361,7 @@ class Network:
|
||||
logger.error('error updating details')
|
||||
return None
|
||||
|
||||
def add_playlist_tracks(self, playlistid, uris):
|
||||
def add_playlist_tracks(self, playlistid: str, uris: List[str]):
|
||||
|
||||
logger.info(f"{playlistid}")
|
||||
|
||||
@ -410,7 +405,34 @@ class Network:
|
||||
else:
|
||||
resp = self._make_get_request('getRecommendations', 'recommendations', params=params)
|
||||
if resp:
|
||||
return resp
|
||||
if 'tracks' in resp:
|
||||
return [parse.parse_track(i) for i in resp['tracks']]
|
||||
else:
|
||||
logger.error('no tracks returned')
|
||||
return None
|
||||
else:
|
||||
logger.error('error getting recommendations')
|
||||
return None
|
||||
|
||||
def write_playlist_object(self,
|
||||
playlist: Playlist,
|
||||
append_tracks: bool = False):
|
||||
|
||||
if playlist.playlist_id:
|
||||
if playlist.tracks == -1:
|
||||
self.replace_playlist_tracks(playlist.playlist_id, [])
|
||||
elif playlist.tracks:
|
||||
if append_tracks:
|
||||
self.add_playlist_tracks(playlist.playlist_id, [i.uri for i in playlist.tracks])
|
||||
else:
|
||||
self.replace_playlist_tracks(playlist.playlist_id, [i.uri for i in playlist.tracks])
|
||||
|
||||
if playlist.name or playlist.collaborative or playlist.public or playlist.description:
|
||||
self.change_playlist_details(playlist.playlist_id,
|
||||
playlist.name,
|
||||
playlist.public,
|
||||
playlist.collaborative,
|
||||
playlist.description)
|
||||
|
||||
else:
|
||||
logger.error('playlist has no id')
|
||||
|
0
spotframework/net/parse/__init__.py
Normal file
0
spotframework/net/parse/__init__.py
Normal file
186
spotframework/net/parse/parse.py
Normal file
186
spotframework/net/parse/parse.py
Normal file
@ -0,0 +1,186 @@
|
||||
from spotframework.model.artist import Artist, SpotifyArtist
|
||||
from spotframework.model.album import Album, SpotifyAlbum
|
||||
from spotframework.model.track import Track, SpotifyTrack, PlaylistTrack
|
||||
from spotframework.model.playlist import Playlist
|
||||
from spotframework.model.user import User
|
||||
|
||||
|
||||
def parse_artist(artist_dict) -> Artist:
|
||||
|
||||
name = artist_dict.get('name', None)
|
||||
|
||||
href = artist_dict.get('href', None)
|
||||
spotify_id = artist_dict.get('id', None)
|
||||
uri = artist_dict.get('uri', None)
|
||||
|
||||
genres = artist_dict.get('genres', None)
|
||||
popularity = artist_dict.get('popularity', None)
|
||||
|
||||
if name is None:
|
||||
raise KeyError('artist name not found')
|
||||
|
||||
return SpotifyArtist(name,
|
||||
href=href,
|
||||
spotify_id=spotify_id,
|
||||
uri=uri,
|
||||
|
||||
genres=genres,
|
||||
popularity=popularity)
|
||||
|
||||
|
||||
def parse_album(album_dict) -> Album:
|
||||
|
||||
name = album_dict.get('name', None)
|
||||
if name is None:
|
||||
raise KeyError('album name not found')
|
||||
|
||||
artists = [parse_artist(i) for i in album_dict.get('artists', [])]
|
||||
|
||||
href = album_dict.get('href', None)
|
||||
spotify_id = album_dict.get('id', None)
|
||||
uri = album_dict.get('uri', None)
|
||||
|
||||
genres = album_dict.get('genres', None)
|
||||
tracks = [parse_track(i) for i in album_dict.get('tracks', [])]
|
||||
|
||||
release_date = album_dict.get('release_date', None)
|
||||
release_date_precision = album_dict.get('release_date_precision', None)
|
||||
|
||||
label = album_dict.get('label', None)
|
||||
popularity = album_dict.get('popularity', None)
|
||||
|
||||
return SpotifyAlbum(name=name,
|
||||
artists=artists,
|
||||
|
||||
href=href,
|
||||
spotify_id=spotify_id,
|
||||
uri=uri,
|
||||
|
||||
genres=genres,
|
||||
tracks=tracks,
|
||||
|
||||
release_date=release_date,
|
||||
release_date_precision=release_date_precision,
|
||||
|
||||
label=label,
|
||||
popularity=popularity)
|
||||
|
||||
|
||||
def parse_track(track_dict) -> Track:
|
||||
|
||||
if 'track' in track_dict:
|
||||
track = track_dict.get('track', None)
|
||||
else:
|
||||
track = track_dict
|
||||
|
||||
name = track.get('name', None)
|
||||
if name is None:
|
||||
raise KeyError('track name not found')
|
||||
|
||||
if track.get('album', None):
|
||||
album = parse_album(track['album'])
|
||||
else:
|
||||
album = None
|
||||
|
||||
# print(album.name)
|
||||
|
||||
artists = [parse_artist(i) for i in track.get('artists', [])]
|
||||
|
||||
href = track.get('href', None)
|
||||
spotify_id = track.get('id', None)
|
||||
uri = track.get('uri', None)
|
||||
|
||||
disc_number = track.get('disc_number', None)
|
||||
duration_ms = track.get('duration_ms', None)
|
||||
explicit = track.get('explicit', None)
|
||||
is_playable = track.get('is_playable', None)
|
||||
|
||||
popularity = track.get('popularity', None)
|
||||
|
||||
added_by = parse_user(track_dict.get('added_by')) if track_dict.get('added_by', None) else None
|
||||
added_at = track_dict.get('added_at', None)
|
||||
is_local = track_dict.get('is_local', None)
|
||||
|
||||
# print(album.name)
|
||||
|
||||
if added_at or added_by or is_local:
|
||||
return PlaylistTrack(name=name,
|
||||
album=album,
|
||||
artists=artists,
|
||||
|
||||
added_at=added_at,
|
||||
added_by=added_by,
|
||||
is_local=is_local,
|
||||
|
||||
href=href,
|
||||
spotify_id=spotify_id,
|
||||
uri=uri,
|
||||
|
||||
disc_number=disc_number,
|
||||
duration_ms=duration_ms,
|
||||
explicit=explicit,
|
||||
is_playable=is_playable,
|
||||
|
||||
popularity=popularity)
|
||||
else:
|
||||
return SpotifyTrack(name=name,
|
||||
album=album,
|
||||
artists=artists,
|
||||
|
||||
href=href,
|
||||
spotify_id=spotify_id,
|
||||
uri=uri,
|
||||
|
||||
disc_number=disc_number,
|
||||
duration_ms=duration_ms,
|
||||
explicit=explicit,
|
||||
is_playable=is_playable,
|
||||
|
||||
popularity=popularity)
|
||||
|
||||
|
||||
def parse_user(user_dict):
|
||||
display_name = user_dict.get('display_name', None)
|
||||
|
||||
spotify_id = user_dict.get('id', None)
|
||||
href = user_dict.get('href', None)
|
||||
uri = user_dict.get('uri', None)
|
||||
|
||||
return User(spotify_id,
|
||||
href=href,
|
||||
uri=uri,
|
||||
display_name=display_name)
|
||||
|
||||
|
||||
def parse_playlist(playlist_dict):
|
||||
|
||||
collaborative = playlist_dict.get('collaborative', None)
|
||||
|
||||
ext_spotify = None
|
||||
if playlist_dict.get('external_urls', None):
|
||||
if playlist_dict['external_urls'].get('spotify', None):
|
||||
ext_spotify = playlist_dict['external_urls']['spotify']
|
||||
|
||||
href = playlist_dict.get('href', None)
|
||||
playlist_id = playlist_dict.get('id', None)
|
||||
description = playlist_dict.get('description', None)
|
||||
|
||||
name = playlist_dict.get('name', None)
|
||||
|
||||
if playlist_dict.get('owner', None):
|
||||
owner = parse_user(playlist_dict.get('owner'))
|
||||
else:
|
||||
owner = None
|
||||
|
||||
public = playlist_dict.get('public', None)
|
||||
uri = playlist_dict.get('uri', None)
|
||||
|
||||
return Playlist(playlistid=playlist_id,
|
||||
name=name,
|
||||
owner=owner,
|
||||
description=description,
|
||||
href=href,
|
||||
uri=uri,
|
||||
collaborative=collaborative,
|
||||
public=public,
|
||||
ext_spotify=ext_spotify)
|
@ -1,13 +1,16 @@
|
||||
import requests
|
||||
from spotframework.model.user import User
|
||||
from base64 import b64encode
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class User:
|
||||
class NetworkUser(User):
|
||||
|
||||
def __init__(self, client_id, client_secret, access_token, refresh_token):
|
||||
super().__init__('')
|
||||
|
||||
self.accesstoken = access_token
|
||||
self.refreshtoken = refresh_token
|
||||
|
||||
@ -15,8 +18,7 @@ class User:
|
||||
self.client_secret = client_secret
|
||||
|
||||
self.refresh_token()
|
||||
|
||||
self.username = self.get_info()['id']
|
||||
self.refresh_info()
|
||||
|
||||
def refresh_token(self):
|
||||
|
||||
@ -33,6 +35,25 @@ class User:
|
||||
else:
|
||||
logger.error(f'http error {req.status_code}')
|
||||
|
||||
def refresh_info(self):
|
||||
info = self.get_info()
|
||||
|
||||
if info.get('display_name', None):
|
||||
self.display_name = info['display_name']
|
||||
|
||||
if info.get('external_urls', None):
|
||||
if info['external_urls'].get('spotify', None):
|
||||
self.ext_spotify = info['external_urls']['spotify']
|
||||
|
||||
if info.get('href', None):
|
||||
self.href = info['href']
|
||||
|
||||
if info.get('id', None):
|
||||
self.username = info['id']
|
||||
|
||||
if info.get('uri', None):
|
||||
self.uri = info['uri']
|
||||
|
||||
def get_info(self):
|
||||
|
||||
headers = {'Authorization': 'Bearer %s' % self.accesstoken}
|
||||
|
Loading…
Reference in New Issue
Block a user