Passed
Branch main (d68b9c)
by smiley
09:54
created

LastFM   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 173
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 16
eloc 73
c 1
b 0
f 0
dl 0
loc 173
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getAccessToken() 0 15 1
A getAuthURL() 0 7 1
A request() 0 29 4
A requestParams() 0 12 1
A parseTokenResponse() 0 26 5
A getRequestAuthorization() 0 2 1
A getSignature() 0 12 3
1
<?php
2
/**
3
 * Class LastFM
4
 *
5
 * @link https://www.last.fm/api/authentication
6
 *
7
 * @filesource   LastFM.php
8
 * @created      10.04.2018
9
 * @package      chillerlan\OAuth\Providers\LastFM
10
 * @author       Smiley <[email protected]>
11
 * @copyright    2018 Smiley
12
 * @license      MIT
13
 */
14
15
namespace chillerlan\OAuth\Providers\LastFM;
16
17
use chillerlan\OAuth\Core\{AccessToken, OAuthProvider, ProviderException};
18
use Psr\Http\Message\{RequestInterface, ResponseInterface, UriInterface};
19
20
use function array_merge, http_build_query, in_array, is_array,ksort, md5;
21
use function chillerlan\HTTP\Psr7\{get_json, merge_query};
0 ignored issues
show
Bug introduced by
The type chillerlan\HTTP\Psr7\merge_query was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type chillerlan\HTTP\Psr7\get_json was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
23
use const PHP_QUERY_RFC1738;
24
25
/**
26
 * @method \Psr\Http\Message\ResponseInterface albumAddTags(array $body = ['mbid', 'album', 'artist', 'tags'])
27
 * @method \Psr\Http\Message\ResponseInterface albumGetInfo(array $params = ['mbid', 'album', 'artist', 'username', 'lang', 'autocorrect'])
28
 * @method \Psr\Http\Message\ResponseInterface albumGetTags(array $params = ['mbid', 'album', 'artist', 'user', 'autocorrect'])
29
 * @method \Psr\Http\Message\ResponseInterface albumGetTopTags(array $params = ['mbid', 'album', 'artist', 'autocorrect'])
30
 * @method \Psr\Http\Message\ResponseInterface albumRemoveTag(array $body = ['mbid', 'album', 'artist', 'tag'])
31
 * @method \Psr\Http\Message\ResponseInterface albumSearch(array $params = ['album', 'limit', 'page'])
32
 * @method \Psr\Http\Message\ResponseInterface artistAddTags(array $body = ['mbid', 'artist', 'tags'])
33
 * @method \Psr\Http\Message\ResponseInterface artistGetCorrection(array $params = ['artist'])
34
 * @method \Psr\Http\Message\ResponseInterface artistGetInfo(array $params = ['mbid', 'artist', 'username', 'lang', 'autocorrect'])
35
 * @method \Psr\Http\Message\ResponseInterface artistGetSimilar(array $params = ['mbid', 'artist', 'limit', 'autocorrect'])
36
 * @method \Psr\Http\Message\ResponseInterface artistGetTags(array $params = ['mbid', 'artist', 'user', 'autocorrect'])
37
 * @method \Psr\Http\Message\ResponseInterface artistGetTopAlbums(array $params = ['mbid', 'artist', 'autocorrect', 'page', 'limit'])
38
 * @method \Psr\Http\Message\ResponseInterface artistGetTopTags(array $params = ['mbid', 'artist', 'autocorrect'])
39
 * @method \Psr\Http\Message\ResponseInterface artistGetTopTracks(array $params = ['mbid', 'artist', 'autocorrect', 'page', 'limit'])
40
 * @method \Psr\Http\Message\ResponseInterface artistRemoveTag(array $body = ['mbid', 'artist', 'tag'])
41
 * @method \Psr\Http\Message\ResponseInterface artistSearch(array $params = ['artist', 'limit', 'page'])
42
 * @method \Psr\Http\Message\ResponseInterface chartGetTopArtists(array $params = ['limit', 'page'])
43
 * @method \Psr\Http\Message\ResponseInterface chartGetTopTags(array $params = ['limit', 'page'])
44
 * @method \Psr\Http\Message\ResponseInterface chartGetTopTracks(array $params = ['limit', 'page'])
45
 * @method \Psr\Http\Message\ResponseInterface geoGetTopArtists(array $params = ['country', 'location', 'limit', 'page'])
46
 * @method \Psr\Http\Message\ResponseInterface geoGetTopTracks(array $params = ['country', 'location', 'limit', 'page'])
47
 * @method \Psr\Http\Message\ResponseInterface libraryGetArtists(array $params = ['user', 'limit', 'page'])
48
 * @method \Psr\Http\Message\ResponseInterface tagGetInfo(array $params = ['tag', 'lang'])
49
 * @method \Psr\Http\Message\ResponseInterface tagGetSimilar(array $params = ['tag'])
50
 * @method \Psr\Http\Message\ResponseInterface tagGetTopAlbums(array $params = ['tag', 'limit', 'page'])
51
 * @method \Psr\Http\Message\ResponseInterface tagGetTopArtists(array $params = ['tag', 'limit', 'page'])
52
 * @method \Psr\Http\Message\ResponseInterface tagGetTopTags()
53
 * @method \Psr\Http\Message\ResponseInterface tagGetTopTracks(array $params = ['tag', 'limit', 'page'])
54
 * @method \Psr\Http\Message\ResponseInterface tagGetWeeklyChartList(array $params = ['tag'])
55
 * @method \Psr\Http\Message\ResponseInterface trackAddTags(array $body = ['mbid', 'artist', 'track', 'tags'])
56
 * @method \Psr\Http\Message\ResponseInterface trackGetCorrection(array $params = ['artist', 'track'])
57
 * @method \Psr\Http\Message\ResponseInterface trackGetInfo(array $params = ['mbid', 'artist', 'track', 'username', 'autocorrect'])
58
 * @method \Psr\Http\Message\ResponseInterface trackGetSimilar(array $params = ['mbid', 'artist', 'track', 'autocorrect', 'limit'])
59
 * @method \Psr\Http\Message\ResponseInterface trackGetTags(array $params = ['mbid', 'artist', 'track', 'autocorrect', 'user'])
60
 * @method \Psr\Http\Message\ResponseInterface trackGetTopTags(array $params = ['mbid', 'artist', 'track', 'autocorrect'])
61
 * @method \Psr\Http\Message\ResponseInterface trackLove(array $body = ['mbid', 'artist', 'track'])
62
 * @method \Psr\Http\Message\ResponseInterface trackRemoveTag(array $body = ['mbid', 'artist', 'track', 'tag'])
63
 * @method \Psr\Http\Message\ResponseInterface trackSearch(array $params = ['artist', 'track', 'limit', 'page'])
64
 * @method \Psr\Http\Message\ResponseInterface trackUnlove(array $body = ['mbid', 'artist', 'track'])
65
 * @method \Psr\Http\Message\ResponseInterface trackUpdateNowPlaying(array $body = ['mbid', 'artist', 'track', 'album', 'trackNumber', 'context', 'duration', 'albumArtist'])
66
 * @method \Psr\Http\Message\ResponseInterface userGetArtistTracks(array $params = ['user', 'artist', 'limit', 'page', 'startTimestamp', 'endTimestamp'])
67
 * @method \Psr\Http\Message\ResponseInterface userGetFriends(array $params = ['user', 'limit', 'page', 'recenttracks'])
68
 * @method \Psr\Http\Message\ResponseInterface userGetInfo(array $params = ['user'])
69
 * @method \Psr\Http\Message\ResponseInterface userGetLovedTracks(array $params = ['user', 'limit', 'page'])
70
 * @method \Psr\Http\Message\ResponseInterface userGetPersonalTags(array $params = ['user', 'limit', 'page', 'tag', 'taggingtype'])
71
 * @method \Psr\Http\Message\ResponseInterface userGetRecentTracks(array $params = ['user', 'limit', 'page', 'from', 'to', 'extended'])
72
 * @method \Psr\Http\Message\ResponseInterface userGetTopAlbums(array $params = ['user', 'limit', 'page', 'period'])
73
 * @method \Psr\Http\Message\ResponseInterface userGetTopArtists(array $params = ['user', 'limit', 'page', 'period'])
74
 * @method \Psr\Http\Message\ResponseInterface userGetTopTags(array $params = ['user', 'limit', 'page'])
75
 * @method \Psr\Http\Message\ResponseInterface userGetWeeklyAlbumChart(array $params = ['user', 'from', 'to'])
76
 * @method \Psr\Http\Message\ResponseInterface userGetWeeklyArtistChart(array $params = ['user', 'from', 'to'])
77
 * @method \Psr\Http\Message\ResponseInterface userGetWeeklyTrackChart(array $params = ['user', 'from', 'to'])
78
 */
