Passed
Push — main ( 0cb63f...7280b6 )
by smiley
12:53
created

LastFM   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 174
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 18
eloc 77
c 1
b 0
f 0
dl 0
loc 174
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getAccessToken() 0 15 1
A getAuthURL() 0 7 1
A getSignature() 0 12 3
B request() 0 44 6
A parseTokenResponse() 0 33 6
A getRequestAuthorization() 0 2 1
1
<?php
2
/**
3
 * Class LastFM
4
 *
5
 * @link https://www.last.fm/api/authentication
6
 *
7
 * @created      10.04.2018
8
 * @author       Smiley <[email protected]>
9
 * @copyright    2018 Smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\OAuth\Providers\LastFM;
14
15
use chillerlan\OAuth\Core\{AccessToken, OAuthProvider, ProviderException};
16
use chillerlan\HTTP\Utils\MessageUtil;
17
use Psr\Http\Message\{RequestInterface, ResponseInterface, StreamInterface, UriInterface};
18
use Throwable;
19
use function array_merge, in_array, is_array, ksort, md5, trigger_error;
20
use const PHP_QUERY_RFC1738;
21
22
/**
23
 * @method \Psr\Http\Message\ResponseInterface albumAddTags(array $body = ['album', 'artist', 'tags'])
24
 * @method \Psr\Http\Message\ResponseInterface albumGetInfo(array $params = ['album', 'artist', 'autocorrect', 'lang', 'mbid', 'username'])
25
 * @method \Psr\Http\Message\ResponseInterface albumGetTags(array $params = ['album', 'artist', 'autocorrect', 'mbid', 'user'])
26
 * @method \Psr\Http\Message\ResponseInterface albumGetTopTags(array $params = ['album', 'artist', 'autocorrect', 'mbid'])
27
 * @method \Psr\Http\Message\ResponseInterface albumRemoveTag(array $body = ['album', 'artist', 'tag'])
28
 * @method \Psr\Http\Message\ResponseInterface albumSearch(array $params = ['album', 'limit', 'page'])
29
 * @method \Psr\Http\Message\ResponseInterface artistAddTags(array $body = ['artist', 'tags'])
30
 * @method \Psr\Http\Message\ResponseInterface artistGetCorrection(array $params = ['artist'])
31
 * @method \Psr\Http\Message\ResponseInterface artistGetInfo(array $params = ['artist', 'autocorrect', 'lang', 'mbid', 'username'])
32
 * @method \Psr\Http\Message\ResponseInterface artistGetSimilar(array $params = ['artist', 'autocorrect', 'limit', 'mbid'])
33
 * @method \Psr\Http\Message\ResponseInterface artistGetTags(array $params = ['artist', 'autocorrect', 'mbid', 'user'])
34
 * @method \Psr\Http\Message\ResponseInterface artistGetTopAlbums(array $params = ['artist', 'autocorrect', 'limit', 'mbid', 'page'])
35
 * @method \Psr\Http\Message\ResponseInterface artistGetTopTags(array $params = ['artist', 'autocorrect', 'mbid'])
36
 * @method \Psr\Http\Message\ResponseInterface artistGetTopTracks(array $params = ['artist', 'autocorrect', 'limit', 'mbid', 'page'])
37
 * @method \Psr\Http\Message\ResponseInterface artistRemoveTag(array $body = ['artist', 'tag'])
38
 * @method \Psr\Http\Message\ResponseInterface artistSearch(array $params = ['artist', 'limit', 'page'])
39
 * @method \Psr\Http\Message\ResponseInterface chartGetTopArtists(array $params = ['limit', 'page'])
40
 * @method \Psr\Http\Message\ResponseInterface chartGetTopTags(array $params = ['limit', 'page'])
41
 * @method \Psr\Http\Message\ResponseInterface chartGetTopTracks(array $params = ['limit', 'page'])
42
 * @method \Psr\Http\Message\ResponseInterface geoGetTopArtists(array $params = ['country', 'limit', 'page'])
43
 * @method \Psr\Http\Message\ResponseInterface geoGetTopTracks(array $params = ['country', 'limit', 'location', 'page'])
44
 * @method \Psr\Http\Message\ResponseInterface libraryGetArtists(array $params = ['limit', 'page', 'user'])
45
 * @method \Psr\Http\Message\ResponseInterface tagGetInfo(array $params = ['lang', 'tag'])
46
 * @method \Psr\Http\Message\ResponseInterface tagGetSimilar(array $params = ['tag'])
47
 * @method \Psr\Http\Message\ResponseInterface tagGetTopAlbums(array $params = ['limit', 'page', 'tag'])
48
 * @method \Psr\Http\Message\ResponseInterface tagGetTopArtists(array $params = ['limit', 'page', 'tag'])
49
 * @method \Psr\Http\Message\ResponseInterface tagGetTopTags()
50
 * @method \Psr\Http\Message\ResponseInterface tagGetTopTracks(array $params = ['limit', 'page', 'tag'])
51
 * @method \Psr\Http\Message\ResponseInterface tagGetWeeklyChartList(array $params = ['tag'])
52
 * @method \Psr\Http\Message\ResponseInterface trackAddTags(array $body = ['artist', 'tags', 'track'])
53
 * @method \Psr\Http\Message\ResponseInterface trackGetCorrection(array $params = ['artist', 'track'])
54
 * @method \Psr\Http\Message\ResponseInterface trackGetInfo(array $params = ['artist', 'autocorrect', 'mbid', 'track', 'username'])
55
 * @method \Psr\Http\Message\ResponseInterface trackGetSimilar(array $params = ['artist', 'autocorrect', 'limit', 'mbid', 'track'])
56
 * @method \Psr\Http\Message\ResponseInterface trackGetTags(array $params = ['artist', 'autocorrect', 'mbid', 'track', 'user'])
57
 * @method \Psr\Http\Message\ResponseInterface trackGetTopTags(array $params = ['artist', 'autocorrect', 'mbid', 'track'])
58
 * @method \Psr\Http\Message\ResponseInterface trackLove(array $body = ['artist', 'track'])
59
 * @method \Psr\Http\Message\ResponseInterface trackRemoveTag(array $body = ['artist', 'tag', 'track'])
60
 * @method \Psr\Http\Message\ResponseInterface trackSearch(array $params = ['artist', 'limit', 'page', 'track'])
61
 * @method \Psr\Http\Message\ResponseInterface trackUnlove(array $body = ['artist', 'track'])
62
 * @method \Psr\Http\Message\ResponseInterface trackUpdateNowPlaying(array $body = ['album', 'albumArtist', 'artist', 'context', 'duration', 'mbid', 'track', 'trackNumber'])
63
 * @method \Psr\Http\Message\ResponseInterface userGetFriends(array $params = ['limit', 'page', 'recenttracks', 'user'])
64
 * @method \Psr\Http\Message\ResponseInterface userGetInfo(array $params = ['user'])
65
 * @method \Psr\Http\Message\ResponseInterface userGetLovedTracks(array $params = ['limit', 'page', 'user'])
66
 * @method \Psr\Http\Message\ResponseInterface userGetPersonalTags(array $params = ['limit', 'page', 'tag', 'taggingtype[artist|album|track]', 'user'])
67
 * @method \Psr\Http\Message\ResponseInterface userGetRecentTracks(array $params = ['extended', 'from', 'limit', 'page', 'to', 'user'])
68
 * @method \Psr\Http\Message\ResponseInterface userGetTopAlbums(array $params = ['limit', 'page', 'period', 'user'])
69
 * @method \Psr\Http\Message\ResponseInterface userGetTopArtists(array $params = ['limit', 'page', 'period', 'user'])
70
 * @method \Psr\Http\Message\ResponseInterface userGetTopTags(array $params = ['limit', 'user'])
71
 * @method \Psr\Http\Message\ResponseInterface userGetTopTracks(array $params = ['limit', 'page', 'period', 'user'])
72
 * @method \Psr\Http\Message\ResponseInterface userGetWeeklyAlbumChart(array $params = ['from', 'to', 'user'])
73
 * @method \Psr\Http\Message\ResponseInterface userGetWeeklyArtistChart(array $params = ['from', 'to', 'user'])
74
 * @method \Psr\Http\Message\ResponseInterface userGetWeeklyChartList(array $params = ['user'])
75
 * @method \Psr\Http\Message\ResponseInterface userGetWeeklyTrackChart(array $params = ['from', 'to', 'user'])
76
 */
