2015-12-19 16:36:44 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
2021-06-04 16:19:34 +00:00
|
|
|
use App\Models\User;
|
2021-06-04 15:19:33 +00:00
|
|
|
use App\Values\LastfmLoveTrackParameters;
|
|
|
|
use GuzzleHttp\Promise\Promise;
|
|
|
|
use GuzzleHttp\Promise\Utils;
|
|
|
|
use Illuminate\Support\Collection;
|
2020-12-22 20:11:22 +00:00
|
|
|
use Throwable;
|
2015-12-19 16:36:44 +00:00
|
|
|
|
2019-04-07 21:09:25 +00:00
|
|
|
class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
2015-12-19 16:36:44 +00:00
|
|
|
{
|
|
|
|
/**
|
2021-06-04 15:19:33 +00:00
|
|
|
* Override the key param, since, again, Last.fm wants to be different.
|
2015-12-19 16:36:44 +00:00
|
|
|
*/
|
|
|
|
protected $keyParam = 'api_key';
|
|
|
|
|
2016-06-05 04:37:03 +00:00
|
|
|
/**
|
|
|
|
* Determine if our application is using Last.fm.
|
|
|
|
*/
|
2018-08-24 15:27:19 +00:00
|
|
|
public function used(): bool
|
2016-06-05 04:37:03 +00:00
|
|
|
{
|
2018-09-03 12:41:49 +00:00
|
|
|
return (bool) $this->getKey();
|
2016-06-05 04:37:03 +00:00
|
|
|
}
|
|
|
|
|
2015-12-20 12:17:35 +00:00
|
|
|
/**
|
|
|
|
* Determine if Last.fm integration is enabled.
|
|
|
|
*/
|
2018-08-24 15:27:19 +00:00
|
|
|
public function enabled(): bool
|
2015-12-20 12:17:35 +00:00
|
|
|
{
|
|
|
|
return $this->getKey() && $this->getSecret();
|
|
|
|
}
|
|
|
|
|
2021-06-04 15:19:33 +00:00
|
|
|
/** @return array<mixed>|null */
|
2018-08-24 15:27:19 +00:00
|
|
|
public function getArtistInformation(string $name): ?array
|
2015-12-19 16:36:44 +00:00
|
|
|
{
|
|
|
|
if (!$this->enabled()) {
|
2018-08-24 15:27:19 +00:00
|
|
|
return null;
|
2015-12-19 16:36:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$name = urlencode($name);
|
|
|
|
|
|
|
|
try {
|
2018-08-29 07:05:24 +00:00
|
|
|
return $this->cache->remember(md5("lastfm_artist_$name"), 24 * 60 * 7, function () use ($name): ?array {
|
2020-04-18 14:15:07 +00:00
|
|
|
$response = $this->get("?method=artist.getInfo&autocorrect=1&artist=$name&format=json");
|
2015-12-19 16:36:44 +00:00
|
|
|
|
2020-04-27 18:55:12 +00:00
|
|
|
if (!$response || !isset($response->artist)) {
|
2018-08-29 07:05:24 +00:00
|
|
|
return null;
|
2015-12-19 16:36:44 +00:00
|
|
|
}
|
|
|
|
|
2020-04-18 14:15:07 +00:00
|
|
|
return $this->buildArtistInformation($response->artist);
|
2018-08-29 07:05:24 +00:00
|
|
|
});
|
2020-12-22 20:11:22 +00:00
|
|
|
} catch (Throwable $e) {
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->error($e);
|
2015-12-19 16:36:44 +00:00
|
|
|
|
2018-08-24 15:27:19 +00:00
|
|
|
return null;
|
2015-12-19 16:36:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-03 23:21:50 +00:00
|
|
|
/**
|
|
|
|
* Build a Koel-usable array of artist information using the data from Last.fm.
|
|
|
|
*
|
2020-12-22 20:11:22 +00:00
|
|
|
* @param mixed $data
|
2017-06-03 23:21:50 +00:00
|
|
|
*
|
2020-12-22 20:11:22 +00:00
|
|
|
* @return array<mixed>
|
2017-06-03 23:21:50 +00:00
|
|
|
*/
|
2020-04-18 14:15:07 +00:00
|
|
|
private function buildArtistInformation($data): array
|
2017-06-03 23:21:50 +00:00
|
|
|
{
|
|
|
|
return [
|
2020-04-18 14:15:07 +00:00
|
|
|
'url' => $data->url,
|
|
|
|
'image' => count($data->image) > 3 ? $data->image[3]->{'#text'} : $data->image[0]->{'#text'},
|
2017-06-03 23:21:50 +00:00
|
|
|
'bio' => [
|
2020-04-22 22:14:05 +00:00
|
|
|
'summary' => isset($data->bio) ? $this->formatText($data->bio->summary) : '',
|
|
|
|
'full' => isset($data->bio) ? $this->formatText($data->bio->content) : '',
|
2017-06-03 23:21:50 +00:00
|
|
|
],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2021-06-04 15:19:33 +00:00
|
|
|
/** @return array<mixed>|null */
|
2018-08-24 15:27:19 +00:00
|
|
|
public function getAlbumInformation(string $albumName, string $artistName): ?array
|
2015-12-19 16:36:44 +00:00
|
|
|
{
|
|
|
|
if (!$this->enabled()) {
|
2018-08-24 15:27:19 +00:00
|
|
|
return null;
|
2015-12-19 16:36:44 +00:00
|
|
|
}
|
|
|
|
|
2018-08-24 15:27:19 +00:00
|
|
|
$albumName = urlencode($albumName);
|
2015-12-19 16:36:44 +00:00
|
|
|
$artistName = urlencode($artistName);
|
|
|
|
|
|
|
|
try {
|
2018-08-24 15:27:19 +00:00
|
|
|
$cacheKey = md5("lastfm_album_{$albumName}_{$artistName}");
|
2015-12-19 16:36:44 +00:00
|
|
|
|
2018-08-29 07:05:24 +00:00
|
|
|
return $this->cache->remember($cacheKey, 24 * 60 * 7, function () use ($albumName, $artistName): ?array {
|
2020-04-18 14:15:07 +00:00
|
|
|
$response = $this
|
|
|
|
->get("?method=album.getInfo&autocorrect=1&album=$albumName&artist=$artistName&format=json");
|
2015-12-19 16:36:44 +00:00
|
|
|
|
2020-04-27 18:55:12 +00:00
|
|
|
if (!$response || !isset($response->album)) {
|
2018-08-29 07:05:24 +00:00
|
|
|
return null;
|
|
|
|
}
|
2015-12-19 16:36:44 +00:00
|
|
|
|
2020-04-18 14:15:07 +00:00
|
|
|
return $this->buildAlbumInformation($response->album);
|
2018-08-29 07:05:24 +00:00
|
|
|
});
|
2020-12-22 20:11:22 +00:00
|
|
|
} catch (Throwable $e) {
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->error($e);
|
2015-12-19 16:36:44 +00:00
|
|
|
|
2018-08-24 15:27:19 +00:00
|
|
|
return null;
|
2015-12-19 16:36:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-03 23:21:50 +00:00
|
|
|
/**
|
|
|
|
* Build a Koel-usable array of album information using the data from Last.fm.
|
|
|
|
*
|
2020-12-22 20:11:22 +00:00
|
|
|
* @param mixed $data
|
2017-06-03 23:21:50 +00:00
|
|
|
*
|
2020-12-22 20:11:22 +00:00
|
|
|
* @return array<mixed>
|
2017-06-03 23:21:50 +00:00
|
|
|
*/
|
2020-04-18 14:15:07 +00:00
|
|
|
private function buildAlbumInformation($data): array
|
2017-06-03 23:21:50 +00:00
|
|
|
{
|
|
|
|
return [
|
2020-04-18 14:15:07 +00:00
|
|
|
'url' => $data->url,
|
|
|
|
'image' => count($data->image) > 3 ? $data->image[3]->{'#text'} : $data->image[0]->{'#text'},
|
2017-06-03 23:21:50 +00:00
|
|
|
'wiki' => [
|
2020-04-22 22:14:05 +00:00
|
|
|
'summary' => isset($data->wiki) ? $this->formatText($data->wiki->summary) : '',
|
|
|
|
'full' => isset($data->wiki) ? $this->formatText($data->wiki->content) : '',
|
2017-06-03 23:21:50 +00:00
|
|
|
],
|
2020-04-18 14:15:07 +00:00
|
|
|
'tracks' => array_map(static function ($track): array {
|
2017-06-03 23:21:50 +00:00
|
|
|
return [
|
2020-04-18 14:15:07 +00:00
|
|
|
'title' => $track->name,
|
|
|
|
'length' => (int) $track->duration,
|
|
|
|
'url' => $track->url,
|
2017-06-03 23:21:50 +00:00
|
|
|
];
|
2020-04-22 22:14:05 +00:00
|
|
|
}, isset($data->tracks) ? $data->tracks->track : []),
|
2017-06-03 23:21:50 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2015-12-19 16:36:44 +00:00
|
|
|
/**
|
2015-12-20 12:17:35 +00:00
|
|
|
* Get Last.fm's session key for the authenticated user using a token.
|
2016-03-06 04:11:28 +00:00
|
|
|
*
|
2015-12-20 12:17:35 +00:00
|
|
|
* @param string $token The token after successfully connecting to Last.fm
|
2015-12-19 16:36:44 +00:00
|
|
|
*
|
2020-09-06 21:20:42 +00:00
|
|
|
* @see http://www.last.fm/api/webauth#4
|
2015-12-20 12:17:35 +00:00
|
|
|
*/
|
2018-09-04 06:25:24 +00:00
|
|
|
public function getSessionKey(string $token): ?string
|
2015-12-20 12:17:35 +00:00
|
|
|
{
|
|
|
|
$query = $this->buildAuthCallParams([
|
|
|
|
'method' => 'auth.getSession',
|
|
|
|
'token' => $token,
|
|
|
|
], true);
|
|
|
|
|
|
|
|
try {
|
2020-04-18 14:15:07 +00:00
|
|
|
return $this->get("/?$query&format=json", [], false)->session->key;
|
2020-12-22 20:11:22 +00:00
|
|
|
} catch (Throwable $e) {
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->error($e);
|
2015-12-20 12:17:35 +00:00
|
|
|
|
2018-08-24 15:27:19 +00:00
|
|
|
return null;
|
2015-12-20 12:17:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-04 15:19:33 +00:00
|
|
|
public function scrobble(
|
|
|
|
string $artistName,
|
|
|
|
string $trackName,
|
|
|
|
$timestamp,
|
|
|
|
string $albumName,
|
|
|
|
string $sessionKey
|
|
|
|
): void {
|
|
|
|
$params = [
|
|
|
|
'artist' => $artistName,
|
|
|
|
'track' => $trackName,
|
|
|
|
'timestamp' => $timestamp,
|
|
|
|
'sk' => $sessionKey,
|
|
|
|
'method' => 'track.scrobble',
|
|
|
|
];
|
2015-12-20 12:17:35 +00:00
|
|
|
|
2021-06-04 15:19:33 +00:00
|
|
|
if ($albumName) {
|
|
|
|
$params['album'] = $albumName;
|
2015-12-20 12:17:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2018-08-24 15:27:19 +00:00
|
|
|
$this->post('/', $this->buildAuthCallParams($params), false);
|
2020-12-22 20:11:22 +00:00
|
|
|
} catch (Throwable $e) {
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->error($e);
|
2015-12-20 12:17:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-04 15:19:33 +00:00
|
|
|
public function toggleLoveTrack(LastfmLoveTrackParameters $params, string $sessionKey, bool $love = true): void
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
$this->post('/', $this->buildAuthCallParams([
|
|
|
|
'track' => $params->getTrackName(),
|
|
|
|
'artist' => $params->getArtistName(),
|
|
|
|
'sk' => $sessionKey,
|
|
|
|
'method' => $love ? 'track.love' : 'track.unlove',
|
|
|
|
]), false);
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
$this->logger->error($e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-21 13:49:00 +00:00
|
|
|
/**
|
2021-06-04 15:19:33 +00:00
|
|
|
* @param Collection|array<LastfmLoveTrackParameters> $parameterCollection
|
2015-12-21 13:49:00 +00:00
|
|
|
*/
|
2021-06-04 15:19:33 +00:00
|
|
|
public function batchToggleLoveTracks(Collection $parameterCollection, string $sessionKey, bool $love = true): void
|
2015-12-21 13:49:00 +00:00
|
|
|
{
|
2021-06-04 15:19:33 +00:00
|
|
|
$promises = $parameterCollection->map(
|
|
|
|
function (LastfmLoveTrackParameters $params) use ($sessionKey, $love): Promise {
|
|
|
|
return $this->postAsync('/', $this->buildAuthCallParams([
|
|
|
|
'track' => $params->getTrackName(),
|
|
|
|
'artist' => $params->getArtistName(),
|
|
|
|
'sk' => $sessionKey,
|
|
|
|
'method' => $love ? 'track.love' : 'track.unlove',
|
|
|
|
]), false);
|
|
|
|
}
|
|
|
|
);
|
2015-12-21 13:49:00 +00:00
|
|
|
|
|
|
|
try {
|
2021-06-04 15:19:33 +00:00
|
|
|
Utils::unwrap($promises);
|
2020-12-22 20:11:22 +00:00
|
|
|
} catch (Throwable $e) {
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->error($e);
|
2015-12-21 13:49:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-23 06:26:16 +00:00
|
|
|
/**
|
|
|
|
* @param int|float $duration Duration of the track, in seconds
|
|
|
|
*/
|
2021-06-04 15:19:33 +00:00
|
|
|
public function updateNowPlaying(
|
|
|
|
string $artistName,
|
|
|
|
string $trackName,
|
|
|
|
string $albumName,
|
|
|
|
$duration,
|
|
|
|
string $sessionKey
|
|
|
|
): void {
|
|
|
|
$params = [
|
|
|
|
'artist' => $artistName,
|
|
|
|
'track' => $trackName,
|
|
|
|
'duration' => $duration,
|
|
|
|
'sk' => $sessionKey,
|
|
|
|
'method' => 'track.updateNowPlaying',
|
|
|
|
];
|
2015-12-23 06:26:16 +00:00
|
|
|
|
2021-06-04 15:19:33 +00:00
|
|
|
if ($albumName) {
|
|
|
|
$params['album'] = $albumName;
|
2015-12-23 06:26:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2018-08-24 15:27:19 +00:00
|
|
|
$this->post('/', $this->buildAuthCallParams($params), false);
|
2020-12-22 20:11:22 +00:00
|
|
|
} catch (Throwable $e) {
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->error($e);
|
2015-12-23 06:26:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-20 12:17:35 +00:00
|
|
|
/**
|
|
|
|
* Build the parameters to use for _authenticated_ Last.fm API calls.
|
|
|
|
* Such calls require:
|
|
|
|
* - The API key (api_key)
|
|
|
|
* - The API signature (api_sig).
|
|
|
|
*
|
2020-09-06 21:20:42 +00:00
|
|
|
* @see http://www.last.fm/api/webauth#5
|
2016-03-06 04:11:28 +00:00
|
|
|
*
|
2020-12-22 23:01:49 +00:00
|
|
|
* @param array $params The array of parameters
|
2020-12-22 20:11:22 +00:00
|
|
|
* @param bool $toString Whether to turn the array into a query string
|
2015-12-20 12:17:35 +00:00
|
|
|
*
|
2020-12-22 20:11:22 +00:00
|
|
|
* @return array<mixed>|string
|
2015-12-20 12:17:35 +00:00
|
|
|
*/
|
2020-12-22 23:01:49 +00:00
|
|
|
public function buildAuthCallParams(array $params, bool $toString = false)
|
2015-12-20 12:17:35 +00:00
|
|
|
{
|
|
|
|
$params['api_key'] = $this->getKey();
|
|
|
|
ksort($params);
|
|
|
|
|
|
|
|
// Generate the API signature.
|
|
|
|
// @link http://www.last.fm/api/webauth#6
|
|
|
|
$str = '';
|
2018-08-24 15:27:19 +00:00
|
|
|
|
2015-12-20 12:17:35 +00:00
|
|
|
foreach ($params as $name => $value) {
|
2020-12-22 20:11:22 +00:00
|
|
|
$str .= $name . $value;
|
2015-12-20 12:17:35 +00:00
|
|
|
}
|
2018-08-24 15:27:19 +00:00
|
|
|
|
2015-12-20 12:17:35 +00:00
|
|
|
$str .= $this->getSecret();
|
|
|
|
$params['api_sig'] = md5($str);
|
|
|
|
|
|
|
|
if (!$toString) {
|
|
|
|
return $params;
|
|
|
|
}
|
|
|
|
|
|
|
|
$query = '';
|
2020-12-22 20:11:22 +00:00
|
|
|
|
2015-12-20 12:17:35 +00:00
|
|
|
foreach ($params as $key => $value) {
|
|
|
|
$query .= "$key=$value&";
|
|
|
|
}
|
|
|
|
|
|
|
|
return rtrim($query, '&');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-09-04 06:09:52 +00:00
|
|
|
* Correctly format a value returned by Last.fm.
|
2015-12-20 12:17:35 +00:00
|
|
|
*/
|
2020-04-18 14:15:07 +00:00
|
|
|
protected function formatText(?string $value): string
|
2015-12-20 12:17:35 +00:00
|
|
|
{
|
2018-09-04 06:09:52 +00:00
|
|
|
if (!$value) {
|
2015-12-20 12:17:35 +00:00
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
2018-09-04 06:09:52 +00:00
|
|
|
return trim(str_replace('Read more on Last.fm', '', nl2br(strip_tags(html_entity_decode($value)))));
|
2015-12-19 16:36:44 +00:00
|
|
|
}
|
2018-08-19 11:08:16 +00:00
|
|
|
|
2018-08-29 10:36:05 +00:00
|
|
|
public function getKey(): ?string
|
2018-08-19 11:08:16 +00:00
|
|
|
{
|
|
|
|
return config('koel.lastfm.key');
|
|
|
|
}
|
|
|
|
|
2018-08-29 10:36:05 +00:00
|
|
|
public function getEndpoint(): ?string
|
2018-08-19 11:08:16 +00:00
|
|
|
{
|
|
|
|
return config('koel.lastfm.endpoint');
|
|
|
|
}
|
|
|
|
|
2018-08-29 10:36:05 +00:00
|
|
|
public function getSecret(): ?string
|
2018-08-19 11:08:16 +00:00
|
|
|
{
|
|
|
|
return config('koel.lastfm.secret');
|
|
|
|
}
|
2021-06-04 16:19:34 +00:00
|
|
|
|
|
|
|
public function setUserSessionKey(User $user, ?string $sessionKey): void
|
|
|
|
{
|
|
|
|
$user->preferences->lastFmSessionKey = $sessionKey;
|
|
|
|
$user->save();
|
|
|
|
}
|
2015-12-19 16:36:44 +00:00
|
|
|
}
|