1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/* |
6
|
|
|
* (c) Christian Gripp <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Core23\LastFm\Service; |
13
|
|
|
|
14
|
|
|
use Core23\LastFm\Connection\SessionInterface; |
15
|
|
|
use Core23\LastFm\Exception\ApiException; |
16
|
|
|
use Core23\LastFm\Exception\NotFoundException; |
17
|
|
|
use Core23\LastFm\Model\NowPlaying; |
18
|
|
|
use Core23\LastFm\Model\Song; |
19
|
|
|
use Core23\LastFm\Model\SongInfo; |
20
|
|
|
use Core23\LastFm\Model\Tag; |
21
|
|
|
|
22
|
|
|
final class TrackService extends AbstractService |
23
|
|
|
{ |
24
|
|
|
/** |
25
|
|
|
* Tag an track using a list of user supplied tags. |
26
|
|
|
* |
27
|
|
|
* @param SessionInterface $session |
28
|
|
|
* @param string $artist |
29
|
|
|
* @param string $track |
30
|
|
|
* @param array $tags |
31
|
|
|
* |
32
|
|
|
* @throws ApiException |
33
|
|
|
* @throws NotFoundException |
34
|
|
|
*/ |
35
|
|
|
public function addTags(SessionInterface $session, string $artist, string $track, array $tags): void |
36
|
|
|
{ |
37
|
|
|
$count = \count($tags); |
38
|
|
|
|
39
|
|
|
if (0 === $count) { |
40
|
|
|
return; |
41
|
|
|
} |
42
|
|
|
if ($count > 10) { |
43
|
|
|
throw new \InvalidArgumentException('A maximum of 10 tags is allowed'); |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
$this->signedCall('track.addTags', [ |
47
|
|
|
'artist' => $artist, |
48
|
|
|
'track' => $track, |
49
|
|
|
'tags' => implode(',', $tags), |
50
|
|
|
], $session, 'POST'); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Check whether the supplied track has a correction to a canonical artist. |
55
|
|
|
* |
56
|
|
|
* @param string $artist |
57
|
|
|
* @param string $track |
58
|
|
|
* |
59
|
|
|
* @throws ApiException |
60
|
|
|
* @throws NotFoundException |
61
|
|
|
* |
62
|
|
|
* @return Song|null |
63
|
|
|
*/ |
64
|
|
|
public function getCorrection(string $artist, string $track): ?Song |
65
|
|
|
{ |
66
|
|
|
$response = $this->unsignedCall('track.getCorrection', [ |
67
|
|
|
'artist' => $artist, |
68
|
|
|
'track' => $track, |
69
|
|
|
]); |
70
|
|
|
|
71
|
|
|
if (!isset($response['corrections']['correction']['track'])) { |
72
|
|
|
return null; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
return Song::fromApi($response['corrections']['correction']['track']); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Get the metadata for a track on Last.fm using the artist/track name. |
80
|
|
|
* |
81
|
|
|
* @param string $artist |
82
|
|
|
* @param string $track |
83
|
|
|
* @param string|null $username |
84
|
|
|
* @param bool $autocorrect |
85
|
|
|
* |
86
|
|
|
* @throws ApiException |
87
|
|
|
* @throws NotFoundException |
88
|
|
|
* |
89
|
|
|
* @return SongInfo|null |
90
|
|
|
*/ |
91
|
|
|
public function getInfo(string $artist, string $track, ?string $username = null, $autocorrect = false): ?SongInfo |
92
|
|
|
{ |
93
|
|
|
$response = $this->unsignedCall('track.getInfo', [ |
94
|
|
|
'artist' => $artist, |
95
|
|
|
'track' => $track, |
96
|
|
|
'autocorrect' => (int) $autocorrect, |
97
|
|
|
'username' => $username, |
98
|
|
|
]); |
99
|
|
|
|
100
|
|
|
if (!isset($response['track'])) { |
101
|
|
|
return null; |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
return SongInfo::fromApi($response['track']); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* Get the metadata for a track on Last.fm using the musicbrainz id. |
109
|
|
|
* |
110
|
|
|
* @param string $mbid |
111
|
|
|
* @param string|null $username |
112
|
|
|
* @param bool $autocorrect |
113
|
|
|
* |
114
|
|
|
* @throws ApiException |
115
|
|
|
* @throws NotFoundException |
116
|
|
|
* |
117
|
|
|
* @return SongInfo|null |
118
|
|
|
*/ |
119
|
|
|
public function getInfoByMBID(string $mbid, ?string $username = null, $autocorrect = false): ?SongInfo |
120
|
|
|
{ |
121
|
|
|
$response = $this->unsignedCall('track.getInfo', [ |
122
|
|
|
'mbid' => $mbid, |
123
|
|
|
'autocorrect' => (int) $autocorrect, |
124
|
|
|
'username' => $username, |
125
|
|
|
]); |
126
|
|
|
|
127
|
|
|
if (!isset($response['track'])) { |
128
|
|
|
return null; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
return SongInfo::fromApi($response['track']); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Get the similar tracks for this track on Last.fm, based on listening data. |
136
|
|
|
* |
137
|
|
|
* @param string $artist |
138
|
|
|
* @param string $track |
139
|
|
|
* @param int $limit |
140
|
|
|
* @param bool $autocorrect |
141
|
|
|
* |
142
|
|
|
* @throws ApiException |
143
|
|
|
* @throws NotFoundException |
144
|
|
|
* |
145
|
|
|
* @return SongInfo[] |
146
|
|
|
*/ |
147
|
|
|
public function getSimilar(string $artist, string $track, int $limit = 10, bool $autocorrect = false): array |
148
|
|
|
{ |
149
|
|
|
$response = $this->unsignedCall('track.getSimilar', [ |
150
|
|
|
'artist' => $artist, |
151
|
|
|
'track' => $track, |
152
|
|
|
'limit' => $limit, |
153
|
|
|
'autocorrect' => (int) $autocorrect, |
154
|
|
|
]); |
155
|
|
|
|
156
|
|
|
if (!isset($response['similartracks']['track'])) { |
157
|
|
|
return []; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
return array_map(function ($data) { |
161
|
|
|
return SongInfo::fromApi($data); |
162
|
|
|
}, $response['similartracks']['track']); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Get the similar tracks for this track using the musicbrainz id on Last.fm, based on listening data. |
167
|
|
|
* |
168
|
|
|
* @param string $mbid |
169
|
|
|
* @param int $limit |
170
|
|
|
* @param bool $autocorrect |
171
|
|
|
* |
172
|
|
|
* @throws ApiException |
173
|
|
|
* @throws NotFoundException |
174
|
|
|
* |
175
|
|
|
* @return SongInfo[] |
176
|
|
|
*/ |
177
|
|
|
public function getSimilarByMBID(string $mbid, int $limit = 10, bool $autocorrect = false): array |
178
|
|
|
{ |
179
|
|
|
$response = $this->unsignedCall('track.getSimilar', [ |
180
|
|
|
'mbid' => $mbid, |
181
|
|
|
'limit' => $limit, |
182
|
|
|
'autocorrect' => (int) $autocorrect, |
183
|
|
|
]); |
184
|
|
|
|
185
|
|
|
if (!isset($response['similartracks']['track'])) { |
186
|
|
|
return []; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
return array_map(function ($data) { |
190
|
|
|
return SongInfo::fromApi($data); |
191
|
|
|
}, $response['similartracks']['track']); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Get the tags applied by an individual user to a track on Last.fm. |
196
|
|
|
* |
197
|
|
|
* @param string $artist |
198
|
|
|
* @param string $track |
199
|
|
|
* @param string $username |
200
|
|
|
* @param bool $autocorrect |
201
|
|
|
* |
202
|
|
|
* @throws ApiException |
203
|
|
|
* @throws NotFoundException |
204
|
|
|
* |
205
|
|
|
* @return Tag[] |
206
|
|
|
*/ |
207
|
|
|
public function getTags(string $artist, string $track, string $username, bool $autocorrect = false): array |
208
|
|
|
{ |
209
|
|
|
$response = $this->unsignedCall('track.getTags', [ |
210
|
|
|
'artist' => $artist, |
211
|
|
|
'track' => $track, |
212
|
|
|
'user' => $username, |
213
|
|
|
'autocorrect' => (int) $autocorrect, |
214
|
|
|
]); |
215
|
|
|
|
216
|
|
|
if (!isset($response['tags']['tag'])) { |
217
|
|
|
return []; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
return array_map(function ($data) { |
221
|
|
|
return Tag::fromApi($data); |
222
|
|
|
}, $response['tags']['tag']); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Get the tags applied by an individual user to a track using the musicbrainz id on Last.fm. |
227
|
|
|
* |
228
|
|
|
* @param string $mbid |
229
|
|
|
* @param string $username |
230
|
|
|
* @param bool $autocorrect |
231
|
|
|
* |
232
|
|
|
* @throws ApiException |
233
|
|
|
* @throws NotFoundException |
234
|
|
|
* |
235
|
|
|
* @return Tag[] |
236
|
|
|
*/ |
237
|
|
|
public function getTagsByMBID(string $mbid, string $username, bool $autocorrect = false): array |
238
|
|
|
{ |
239
|
|
|
$response = $this->unsignedCall('track.getTags', [ |
240
|
|
|
'mbid' => $mbid, |
241
|
|
|
'user' => $username, |
242
|
|
|
'autocorrect' => (int) $autocorrect, |
243
|
|
|
]); |
244
|
|
|
|
245
|
|
|
if (!isset($response['tags']['tag'])) { |
246
|
|
|
return []; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
return array_map(function ($data) { |
250
|
|
|
return Tag::fromApi($data); |
251
|
|
|
}, $response['tags']['tag']); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* Get the top tags for this track on Last.fm, ordered by tag count. |
256
|
|
|
* |
257
|
|
|
* @param string $artist |
258
|
|
|
* @param string $track |
259
|
|
|
* @param bool $autocorrect |
260
|
|
|
* |
261
|
|
|
* @throws ApiException |
262
|
|
|
* @throws NotFoundException |
263
|
|
|
* |
264
|
|
|
* @return Tag[] |
265
|
|
|
*/ |
266
|
|
|
public function getTopTags(string $artist, string $track, bool $autocorrect = false): array |
267
|
|
|
{ |
268
|
|
|
$response = $this->unsignedCall('track.getTopTags', [ |
269
|
|
|
'artist' => $artist, |
270
|
|
|
'track' => $track, |
271
|
|
|
'autocorrect' => (int) $autocorrect, |
272
|
|
|
]); |
273
|
|
|
|
274
|
|
|
if (!isset($response['toptags']['tag'])) { |
275
|
|
|
return []; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
return array_map(function ($data) { |
279
|
|
|
return Tag::fromApi($data); |
280
|
|
|
}, $response['toptags']['tag']); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* Get the top tags for this track using the musicbrainz id on Last.fm, ordered by tag count. |
285
|
|
|
* |
286
|
|
|
* @param string $bdid |
287
|
|
|
* @param bool $autocorrect |
288
|
|
|
* |
289
|
|
|
* @throws ApiException |
290
|
|
|
* @throws NotFoundException |
291
|
|
|
* |
292
|
|
|
* @return Tag[] |
293
|
|
|
*/ |
294
|
|
|
public function getTopTagsByMBID(string $bdid, bool $autocorrect = false): array |
295
|
|
|
{ |
296
|
|
|
$response = $this->unsignedCall('track.getTopTags', [ |
297
|
|
|
'bdid' => $bdid, |
298
|
|
|
'autocorrect' => (int) $autocorrect, |
299
|
|
|
]); |
300
|
|
|
|
301
|
|
|
if (!isset($response['toptags']['tag'])) { |
302
|
|
|
return []; |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
return array_map(function ($data) { |
306
|
|
|
return Tag::fromApi($data); |
307
|
|
|
}, $response['toptags']['tag']); |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* Love a track for a user profile. |
312
|
|
|
* |
313
|
|
|
* @param SessionInterface $session |
314
|
|
|
* @param string $artist |
315
|
|
|
* @param string $track |
316
|
|
|
* |
317
|
|
|
* @throws ApiException |
318
|
|
|
* @throws NotFoundException |
319
|
|
|
*/ |
320
|
|
|
public function love(SessionInterface $session, string $artist, string $track): void |
321
|
|
|
{ |
322
|
|
|
$this->signedCall('track.love', [ |
323
|
|
|
'artist' => $artist, |
324
|
|
|
'track' => $track, |
325
|
|
|
], $session, 'POST'); |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Remove a user's tag from a track. |
330
|
|
|
* |
331
|
|
|
* @param SessionInterface $session |
332
|
|
|
* @param string $artist |
333
|
|
|
* @param string $track |
334
|
|
|
* @param string $tag |
335
|
|
|
* |
336
|
|
|
* @throws ApiException |
337
|
|
|
* @throws NotFoundException |
338
|
|
|
*/ |
339
|
|
|
public function removeTag(SessionInterface $session, string $artist, string $track, string $tag): void |
340
|
|
|
{ |
341
|
|
|
$this->signedCall('track.removeTag', [ |
342
|
|
|
'artist' => $artist, |
343
|
|
|
'track' => $track, |
344
|
|
|
'tag' => $tag, |
345
|
|
|
], $session, 'POST'); |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** |
349
|
|
|
* Share a track twith one or more Last.fm users or other friends. |
350
|
|
|
* |
351
|
|
|
* @param SessionInterface $session |
352
|
|
|
* @param array $tracks |
353
|
|
|
* |
354
|
|
|
* @throws ApiException |
355
|
|
|
* @throws NotFoundException |
356
|
|
|
*/ |
357
|
|
|
public function scrobble(SessionInterface $session, array $tracks): void |
358
|
|
|
{ |
359
|
|
|
$count = \count($tracks); |
360
|
|
|
|
361
|
|
|
if (0 === $count) { |
362
|
|
|
return; |
363
|
|
|
} |
364
|
|
|
if ($count > 10) { |
365
|
|
|
throw new \InvalidArgumentException('A maximum of 50 tracks is allowed'); |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
$data = self::buildTrackList($tracks); |
369
|
|
|
|
370
|
|
|
$this->signedCall('album.scrobble', $data, $session, 'POST'); |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
/** |
374
|
|
|
* Search for a track by track name. Returns track matches sorted by relevance. |
375
|
|
|
* |
376
|
|
|
* @param string $track |
377
|
|
|
* @param int $limit |
378
|
|
|
* @param int $page |
379
|
|
|
* |
380
|
|
|
* @throws ApiException |
381
|
|
|
* @throws NotFoundException |
382
|
|
|
* |
383
|
|
|
* @return SongInfo[] |
384
|
|
|
*/ |
385
|
|
|
public function search(string $track, int $limit = 50, int $page = 1): array |
386
|
|
|
{ |
387
|
|
|
$response = $this->unsignedCall('track.search', [ |
388
|
|
|
'track' => $track, |
389
|
|
|
'limit' => $limit, |
390
|
|
|
'page' => $page, |
391
|
|
|
]); |
392
|
|
|
|
393
|
|
|
if (!isset($response['results']['trackmatches']['track'])) { |
394
|
|
|
return []; |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
return array_map(function ($data) { |
398
|
|
|
return SongInfo::fromApi($data); |
399
|
|
|
}, $response['results']['trackmatches']['track']); |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
/** |
403
|
|
|
* Unlove a track for a user profile. |
404
|
|
|
* |
405
|
|
|
* @param SessionInterface $session |
406
|
|
|
* @param string $artist |
407
|
|
|
* @param string $track |
408
|
|
|
* |
409
|
|
|
* @throws ApiException |
410
|
|
|
* @throws NotFoundException |
411
|
|
|
*/ |
412
|
|
|
public function unlove(SessionInterface $session, string $artist, string $track): void |
413
|
|
|
{ |
414
|
|
|
$this->signedCall('track.love', [ |
415
|
|
|
'artist' => $artist, |
416
|
|
|
'track' => $track, |
417
|
|
|
], $session, 'POST'); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* Share a track twith one or more Last.fm users or other friends. |
422
|
|
|
* |
423
|
|
|
* @param SessionInterface $session |
424
|
|
|
* @param NowPlaying $nowPlaying |
425
|
|
|
*/ |
426
|
|
|
public function updateNowPlaying(SessionInterface $session, NowPlaying $nowPlaying): void |
427
|
|
|
{ |
428
|
|
|
$this->signedCall('track.updateNowPlaying', $nowPlaying->toArray(), $session, 'POST'); |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
/** |
432
|
|
|
* @param array $tracks |
433
|
|
|
* |
434
|
|
|
* @return array |
435
|
|
|
*/ |
436
|
|
|
private static function buildTrackList(array $tracks): array |
437
|
|
|
{ |
438
|
|
|
$data = []; |
439
|
|
|
|
440
|
|
|
$i = 0; |
441
|
|
|
foreach ($tracks as $track) { |
442
|
|
|
// Required fields |
443
|
|
|
foreach (['artist', 'track', 'timestamp'] as $field) { |
444
|
|
|
if (!\array_key_exists($field, $track)) { |
445
|
|
|
throw new \InvalidArgumentException(sprintf('Field "%s" not set on entry %s', $field, $i)); |
446
|
|
|
} |
447
|
|
|
$data[$field.'['.$i.']'] = $track[$field]; |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
// Optional fields |
451
|
|
|
foreach (['album', 'context', 'streamId', 'chosenByUser', 'trackNumber', 'mbid', 'albumArtist', 'duration'] as $field) { |
452
|
|
|
if (\array_key_exists($field, $track)) { |
453
|
|
|
$data[$field.'['.$i.']'] = $track[$field]; |
454
|
|
|
} |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
++$i; |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
return $data; |
461
|
|
|
} |
462
|
|
|
} |
463
|
|
|
|