Passed
Push — main ( a1d9c6...449c3a )
by smiley
02:06
created

LastFM::requestParams()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

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

217
		$request = $this->requestFactory->createRequest($method, merge_query(/** @scrutinizer ignore-type */ $this->apiURL, $params));
Loading history...
218
219
		foreach(array_merge($this->apiHeaders, $headers ?? []) as $header => $value){
220
			$request = $request->withAddedHeader($header, $value);
221
		}
222
223
		if($method === 'POST'){
224
			$request = $request->withHeader('Content-Type', 'application/x-www-form-urlencoded');
225
			$body    = $this->streamFactory->createStream(http_build_query($body, '', '&', PHP_QUERY_RFC1738));
226
			$request = $request->withBody($body);
227
		}
228
229
		return $this->http->sendRequest($request);
230
	}
231
232
	/**
233
	 * @inheritDoc
234
	 * @codeCoverageIgnore
235
	 */
236
	public function getRequestAuthorization(RequestInterface $request, AccessToken $token):RequestInterface{
237
		return $request;
238
	}
239
240
	/**
241
	 * @todo
242
	 *
243
	 * @param array $tracks
244
	 */
245
#	public function scrobble(array $tracks){}
246
247
}
248