Issues (49)

app/Services/LastfmService.php (2 issues)

1
<?php
2
3
namespace App\Services;
4
5
use Exception;
6
7
class LastfmService extends AbstractApiClient implements ApiConsumerInterface
8
{
9
    /**
10
     * Specify the response format, since Last.fm only returns XML.
11
     *
12
     * @var string
13
     */
14
    protected $responseFormat = 'xml';
15
16
    /**
17
     * Override the key param, since, again, Lastfm wants to be different.
18
     *
19
     * @var string
20
     */
21
    protected $keyParam = 'api_key';
22
23
    /**
24
     * Determine if our application is using Last.fm.
25
     */
26
    public function used(): bool
27
    {
28
        return (bool) $this->getKey();
29
    }
30
31
    /**
32
     * Determine if Last.fm integration is enabled.
33
     */
34 5
    public function enabled(): bool
35
    {
36 5
        return $this->getKey() && $this->getSecret();
37
    }
38
39
    /**
40
     * Get information about an artist.
41
     *
42
     * @param string $name Name of the artist
43
     *
44
     * @return mixed[]|null
45
     */
46 2
    public function getArtistInformation(string $name): ?array
47
    {
48 2
        if (!$this->enabled()) {
49
            return null;
50
        }
51
52 2
        $name = urlencode($name);
53
54
        try {
55
            return $this->cache->remember(md5("lastfm_artist_$name"), 24 * 60 * 7, function () use ($name): ?array {
56 2
                $response = $this->get("?method=artist.getInfo&autocorrect=1&artist=$name");
57
58 2
                if (!$response) {
0 ignored issues
show
$response is of type object, thus it always evaluated to true.
Loading history...
59
                    return null;
60
                }
61
62 2
                $response = simplexml_load_string($response->asXML());
63 2
                $response = json_decode(json_encode($response), true);
64
65 2
                if (!$response || !$artist = array_get($response, 'artist')) {
66 1
                    return null;
67
                }
68
69 1
                return $this->buildArtistInformation($artist);
70 2
            });
71
        } catch (Exception $e) {
72
            $this->logger->error($e);
73
74
            return null;
75
        }
76
    }
77
78
    /**
79
     * Build a Koel-usable array of artist information using the data from Last.fm.
80
     *
81
     * @param mixed[] $artistData
82
     *
83
     * @return mixed[]
84
     */
85 1
    private function buildArtistInformation(array $artistData): array
86
    {
87
        return [
88 1
            'url' => array_get($artistData, 'url'),
89 1
            'image' => count($artistData['image']) > 3 ? $artistData['image'][3] : $artistData['image'][0],
90
            'bio' => [
91 1
                'summary' => $this->formatText(array_get($artistData, 'bio.summary', '')),
92 1
                'full' => $this->formatText(array_get($artistData, 'bio.content', '')),
93
            ],
94
        ];
95
    }
96
97
    /**
98
     * Get information about an album.
99
     *
100
     * @return mixed[]|null
101
     */
102 2
    public function getAlbumInformation(string $albumName, string $artistName): ?array
103
    {
104 2
        if (!$this->enabled()) {
105
            return null;
106
        }
107
108 2
        $albumName = urlencode($albumName);
109 2
        $artistName = urlencode($artistName);
110
111
        try {
112 2
            $cacheKey = md5("lastfm_album_{$albumName}_{$artistName}");
113
114
            return $this->cache->remember($cacheKey, 24 * 60 * 7, function () use ($albumName, $artistName): ?array {
115 2
                $response = $this->get("?method=album.getInfo&autocorrect=1&album=$albumName&artist=$artistName");
116
117 2
                if (!$response) {
0 ignored issues
show
$response is of type object, thus it always evaluated to true.
Loading history...
118
                    return null;
119
                }
120
121 2
                $response = simplexml_load_string($response->asXML());
122 2
                $response = json_decode(json_encode($response), true);
123
124 2
                if (!$response || !$album = array_get($response, 'album')) {
125 1
                    return null;
126
                }
127
128 1
                return $this->buildAlbumInformation($album);
129 2
            });
130
        } catch (Exception $e) {
131
            $this->logger->error($e);
132
133
            return null;
134
        }
135
    }
136
137
    /**
138
     * Build a Koel-usable array of album information using the data from Last.fm.
139
     *
140
     * @param mixed[] $albumData
141
     *
142
     * @return mixed[]
143
     */
144 1
    private function buildAlbumInformation(array $albumData): array
145
    {
146
        return [
147 1
            'url' => array_get($albumData, 'url'),
148 1
            'image' => count($albumData['image']) > 3 ? $albumData['image'][3] : $albumData['image'][0],
149
            'wiki' => [
150 1
                'summary' => $this->formatText(array_get($albumData, 'wiki.summary', '')),
151 1
                'full' => $this->formatText(array_get($albumData, 'wiki.content', '')),
152
            ],
153
            'tracks' => array_map(function ($track) {
154
                return [
155 1
                    'title' => $track['name'],
156 1
                    'length' => (int) $track['duration'],
157 1
                    'url' => $track['url'],
158
                ];
159 1
            }, array_get($albumData, 'tracks.track', [])),
160
        ];
161
    }
162
163
    /**
164
     * Get Last.fm's session key for the authenticated user using a token.
165
     *
166
     * @param string $token The token after successfully connecting to Last.fm
167
     *
168
     * @link http://www.last.fm/api/webauth#4
169
     */
170 1
    public function getSessionKey(string $token): ?string
171
    {
172 1
        $query = $this->buildAuthCallParams([
173 1
            'method' => 'auth.getSession',
174 1
            'token' => $token,
175 1
        ], true);
176
177
        try {
178 1
            return (string) $this->get("/?$query", [], false)->session->key;
179
        } catch (Exception $e) {
180
            $this->logger->error($e);
181
182
            return null;
183
        }
184
    }
185
186
    /**
187
     * Scrobble a song.
188
     *
189
     * @param string     $artist    The artist name
190
     * @param string     $track     The track name
191
     * @param string|int $timestamp The UNIX timestamp
192
     * @param string     $album     The album name
193
     * @param string     $sk        The session key
194
     */
195
    public function scrobble(string $artist, string $track, $timestamp, string $album, string $sk): void
196
    {
197
        $params = compact('artist', 'track', 'timestamp', 'sk');
198
199
        if ($album) {
200
            $params['album'] = $album;
201
        }
202
203
        $params['method'] = 'track.scrobble';
204
205
        try {
206
            $this->post('/', $this->buildAuthCallParams($params), false);
207
        } catch (Exception $e) {
208
            $this->logger->error($e);
209
        }
210
    }
211
212
    /**
213
     * Love or unlove a track on Last.fm.
214
     *
215
     * @param string $track  The track name
216
     * @param string $artist The artist's name
217
     * @param string $sk     The session key
218
     * @param bool   $love   Whether to love or unlove. Such cheesy terms... urrgggh
219
     */
220
    public function toggleLoveTrack(string $track, string $artist, string $sk, ?bool $love = true): void
221
    {
222
        $params = compact('track', 'artist', 'sk');
223
        $params['method'] = $love ? 'track.love' : 'track.unlove';
224
225
        try {
226
            $this->post('/', $this->buildAuthCallParams($params), false);
227
        } catch (Exception $e) {
228
            $this->logger->error($e);
229
        }
230
    }
231
232
    /**
233
     * Update a track's "now playing" on Last.fm.
234
     *
235
     * @param string    $artist   Name of the artist
236
     * @param string    $track    Name of the track
237
     * @param string    $album    Name of the album
238
     * @param int|float $duration Duration of the track, in seconds
239
     * @param string    $sk       The session key
240
     */
241
    public function updateNowPlaying(string $artist, string $track, string $album, $duration, string $sk): void
242
    {
243
        $params = compact('artist', 'track', 'duration', 'sk');
244
        $params['method'] = 'track.updateNowPlaying';
245
246
        if ($album) {
247
            $params['album'] = $album;
248
        }
249
250
        try {
251
            $this->post('/', $this->buildAuthCallParams($params), false);
252
        } catch (Exception $e) {
253
            $this->logger->error($e);
254
        }
255
    }
256
257
    /**
258
     * Build the parameters to use for _authenticated_ Last.fm API calls.
259
     * Such calls require:
260
     * - The API key (api_key)
261
     * - The API signature (api_sig).
262
     *
263
     * @link http://www.last.fm/api/webauth#5
264
     *
265
     * @param array $params   The array of parameters.
266
     * @param bool  $toString Whether to turn the array into a query string
267
     *
268
     * @return array|string
269
     */
270 2
    public function buildAuthCallParams(array $params, bool $toString = false)
271
    {
272 2
        $params['api_key'] = $this->getKey();
273 2
        ksort($params);
274
275
        // Generate the API signature.
276
        // @link http://www.last.fm/api/webauth#6
277 2
        $str = '';
278
279 2
        foreach ($params as $name => $value) {
280 2
            $str .= $name.$value;
281
        }
282
283 2
        $str .= $this->getSecret();
284 2
        $params['api_sig'] = md5($str);
285
286 2
        if (!$toString) {
287 1
            return $params;
288
        }
289
290 2
        $query = '';
291 2
        foreach ($params as $key => $value) {
292 2
            $query .= "$key=$value&";
293
        }
294
295 2
        return rtrim($query, '&');
296
    }
297
298
    /**
299
     * Correctly format a value returned by Last.fm.
300
     *
301
     * @param string|array $value
302
     */
303 2
    protected function formatText($value): string
304
    {
305 2
        if (!$value) {
306
            return '';
307
        }
308
309 2
        return trim(str_replace('Read more on Last.fm', '', nl2br(strip_tags(html_entity_decode($value)))));
310
    }
311
312 6
    public function getKey(): ?string
313
    {
314 6
        return config('koel.lastfm.key');
315
    }
316
317 5
    public function getEndpoint(): ?string
318
    {
319 5
        return config('koel.lastfm.endpoint');
320
    }
321
322 6
    public function getSecret(): ?string
323
    {
324 6
        return config('koel.lastfm.secret');
325
    }
326
}
327