koel/app/Services/LastfmService.php

365 lines
10 KiB
PHP
Raw Normal View History

<?php
namespace App\Services;
2018-09-04 05:34:02 +00:00
use App\Models\User;
2016-04-02 13:16:09 +00:00
use Exception;
2018-09-04 05:34:02 +00:00
use GuzzleHttp\Client;
use Illuminate\Contracts\Cache\Repository as Cache;
use Illuminate\Log\Logger;
class LastfmService extends ApiClient implements ApiConsumerInterface
{
2018-09-04 05:34:02 +00:00
public const SESSION_KEY_PREFERENCE_KEY = 'lastfm_session_key';
/**
* Specify the response format, since Last.fm only returns XML.
2016-03-06 04:11:28 +00:00
*
* @var string
*/
protected $responseFormat = 'xml';
/**
* Override the key param, since, again, Lastfm wants to be different.
2016-03-06 04:11:28 +00:00
*
* @var string
*/
protected $keyParam = 'api_key';
2018-09-04 05:34:02 +00:00
private $userPreferenceService;
public function __construct(
Client $client,
Cache $cache,
Logger $logger,
UserPreferenceService $userPreferenceService
2018-09-04 05:43:12 +00:00
) {
2018-09-04 05:34:02 +00:00
parent::__construct($client, $cache, $logger);
$this->userPreferenceService = $userPreferenceService;
}
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();
}
/**
* Get information about an artist.
2016-03-06 04:11:28 +00:00
*
* @param string $name Name of the artist
*
2018-08-24 15:27:19 +00:00
* @return mixed[]|null
*/
2018-08-24 15:27:19 +00:00
public function getArtistInformation(string $name): ?array
{
if (!$this->enabled()) {
2018-08-24 15:27:19 +00:00
return null;
}
$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 {
$response = $this->get("?method=artist.getInfo&autocorrect=1&artist=$name");
2018-08-29 07:05:24 +00:00
if (!$response) {
return null;
}
2018-08-29 07:05:24 +00:00
$response = simplexml_load_string($response->asXML());
$response = json_decode(json_encode($response), true);
2018-08-29 07:05:24 +00:00
if (!$response || !$artist = array_get($response, 'artist')) {
return null;
}
2018-08-29 07:05:24 +00:00
return $this->buildArtistInformation($artist);
});
2016-04-02 13:16:09 +00:00
} catch (Exception $e) {
2018-08-31 13:47:15 +00:00
$this->logger->error($e);
2018-08-24 15:27:19 +00:00
return null;
}
}
2017-06-03 23:21:50 +00:00
/**
* Build a Koel-usable array of artist information using the data from Last.fm.
*
2018-08-24 15:27:19 +00:00
* @param mixed[] $artistData
2017-06-03 23:21:50 +00:00
*
2018-08-24 15:27:19 +00:00
* @return mixed[]
2017-06-03 23:21:50 +00:00
*/
2018-08-24 15:27:19 +00:00
private function buildArtistInformation(array $artistData): array
2017-06-03 23:21:50 +00:00
{
return [
2018-08-24 15:27:19 +00:00
'url' => array_get($artistData, 'url'),
'image' => count($artistData['image']) > 3 ? $artistData['image'][3] : $artistData['image'][0],
2017-06-03 23:21:50 +00:00
'bio' => [
2018-08-29 07:05:24 +00:00
'summary' => $this->formatText(array_get($artistData, 'bio.summary', '')),
'full' => $this->formatText(array_get($artistData, 'bio.content', '')),
2017-06-03 23:21:50 +00:00
],
];
}
/**
* Get information about an album.
2016-03-06 04:11:28 +00:00
*
2018-08-24 15:27:19 +00:00
* @return mixed[]|null
*/
2018-08-24 15:27:19 +00:00
public function getAlbumInformation(string $albumName, string $artistName): ?array
{
if (!$this->enabled()) {
2018-08-24 15:27:19 +00:00
return null;
}
2018-08-24 15:27:19 +00:00
$albumName = urlencode($albumName);
$artistName = urlencode($artistName);
try {
2018-08-24 15:27:19 +00:00
$cacheKey = md5("lastfm_album_{$albumName}_{$artistName}");
2018-08-29 07:05:24 +00:00
return $this->cache->remember($cacheKey, 24 * 60 * 7, function () use ($albumName, $artistName): ?array {
$response = $this->get("?method=album.getInfo&autocorrect=1&album=$albumName&artist=$artistName");
if (!$response) {
return null;
}
2018-08-29 07:05:24 +00:00
$response = simplexml_load_string($response->asXML());
$response = json_decode(json_encode($response), true);
2018-08-29 07:05:24 +00:00
if (!$response || !$album = array_get($response, 'album')) {
return null;
}
2018-08-29 07:05:24 +00:00
return $this->buildAlbumInformation($album);
});
2016-04-02 13:16:09 +00:00
} catch (Exception $e) {
2018-08-31 13:47:15 +00:00
$this->logger->error($e);
2018-08-24 15:27:19 +00:00
return null;
}
}
2017-06-03 23:21:50 +00:00
/**
* Build a Koel-usable array of album information using the data from Last.fm.
*
2018-08-24 15:27:19 +00:00
* @param mixed[] $albumData
2017-06-03 23:21:50 +00:00
*
2018-08-24 15:27:19 +00:00
* @return mixed[]
2017-06-03 23:21:50 +00:00
*/
2018-08-24 15:27:19 +00:00
private function buildAlbumInformation(array $albumData): array
2017-06-03 23:21:50 +00:00
{
return [
2018-08-24 15:27:19 +00:00
'url' => array_get($albumData, 'url'),
'image' => count($albumData['image']) > 3 ? $albumData['image'][3] : $albumData['image'][0],
2017-06-03 23:21:50 +00:00
'wiki' => [
2018-08-29 07:05:24 +00:00
'summary' => $this->formatText(array_get($albumData, 'wiki.summary', '')),
'full' => $this->formatText(array_get($albumData, 'wiki.content', '')),
2017-06-03 23:21:50 +00:00
],
'tracks' => array_map(function ($track) {
return [
'title' => $track['name'],
'length' => (int) $track['duration'],
'url' => $track['url'],
];
2018-08-24 15:27:19 +00:00
}, array_get($albumData, 'tracks.track', [])),
2017-06-03 23:21:50 +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-20 12:17:35 +00:00
* @link http://www.last.fm/api/webauth#4
*/
2018-09-04 05:34:02 +00:00
public function fetchSessionKeyUsingToken(string $token): ?string
2015-12-20 12:17:35 +00:00
{
$query = $this->buildAuthCallParams([
'method' => 'auth.getSession',
'token' => $token,
], true);
try {
2017-06-03 23:21:50 +00:00
return (string) $this->get("/?$query", [], false)->session->key;
2016-04-02 13:16:09 +00:00
} catch (Exception $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
}
}
/**
* Scrobble a song.
2016-03-06 04:11:28 +00:00
*
2015-12-20 12:17:35 +00:00
* @param string $artist The artist name
* @param string $track The track name
* @param string|int $timestamp The UNIX timestamp
* @param string $album The album name
* @param string $sk The session key
*/
public function scrobble(string $artist, string $track, $timestamp, string $album, string $sk): void
{
2015-12-21 13:49:00 +00:00
$params = compact('artist', 'track', 'timestamp', 'sk');
2015-12-20 12:17:35 +00:00
if ($album) {
$params['album'] = $album;
}
$params['method'] = 'track.scrobble';
try {
2018-08-24 15:27:19 +00:00
$this->post('/', $this->buildAuthCallParams($params), false);
2016-04-02 13:16:09 +00:00
} catch (Exception $e) {
2018-08-31 13:47:15 +00:00
$this->logger->error($e);
2015-12-20 12:17:35 +00:00
}
}
2015-12-21 13:49:00 +00:00
/**
* Love or unlove a track on Last.fm.
2016-03-06 04:11:28 +00:00
*
2015-12-21 13:50:26 +00:00
* @param string $track The track name
* @param string $artist The artist's name
* @param string $sk The session key
* @param bool $love Whether to love or unlove. Such cheesy terms... urrgggh
2015-12-21 13:49:00 +00:00
*/
2018-08-24 15:27:19 +00:00
public function toggleLoveTrack(string $track, string $artist, string $sk, ?bool $love = true): void
2015-12-21 13:49:00 +00:00
{
$params = compact('track', 'artist', 'sk');
$params['method'] = $love ? 'track.love' : 'track.unlove';
try {
2018-08-24 15:27:19 +00:00
$this->post('/', $this->buildAuthCallParams($params), false);
2016-04-02 13:16:09 +00:00
} catch (Exception $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
/**
* Update a track's "now playing" on Last.fm.
2016-03-06 04:11:28 +00:00
*
2015-12-23 06:26:16 +00:00
* @param string $artist Name of the artist
* @param string $track Name of the track
* @param string $album Name of the album
* @param int|float $duration Duration of the track, in seconds
* @param string $sk The session key
*/
public function updateNowPlaying(string $artist, string $track, string $album, $duration, string $sk): void
2015-12-23 06:26:16 +00:00
{
$params = compact('artist', 'track', 'duration', 'sk');
$params['method'] = 'track.updateNowPlaying';
if ($album) {
$params['album'] = $album;
}
try {
2018-08-24 15:27:19 +00:00
$this->post('/', $this->buildAuthCallParams($params), false);
2016-04-02 13:16:09 +00:00
} catch (Exception $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).
*
* @link http://www.last.fm/api/webauth#5
2016-03-06 04:11:28 +00:00
*
2015-12-20 12:17:35 +00:00
* @param array $params The array of parameters.
* @param bool $toString Whether to turn the array into a query string
*
* @return array|string
*/
2018-08-24 15:27:19 +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) {
$str .= $name.$value;
}
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 = '';
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.
*
* @param string|array $value
2015-12-20 12:17:35 +00:00
*/
2018-09-04 06:09:52 +00:00
protected function formatText($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)))));
}
2018-09-04 05:34:02 +00:00
public function getUserSessionKey(User $user): ?string
{
return $this->userPreferenceService->get($user, self::SESSION_KEY_PREFERENCE_KEY);
}
public function setUserSessionKey(User $user, string $sessionKey): void
{
$this->userPreferenceService->set($user, self::SESSION_KEY_PREFERENCE_KEY, $sessionKey);
}
public function deleteUserSessionKey(User $user): void
{
$this->userPreferenceService->delete($user, self::SESSION_KEY_PREFERENCE_KEY);
}
public function isUserConnected(User $user): bool
{
return (bool) $this->getUserSessionKey($user);
}
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');
}
}