diff --git a/spotify/__init__.py b/spotify/__init__.py index ce8a4d9..b0d5691 100644 --- a/spotify/__init__.py +++ b/spotify/__init__.py @@ -7,6 +7,8 @@ logger = logging.getLogger(__name__) logger.setLevel('DEBUG') spotframework_logger = logging.getLogger('spotframework') +fmframework_logger = logging.getLogger('fmframework') +spotfm_logger = logging.getLogger('spotfm') if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD': from google.cloud import logging as glogging @@ -21,6 +23,8 @@ if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD': logger.addHandler(handler) spotframework_logger.addHandler(handler) + fmframework_logger.addHandler(handler) + spotfm_logger.addHandler(handler) else: log_format = '%(levelname)s %(name)s:%(funcName)s - %(message)s' @@ -31,3 +35,5 @@ else: logger.addHandler(stream_handler) spotframework_logger.addHandler(stream_handler) + fmframework_logger.addHandler(stream_handler) + spotfm_logger.addHandler(stream_handler) diff --git a/spotify/api/__init__.py b/spotify/api/__init__.py index 69f0936..6b924ea 100644 --- a/spotify/api/__init__.py +++ b/spotify/api/__init__.py @@ -1,2 +1,4 @@ from .api import blueprint as api_blueprint from .player import blueprint as player_blueprint +from .fm import blueprint as fm_blueprint +from .spotfm import blueprint as spotfm_blueprint diff --git a/spotify/api/decorators.py b/spotify/api/decorators.py index 8c8e2d2..8030357 100644 --- a/spotify/api/decorators.py +++ b/spotify/api/decorators.py @@ -85,6 +85,24 @@ def spotify_link_required(func): return spotify_link_required_wrapper +def lastfm_username_required(func): + @functools.wraps(func) + def lastfm_username_required_wrapper(*args, **kwargs): + user_dict = database.get_user_doc_ref(kwargs.get('username')).get().to_dict() + + if user_dict: + if user_dict.get('lastfm_username'): + return func(*args, **kwargs) + else: + logger.warning(f'no last.fm username for {user_dict["username"]}') + return jsonify({'status': 'error', 'message': 'no last.fm username'}), 401 + else: + logger.warning('user not logged in') + return jsonify({'error': 'not logged in'}), 401 + + return lastfm_username_required_wrapper + + def gae_cron(func): @functools.wraps(func) def gae_cron_wrapper(*args, **kwargs): diff --git a/spotify/api/fm.py b/spotify/api/fm.py new file mode 100644 index 0000000..bd830b3 --- /dev/null +++ b/spotify/api/fm.py @@ -0,0 +1,29 @@ +from flask import Blueprint, jsonify +from datetime import date +import logging + +from google.cloud import firestore + +from spotify.api.decorators import login_or_basic_auth, lastfm_username_required + +import spotify.db.database as database + +blueprint = Blueprint('fm-api', __name__) +db = firestore.Client() + +logger = logging.getLogger(__name__) + + +@blueprint.route('/today', methods=['GET']) +@login_or_basic_auth +@lastfm_username_required +def daily_scrobbles(username=None): + + net = database.get_authed_lastfm_network(username) + + total = net.get_scrobble_count_from_date(input_date=date.today()) + + return jsonify({ + 'username': net.username, + 'scrobbles_today': total + }), 200 diff --git a/spotify/api/player.py b/spotify/api/player.py index 98b97bc..880a7f3 100644 --- a/spotify/api/player.py +++ b/spotify/api/player.py @@ -30,7 +30,7 @@ def play(username=None): if uri.object_type in [Uri.ObjectType.album, Uri.ObjectType.artist, Uri.ObjectType.playlist]: context = Context(uri) - net = database.get_authed_network(username) + net = database.get_authed_spotify_network(username) player = Player(net) player.play(context=context, device_name=request_json.get('device_name', None)) @@ -42,7 +42,7 @@ def play(username=None): except ValueError: return jsonify({'error': "malformed uri provided"}), 400 elif 'playlist_name' in request_json: - net = database.get_authed_network(username) + net = database.get_authed_spotify_network(username) playlists = net.get_playlists() if playlists is not None: playlist_to_play = next((i for i in playlists if i.name == request_json['playlist_name']), None) @@ -63,7 +63,7 @@ def play(username=None): uris = [SpotifyTrack.wrap(uri=i) for i in uris if i.object_type == Uri.ObjectType.track] if len(uris) > 0: - net = database.get_authed_network(username) + net = database.get_authed_spotify_network(username) player = Player(net) player.play(tracks=uris, device_name=request_json.get('device_name', None)) @@ -82,7 +82,7 @@ def play(username=None): @login_or_basic_auth @spotify_link_required def next_track(username=None): - net = database.get_authed_network(username) + net = database.get_authed_spotify_network(username) player = Player(net) player.next() @@ -97,7 +97,7 @@ def shuffle(username=None): if 'state' in request_json: if isinstance(request_json['state'], bool): - net = database.get_authed_network(username) + net = database.get_authed_spotify_network(username) player = Player(net) player.shuffle(state=request_json['state']) @@ -117,7 +117,7 @@ def volume(username=None): if 'volume' in request_json: if isinstance(request_json['volume'], int): if 0 <= request_json['volume'] <= 100: - net = database.get_authed_network(username) + net = database.get_authed_spotify_network(username) player = Player(net) player.volume(value=request_json['volume']) diff --git a/spotify/api/spotfm.py b/spotify/api/spotfm.py new file mode 100644 index 0000000..719312b --- /dev/null +++ b/spotify/api/spotfm.py @@ -0,0 +1,62 @@ +from flask import Blueprint, jsonify, request +import logging + +from google.cloud import firestore + +from spotify.api.decorators import login_or_basic_auth, lastfm_username_required, spotify_link_required + +import spotify.db.database as database + +from spotfm.maths.counter import Counter +from spotframework.model.uri import Uri + +blueprint = Blueprint('spotfm-api', __name__) +db = firestore.Client() + +logger = logging.getLogger(__name__) + + +@blueprint.route('/count', methods=['GET']) +@login_or_basic_auth +@spotify_link_required +@lastfm_username_required +def count(username=None): + + uri = request.args.get('uri', None) + playlist_name = request.args.get('playlist_name', None) + + if uri is None and playlist_name is None: + return jsonify({'error': 'no input provided'}), 401 + + if uri: + try: + uri = Uri(uri) + except ValueError: + return jsonify({'error': 'malformed uri provided'}), 401 + + spotnet = database.get_authed_spotify_network(username) + fmnet = database.get_authed_lastfm_network(username) + counter = Counter(fmnet=fmnet, spotnet=spotnet) + + if uri: + uri_count = counter.count(uri=uri) + return jsonify({ + "uri": str(uri), + "count": uri_count, + 'uri_type': str(uri.object_type), + 'last.fm_username': fmnet.username + }), 200 + elif playlist_name: + + playlists = spotnet.get_playlists() + playlist = next((i for i in playlists if i.name == playlist_name), None) + + if playlist is not None: + playlist_count = counter.count_playlist(playlist=playlist) + return jsonify({ + "count": playlist_count, + 'playlist_name': playlist_name, + 'last.fm_username': fmnet.username + }), 200 + else: + return jsonify({'error': f'playlist {playlist_name} not found'}), 404 diff --git a/spotify/db/database.py b/spotify/db/database.py index 2d816c6..1157eba 100644 --- a/spotify/db/database.py +++ b/spotify/db/database.py @@ -4,7 +4,8 @@ from datetime import timedelta, datetime, timezone from typing import List, Optional from werkzeug.security import check_password_hash -from spotframework.net.network import Network +from spotframework.net.network import Network as SpotifyNetwork +from fmframework.net.network import Network as FmNetwork from spotify.db.user import DatabaseUser db = firestore.Client() @@ -27,7 +28,7 @@ def refresh_token_database_callback(user): logger.error('user has no attached id') -def get_authed_network(username): +def get_authed_spotify_network(username): user = get_user_doc_ref(username) if user: @@ -48,13 +49,29 @@ def get_authed_network(username): user_obj.refresh_access_token() user_obj.refresh_info() - return Network(user_obj) + return SpotifyNetwork(user_obj) else: logger.error('user spotify not linked') else: logger.error(f'user {username} not found') +def get_authed_lastfm_network(username): + + user = get_user_doc_ref(username) + if user: + user_dict = user.get().to_dict() + + if user_dict.get('lastfm_username', None): + fm_keys = db.document('key/fm').get().to_dict() + + return FmNetwork(username=user_dict['lastfm_username'], api_key=fm_keys['clientid']) + else: + logger.error(f'{username} has no last.fm username') + else: + logger.error(f'user {username} not found') + + def check_user_password(username, password): user = get_user_doc_ref(user=username) diff --git a/spotify/spotify.py b/spotify/spotify.py index be85c5a..32e8366 100644 --- a/spotify/spotify.py +++ b/spotify/spotify.py @@ -4,7 +4,7 @@ from google.cloud import firestore import os from spotify.auth import auth_blueprint -from spotify.api import api_blueprint, player_blueprint +from spotify.api import api_blueprint, player_blueprint, fm_blueprint, spotfm_blueprint db = firestore.Client() @@ -13,6 +13,8 @@ app.secret_key = db.collection(u'spotify').document(u'config').get().to_dict()[' app.register_blueprint(auth_blueprint, url_prefix='/auth') app.register_blueprint(api_blueprint, url_prefix='/api') app.register_blueprint(player_blueprint, url_prefix='/api/player') +app.register_blueprint(fm_blueprint, url_prefix='/api/fm') +app.register_blueprint(spotfm_blueprint, url_prefix='/api/spotfm') @app.route('/') diff --git a/spotify/tasks/create_playlist.py b/spotify/tasks/create_playlist.py index 90d2248..8be5998 100644 --- a/spotify/tasks/create_playlist.py +++ b/spotify/tasks/create_playlist.py @@ -17,7 +17,7 @@ def create_playlist(username, name): if len(users) == 1: - net = database.get_authed_network(username) + net = database.get_authed_spotify_network(username) playlist = net.create_playlist(net.user.username, name) diff --git a/spotify/tasks/play_user_playlist.py b/spotify/tasks/play_user_playlist.py index 90ab5f5..b8d2cc5 100644 --- a/spotify/tasks/play_user_playlist.py +++ b/spotify/tasks/play_user_playlist.py @@ -49,7 +49,7 @@ def play_user_playlist(username, logger.critical(f'no playlists to use for creation ({username})') return None - net = database.get_authed_network(username) + net = database.get_authed_spotify_network(username) device = None if device_name: diff --git a/spotify/tasks/run_user_playlist.py b/spotify/tasks/run_user_playlist.py index 8c0d0b6..9b57b6c 100644 --- a/spotify/tasks/run_user_playlist.py +++ b/spotify/tasks/run_user_playlist.py @@ -42,7 +42,7 @@ def run_user_playlist(username, playlist_name): logger.critical(f'no playlists to use for creation ({username}/{playlist_name})') return None - net = database.get_authed_network(username) + net = database.get_authed_spotify_network(username) engine = PlaylistEngine(net)