Skip to content
This repository was archived by the owner on Mar 23, 2024. It is now read-only.

Commit 29b94f0

Browse files
committed
🚿 clean up the new releases script
1 parent a1df110 commit 29b94f0

File tree

3 files changed

+441
-178
lines changed

3 files changed

+441
-178
lines changed
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
<?php
2+
/**
3+
* Class SpotifyClient
4+
*
5+
* @created 28.08.2023
6+
* @author smiley <smiley@chillerlan.net>
7+
* @copyright 2023 smiley
8+
* @license MIT
9+
*/
10+
11+
namespace chillerlan\OAuthExamples\Providers\Spotify;
12+
13+
use chillerlan\HTTP\Utils\MessageUtil;
14+
use chillerlan\OAuth\Providers\Spotify;
15+
use Psr\Log\LoggerInterface;
16+
use Psr\Log\NullLogger;
17+
use RuntimeException;
18+
use function array_chunk;
19+
use function array_map;
20+
use function array_values;
21+
use function file_exists;
22+
use function file_get_contents;
23+
use function file_put_contents;
24+
use function json_decode;
25+
use function json_encode;
26+
use function rtrim;
27+
use function sprintf;
28+
use function usleep;
29+
use const JSON_PRETTY_PRINT;
30+
use const JSON_UNESCAPED_SLASHES;
31+
use const JSON_UNESCAPED_UNICODE;
32+
33+
/**
34+
*
35+
*/
36+
abstract class SpotifyClient{
37+
38+
protected const sleepTimer = 250000; // sleep between requests (µs)
39+
40+
protected object $me;
41+
protected string $id;
42+
protected string $market;
43+
protected array $artists = [];
44+
protected array $albums = [];
45+
46+
/**
47+
*
48+
*/
49+
public function __construct(
50+
protected Spotify $spotify,
51+
protected LoggerInterface $logger = new NullLogger(),
52+
){
53+
$this->getMe();
54+
}
55+
56+
/**
57+
* @param string[] $vars
58+
*/
59+
protected function saveToFile(array $vars, string $dir):void{
60+
61+
foreach($vars as $var){
62+
file_put_contents(
63+
sprintf('%s/%s.json', rtrim($dir, '\\/'), $var),
64+
json_encode($this->{$var}, (JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE))
65+
);
66+
}
67+
68+
}
69+
70+
/**
71+
* @param string[] $vars
72+
*/
73+
protected function loadFromFile(array $vars, string $dir):bool{
74+
75+
foreach($vars as $var){
76+
$file = sprintf('%s/%s.json', rtrim($dir, '\\/'), $var);
77+
78+
if(!file_exists($file)){
79+
return false;
80+
}
81+
82+
83+
$data = json_decode(file_get_contents($file));
84+
85+
foreach($data as $k => $v){
86+
$this->{$var}[$k] = $v;
87+
}
88+
89+
}
90+
91+
return true;
92+
}
93+
94+
/**
95+
* fetch the currently authenticated user
96+
*/
97+
protected function getMe():void{
98+
$me = $this->spotify->request('/v1/me');
99+
100+
if($me->getStatusCode() !== 200){
101+
throw new RuntimeException('could not fetch data from /me endpoint');
102+
}
103+
104+
$json = MessageUtil::decodeJSON($me);
105+
106+
if($json === false || !isset($json->country, $json->id)){
107+
throw new RuntimeException('invalid response from /me endpoint');
108+
}
109+
110+
$this->me = $json;
111+
$this->id = $this->me->id;
112+
$this->market = $this->me->country;
113+
}
114+
115+
/**
116+
* fetch the artists the user is following
117+
*/
118+
protected function getFollowedArtists():void{
119+
$this->artists = [];
120+
121+
$params = [
122+
'type' => 'artist',
123+
'limit' => 50, // API max = 50 artists
124+
'after' => null,
125+
];
126+
127+
do{
128+
$meFollowing = $this->spotify->request('/v1/me/following', $params);
129+
$data = MessageUtil::decodeJSON($meFollowing);
130+
131+
if($meFollowing->getStatusCode() === 200){
132+
133+
foreach($data->artists->items as $artist){
134+
$this->artists[$artist->id] = $artist;
135+
136+
$this->logger->info('artist: '.$artist->name);
137+
}
138+
139+
$params['after'] = ($data->artists->cursors->after ?? '');
140+
141+
$this->logger->info(sprintf('next cursor: %s', $params['after']));
142+
}
143+
// not dealing with this
144+
else{
145+
146+
if(isset($data->error)){
147+
$this->logger->error($data->error->message.' ('.$data->error->status.')');
148+
}
149+
150+
break;
151+
}
152+
153+
usleep(self::sleepTimer);
154+
}
155+
while($params['after'] !== '');
156+
157+
$this->logger->info(sprintf('fetched %s artists', count($this->artists)));
158+
}
159+
160+
/**
161+
* fetch the releases for the followed artists
162+
*/
163+
protected function getArtistReleases():void{
164+
$this->albums = [];
165+
166+
foreach($this->artists as $artistID => $artist){
167+
// WTB bulk endpoint /artists/albums?ids=artist_id1,artist_id2,...
168+
$artistAlbums = $this->spotify->request(sprintf('/v1/artists/%s/albums', $artistID), ['market' => $this->market]);
169+
170+
if($artistAlbums->getStatusCode() !== 200){
171+
$this->logger->warning(sprintf('could not fetch albums for artist "%s"', $artist->name));
172+
173+
continue;
174+
}
175+
176+
$data = MessageUtil::decodeJSON($artistAlbums);
177+
178+
if(!isset($data->items)){
179+
$this->logger->warning(sprintf('albums response empty for artist "%s"', $artist->name));
180+
181+
continue;
182+
}
183+
184+
foreach($data->items as $album){
185+
$this->albums[$artistID][$album->id] = $album;
186+
187+
$this->logger->info(sprintf('album: %s - %s', $artist->name, $album->name));
188+
189+
}
190+
191+
usleep(self::sleepTimer);
192+
}
193+
194+
}
195+
196+
/**
197+
* create a new playlist
198+
*/
199+
protected function createPlaylist(string $name, string $description):string{
200+
201+
$createPlaylist = $this->spotify->request(
202+
path : sprintf('/v1/users/%s/playlists', $this->id),
203+
method : 'POST',
204+
body : [
205+
'name' => $name,
206+
'description' => $description,
207+
// we'll never create public playlists - that's up to the user to decide
208+
'public' => false,
209+
'collaborative' => false,
210+
],
211+
headers: ['Content-Type' => 'application/json'],
212+
);
213+
214+
if($createPlaylist->getStatusCode() !== 201){
215+
throw new RuntimeException('could not create a new playlist');
216+
}
217+
218+
$playlist = MessageUtil::decodeJSON($createPlaylist);
219+
220+
if(!isset($playlist->id)){
221+
throw new RuntimeException('invalid create playlist response');
222+
}
223+
224+
$this->logger->info(sprintf('created playlist: "%s" ("%s")', $name, $description));
225+
$this->logger->info(sprintf('spotify:user:%s:playlist:%s', $this->id, $playlist->id));
226+
$this->logger->info(sprintf('https://open.spotify.com/playlist/%s', $playlist->id));
227+
228+
return $playlist->id;
229+
}
230+
231+
/**
232+
* add the tracks to the given playlist
233+
*/
234+
protected function addTracks(string $playlistID, array $trackIDs):void{
235+
236+
$uris = array_chunk(
237+
array_map(fn(string $t):string => 'spotify:track:'.$t , array_values($trackIDs)), // why not just ids???
238+
100 // API max = 100 track URIs
239+
);
240+
241+
foreach($uris as $i => $chunk){
242+
243+
$playlistAddTracks = $this->spotify->request(
244+
path : sprintf('/v1/playlists/%s/tracks', $playlistID),
245+
method : 'POST',
246+
body : ['uris' => $chunk],
247+
headers: ['Content-Type' => 'application/json'],
248+
);
249+
250+
if($playlistAddTracks->getStatusCode() === 201){
251+
$json = MessageUtil::decodeJSON($playlistAddTracks);
252+
253+
$this->logger->info(sprintf('added tracks %s/%s [%s]', ++$i, count($uris), $json->snapshot_id));
254+
255+
continue;
256+
}
257+
258+
$this->logger->warning(sprintf('error adding tracks: http/%s', $playlistAddTracks->getStatusCode())); // idc
259+
}
260+
261+
}
262+
263+
}

0 commit comments

Comments
 (0)