1:- module(spotify, [access_token/2, playlist_to_csv/3, run_curl/2, run_curl/4,
2 retrieve_all/2, retrieve_all/4, playlist/2, playlist_track/2,
3 track_name/2, track_artists/2, track_info/2, playlist_info/2,
4 playlist_track_info/3, test/1]). 5
6:- use_module(library(clpfd)). 7:- use_module(library(filesex)). 8:- use_module(library(achelois)). 9:- use_module(library(http/json)). 10:- use_module(library(url)). 11
12timestamp(Timestamp) :-
13 get_time(Temp),
14 Timestamp is round(Temp).
15
16encoded_auth(Auth) :-
17 client_id(ClientId),
18 client_secret(ClientSecret),
19 atomic_list_concat([ClientId, ClientSecret], ':', AuthStr),
20 base64(AuthStr, AuthStr64),
21 atom_concat('Authorization: Basic ', AuthStr64, Auth).
22
23request_new_access_token(token(AccessToken, TokenType, ExpireTime)) :-
24 timestamp(Now),
25 encoded_auth(Auth),
26 run_curl(_, [auth(Auth), method(post), data('grant_type=client_credentials'), url('https://accounts.spotify.com/api/token')], _, JSON),
27 JSON = json(['access_token'=AccessToken, 'token_type'=TokenType, 'expires_in'=ExpireOffset|_]),
28 ExpireTime #= Now + ExpireOffset.
29
30access_token(Token, NewToken) :-
31 Token = token(_AccessToken, _TokenType, ExpireTime),
32 timestamp(Timestamp),
33
34 (
35 ( var(ExpireTime); ExpireTime #=< Timestamp) ->
36 request_new_access_token(NewToken);
37
38 ExpireTime #> Timestamp -> NewToken = Token
39 ).
40
41access_token_auth(token(AccessToken, _TokenType, _ExpireTime), AuthStr) :-
42 atom_concat('Authorization: Bearer ', AccessToken, AuthStr).
43
44select_or_default(X, Xs, NewXs, Default) :-
45 select(X, Xs, NewXs) -> true;
46 X = Default, NewXs = Xs.
47
48add_params(Url, Params, BuiltUrl) :-
49 parse_url(Url, Attributes),
50 select_or_default(search(CurParams), Attributes, Temp, search([])),
51 append(CurParams, Params, AllParams),
52 parse_url(BuiltUrl, [search(AllParams) | Temp]).
53
54build_curl_option(endpoint(Endpoint), Options) :-
55 build_curl_option(endpoint(Endpoint, []), Options).
56build_curl_option(endpoint(Endpoint, Params), [BuiltUrl]) :-
57 atom_concat('https://api.spotify.com/v1/', Endpoint, Url),
58 add_params(Url, Params, BuiltUrl).
59build_curl_option(url(Url), Options) :-
60 build_curl_option(url(Url, []), Options).
61build_curl_option(url(Url, Params), [BuiltUrl]) :-
62 add_params(Url, Params, BuiltUrl).
63build_curl_option(method(Method), ['-X', MethodStr]) :-
64 upcase_atom(Method, MethodStr).
65build_curl_option(data(Data), ['-d', Data]).
66build_curl_option(silent, ['-s']).
67build_curl_option(auth(Auth), ['-H', Auth]).
68
69with_option(Option, Options, [Option|Temp]) :-
70 Option =.. [F|Params],
71 length(Params, L),
72 length(NewParams, L),
73 VarOption =.. [F|NewParams],
74
75 (
76 select(VarOption, Options, Temp) -> true;
77 Temp = Options
78 ).
79
80add_default(Option, Options, NewOptions) :-
81 Option =.. [F|Params],
82 length(Params, L),
83 length(NewParams, L),
84 VarOption =.. [F|NewParams],
85
86 (
87 member(VarOption, Options) -> NewOptions = Options;
88 NewOptions = [Option|Options]
89 ).
90
91add_defaults(Token, Options, NewToken, NewOptions) :-
92 Defaults = [silent],
93
94 (
95 not(member(auth(_), Options)) ->
96 access_token(Token, NewToken),
97 access_token_auth(NewToken, Auth),
98 append([auth(Auth)], Defaults, AllDefaults);
99
100 Token = NewToken, AllDefaults = Defaults
101 ),
102
103 foldl(add_default, AllDefaults, Options, NewOptions).
104
105curl_options(Token, Options, NewToken, CurlOptions) :-
106 add_defaults(Token, Options, NewToken, AllOptions),
107 maplist(build_curl_option, AllOptions, Temp),
108 flatten(Temp, CurlOptions).
109
110run_curl(Options, Response) :- run_curl(_, Options, _, Response).
111run_curl(Token, Options, NewToken, Response) :-
112 curl_options(Token, Options, NewToken, CurlOptions),
113 process(path(curl), CurlOptions, [output(Output)]),
114 catch(atom_json_term(Output, Response, []), _Error, Response = atom(Output)).
115
116retrieve_all(Options, Result) :- retrieve_all(_, Options, _, Result).
117retrieve_all(Token, Options, NewToken, Result) :-
118 run_curl(Token, Options, TempToken, Response),
119
120 (
121 Response = json(JSON) ->
122 (
123 member(next='@'(null), JSON), member(items=AllItems, JSON) -> member(Result, AllItems);
124 member(next=Url, JSON), member(items=Items, JSON) ->
125 (
126 member(Result, Items);
127 with_option(url(Url), Options, NewOptions),
128 retrieve_all(TempToken, NewOptions, NewToken, Result)
129 );
130 Result = Response
131 );
132 Result = Response
133 ).
134
135playlist(User, Playlist) :-
136 atomic_list_concat(['users', User, 'playlists'], '/', Endpoint),
137 retrieve_all(_, [endpoint(Endpoint)], _, Playlist).
138
139playlist_track(PlaylistId, Track) :-
140 atomic_list_concat(['playlists', PlaylistId, 'tracks'], '/', Endpoint),
141 retrieve_all(_, [endpoint(Endpoint)], _, Track).
142
143track_name(json(Track), Name) :-
144 member(track=json(T), Track),
145 member(name=Name, T).
146
147track_artists(json(Track), ArtistNames) :-
148 member(track=json(T), Track),
149 member(artists=Artists, T),
150 findall(Name, (member(json(Artist), Artists), member(name=Name, Artist)), ArtistNames).
151
152track_info(Track, Name-Artists) :-
153 track_name(Track, Name),
154 track_artists(Track, Artists).
155
156playlist_info(json(Playlist), Id-Name) :-
157 member(id=Id, Playlist),
158 member(name=Name, Playlist).
159
160playlist_track_info(User, Id-Name, Info) :-
161 playlist(User, Playlist),
162 playlist_info(Playlist, Id-Name),
163 playlist_track(Id, Track),
164 track_info(Track, Info).
165
166track_to_csv(Name-Artists, row(Name, ArtistsStr)) :-
167 atomic_list_concat(Artists, ',', ArtistsStr).
168
171playlist_to_csv(User, Id-Name, Path) :-
172 findall(Track,
173 (
174 playlist_track_info(User, Id-Name, Track),
175 format('Retrieved track ~w~n', [Track])
176 ), Tracks),
177 maplist(track_to_csv, Tracks, Rows),
178 csv_write_file(Path, Rows).
179
180test(X) :- client_id(X)