Passed
Push — master ( 0b9cbc...153c68 )
by Kylian
01:27
created

BeatSaverAPI::searchMap()   F

Complexity

Conditions 18
Paths > 20000

Size

Total Lines 29
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 18
eloc 23
c 4
b 0
f 0
nc 131072
nop 20
dl 0
loc 29
rs 0.7

How to fix   Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace KriKrixs;
4
5
use KriKrixs\functions\MultiQuery;
6
use KriKrixs\object\beatmap\BeatMap;
7
use KriKrixs\object\response\ResponseDownload;
8
use KriKrixs\object\response\ResponseMap;
9
use KriKrixs\object\response\ResponseMaps;
10
use KriKrixs\object\response\ResponseUser;
11
use KriKrixs\object\user\User;
12
13
class BeatSaverAPI
14
{
15
    const BEATSAVER_URL = "https://api.beatsaver.com/";
16
17
    private string $userAgent;
18
    private MultiQuery $multiQuery;
19
20
    /**
21
     * BeatSaverAPI constructor
22
     * @param string $userAgent User Agent to provide to Beat Saver API
23
     */
24
    public function __construct(string $userAgent)
25
    {
26
        $this->autoload("./");
27
28
        $this->userAgent = $userAgent;
29
        $this->multiQuery = new MultiQuery(self::BEATSAVER_URL, $userAgent);
30
    }
31
32
    private function autoload($directory) {
33
        if(is_dir($directory)) {
34
            $scan = scandir($directory);
35
            unset($scan[0], $scan[1]); //unset . and ..
36
            foreach($scan as $file) {
37
                if(is_dir($directory."/".$file)) {
38
                    $this->autoload($directory."/".$file);
39
                } else {
40
                    if(strpos($file, '.php') !== false) {
41
                        include_once($directory."/".$file);
42
                    }
43
                }
44
            }
45
        }
46
    }
47
48
    /**
49
     * Private calling API function
50
     * @param string $endpoint
51
     * @return string|null
52
     */
53
    private function callAPI(string $endpoint): ?string
54
    {
55
        $curl = curl_init();
56
57
        curl_setopt_array($curl, [
58
            CURLOPT_URL => self::BEATSAVER_URL . $endpoint,
59
            CURLOPT_USERAGENT => $this->userAgent,
60
            CURLOPT_RETURNTRANSFER => true
61
        ]);
62
63
        $result = curl_exec($curl);
64
65
        curl_close($curl);
66
67
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result could return the type true which is incompatible with the type-hinted return null|string. Consider adding an additional type-check to rule them out.
Loading history...
68
    }
69
70
    ////////////
71
    /// CALL ///
72
    ///////////
73
74
    /**
75
     * Private building response functions
76
     * @param string $endpoint
77
     * @return ResponseMap
78
     */
79
    private function getMap(string $endpoint): ResponseMap
80
    {
81
        $response = new ResponseMap();
82
83
        $apiResult = $this->callAPI($endpoint);
84
85
        if($apiResult === false || $apiResult == "Not Found") {
86
            $response->setErrorStatus(true)->setErrorMessage("[getMap] Something went wrong with the API call.");
87
            return $response;
88
        }
89
90
        $response->setBeatMap(new BeatMap(json_decode($apiResult)));
91
92
        return $response;
93
    }
94
95
    ///////////////
96
    /// Get Map ///
97
    ///////////////
98
99
    /**
100
     * Get map by ID (Same as BSR Key)
101
     * @param string $id Map ID
102
     * @return ResponseMap
103
     */
104
    public function getMapByID(string $id): ResponseMap
105
    {
106
        return $this->getMap("/maps/id/" . $id);
107
    }
108
109
    /**
110
     * Get map by BSR Key (Same as ID)
111
     * @param string $bsrKey Map BSR key
112
     * @return ResponseMap
113
     */
114
    public function getMapByKey(string $bsrKey): ResponseMap
115
    {
116
        return $this->getMap("/maps/id/" . $bsrKey);
117
    }
118
119
    /**
120
     * Get map by Hash
121
     * @param string $hash Hash of the map
122
     * @return ResponseMap
123
     */
124
    public function getMapByHash(string $hash): ResponseMap
125
    {
126
        return $this->getMap("/maps/hash/" . $hash);
127
    }
128
129
    ////////////////
130
    /// Get Maps ///
131
    ////////////////
132
133
    /**
134
     * Get maps by IDs (Same as BSR keys)
135
     * @param array $ids Array of maps ID (Same as BSR keys)
136
     * @return array Array of BeatMap object
137
     */
138
    public function getMapsByIds(array $ids): array
139
    {
140
        return $this->multiQuery->DoMultiQuery($ids, false);
141
    }
142
143
    /**
144
     * Get maps by BSR Keys (Same as IDs)
145
     * @param array $keys Array of maps BSR key (Same as IDs)
146
     * @return array Array of BeatMap object
147
     */
148
    public function getMapsByKeys(array $keys): array
149
    {
150
        return $this->multiQuery->DoMultiQuery($keys, false);
151
    }
152
153
    /**
154
     * Get maps by hashes
155
     * @param array $hashes Array of maps hash
156
     * @return array Array of BeatMap object
157
     */
158
    public function getMapsByHashes(array $hashes): array
159
    {
160
        return $this->multiQuery->DoMultiQuery($hashes, true);
161
    }
162
163
    /**
164
     * Private building response functions
165
     * @param string $endpoint
166
     * @param int $numberOfPage
167
     * @param int $startPage
168
     * @return ResponseMaps
169
     */
170
    private function getMapsByEndpoint(string $endpoint, int $numberOfPage = 0, int $startPage = 0): ResponseMaps
171
    {
172
        $response = new ResponseMaps();
173
        $maps = [];
174
175
        // Latest
176
        if($numberOfPage === 0 && $startPage === 0){
177
            $apiResult = json_decode($this->callAPI($endpoint));
178
179
            if($apiResult === false || $apiResult == "Not Found") {
180
                $response->setErrorStatus(true)->setErrorMessage("[getMaps] Something went wrong with the API call while calling the first page.");
181
                return $response;
182
            } else{
183
                foreach ($apiResult->docs as $beatmap) {
184
                    $maps[] = new BeatMap($beatmap);
185
                }
186
            }
187
        } else {
188
            for($i = $startPage; $i < ($i + $numberOfPage); $i++){
189
                $apiResult = json_decode($this->callAPI(str_ireplace("page", $i, $endpoint)));
0 ignored issues
show
Bug introduced by
It seems like str_ireplace('page', $i, $endpoint) can also be of type array; however, parameter $endpoint of KriKrixs\BeatSaverAPI::callAPI() 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

189
                $apiResult = json_decode($this->callAPI(/** @scrutinizer ignore-type */ str_ireplace("page", $i, $endpoint)));
Loading history...
190
191
                if($apiResult === false || $apiResult == "Not Found") {
192
                    $response->setErrorStatus(true)->setErrorMessage("[getMaps] Something went wrong with the API call while calling page number " . $i . ".");
193
194
                    if($apiResult == "Not Found")
195
                        return $response;
196
                }
197
198
                foreach ($apiResult->docs as $beatmap) {
199
                    $maps[] = new BeatMap($beatmap);
200
                }
201
            }
202
        }
203
204
        $response->setBeatMaps($maps);
205
206
        return $response;
207
    }
208
209
    /**
210
     * Get maps by Uploader ID! Not the uploader name!
211
     * @param int $uploaderID Uploader ID on BeatSaver
212
     * @param int $numberOfPage The number of page you want to be returned
213
     * @param int $startPage The starting page
214
     * @return ResponseMaps
215
     */
216
    public function getMapsByUploaderID(int $uploaderID, int $numberOfPage, int $startPage): ResponseMaps
217
    {
218
        return $this->getMapsByEndpoint("/maps/uploader/" . $uploaderID . "/page", $numberOfPage, $startPage);
219
    }
220
221
    /**
222
     * Get 20 latest maps
223
     * @param bool $autoMapper Do you want automapper or not ?
224
     * @return ResponseMaps
225
     */
226
    public function getMapsSortedByLatest(bool $autoMapper): ResponseMaps
227
    {
228
        return $this->getMapsByEndpoint("/maps/latest?automapper=" . var_export($autoMapper, true));
229
    }
230
231
    /**
232
     * Get maps sorted by plays numbers
233
     * @param int $numberOfPage The number of page you want to be returned
234
     * @param int $startPage The starting page
235
     * @return ResponseMaps
236
     */
237
    public function getMapsSortedByPlays(int $numberOfPage, int $startPage): ResponseMaps
238
    {
239
        return $this->getMapsByEndpoint("/maps/plays/page", $numberOfPage, $startPage);
240
    }
241
242
    /**
243
     * Search a map (Set null to a parameter to not use it)
244
     * @param int $startPage Start page number
245
     * @param int $numberOfPage Number of page wanted
246
     * @param int $sortOrder (Default 1) 1 = Latest | 2 = Relevance | 3 = Rating
247
     * @param string|null $mapName (Optional) Map name
248
     * @param \DateTime|null $startDate (Optional) Map made from this date
249
     * @param \DateTime|null $endDate (Optional) Map made to this date
250
     * @param bool $ranked (Optional) Want ranked or not ?
251
     * @param bool $automapper (Optional) Want automapper or not ?
252
     * @param bool $chroma (Optional) Want chroma or not ?
253
     * @param bool $noodle (Optional) Want noodle or not ?
254
     * @param bool $cinema (Optional) Want cinema or not ?
255
     * @param bool $fullSpread (Optional) Want fullSpread or not ?
256
     * @param float|null $minBpm (Optional) Minimum BPM
257
     * @param float|null $maxBpm (Optional) Maximum BPM
258
     * @param float|null $minNps (Optional) Minimum NPS
259
     * @param float|null $maxNps (Optional) Maximum NPS
260
     * @param float|null $minRating (Optional) Minimum Rating
261
     * @param float|null $maxRating (Optional) Maximum Rating
262
     * @param int|null $minDuration (Optional) Minimum Duration
263
     * @param int|null $maxDuration (Optional) Maximum Duration
264
     * @return ResponseMaps
265
     */
266
    public function searchMap(int $startPage = 0, int $numberOfPage = 1, int $sortOrder = 1, string $mapName = null, \DateTime $startDate = null, \DateTime $endDate = null, bool $ranked = false, bool $automapper = false, bool $chroma = false, bool $noodle = false, bool $cinema = false, bool $fullSpread = false, float $minBpm = null, float $maxBpm = null, float $minNps = null, float $maxNps = null, float $minRating = null, float $maxRating = null, int $minDuration = null, int $maxDuration = null): ResponseMaps
267
    {
268
        $sort = [
269
            1 => "Latest",
270
            2 => "Relevance",
271
            3 => "Rating"
272
        ];
273
274
        $endpoint = "/search/text/page?sortOrder=" . $sort[$sortOrder];
275
276
        if($mapName)                $endpoint .= "&q=" . urlencode($mapName);
277
        if($startDate)              $endpoint .= "&from=" . $startDate->format("Y-m-d");
278
        if($endDate)                $endpoint .= "&to=" . $endDate->format("Y-m-d");
279
        if($ranked)                 $endpoint .= "&ranked=" . /** @scrutinizer ignore-type */ var_export($ranked, true);
280
        if($automapper)             $endpoint .= "&automapper=" . /** @scrutinizer ignore-type */ var_export($automapper, true);
281
        if($chroma)                 $endpoint .= "&chroma=" . /** @scrutinizer ignore-type */ var_export($chroma, true);
282
        if($noodle)                 $endpoint .= "&noodle=" . /** @scrutinizer ignore-type */ var_export($noodle, true);
283
        if($cinema)                 $endpoint .= "&cinema=" . /** @scrutinizer ignore-type */ var_export($cinema, true);
284
        if($fullSpread)             $endpoint .= "&fullSpread=" . /** @scrutinizer ignore-type */ var_export($fullSpread, true);
285
        if($minBpm)                 $endpoint .= "&minBpm=" . /** @scrutinizer ignore-type */ $minBpm;
286
        if($maxBpm)                 $endpoint .= "&maxBpm=" . /** @scrutinizer ignore-type */ $maxBpm;
287
        if($minNps)                 $endpoint .= "&minNps=" . /** @scrutinizer ignore-type */ $minNps;
288
        if($maxNps)                 $endpoint .= "&maxNps=" . /** @scrutinizer ignore-type */ $maxNps;
289
        if($minRating)              $endpoint .= "&minRating=" . /** @scrutinizer ignore-type */ $minRating;
290
        if($maxRating)              $endpoint .= "&maxRating=" . /** @scrutinizer ignore-type */ $maxRating;
291
        if($minDuration !== null)   $endpoint .= "&minDuration=" . /** @scrutinizer ignore-type */ $minDuration;
292
        if($maxDuration !== null)   $endpoint .= "&maxDuration=" . /** @scrutinizer ignore-type */ $maxDuration;
293
294
        return $this->getMapsByEndpoint($endpoint, $numberOfPage, $startPage);
295
    }
296
297
    ////////////////
298
    /// Get User ///
299
    ////////////////
300
301
    /**
302
     * Private building response functions
303
     * @param string $endpoint
304
     * @return ResponseUser
305
     */
306
    private function getUser(string $endpoint): ResponseUser
307
    {
308
        $response = new ResponseUser();
309
310
        $apiResult = $this->callAPI($endpoint);
311
312
        if($apiResult === false || $apiResult == "Not Found") {
313
            $response->setErrorStatus(true)->setErrorMessage("[getMap] Something went wrong with the API call.");
314
            return $response;
315
        }
316
317
        $response->setUser(new User(json_decode($apiResult)));
318
319
        return $response;
320
    }
321
322
    /**
323
     * Get user's infos by UserID
324
     * @param int $id User ID
325
     * @return ResponseUser
326
     */
327
    public function getUserByID(int $id): ResponseUser
328
    {
329
        return $this->getUser("/users/id/" . $id);
330
    }
331
332
    ////////////////////
333
    /// Download map ///
334
    ////////////////////
335
336
    /**
337
     * Download maps using id (Same as BSR Key)
338
     * @param array $ids Array of maps IDs (Same as BSR Key)
339
     * @param string $targetDir Path to download dir
340
     * @return ResponseDownload
341
     */
342
    public function downloadMapByIds(array $ids, string $targetDir): ResponseDownload
343
    {
344
        return $this->multiQuery->downloadMapZipAndCover($this->multiQuery->buildDownloadArray($ids, false), $targetDir);
345
    }
346
347
    /**
348
     * Download maps using bsr key (Same as ID)
349
     * @param array $keys Array of maps keys (Same as ID)
350
     * @param string $targetDir Path to download dir
351
     * @return ResponseDownload
352
     */
353
    public function downloadMapByKeys(array $keys, string $targetDir): ResponseDownload
354
    {
355
        return $this->multiQuery->downloadMapZipAndCover($this->multiQuery->buildDownloadArray($keys, false), $targetDir);
356
    }
357
358
    /**
359
     * Download maps using hashes
360
     * @param array $hashes
361
     * @param string $targetDir
362
     * @return ResponseDownload
363
     */
364
    public function downloadMapByHashes(array $hashes, string $targetDir): ResponseDownload
365
    {
366
        return $this->multiQuery->downloadMapZipAndCover($this->multiQuery->buildDownloadArray($hashes, true), $targetDir);
367
    }
368
}