fuzzyworld

hawk / files / public / js / api.js

api.js

6.2 KB · history · blame

1const apiExpiry = 3600 * 1000; // 1 hour
2
3function createCallbackState(processedCount, totalCount) {
4 return {
5 processedCount: processedCount,
6 totalCount: totalCount
7 };
8}
9
10async function getAlbums(statusCallback) {
11 const db = await openDb();
12 const cacheKey = 'albums';
13 const cachedAlbums = await getWithExpiry(db, cacheKey);
14 if (cachedAlbums) {
15 if (statusCallback) {
16 statusCallback(createCallbackState(cachedAlbums.length, cachedAlbums.length))
17 }
18 return cachedAlbums;
19 }
20
21 const accessToken = await getAccessToken();
22 const albums = [];
23 let url = 'https://api.spotify.com/v1/me/albums?limit=50';
24 while (url) {
25 const response = await fetch(url, {
26 headers: {
27 Authorization: 'Bearer ' + accessToken
28 }
29 });
30 const data = await response.json();
31 albums.push(...data.items);
32 if (statusCallback) {
33 statusCallback(createCallbackState(albums.length, data.total))
34 }
35 url = data.next;
36 }
37
38 await setWithExpiry(db, cacheKey, albums, apiExpiry);
39 return albums;
40}
41
42async function batchGetTracks(albumIds, statusCallback) {
43 const batchSize = 20
44 if (albumIds.length == 0) {
45 return [];
46 }
47 if (albumIds.length > batchSize) {
48 const allTracks = []
49 while (albumIds.length > 0) {
50 const batch = albumIds.splice(0, batchSize);
51 albumIds = albumIds.splice(batchSize);
52 const tracks = await batchGetTracks(batch, statusCallback);
53 allTracks.push(...tracks);
54 }
55 return allTracks;
56 }
57
58 const db = await openDb();
59 const cacheKey = `tracks-${albumIds.join('-')}`;
60 const cachedTracks = await getWithExpiry(db, cacheKey);
61 if (cachedTracks) {
62 if (statusCallback) {
63 statusCallback(createCallbackState(cachedTracks.length, cachedTracks.length))
64 }
65 return cachedTracks;
66 }
67
68 const tracks = [];
69 const accessToken = await getAccessToken();
70 let url = `https://api.spotify.com/v1/albums?ids=${albumIds.join(',')}&market=from_token`;
71 while (url) {
72 const response = await fetch(url, {
73 headers: {
74 Authorization: 'Bearer ' + accessToken
75 }
76 });
77 const data = await response.json();
78 data.albums.forEach(album => {
79 // combine the album data with the track data
80 tracks.push(...album.tracks.items.map(track => {
81 track.album = {
82 id: album.id,
83 name: album.name,
84 images: album.images,
85 external_urls: album.external_urls
86 }
87 return track;
88 }));
89 });
90 if (statusCallback) {
91 statusCallback(createCallbackState(tracks.length, tracks.length))
92 }
93 url = data.next;
94 }
95
96 await setWithExpiry(db, cacheKey, tracks, apiExpiry);
97 return tracks;
98}
99
100async function getTracks(albumId, statusCallback) {
101 const db = await openDb();
102 const cacheKey = `tracks-${albumId}`;
103 const cachedTracks = await getWithExpiry(db, cacheKey);
104 if (cachedTracks) {
105 if (statusCallback) {
106 statusCallback(createCallbackState(cachedTracks.length, cachedTracks.length))
107 }
108 return cachedTracks;
109 }
110
111 const tracks = [];
112 const accessToken = await getAccessToken();
113 let url = `https://api.spotify.com/v1/albums/${albumId}/tracks?limit=50`;
114 while (url) {
115 const response = await fetch(url, {
116 headers: {
117 Authorization: 'Bearer ' + accessToken
118 }
119 });
120 const data = await response.json();
121 tracks.push(...data.items);
122 if (statusCallback) {
123 statusCallback(createCallbackState(tracks.length, data.total))
124 }
125 url = data.next;
126 }
127
128 await setWithExpiry(db, cacheKey, tracks, apiExpiry);
129 return tracks;
130}
131
132async function playTrack(trackId) {
133 let accessToken = await getAccessToken();
134 console.info(`Playing track ${trackId}`);
135 const response = await fetch(`https://api.spotify.com/v1/me/player/play`, {
136 method: 'PUT',
137 headers: {
138 Authorization: 'Bearer ' + accessToken
139 },
140 body: JSON.stringify({
141 uris: [`spotify:track:${trackId}`]
142 })
143 });
144 if (response.status != 204) {
145 const json = await response.json();
146 throw new Error(`Failed to play track ${trackId}: ${response.status}\n${JSON.stringify(json)}`);
147 }
148}
149
150async function getPlayingTrack() {
151 const accessToken = await getAccessToken();
152 const response = await fetch("https://api.spotify.com/v1/me/player", {
153 headers: {
154 Authorization: 'Bearer ' + accessToken
155 }
156 });
157
158 if (response.status == 204) {
159 return null;
160 }
161
162 const data = await response.json();
163 return data;
164}
165
166async function play() {
167 const accessToken = await getAccessToken();
168 await fetch("https://api.spotify.com/v1/me/player/play", {
169 method: "PUT",
170 headers: {
171 Authorization: 'Bearer ' + accessToken
172 }
173 });
174}
175
176async function pause() {
177 const accessToken = await getAccessToken();
178 await fetch("https://api.spotify.com/v1/me/player/pause", {
179 method: "PUT",
180 headers: {
181 Authorization: 'Bearer ' + accessToken
182 }
183 });
184}
185
186async function getAvailableDevices() {
187 const accessToken = await getAccessToken();
188 const response = await fetch("https://api.spotify.com/v1/me/player/devices", {
189 headers: {
190 Authorization: 'Bearer ' + accessToken
191 }
192 });
193 const data = await response.json();
194 return data.devices;
195}
196
197async function transferPlayback(deviceId) {
198 const accessToken = await getAccessToken();
199 const response = await fetch("https://api.spotify.com/v1/me/player", {
200 method: "PUT",
201 headers: {
202 Authorization: 'Bearer ' + accessToken
203 },
204 body: JSON.stringify({
205 device_ids: [deviceId]
206 })
207 });
208 if (response.status != 204) {
209 const json = await response.json();
210 throw new Error(`Failed to transfer playback to ${deviceId}: ${response.status}\n${JSON.stringify(json)}`);
211 }
212}