79
class LastFM extends OAuthProvider{
80
81
	public const PERIOD_OVERALL = 'overall';
82
	public const PERIOD_7DAY    = '7day';
83
	public const PERIOD_1MONTH  = '1month';
84
	public const PERIOD_3MONTH  = '3month';
85
	public const PERIOD_6MONTH  = '6month';
86
	public const PERIOD_12MONTH = '12month';
87
88
	public const PERIODS = [
89
		self::PERIOD_OVERALL,
90
		self::PERIOD_7DAY,
91
		self::PERIOD_1MONTH,
92
		self::PERIOD_3MONTH,
93
		self::PERIOD_6MONTH,
94
		self::PERIOD_12MONTH,
95
	];
96
97
	protected string $authURL        = 'https://www.last.fm/api/auth';
98
	protected ?string $apiURL         = 'https://ws.audioscrobbler.com/2.0';
99
	protected ?string $userRevokeURL  = 'https://www.last.fm/settings/applications';
100
	protected ?string $endpointMap    = LastFMEndpoints::class;
101
	protected ?string $apiDocs        = 'https://www.last.fm/api/';
102
	protected ?string $applicationURL = 'https://www.last.fm/api/account/create';
103
104
	/**
105
	 * @inheritdoc
106
	 */
107
	public function getAuthURL(array $params = null):UriInterface{
108
109
		$params = array_merge($params ?? [], [
110
			'api_key' => $this->options->key,
111
		]);
112
113
		return $this->uriFactory->createUri(merge_query($this->authURL, $params));
114
	}
115
116
	/**
117
	 * @param array $params
118
	 *
119
	 * @return string
120
	 */
121
	protected function getSignature(array $params):string{
122
		ksort($params);
123
124
		$signature = '';
125
126
		foreach($params as $k => $v){
127
			if(!in_array($k, ['format', 'callback'])){
128
				$signature .= $k.$v;
129
			}
130
		}
131
132
		return md5($signature.$this->options->secret);
133
	}
134
135
	/**
136
	 * @param string $session_token
137
	 *
138
	 * @return \chillerlan\OAuth\Core\AccessToken
139
	 */
140
	public function getAccessToken(string $session_token):AccessToken{
141
142
		$params = [
143
			'method'  => 'auth.getSession',
144
			'format'  => 'json',
145
			'api_key' => $this->options->key,
146
			'token'   => $session_token,
147
		];
148
149
		$params['api_sig'] = $this->getSignature($params);
150
151
		/** @phan-suppress-next-line PhanTypeMismatchArgumentNullable */
152
		$request = $this->requestFactory->createRequest('GET', merge_query($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\HTTP\Psr7\merge_query() 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

152
		$request = $this->requestFactory->createRequest('GET', merge_query(/** @scrutinizer ignore-type */ $this->apiURL, $params));
Loading history...
153
154
		return $this->parseTokenResponse($this->http->sendRequest($request));
155
	}
156
157
	/**
158
	 * @param \Psr\Http\Message\ResponseInterface $response
159
	 *
160
	 * @return \chillerlan\OAuth\Core\AccessToken
161
	 * @throws \chillerlan\OAuth\Core\ProviderException
162
	 */
163
	protected function parseTokenResponse(ResponseInterface $response):AccessToken{
164
		$data = get_json($response, true);
165
166
		if(!$data || !is_array($data)){
167
			throw new ProviderException('unable to parse token response');
168
		}
169
		elseif(isset($data['error'])){
170
			throw new ProviderException('error retrieving access token: '.$data['message']);
171
		}
172
		elseif(!isset($data['session']['key'])){
173
			throw new ProviderException('token missing');
174
		}
175
176
		$token = new AccessToken([
177
			'provider'    => $this->serviceName,
178
			'accessToken' => $data['session']['key'],
179
			'expires'     => AccessToken::EOL_NEVER_EXPIRES,
180
		]);
181
182
		unset($data['session']['key']);
183
184
		$token->extraParams = $data;
185
186
		$this->storage->storeAccessToken($this->serviceName, $token);
187
188
		return $token;
189
	}
190
191
	/**
192
	 * @param string $apiMethod
193
	 * @param array  $params
194
	 * @param array  $body
195
	 *
196
	 * @return array
197
	 */
198
	protected function requestParams(string $apiMethod, array $params, array $body):array{
199
200
		$params = array_merge($params, $body, [
201
			'method'  => $apiMethod,
202
			'format'  => 'json',
203
			'api_key' => $this->options->key,
204
			'sk'      => $this->storage->getAccessToken($this->serviceName)->accessToken,
205
		]);
206
207
		$params['api_sig'] = $this->getSignature($params);
208
209
		return $params;
210
	}
211
212
	/**
213
	 * @inheritDoc
214
	 */
215
	public function request(
216
		string $path,
217
		array $params = null,
218
		string $method = null,
219
		$body = null,
220
		array $headers = null
221
	):ResponseInterface{
222
		$method ??= 'GET';
223
		$params = $this->requestParams($path, $params ?? [], $body ?? []);
224
225
		if($method === 'POST'){
226
			$body   = $params;
227
			$params = [];
228
		}
229
230
		/** @phan-suppress-next-line PhanTypeMismatchArgumentNullable */
231
		$request = $this->requestFactory->createRequest($method, merge_query($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\HTTP\Psr7\merge_query() 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

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