Test Setup Failed
Branch master (096eb7)
by Phan
03:51
created

LastfmService::getSessionKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 4
cts 4
cp 1
rs 9.7666
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
namespace App\Services;
4
5
use Exception;
6
7
class LastfmService extends ApiClient 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
    public function enabled(): bool
35
    {
36
        return $this->getKey() && $this->getSecret();
37
    }
38
39 5
    /**
40
     * Get information about an artist.
41 5
     *
42
     * @param string $name Name of the artist
43
     *
44
     * @return mixed[]|null
45
     */
46
    public function getArtistInformation(string $name): ?array
47
    {
48
        if (!$this->enabled()) {
49
            return null;
50
        }
51 2
52
        $name = urlencode($name);
53 2
54
        try {
55
            return $this->cache->remember(md5("lastfm_artist_$name"), 24 * 60 * 7, function () use ($name): ?array {
56
                $response = $this->get("?method=artist.getInfo&autocorrect=1&artist=$name");
0 ignored issues
show
Bug introduced by
The call to get() misses a required argument $...$args.

This check looks for function calls that miss required arguments.

Loading history...
57 2
58
                if (!$response) {
59
                    return null;
60 2
                }
61
62 2
                $response = simplexml_load_string($response->asXML());
63
                $response = json_decode(json_encode($response), true);
64
65 2
                if (!$response || !$artist = array_get($response, 'artist')) {
66 2
                    return null;
67
                }
68
69
                return $this->buildArtistInformation($artist);
70 2
            });
71
        } catch (Exception $e) {
72 2
            $this->logger->error($e);
73 1
74
            return null;
75
        }
76 1
    }
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
    private function buildArtistInformation(array $artistData): array
86
    {
87
        return [
88
            'url' => array_get($artistData, 'url'),
89
            'image' => count($artistData['image']) > 3 ? $artistData['image'][3] : $artistData['image'][0],
90
            'bio' => [
91 1
                'summary' => $this->formatText(array_get($artistData, 'bio.summary', '')),
92
                'full' => $this->formatText(array_get($artistData, 'bio.content', '')),
93
            ],
94 1
        ];
95 1
    }
96
97 1
    /**
98 1
     * Get information about an album.
99
     *
100
     * @return mixed[]|null
101
     */
102
    public function getAlbumInformation(string $albumName, string $artistName): ?array
103
    {
104
        if (!$this->enabled()) {
105
            return null;
106
        }
107
108
        $albumName = urlencode($albumName);
109
        $artistName = urlencode($artistName);
110
111 2
        try {
112
            $cacheKey = md5("lastfm_album_{$albumName}_{$artistName}");
113 2
114
            return $this->cache->remember($cacheKey, 24 * 60 * 7, function () use ($albumName, $artistName): ?array {
115
                $response = $this->get("?method=album.getInfo&autocorrect=1&album=$albumName&artist=$artistName");
0 ignored issues
show
Bug introduced by
The call to get() misses a required argument $...$args.

This check looks for function calls that miss required arguments.

Loading history...
116
117 2
                if (!$response) {
118 2
                    return null;
119
                }
120
121 2
                $response = simplexml_load_string($response->asXML());
122
                $response = json_decode(json_encode($response), true);
123 2
124
                if (!$response || !$album = array_get($response, 'album')) {
125
                    return null;
126 2
                }
127 2
128
                return $this->buildAlbumInformation($album);
129
            });
130
        } catch (Exception $e) {
131 2
            $this->logger->error($e);
132
133 2
            return null;
134 1
        }
135
    }
136
137 1
    /**
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
    private function buildAlbumInformation(array $albumData): array
145
    {
146
        return [
147
            'url' => array_get($albumData, 'url'),
148
            'image' => count($albumData['image']) > 3 ? $albumData['image'][3] : $albumData['image'][0],
149
            'wiki' => [
150
                'summary' => $this->formatText(array_get($albumData, 'wiki.summary', '')),
151
                'full' => $this->formatText(array_get($albumData, 'wiki.content', '')),
152 1
            ],
153
            'tracks' => array_map(function ($track) {
154
                return [
155 1
                    'title' => $track['name'],
156 1
                    'length' => (int) $track['duration'],
157
                    'url' => $track['url'],
158 1
                ];
159 1
            }, array_get($albumData, 'tracks.track', [])),
160
        ];
161 1
    }
162
163 1
    /**
164 1
     * Get Last.fm's session key for the authenticated user using a token.
165 1
     *
166
     * @param string $token The token after successfully connecting to Last.fm
167 1
     *
168
     * @link http://www.last.fm/api/webauth#4
169
     */
170
    public function getSessionKey(string $token): ?string
171
    {
172
        $query = $this->buildAuthCallParams([
173
            'method' => 'auth.getSession',
174
            'token' => $token,
175
        ], true);
176
177
        try {
178
            return (string) $this->get("/?$query", [], false)->session->key;
0 ignored issues
show
Unused Code introduced by
The call to LastfmService::get() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
179
        } catch (Exception $e) {
180 1
            $this->logger->error($e);
181
182 1
            return null;
183 1
        }
184 1
    }
185 1
186
    /**
187
     * Scrobble a song.
188 1
     *
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);
0 ignored issues
show
Unused Code introduced by
The call to LastfmService::post() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
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);
0 ignored issues
show
Unused Code introduced by
The call to LastfmService::post() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
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);
0 ignored issues
show
Unused Code introduced by
The call to LastfmService::post() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
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
    public function buildAuthCallParams(array $params, bool $toString = false)
271
    {
272
        $params['api_key'] = $this->getKey();
273
        ksort($params);
274
275
        // Generate the API signature.
276
        // @link http://www.last.fm/api/webauth#6
277
        $str = '';
278
279
        foreach ($params as $name => $value) {
280
            $str .= $name.$value;
281
        }
282
283
        $str .= $this->getSecret();
284
        $params['api_sig'] = md5($str);
285
286
        if (!$toString) {
287
            return $params;
288
        }
289
290
        $query = '';
291
        foreach ($params as $key => $value) {
292 2
            $query .= "$key=$value&";
293
        }
294 2
295 2
        return rtrim($query, '&');
296
    }
297
298
    /**
299 2
     * Correctly format a value returned by Last.fm.
300 2
     *
301 2
     * @param string|array $value
302
     */
303 2
    protected function formatText($value): string
304 2
    {
305
        if (!$value) {
306 2
            return '';
307 1
        }
308
309
        return trim(str_replace('Read more on Last.fm', '', nl2br(strip_tags(html_entity_decode($value)))));
310 2
    }
311 2
312 2
    public function getKey(): ?string
313
    {
314
        return config('koel.lastfm.key');
315 2
    }
316
317
    public function getEndpoint(): ?string
318
    {
319
        return config('koel.lastfm.endpoint');
320
    }
321
322
    public function getSecret(): ?string
323
    {
324
        return config('koel.lastfm.secret');
325 2
    }
326
}
327