freezer/lib/api/spotify.dart

227 lines
7 KiB
Dart
Raw Normal View History

2020-06-25 12:28:56 +00:00
import 'package:freezer/api/deezer.dart';
2021-04-05 20:22:32 +00:00
import 'package:freezer/api/importer.dart';
import 'package:freezer/settings.dart';
2020-06-25 12:28:56 +00:00
import 'package:html/parser.dart';
import 'package:html/dom.dart' as dom;
import 'package:http/http.dart' as http;
2021-04-05 20:22:32 +00:00
import 'package:spotify/spotify.dart';
2020-06-25 12:28:56 +00:00
import 'dart:convert';
2021-04-05 20:22:32 +00:00
import 'dart:io';
2020-06-25 12:28:56 +00:00
2021-04-05 20:22:32 +00:00
import 'package:url_launcher/url_launcher.dart';
2020-06-25 12:28:56 +00:00
2021-04-05 20:22:32 +00:00
class SpotifyScrapper {
2020-06-25 12:28:56 +00:00
//Parse spotify URL to URI (spotify:track:1234)
2021-04-05 20:22:32 +00:00
static String parseUrl(String url) {
2020-06-25 12:28:56 +00:00
Uri uri = Uri.parse(url);
if (uri.pathSegments.length > 3) return null; //Invalid URL
2021-08-30 12:51:51 +00:00
if (uri.pathSegments.length == 3)
return 'spotify:${uri.pathSegments[1]}:${uri.pathSegments[2]}';
if (uri.pathSegments.length == 2)
return 'spotify:${uri.pathSegments[0]}:${uri.pathSegments[1]}';
2020-06-25 12:28:56 +00:00
return null;
}
//Get spotify embed url from uri
2021-08-30 12:51:51 +00:00
static String getEmbedUrl(String uri) =>
'https://embed.spotify.com/?uri=$uri';
2020-06-25 12:28:56 +00:00
//https://link.tospotify.com/ or https://spotify.app.link/
2021-04-05 20:22:32 +00:00
static Future resolveLinkUrl(String url) async {
http.Response response = await http.get(Uri.parse(url));
2021-08-30 12:51:51 +00:00
Match match = RegExp(r'window\.top\.location = validate\("(.+)"\);')
.firstMatch(response.body);
return match.group(1);
}
2021-04-05 20:22:32 +00:00
static Future resolveUrl(String url) async {
if (url.contains("link.tospotify") || url.contains("spotify.app.link")) {
return parseUrl(await resolveLinkUrl(url));
}
return parseUrl(url);
}
2020-06-25 12:28:56 +00:00
//Extract JSON data form spotify embed page
2021-04-05 20:22:32 +00:00
static Future<Map> getEmbedData(String url) async {
2020-06-25 12:28:56 +00:00
//Fetch
2021-08-30 12:51:51 +00:00
http.Response response = await http.get(Uri.parse(url));
2020-06-25 12:28:56 +00:00
//Parse
dom.Document document = parse(response.body);
dom.Element element = document.getElementById('resource');
//Some are URL encoded
try {
return jsonDecode(element.innerHtml);
} catch (e) {
return jsonDecode(Uri.decodeComponent(element.innerHtml));
}
2020-06-25 12:28:56 +00:00
}
2021-04-05 20:22:32 +00:00
static Future<SpotifyPlaylist> playlist(String uri) async {
2020-06-25 12:28:56 +00:00
//Load data
String url = getEmbedUrl(uri);
Map data = await getEmbedData(url);
//Parse
SpotifyPlaylist playlist = SpotifyPlaylist.fromJson(data);
return playlist;
}
//Get Deezer track ID from Spotify URI
2021-04-05 20:22:32 +00:00
static Future<String> convertTrack(String uri) async {
Map data = await getEmbedData(getEmbedUrl(uri));
SpotifyTrack track = SpotifyTrack.fromJson(data);
Map deezer = await deezerAPI.callPublicApi('track/isrc:' + track.isrc);
return deezer['id'].toString();
}
//Get Deezer album ID by UPC
2021-04-05 20:22:32 +00:00
static Future<String> convertAlbum(String uri) async {
Map data = await getEmbedData(getEmbedUrl(uri));
SpotifyAlbum album = SpotifyAlbum.fromJson(data);
Map deezer = await deezerAPI.callPublicApi('album/upc:' + album.upc);
return deezer['id'].toString();
}
2020-06-25 12:28:56 +00:00
}
class SpotifyTrack {
String title;
2021-04-05 20:22:32 +00:00
List<String> artists;
2020-06-25 12:28:56 +00:00
String isrc;
SpotifyTrack({this.title, this.artists, this.isrc});
//JSON
factory SpotifyTrack.fromJson(Map json) => SpotifyTrack(
2021-08-30 12:51:51 +00:00
title: json['name'],
artists:
json['artists'].map<String>((a) => a["name"].toString()).toList(),
isrc: json['external_ids']['isrc']);
2021-04-05 20:22:32 +00:00
//Convert track to importer track
ImporterTrack toImporter() {
return ImporterTrack(title, artists, isrc: isrc);
}
2020-06-25 12:28:56 +00:00
}
class SpotifyPlaylist {
String name;
String description;
List<SpotifyTrack> tracks;
String image;
SpotifyPlaylist({this.name, this.description, this.tracks, this.image});
//JSON
factory SpotifyPlaylist.fromJson(Map json) => SpotifyPlaylist(
2021-08-30 12:51:51 +00:00
name: json['name'],
description: json['description'],
image: (json['images'].length > 0) ? json['images'][0]['url'] : null,
tracks: json['tracks']['items']
.map<SpotifyTrack>((j) => SpotifyTrack.fromJson(j['track']))
.toList());
2021-04-05 20:22:32 +00:00
//Convert to importer tracks
List<ImporterTrack> toImporter() {
return tracks.map((t) => t.toImporter()).toList();
}
2020-06-25 12:28:56 +00:00
}
class SpotifyAlbum {
String upc;
SpotifyAlbum({this.upc});
//JSON
2021-08-30 12:51:51 +00:00
factory SpotifyAlbum.fromJson(Map json) =>
SpotifyAlbum(upc: json['external_ids']['upc']);
}
2021-04-05 20:22:32 +00:00
class SpotifyAPIWrapper {
HttpServer _server;
SpotifyApi spotify;
User me;
//Try authorize with saved credentials
Future<bool> trySaved() async {
print(settings.spotifyCredentials);
2021-08-30 12:51:51 +00:00
if (settings.spotifyClientId == null ||
settings.spotifyClientSecret == null ||
settings.spotifyCredentials == null) return false;
final credentials = SpotifyApiCredentials(
2021-08-30 12:51:51 +00:00
settings.spotifyClientId, settings.spotifyClientSecret,
accessToken: settings.spotifyCredentials.accessToken,
refreshToken: settings.spotifyCredentials.refreshToken,
scopes: settings.spotifyCredentials.scopes,
expiration: settings.spotifyCredentials.expiration);
spotify = SpotifyApi(credentials);
me = await spotify.me.get();
await _save();
return true;
}
2021-04-05 20:22:32 +00:00
Future authorize(String clientId, String clientSecret) async {
//Spotify
2021-08-30 12:51:51 +00:00
SpotifyApiCredentials credentials =
SpotifyApiCredentials(clientId, clientSecret);
2021-04-05 20:22:32 +00:00
spotify = SpotifyApi(credentials);
//Create server
_server = await HttpServer.bind(InternetAddress.loopbackIPv4, 42069);
String responseUri;
//Get URL
final grant = SpotifyApi.authorizationCodeGrant(credentials);
final redirectUri = "http://localhost:42069";
2021-08-30 12:51:51 +00:00
final scopes = [
'user-read-private',
'playlist-read-private',
'playlist-read-collaborative',
'user-library-read'
];
final authUri =
grant.getAuthorizationUrl(Uri.parse(redirectUri), scopes: scopes);
2021-04-05 20:22:32 +00:00
launch(authUri.toString());
//Wait for code
await for (HttpRequest request in _server) {
//Exit window
request.response.headers.set("Content-Type", "text/html; charset=UTF-8");
2021-08-30 12:51:51 +00:00
request.response.write(
"<body><h1>You can close this page and go back to Freezer.</h1></body><script>window.close();</script>");
2021-04-05 20:22:32 +00:00
request.response.close();
//Get token
if (request.uri.queryParameters["code"] != null) {
_server.close();
_server = null;
responseUri = request.uri.toString();
break;
}
}
//Create spotify
spotify = SpotifyApi.fromAuthCodeGrant(grant, responseUri);
me = await spotify.me.get();
//Save
await _save();
}
Future _save() async {
//Save credentials
final spotifyCredentials = await spotify.getCredentials();
final saveCredentials = SpotifyCredentialsSave(
accessToken: spotifyCredentials.accessToken,
refreshToken: spotifyCredentials.refreshToken,
scopes: spotifyCredentials.scopes,
2021-08-30 12:51:51 +00:00
expiration: spotifyCredentials.expiration);
settings.spotifyClientSecret = spotifyCredentials.clientId;
settings.spotifyClientSecret = spotifyCredentials.clientSecret;
settings.spotifyCredentials = saveCredentials;
await settings.save();
2021-04-05 20:22:32 +00:00
}
//Cancel authorization
void cancelAuthorize() {
if (_server != null) {
_server.close(force: true);
_server = null;
}
}
}