object oriented redesign
This commit is contained in:
parent
bab55bdb55
commit
d3a0ec7b5f
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
env
|
env
|
||||||
__pycache__
|
__pycache__
|
||||||
*.csv
|
*.csv
|
||||||
|
.idea
|
||||||
|
.fm
|
42
backup.py
42
backup.py
@ -1,19 +1,39 @@
|
|||||||
import fmframework.io.csv as csvwrite
|
from fmframework.io.csv import export_scrobbles
|
||||||
import fmframework.net.user as user
|
from fmframework.net.network import Network
|
||||||
|
|
||||||
import sys, datetime, os
|
import sys
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
def backupScrobbles(path):
|
logger = logging.getLogger('fmframework')
|
||||||
userobj = user.User('sarsoo')
|
|
||||||
|
|
||||||
scrobbles = userobj.getRecentTracks()
|
log_format = '%(asctime)s %(levelname)s %(name)s - %(funcName)s - %(message)s'
|
||||||
|
|
||||||
path = sys.argv[1]
|
file_handler = logging.FileHandler(".fm/backup.log")
|
||||||
|
formatter = logging.Formatter(log_format)
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
|
||||||
if not os.path.exists(path):
|
logger.addHandler(file_handler)
|
||||||
os.makedirs(path)
|
|
||||||
|
stream_log_format = '%(levelname)s %(name)s:%(funcName)s - %(message)s'
|
||||||
|
stream_formatter = logging.Formatter(stream_log_format)
|
||||||
|
|
||||||
|
stream_handler = logging.StreamHandler()
|
||||||
|
stream_handler.setFormatter(stream_formatter)
|
||||||
|
|
||||||
|
logger.addHandler(stream_handler)
|
||||||
|
|
||||||
|
|
||||||
|
def backup_scrobbles(file_path):
|
||||||
|
net = Network(username='sarsoo', api_key=os.environ['FMKEY'])
|
||||||
|
|
||||||
|
scrobbles = net.get_recent_tracks()
|
||||||
|
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
os.makedirs(file_path)
|
||||||
|
|
||||||
|
export_scrobbles(scrobbles, file_path)
|
||||||
|
|
||||||
csvwrite.exportScrobbles(scrobbles, path)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
backupScrobbles(sys.argv[1])
|
backup_scrobbles(sys.argv[1])
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.setLevel('DEBUG')
|
@ -1,26 +1,28 @@
|
|||||||
import csv
|
from csv import DictWriter
|
||||||
import datetime
|
import datetime
|
||||||
|
import logging
|
||||||
|
from typing import List
|
||||||
|
from fmframework.model.fm import Scrobble
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
headers = ['track', 'album', 'artist', 'time', 'track id', 'album id', 'artist id']
|
headers = ['track', 'album', 'artist', 'time', 'track id', 'album id', 'artist id']
|
||||||
|
|
||||||
def exportScrobbles(scrobbles, path):
|
|
||||||
|
|
||||||
date = str(datetime.datetime.now()).split(' ')[0]
|
def export_scrobbles(scrobbles: List[Scrobble], path: str):
|
||||||
|
logger.info(f'dumping {len(scrobbles)} to {path}')
|
||||||
|
date = str(datetime.date.today())
|
||||||
|
|
||||||
with open('{}/{}_scrobbles.csv'.format(path, date), 'w') as fileobj:
|
with open('{}/{}_scrobbles.csv'.format(path, date), 'w') as fileobj:
|
||||||
|
|
||||||
writer = csv.DictWriter(fileobj, fieldnames = headers)
|
writer = DictWriter(fileobj, fieldnames=headers)
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
|
|
||||||
for track in scrobbles:
|
for scrobble in scrobbles:
|
||||||
if '@attr' not in track:
|
writer.writerow({
|
||||||
trackdict = {
|
'track': scrobble.track.name.replace(';', '_').replace(',', '_'),
|
||||||
'track':track['name'].replace(';', '_').replace(',', '_'),
|
'album': scrobble.track.album.name.replace(';', '_').replace(',', '_'),
|
||||||
'album':track['album']['#text'].replace(';', '_').replace(',', '_'),
|
'artist': scrobble.track.artist.name.replace(';', '_').replace(',', '_'),
|
||||||
'artist':track['artist']['#text'].replace(';', '_').replace(',', '_'),
|
'time': scrobble.time,
|
||||||
'time': datetime.datetime.fromtimestamp(int(track['date']['uts'])),
|
'track id': scrobble.track.mbid,
|
||||||
'track id':track['mbid'],
|
'album id': scrobble.track.album.mbid,
|
||||||
'album id':track['album']['mbid'],
|
'artist id': scrobble.track.artist.mbid
|
||||||
'artist id':track['artist']['mbid']}
|
})
|
||||||
|
|
||||||
writer.writerow(trackdict)
|
|
||||||
|
0
fmframework/model/__init__.py
Normal file
0
fmframework/model/__init__.py
Normal file
46
fmframework/model/album.py
Normal file
46
fmframework/model/album.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
from fmframework.model.fm import LastFM, Wiki
|
||||||
|
from fmframework.model.artist import Artist
|
||||||
|
from fmframework.util.console import Color
|
||||||
|
|
||||||
|
|
||||||
|
class Album(LastFM):
|
||||||
|
def __init__(self,
|
||||||
|
name: str = None,
|
||||||
|
url: str = None,
|
||||||
|
mbid: str = None,
|
||||||
|
listeners: int = None,
|
||||||
|
play_count: int = None,
|
||||||
|
user_scrobbles: int = None,
|
||||||
|
wiki: Wiki = None,
|
||||||
|
artist: Artist = None,
|
||||||
|
):
|
||||||
|
super().__init__(name=name,
|
||||||
|
url=url,
|
||||||
|
mbid=mbid,
|
||||||
|
listeners=listeners,
|
||||||
|
play_count=play_count,
|
||||||
|
user_scrobbles=user_scrobbles,
|
||||||
|
wiki=wiki)
|
||||||
|
self.artist = artist
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.name} / {self.artist}'
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return super().__repr__() + Color.DARKCYAN + Color.BOLD + ' Album' + Color.END + f': {self.artist}'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def wrap(name: str = None,
|
||||||
|
artist: str = None,
|
||||||
|
url: str = None,
|
||||||
|
mbid: str = None,
|
||||||
|
listeners: int = None,
|
||||||
|
play_count: int = None,
|
||||||
|
user_scrobbles: int = None):
|
||||||
|
return Album(name=name,
|
||||||
|
artist=Artist(name=artist),
|
||||||
|
url=url,
|
||||||
|
mbid=mbid,
|
||||||
|
listeners=listeners,
|
||||||
|
play_count=play_count,
|
||||||
|
user_scrobbles=user_scrobbles)
|
26
fmframework/model/artist.py
Normal file
26
fmframework/model/artist.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from fmframework.util.console import Color
|
||||||
|
from fmframework.model.fm import LastFM, Wiki
|
||||||
|
|
||||||
|
|
||||||
|
class Artist(LastFM):
|
||||||
|
def __init__(self,
|
||||||
|
name: str,
|
||||||
|
url: str = None,
|
||||||
|
mbid: str = None,
|
||||||
|
listeners: int = None,
|
||||||
|
play_count: int = None,
|
||||||
|
user_scrobbles: int = None,
|
||||||
|
wiki: Wiki = None):
|
||||||
|
super().__init__(name=name,
|
||||||
|
url=url,
|
||||||
|
mbid=mbid,
|
||||||
|
listeners=listeners,
|
||||||
|
play_count=play_count,
|
||||||
|
user_scrobbles=user_scrobbles,
|
||||||
|
wiki=wiki)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.name}'
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return super().__repr__() + Color.PURPLE + Color.BOLD + ' Artist' + Color.END
|
61
fmframework/model/fm.py
Normal file
61
fmframework/model/fm.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from fmframework.util.console import Color
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from fmframework.model.track import Track
|
||||||
|
|
||||||
|
|
||||||
|
class Wiki:
|
||||||
|
def __init__(self,
|
||||||
|
date: datetime = None,
|
||||||
|
summary: str = None,
|
||||||
|
content: str = None):
|
||||||
|
self.date = date
|
||||||
|
self.summary = summary
|
||||||
|
self.content = content
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return Color.YELLOW + Color.BOLD + 'Wiki:' + Color.END + \
|
||||||
|
f': {self.date}, {self.summary}, {self.content}'
|
||||||
|
|
||||||
|
|
||||||
|
class LastFM:
|
||||||
|
def __init__(self,
|
||||||
|
name: str = None,
|
||||||
|
url: str = None,
|
||||||
|
mbid: str = None,
|
||||||
|
listeners: int = None,
|
||||||
|
play_count: int = None,
|
||||||
|
user_scrobbles: int = None,
|
||||||
|
wiki: Wiki = None):
|
||||||
|
self.name = name
|
||||||
|
self.url = url,
|
||||||
|
self.mbid = mbid,
|
||||||
|
self.listeners = listeners
|
||||||
|
self.play_count = play_count
|
||||||
|
self.user_scrobbles = user_scrobbles
|
||||||
|
self.wiki = wiki
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return Color.RED + Color.BOLD + 'LastFM' + Color.END + \
|
||||||
|
f': {self.name}, user({self.user_scrobbles}), play_count({self.play_count}), ' \
|
||||||
|
f'listeners({self.listeners}), wiki({self.wiki})'
|
||||||
|
|
||||||
|
|
||||||
|
class Scrobble:
|
||||||
|
def __init__(self,
|
||||||
|
track: Track = None,
|
||||||
|
time: datetime = None):
|
||||||
|
self.track = track
|
||||||
|
self.time = time
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.track
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return Color.BLUE + Color.BOLD + 'Scrobble' + Color.END + f': {self.time} {repr(self.track)}'
|
53
fmframework/model/track.py
Normal file
53
fmframework/model/track.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
from fmframework.model.fm import LastFM, Wiki
|
||||||
|
from fmframework.model.album import Album
|
||||||
|
from fmframework.model.artist import Artist
|
||||||
|
from fmframework.util.console import Color
|
||||||
|
|
||||||
|
|
||||||
|
class Track(LastFM):
|
||||||
|
def __init__(self,
|
||||||
|
name: str = None,
|
||||||
|
url: str = None,
|
||||||
|
mbid: str = None,
|
||||||
|
listeners: int = None,
|
||||||
|
play_count: int = None,
|
||||||
|
user_scrobbles: int = None,
|
||||||
|
wiki: Wiki = None,
|
||||||
|
album: Album = None,
|
||||||
|
artist: Artist = None,
|
||||||
|
):
|
||||||
|
super().__init__(name=name,
|
||||||
|
url=url,
|
||||||
|
mbid=mbid,
|
||||||
|
listeners=listeners,
|
||||||
|
play_count=play_count,
|
||||||
|
user_scrobbles=user_scrobbles,
|
||||||
|
wiki=wiki)
|
||||||
|
self.album = album
|
||||||
|
self.artist = artist
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.name} / {self.album} / {self.artist}'
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return super().__repr__() + Color.YELLOW + Color.BOLD + ' Track' + Color.END + \
|
||||||
|
f': album({repr(self.album)}), artist({repr(self.artist)})'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def wrap(name: str = None,
|
||||||
|
artist: str = None,
|
||||||
|
album: str = None,
|
||||||
|
album_artist: str = None,
|
||||||
|
url: str = None,
|
||||||
|
mbid: str = None,
|
||||||
|
listeners: int = None,
|
||||||
|
play_count: int = None,
|
||||||
|
user_scrobbles: int = None):
|
||||||
|
return Track(name=name,
|
||||||
|
album=Album.wrap(name=album, artist=album_artist),
|
||||||
|
artist=Artist(artist),
|
||||||
|
url=url,
|
||||||
|
mbid=mbid,
|
||||||
|
listeners=listeners,
|
||||||
|
play_count=play_count,
|
||||||
|
user_scrobbles=user_scrobbles)
|
264
fmframework/net/network.py
Normal file
264
fmframework/net/network.py
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
import requests
|
||||||
|
from typing import Optional, List
|
||||||
|
from copy import deepcopy
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from fmframework.model.fm import Scrobble, Wiki
|
||||||
|
from fmframework.model.track import Track
|
||||||
|
from fmframework.model.album import Album
|
||||||
|
from fmframework.model.artist import Artist
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Network:
|
||||||
|
|
||||||
|
def __init__(self, username, api_key):
|
||||||
|
self.api_key = api_key
|
||||||
|
|
||||||
|
self.username = username
|
||||||
|
self.retry_counter = 0
|
||||||
|
|
||||||
|
def get_request(self,
|
||||||
|
method: str,
|
||||||
|
params: dict = None) -> Optional[dict]:
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"format": 'json',
|
||||||
|
"method": method,
|
||||||
|
"api_key": self.api_key,
|
||||||
|
}
|
||||||
|
if params is not None:
|
||||||
|
data.update(params)
|
||||||
|
|
||||||
|
req = requests.get('http://ws.audioscrobbler.com/2.0/', params=data)
|
||||||
|
|
||||||
|
if 200 <= req.status_code < 300:
|
||||||
|
logger.debug(f'{method} {req.status_code}')
|
||||||
|
return req.json()
|
||||||
|
else:
|
||||||
|
resp = req.json()
|
||||||
|
|
||||||
|
code = resp.get('error', None)
|
||||||
|
message = resp.get('message', None)
|
||||||
|
|
||||||
|
if code:
|
||||||
|
if code == 8:
|
||||||
|
if self.retry_counter < 5:
|
||||||
|
self.retry_counter += 1
|
||||||
|
logger.warning(f'{method} {req.status_code} {code} {message} retyring')
|
||||||
|
return self.get_request(method, params)
|
||||||
|
else:
|
||||||
|
self.retry_counter = 0
|
||||||
|
logger.error(f'{method} {req.status_code} {code} {message} retry limit reached')
|
||||||
|
else:
|
||||||
|
logger.error(f'{method} {req.status_code} {code} {message} retry limit reached')
|
||||||
|
else:
|
||||||
|
if message:
|
||||||
|
logger.error(f'{method} {req.status_code} {message}')
|
||||||
|
else:
|
||||||
|
logger.error(f'{method} {req.status_code}')
|
||||||
|
|
||||||
|
def get_recent_tracks(self,
|
||||||
|
username: str = None,
|
||||||
|
limit: int = None,
|
||||||
|
from_time: datetime = None,
|
||||||
|
to_time: datetime = None) -> Optional[List[Scrobble]]:
|
||||||
|
if limit is not None:
|
||||||
|
logger.info(f'pulling {limit} tracks')
|
||||||
|
else:
|
||||||
|
logger.info(f'pulling all tracks')
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'user': self.username if username is None else username
|
||||||
|
}
|
||||||
|
|
||||||
|
if from_time is not None:
|
||||||
|
params['from'] = from_time.timestamp()
|
||||||
|
if to_time is not None:
|
||||||
|
params['to'] = to_time.timestamp()
|
||||||
|
|
||||||
|
iterator = PageCollection(net=self, method='user.getrecenttracks', params=params, response_limit=limit)
|
||||||
|
iterator.response_limit = limit
|
||||||
|
iterator.load()
|
||||||
|
|
||||||
|
return [self.parse_scrobble(i) for i in iterator.items]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_wiki(wiki_dict) -> Optional[Wiki]:
|
||||||
|
if wiki_dict:
|
||||||
|
return Wiki(date=datetime.strptime(wiki_dict.get('published', None), '%d %b %Y, %H:%M'),
|
||||||
|
summary=wiki_dict.get('summary', None),
|
||||||
|
content=wiki_dict.get('content', None))
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def parse_artist(self, artist_dict) -> Artist:
|
||||||
|
return Artist(name=artist_dict.get('name', 'n/a'),
|
||||||
|
url=artist_dict.get('url', None),
|
||||||
|
mbid=artist_dict.get('mbid', None),
|
||||||
|
listeners=artist_dict.get('listeners', None),
|
||||||
|
play_count=artist_dict.get('playcount', None),
|
||||||
|
user_scrobbles=artist_dict.get('userplaycount', None),
|
||||||
|
wiki=self.parse_wiki(artist_dict['wiki']) if artist_dict.get('wiki', None) else None)
|
||||||
|
|
||||||
|
def parse_album(self, album_dict) -> Album:
|
||||||
|
return Album(name=album_dict.get('name', 'n/a'),
|
||||||
|
url=album_dict.get('url', 'n/a'),
|
||||||
|
mbid=album_dict.get('mbid', 'n/a'),
|
||||||
|
listeners=album_dict.get('listeners', 'n/a'),
|
||||||
|
play_count=album_dict.get('playcount', 'n/a'),
|
||||||
|
user_scrobbles=album_dict.get('userplaycount', 'n/a'),
|
||||||
|
wiki=self.parse_wiki(album_dict['wiki']) if album_dict.get('wiki', None) else None,
|
||||||
|
artist=album_dict.get('artist'))
|
||||||
|
|
||||||
|
def parse_track(self, track_dict):
|
||||||
|
track = Track(name=track_dict.get('name', 'n/a'),
|
||||||
|
url=track_dict.get('url', 'n/a'),
|
||||||
|
mbid=track_dict.get('mbid', 'n/a'),
|
||||||
|
listeners=track_dict.get('listeners', 'n/a'),
|
||||||
|
play_count=track_dict.get('playcount', 'n/a'),
|
||||||
|
user_scrobbles=track_dict.get('userplaycount', 'n/a'),
|
||||||
|
wiki=self.parse_wiki(track_dict['wiki']) if track_dict.get('wiki', None) else None)
|
||||||
|
|
||||||
|
if track_dict.get('album', None):
|
||||||
|
track.album = self.parse_album(track_dict['album'])
|
||||||
|
|
||||||
|
if track_dict.get('artist', None):
|
||||||
|
track.album = self.parse_album(track_dict['artist'])
|
||||||
|
|
||||||
|
return track
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_scrobble(scrobble_dict):
|
||||||
|
album = None
|
||||||
|
if scrobble_dict.get('album', None):
|
||||||
|
album = Album(name=scrobble_dict['album'].get('#text', 'n/a'),
|
||||||
|
mbid=scrobble_dict['album'].get('mbid', None))
|
||||||
|
|
||||||
|
artist = None
|
||||||
|
if scrobble_dict.get('artist', None):
|
||||||
|
artist = Artist(name=scrobble_dict['artist'].get('#text', 'n/a'),
|
||||||
|
mbid=scrobble_dict['artist'].get('mbid', None))
|
||||||
|
|
||||||
|
if artist is not None and album is not None:
|
||||||
|
if album.artist is None:
|
||||||
|
album.artist = artist
|
||||||
|
|
||||||
|
track = Track(name=scrobble_dict.get('name', 'n/a'),
|
||||||
|
album=album,
|
||||||
|
artist=artist,
|
||||||
|
mbid=scrobble_dict.get('mbid', None),
|
||||||
|
url=scrobble_dict.get('url', None))
|
||||||
|
|
||||||
|
return Scrobble(track=track, time=datetime.fromtimestamp(int(scrobble_dict['date']['uts'])))
|
||||||
|
|
||||||
|
|
||||||
|
class PageCollection:
|
||||||
|
def __init__(self,
|
||||||
|
net: Network,
|
||||||
|
method: str,
|
||||||
|
params: dict = None,
|
||||||
|
page_limit: int = 50,
|
||||||
|
response_limit: int = 50):
|
||||||
|
self.net = net
|
||||||
|
self.method = method
|
||||||
|
self.params = params
|
||||||
|
self.pages: List[Page] = []
|
||||||
|
self.page_limit = page_limit
|
||||||
|
self.response_limit = response_limit
|
||||||
|
self.counter = 1
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
length = 0
|
||||||
|
for page in self.pages:
|
||||||
|
length += len(page.items)
|
||||||
|
return length
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total(self):
|
||||||
|
if len(self.pages) > 0:
|
||||||
|
return self.pages[0].total
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def items(self):
|
||||||
|
items = []
|
||||||
|
for page in self.pages:
|
||||||
|
items += page.items
|
||||||
|
return items[:self.response_limit]
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
if self.response_limit:
|
||||||
|
tracker = True
|
||||||
|
while len(self) < self.response_limit and tracker:
|
||||||
|
page = self.iterate()
|
||||||
|
if len(page) == 0:
|
||||||
|
tracker = False
|
||||||
|
else:
|
||||||
|
self.pages.append(page)
|
||||||
|
else:
|
||||||
|
tracker = True
|
||||||
|
while tracker:
|
||||||
|
page = self.iterate()
|
||||||
|
if len(page) == 0:
|
||||||
|
tracker = False
|
||||||
|
else:
|
||||||
|
self.pages.append(page)
|
||||||
|
|
||||||
|
def iterate(self):
|
||||||
|
logger.debug(f'iterating {self.method}')
|
||||||
|
self.counter += 1
|
||||||
|
|
||||||
|
params = deepcopy(self.params)
|
||||||
|
|
||||||
|
params.update({
|
||||||
|
'limit': self.page_limit,
|
||||||
|
'page': self.counter
|
||||||
|
})
|
||||||
|
resp = self.net.get_request(method=self.method, params=params)
|
||||||
|
|
||||||
|
if resp:
|
||||||
|
return self.parse_page(resp)
|
||||||
|
# if len(page) > 0:
|
||||||
|
# if self.response_limit:
|
||||||
|
# if len(self) < self.response_limit:
|
||||||
|
# self.iterate()
|
||||||
|
# else:
|
||||||
|
# self.iterate()
|
||||||
|
else:
|
||||||
|
logger.error('no response')
|
||||||
|
|
||||||
|
def add_page(self, page_dict):
|
||||||
|
page = self.parse_page(page_dict)
|
||||||
|
self.pages.append(page)
|
||||||
|
return page
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_page(page_dict):
|
||||||
|
first_value = list(page_dict.values())[0]
|
||||||
|
items = list(first_value.values())[1]
|
||||||
|
return Page(
|
||||||
|
number=first_value['@attr'].get('page', None),
|
||||||
|
size=first_value['@attr'].get('perPage', None),
|
||||||
|
total=first_value['@attr'].get('total', None),
|
||||||
|
total_pages=first_value['@attr'].get('totalPages', None),
|
||||||
|
items=items)
|
||||||
|
|
||||||
|
|
||||||
|
class Page:
|
||||||
|
def __init__(self,
|
||||||
|
number: int,
|
||||||
|
size: int,
|
||||||
|
total: int,
|
||||||
|
total_pages: int,
|
||||||
|
items: list):
|
||||||
|
self.number = number
|
||||||
|
self.size = size
|
||||||
|
self.total = total
|
||||||
|
self.total_pages = total_pages
|
||||||
|
self.items = items
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.items)
|
@ -1,52 +0,0 @@
|
|||||||
import os
|
|
||||||
import requests
|
|
||||||
|
|
||||||
class User:
|
|
||||||
|
|
||||||
def __init__(self, username, pagesize = 200):
|
|
||||||
self.api_key = os.environ['FMKEY']
|
|
||||||
|
|
||||||
self.username = username
|
|
||||||
self.pagesize = pagesize
|
|
||||||
|
|
||||||
def __makeRequest(self, method, extra = {}, page = 1):
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"format": 'json',
|
|
||||||
"method": method,
|
|
||||||
"limit": self.pagesize,
|
|
||||||
"page": page,
|
|
||||||
"user": self.username,
|
|
||||||
"api_key": self.api_key
|
|
||||||
}
|
|
||||||
data.update(extra)
|
|
||||||
|
|
||||||
req = requests.get('http://ws.audioscrobbler.com/2.0/', params = data)
|
|
||||||
|
|
||||||
if req.status_code < 200 or req.status_code > 299:
|
|
||||||
|
|
||||||
if req.json()['error'] == 8:
|
|
||||||
print('ERROR: retrying call ' + method)
|
|
||||||
return self.__makeRequest(method, extra, page)
|
|
||||||
else:
|
|
||||||
raise ValueError('HTTP Error Raised: ' + str(req.json()['error']) + ' ' + req.json()['message'])
|
|
||||||
|
|
||||||
return req.json()
|
|
||||||
|
|
||||||
def getRecentTracks(self, offset = 1, pagelimit = 0):
|
|
||||||
|
|
||||||
scrobbles = []
|
|
||||||
|
|
||||||
print(str(offset) + ' offset')
|
|
||||||
|
|
||||||
json = self.__makeRequest('user.getrecenttracks', page = offset)
|
|
||||||
scrobbles += json['recenttracks']['track']
|
|
||||||
|
|
||||||
if pagelimit > 0:
|
|
||||||
if offset < pagelimit and offset < int(json['recenttracks']['@attr']['totalPages']):
|
|
||||||
scrobbles += self.getRecentTracks(offset = offset + 1, pagelimit = pagelimit)
|
|
||||||
else:
|
|
||||||
if offset < int(json['recenttracks']['@attr']['totalPages']):
|
|
||||||
scrobbles += self.getRecentTracks(offset = offset + 1, pagelimit = pagelimit)
|
|
||||||
|
|
||||||
return scrobbles
|
|
0
fmframework/util/__init__.py
Normal file
0
fmframework/util/__init__.py
Normal file
11
fmframework/util/console.py
Normal file
11
fmframework/util/console.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
class Color:
|
||||||
|
PURPLE = '\033[95m'
|
||||||
|
CYAN = '\033[96m'
|
||||||
|
DARKCYAN = '\033[36m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
BOLD = '\033[1m'
|
||||||
|
UNDERLINE = '\033[4m'
|
||||||
|
END = '\033[0m'
|
Loading…
Reference in New Issue
Block a user