diff --git a/requirements.txt b/requirements.txt index a003e55..5901ee9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ google-api-core==1.14.2 google-auth==1.6.3 google-cloud-core==1.0.3 google-cloud-firestore==1.4.0 +google-cloud-logging==1.12.1 google-cloud-pubsub==0.45.0 google-cloud-tasks==1.2.0 googleapis-common-protos==1.6.0 diff --git a/spotify/api/api.py b/spotify/api/api.py index 149fae9..d649c96 100644 --- a/spotify/api/api.py +++ b/spotify/api/api.py @@ -9,6 +9,8 @@ from google.cloud import tasks_v2 from google.protobuf import timestamp_pb2 from werkzeug.security import check_password_hash, generate_password_hash +from spotify.tasks.run_user_playlist import run_user_playlist as run_user_playlist + import spotify.api.database as database blueprint = Blueprint('api', __name__) @@ -307,7 +309,7 @@ def run_playlist(): if playlist_name: - execute_playlist(session['username'], playlist_name) + run_user_playlist(session['username'], playlist_name) return jsonify({'message': 'execution requested', 'status': 'success'}), 200 @@ -325,7 +327,9 @@ def run_playlist_task(): payload = request.get_data(as_text=True) if payload: payload = json.loads(payload) - execute_playlist(payload['username'], payload['name']) + + run_user_playlist(payload['username'], payload['name']) + return jsonify({'message': 'executed playlist', 'status': 'success'}), 200 else: return jsonify({'error': 'unauthorized'}), 401 @@ -455,7 +459,7 @@ def execute_user(username): # execute_playlist(username, iterate_playlist['name']) -def execute_playlist(username, name): +def push_run_user_playlist_message(username, name): data = u'{}'.format(name) data = data.encode('utf-8') diff --git a/spotify/api/spotify.py b/spotify/api/spotify.py index 9bd3f33..b987123 100644 --- a/spotify/api/spotify.py +++ b/spotify/api/spotify.py @@ -1,7 +1,8 @@ -import requests -from base64 import b64encode from google.cloud import firestore +from spotframework.net.user import User +from spotframework.net.network import Network + db = firestore.Client() @@ -14,30 +15,12 @@ def create_playlist(username, name): user_dict = users[0].to_dict() spotify_keys = db.document('key/spotify').get().to_dict() - idsecret = b64encode(bytes(spotify_keys['clientid'] + ':' + spotify_keys['clientsecret'], "utf-8")).decode("ascii") + net = Network(User(spotify_keys['clientid'], + spotify_keys['clientsecret'], + user_dict['access_token'], + user_dict['refresh_token'])) - token_headers = {'Authorization': 'Basic %s' % idsecret} - headers = {"Content-Type": "application/json"} + net.create_playlist(net.user.username, name) - data = {"grant_type": "refresh_token", "refresh_token": user_dict['refresh_token']} - - token_req = requests.post('https://accounts.spotify.com/api/token', data=data, headers=token_headers) - - if 200 <= token_req.status_code < 300: - accesstoken = token_req.json()['access_token'] - - json = {"name": name, "public": True, "collaborative": False} - - headers['Authorization'] = 'Bearer ' + accesstoken - - info_id = requests.get('https://api.spotify.com/v1/me', headers=headers).json()['id'] - - play_req = requests.post(f'https://api.spotify.com/v1/users/{info_id}/playlists', json=json, headers=headers) - - resp = play_req.json() - - return resp["id"] - - else: - print(token_req.status_code) - raise Exception('failed to get access token') + else: + raise ValueError('no/multiple username(s)') diff --git a/spotify/spotify.py b/spotify/spotify.py index 3965602..9e55151 100644 --- a/spotify/spotify.py +++ b/spotify/spotify.py @@ -2,13 +2,30 @@ from flask import Flask, render_template, redirect, request, session, flash, url from google.cloud import firestore import os +import logging from spotify.auth import auth_blueprint from spotify.api import api_blueprint +from google.cloud.logging.handlers import CloudLoggingHandler +from google.cloud import logging as glogging + # Project ID is determined by the GCLOUD_PROJECT environment variable db = firestore.Client() +logger = logging.getLogger(__name__) +logger.setLevel('INFO') + +log_format = '%(levelname)s %(name)s:%(funcName)s - %(message)s' +formatter = logging.Formatter(log_format) + +client = glogging.Client() +handler = CloudLoggingHandler(client) + +handler.setFormatter(formatter) + +logger.addHandler(handler) + app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), '..', 'build'), template_folder="templates") app.secret_key = db.collection(u'spotify').document(u'config').get().to_dict()['secret_key'] app.register_blueprint(auth_blueprint, url_prefix='/auth') diff --git a/spotify/tasks/__init__.py b/spotify/tasks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/spotify/tasks/run_user_playlist.py b/spotify/tasks/run_user_playlist.py new file mode 100644 index 0000000..489253b --- /dev/null +++ b/spotify/tasks/run_user_playlist.py @@ -0,0 +1,111 @@ +from google.cloud import firestore + +import datetime +import logging + +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.net.network import Network +from spotframework.net.user import User + +db = firestore.Client() + +captured_playlists = [] + + +def run_user_playlist(username, playlist_name): + + logger = logging.getLogger(__name__) + + users = [i for i in db.collection(u'spotify_users').where(u'username', u'==', username).stream()] + + logger.info(f'{username} / {playlist_name}') + + if len(users) == 1: + + user_dict = users[0].to_dict() + + playlist_collection = db.collection(u'spotify_users', u'{}'.format(users[0].id), 'playlists') + + playlists = [i for i in playlist_collection.where(u'name', u'==', playlist_name).stream()] + + if len(playlists) == 1: + + playlist_dict = playlists[0].to_dict() + + if playlist_dict['playlist_id'] is None: + logger.critical(f'no playlist id to populate ({username}/{playlist_name})') + return + + if len(playlist_dict['parts']) == 0 and len(playlist_dict['playlist_references']) == 0: + logger.critical(f'no playlists to use for creation ({username}/{playlist_name})') + return + + spotify_keys = db.document('key/spotify').get().to_dict() + + net = Network(User(spotify_keys['clientid'], + spotify_keys['clientsecret'], + user_dict['access_token'], + user_dict['refresh_token'])) + + engine = PlaylistEngine(net) + engine.load_user_playlists() + + processors = [DeduplicateByID()] + + if playlist_dict['shuffle'] is True: + processors.append(Shuffle()) + else: + processors.append(SortReverseReleaseDate()) + + global captured_playlists + captured_playlists = [] + + submit_parts = playlist_dict['parts'] + generate_parts(users[0].id, playlist_dict['name']) + + submit_parts = [i for i in {j for j in submit_parts}] + + if playlist_dict['type'] == 'recents': + boundary_date = datetime.datetime.now() - datetime.timedelta(days=int(playlist_dict['day_boundary'])) + tracks = engine.get_recent_playlist(boundary_date, + submit_parts, + processors, + include_recommendations=playlist_dict['include_recommendations'], + recommendation_limit=int(playlist_dict['recommendation_sample'])) + else: + tracks = engine.make_playlist(submit_parts, + processors, + include_recommendations=playlist_dict['include_recommendations'], + recommendation_limit=int(playlist_dict['recommendation_sample'])) + + engine.execute_playlist(tracks, playlist_dict['playlist_id']) + engine.change_description(sorted(submit_parts), playlist_dict['playlist_id']) + + else: + logger.critical(f'multiple/no playlists found ({username}/{playlist_name})') + return + + else: + logger.critical(f'multiple/no user(s) found ({username}/{playlist_name})') + return + + +def generate_parts(user_id, name): + + playlist_doc = [i.to_dict() for i in + db.document(u'spotify_users/{}'.format(user_id)) + .collection(u'playlists') + .where(u'name', '==', name).stream()][0] + + return_parts = playlist_doc['parts'] + + captured_playlists.append(name) + + for i in playlist_doc['playlist_references']: + if i not in captured_playlists: + return_parts += generate_parts(user_id, i) + + return return_parts