koel/app/Services/LastfmService.php

266 lines
7.2 KiB
PHP
Raw Normal View History

<?php
namespace App\Services;
2022-07-16 22:42:29 +00:00
use App\Models\Album;
use App\Models\Artist;
use App\Models\User;
use App\Values\AlbumInformation;
use App\Values\ArtistInformation;
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;
2019-04-07 21:09:25 +00:00
class LastfmService extends AbstractApiClient implements ApiConsumerInterface
{
/**
* Override the key param, since, again, Last.fm wants to be different.
*/
2021-06-05 10:47:56 +00:00
protected string $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
{
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();
}
2022-07-16 22:42:29 +00:00
public function getArtistInformation(Artist $artist): ?ArtistInformation
{
if (!$this->enabled()) {
2018-08-24 15:27:19 +00:00
return null;
}
2022-07-16 22:42:29 +00:00
$name = urlencode($artist->name);
try {
2021-06-05 10:47:56 +00:00
return $this->cache->remember(
md5("lastfm_artist_$name"),
now()->addWeek(),
function () use ($name): ?ArtistInformation {
2021-06-05 10:47:56 +00:00
$response = $this->get("?method=artist.getInfo&autocorrect=1&artist=$name&format=json");
return $response?->artist ? ArtistInformation::fromLastFmData($response->artist) : null;
2021-06-05 10:47:56 +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);
2018-08-24 15:27:19 +00:00
return null;
}
}
2022-07-16 22:42:29 +00:00
public function getAlbumInformation(Album $album): ?AlbumInformation
{
if (!$this->enabled()) {
2018-08-24 15:27:19 +00:00
return null;
}
2022-07-16 22:42:29 +00:00
$albumName = urlencode($album->name);
$artistName = urlencode($album->artist->name);
try {
2018-08-24 15:27:19 +00:00
$cacheKey = md5("lastfm_album_{$albumName}_{$artistName}");
2021-06-05 10:47:56 +00:00
return $this->cache->remember(
$cacheKey,
now()->addWeek(),
function () use ($albumName, $artistName): ?AlbumInformation {
2021-06-05 10:47:56 +00:00
$response = $this
->get("?method=album.getInfo&autocorrect=1&album=$albumName&artist=$artistName&format=json");
return $response?->album ? AlbumInformation::fromLastFmData($response->album) : null;
2021-06-05 10:47:56 +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);
2018-08-24 15:27:19 +00:00
return null;
}
}
/**
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
*
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 {
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
}
}
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
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
}
}
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
/**
* @param Collection|array<LastfmLoveTrackParameters> $parameterCollection
2015-12-21 13:49:00 +00:00
*/
public function batchToggleLoveTracks(Collection $parameterCollection, string $sessionKey, bool $love = true): void
2015-12-21 13:49:00 +00:00
{
$promises = $parameterCollection->map(
2021-06-05 10:47:56 +00:00
fn (LastfmLoveTrackParameters $params): Promise => $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 {
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
}
}
public function updateNowPlaying(
string $artistName,
string $trackName,
string $albumName,
int|float $duration,
string $sessionKey
): void {
$params = [
'artist' => $artistName,
'track' => $trackName,
'duration' => $duration,
'sk' => $sessionKey,
'method' => 'track.updateNowPlaying',
];
2015-12-23 06:26:16 +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
*/
public function buildAuthCallParams(array $params, bool $toString = false): array|string
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-08-29 10:36:05 +00:00
public function getKey(): ?string
{
return config('koel.lastfm.key');
}
2018-08-29 10:36:05 +00:00
public function getEndpoint(): ?string
{
return config('koel.lastfm.endpoint');
}
2018-08-29 10:36:05 +00:00
public function getSecret(): ?string
{
return config('koel.lastfm.secret');
}
public function setUserSessionKey(User $user, ?string $sessionKey): void
{
$user->preferences->lastFmSessionKey = $sessionKey;
$user->save();
}
}