77
class LastFM extends OAuthProvider{
78
79
	public const PERIOD_OVERALL = 'overall';
80
	public const PERIOD_7DAY    = '7day';
81
	public const PERIOD_1MONTH  = '1month';
82
	public const PERIOD_3MONTH  = '3month';
83
	public const PERIOD_6MONTH  = '6month';
84
	public const PERIOD_12MONTH = '12month';
85
86
	public const PERIODS = [
87
		self::PERIOD_OVERALL,
88
		self::PERIOD_7DAY,
89
		self::PERIOD_1MONTH,
90
		self::PERIOD_3MONTH,
91
		self::PERIOD_6MONTH,
92
		self::PERIOD_12MONTH,
93
	];
94
95
	protected string $authURL         = 'https://www.last.fm/api/auth';
96
	protected ?string $apiURL         = 'https://ws.audioscrobbler.com/2.0';
97
	protected ?string $userRevokeURL  = 'https://www.last.fm/settings/applications';
98
	protected ?string $endpointMap    = LastFMEndpoints::class;
99
	protected ?string $apiDocs        = 'https://www.last.fm/api/';
100
	protected ?string $applicationURL = 'https://www.last.fm/api/account/create';
101
102
	/**
103
	 * @inheritdoc
104
	 */
105
	public function getAuthURL(array $params = null):UriInterface{
106
107
		$params = array_merge($params ?? [], [
108
			'api_key' => $this->options->key,
109
		]);
110
111
		return $this->uriFactory->createUri($this->mergeQuery($this->authURL, $params));
112
	}
113
114
	/**
115
	 * @param array $params
116
	 *
117
	 * @return string
118
	 */
119
	protected function getSignature(array $params):string{
120
		ksort($params);
121
122
		$signature = '';
123
124
		foreach($params as $k => $v){
125
			if(!in_array($k, ['format', 'callback'])){
126
				$signature .= $k.$v;
127
			}
128
		}
129
130
		return md5($signature.$this->options->secret);
131
	}
132
133
	/**
134
	 * @param string $session_token
135
	 *
136
	 * @return \chillerlan\OAuth\Core\AccessToken
137
	 */
138
	public function getAccessToken(string $session_token):AccessToken{
139
140
		$params = [
141
			'method'  => 'auth.getSession',
142
			'format'  => 'json',
143
			'api_key' => $this->options->key,
144
			'token'   => $session_token,
145
		];
146
147
		$params['api_sig'] = $this->getSignature($params);
148
149
		/** @phan-suppress-next-line PhanTypeMismatchArgumentNullable */
150
		$request = $this->requestFactory->createRequest('GET', $this->mergeQuery($this->apiURL, $params));
0 ignored issues
show
Bug introduced by
It seems like $this->apiURL can also be of type null; however, parameter $uri of chillerlan\OAuth\Core\OAuthProvider::mergeQuery() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

150
		$request = $this->requestFactory->createRequest('GET', $this->mergeQuery(/** @scrutinizer ignore-type */ $this->apiURL, $params));
Loading history...
151
152
		return $this->parseTokenResponse($this->http->sendRequest($request));
153
	}
154
155
	/**
156
	 * @param \Psr\Http\Message\ResponseInterface $response
157
	 *
158
	 * @return \chillerlan\OAuth\Core\AccessToken
159
	 * @throws \chillerlan\OAuth\Core\ProviderException
160
	 */
161
	protected function parseTokenResponse(ResponseInterface $response):AccessToken{
162
163
		try{
164
			$data = MessageUtil::decodeJSON($response, true);
165
166
			if(!$data || !is_array($data)){
167
				trigger_error('');
168
			}
169
		}
170
		catch(Throwable $e){
171
			throw new ProviderException('unable to parse token response');
172
		}
173
174
		if(isset($data['error'])){
175
			throw new ProviderException('error retrieving access token: '.$data['message']);
176
		}
177
		elseif(!isset($data['session']['key'])){
178
			throw new ProviderException('token missing');
179
		}
180
181
		$token = new AccessToken;
182
183
		$token->provider     = $this->serviceName;
184
		$token->accessToken  = $data['session']['key'];
185
		$token->expires      = AccessToken::EOL_NEVER_EXPIRES;
186
187
		unset($data['session']['key']);
188
189
		$token->extraParams = $data;
190
191
		$this->storage->storeAccessToken($this->serviceName, $token);
192
193
		return $token;
194
	}
195
196
	/**
197
	 * @inheritDoc
198
	 */
199
	public function request(
200
		string $path,
201
		array $params = null,
202
		string $method = null,
203
		StreamInterface|array|string $body = null,
204
		array $headers = null
205
	):ResponseInterface{
206
207
		if($body !== null && !is_array($body)){
0 ignored issues
show
introduced by
The condition is_array($body) is always true.
Loading history...
208
			throw new ProviderException('$body must be an array');
209
		}
210
211
		$method ??= 'GET';
212
		$params ??= [];
213
		$body   ??= [];
214
215
		$params = array_merge($params, $body, [
216
			'method'  => $path,
217
			'format'  => 'json',
218
			'api_key' => $this->options->key,
219
			'sk'      => $this->storage->getAccessToken($this->serviceName)->accessToken,
220
		]);
221
222
		$params['api_sig'] = $this->getSignature($params);
223
224
		if($method === 'POST'){
225
			$body   = $params;
226
			$params = [];
227
		}
228
229
		/** @phan-suppress-next-line PhanTypeMismatchArgumentNullable */
230
		$request = $this->requestFactory->createRequest($method, $this->mergeQuery($this->apiURL, $params));
0 ignored issues
show
Bug introduced by
It seems like $this->apiURL can also be of type null; however, parameter $uri of chillerlan\OAuth\Core\OAuthProvider::mergeQuery() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

230
		$request = $this->requestFactory->createRequest($method, $this->mergeQuery(/** @scrutinizer ignore-type */ $this->apiURL, $params));
Loading history...
231
232
		foreach(array_merge($this->apiHeaders, $headers ?? []) as $header => $value){
233
			$request = $request->withAddedHeader($header, $value);
234
		}
235
236
		if($method === 'POST'){
237
			$request = $request->withHeader('Content-Type', 'application/x-www-form-urlencoded');
238
			$body    = $this->streamFactory->createStream($this->buildQuery($body, PHP_QUERY_RFC1738));
239
			$request = $request->withBody($body);
240
		}
241
242
		return $this->http->sendRequest($request);
243
	}
244
245
	/**
246
	 * @inheritDoc
247
	 * @codeCoverageIgnore
248
	 */
249
	public function getRequestAuthorization(RequestInterface $request, AccessToken $token):RequestInterface{
250
		return $request;
251
	}
252
253
	/**
254
	 * @todo
255
	 *
256
	 * @param array $tracks
257
	 */
258
#	public function scrobble(array $tracks){}
259
260
}
261