Passed
Push — master ( 163cac...2d8094 )
by Kylian
07:24 queued 06:00
created

BeatSaverAPI::searchMap()   F

Complexity

Conditions 18
Paths > 20000

Size

Total Lines 29
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 18
eloc 23
c 3
b 0
f 0
nc 131072
nop 19
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)->setErrorStatus("[getMap] Something went wrong with the API call.");
0 ignored issues
show
Bug introduced by
'[getMap] Something went...ong with the API call.' of type string is incompatible with the type boolean expected by parameter $errorStatus of KriKrixs\object\response...ponse::setErrorStatus(). ( Ignorable by Annotation )

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

86
            $response->setErrorStatus(true)->setErrorStatus(/** @scrutinizer ignore-type */ "[getMap] Something went wrong with the API call.");
Loading history...
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
     * Private building response functions
135
     * @param string $endpoint
136
     * @param int $numberOfPage
137
     * @param int $startPage
138
     * @return ResponseMaps
139
     */
140
    private function getMaps(string $endpoint, int $numberOfPage = 0, int $startPage = 0): ResponseMaps
141
    {
142
        $response = new ResponseMaps();
143
        $maps = [];
144
145
        // Latest
146
        if($numberOfPage === 0 && $startPage === 0){
147
            $apiResult = json_decode($this->callAPI($endpoint));
148
149
            if($apiResult === false || $apiResult == "Not Found") {
150
                $response->setErrorStatus(true)->setErrorMessage("[getMaps] Something went wrong with the API call while calling the first page.");
151
                return $response;
152
            } else{
153
                foreach ($apiResult->docs as $beatmap) {
154
                    $maps[] = new BeatMap($beatmap);
155
                }
156
            }
157
        } else {
158
            for($i = $startPage; $i < ($i + $numberOfPage); $i++){
159
                $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

159
                $apiResult = json_decode($this->callAPI(/** @scrutinizer ignore-type */ str_ireplace("page", $i, $endpoint)));
Loading history...
160
161
                if($apiResult === false || $apiResult == "Not Found") {
162
                    $response->setErrorStatus(true)->setErrorMessage("[getMaps] Something went wrong with the API call while calling page number " . $i . ".");
163
164
                    if($apiResult == "Not Found")
165
                        return $response;
166
                }
167
168
                foreach ($apiResult->docs as $beatmap) {
169
                    $maps[] = new BeatMap($beatmap);
170
                }
171
            }
172
        }
173
174
        $response->setBeatMaps($maps);
175
176
        return $response;
177
    }
178
179
    /**
180
     * Get maps by Uploader ID! Not the uploader name!
181
     * @param int $uploaderID Uploader ID on BeatSaver
182
     * @param int $numberOfPage The number of page you want to be returned
183
     * @param int $startPage The starting page
184
     * @return ResponseMaps
185
     */
186
    public function getMapsByUploaderID(int $uploaderID, int $numberOfPage, int $startPage): ResponseMaps
187
    {
188
        return $this->getMaps("/maps/uploader/" . $uploaderID . "/page", $numberOfPage, $startPage);
189
    }
190
191
    /**
192
     * Get 20 latest maps
193
     * @param bool $autoMapper Do you want automapper or not ?
194
     * @return ResponseMaps
195
     */
196
    public function getMapsSortedByLatest(bool $autoMapper): ResponseMaps
197
    {
198
        return $this->getMaps("/maps/latest?automapper=" . var_export($autoMapper, true));
199
    }
200
201
    /**
202
     * Get maps sorted by plays numbers
203
     * @param int $numberOfPage The number of page you want to be returned
204
     * @param int $startPage The starting page
205
     * @return ResponseMaps
206
     */
207
    public function getMapsSortedByPlays(int $numberOfPage, int $startPage): ResponseMaps
208
    {
209
        return $this->getMaps("/maps/plays/page", $numberOfPage, $startPage);
210
    }
211
212
    /**
213
     * Search a map (Set null to a parameter to not use it)
214
     * @param int $limit Limit of map you want
215
     * @param int $sortOrder (Default 1) 1 = Latest | 2 = Relevance | 3 = Rating
216
     * @param string|null $mapName (Optional) Map name
217
     * @param \DateTime|null $startDate (Optional) Map made from this date
218
     * @param \DateTime|null $endDate (Optional) Map made to this date
219
     * @param bool $ranked (Optional) Want ranked or not ?
220
     * @param bool $automapper (Optional) Want automapper or not ?
221
     * @param bool $chroma (Optional) Want chroma or not ?
222
     * @param bool $noodle (Optional) Want noodle or not ?
223
     * @param bool $cinema (Optional) Want cinema or not ?
224
     * @param bool $fullSpread (Optional) Want fullSpread or not ?
225
     * @param float|null $minBpm (Optional) Minimum BPM
226
     * @param float|null $maxBpm (Optional) Maximum BPM
227
     * @param float|null $minNps (Optional) Minimum NPS
228
     * @param float|null $maxNps (Optional) Maximum NPS
229
     * @param float|null $minRating (Optional) Minimum Rating
230
     * @param float|null $maxRating (Optional) Maximum Rating
231
     * @param int|null $minDuration (Optional) Minimum Duration
232
     * @param int|null $maxDuration (Optional) Maximum Duration
233
     * @return ResponseMaps
234
     */
235
    public function searchMap(int $limit, 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
236
    {
237
        $sort = [
238
            1 => "Latest",
239
            2 => "Relevance",
240
            3 => "Rating"
241
        ];
242
243
        $endpoint = "/search/text/page?sortOrder=" . $sort[$sortOrder];
244
245
        if($mapName)                $endpoint .= "&q=" . urlencode($mapName);
246
        if($startDate)              $endpoint .= "&from=" . $startDate->format("Y-m-d");
247
        if($endDate)                $endpoint .= "&to=" . $endDate->format("Y-m-d");
248
        if($ranked)                 $endpoint .= "&ranked=" . /** @scrutinizer ignore-type */ var_export($ranked, true);
249
        if($automapper)             $endpoint .= "&automapper=" . /** @scrutinizer ignore-type */ var_export($automapper, true);
250
        if($chroma)                 $endpoint .= "&chroma=" . /** @scrutinizer ignore-type */ var_export($chroma, true);
251
        if($noodle)                 $endpoint .= "&noodle=" . /** @scrutinizer ignore-type */ var_export($noodle, true);
252
        if($cinema)                 $endpoint .= "&cinema=" . /** @scrutinizer ignore-type */ var_export($cinema, true);
253
        if($fullSpread)             $endpoint .= "&fullSpread=" . /** @scrutinizer ignore-type */ var_export($fullSpread, true);
254
        if($minBpm)                 $endpoint .= "&minBpm=" . /** @scrutinizer ignore-type */ $minBpm;
255
        if($maxBpm)                 $endpoint .= "&maxBpm=" . /** @scrutinizer ignore-type */ $maxBpm;
256
        if($minNps)                 $endpoint .= "&minNps=" . /** @scrutinizer ignore-type */ $minNps;
257
        if($maxNps)                 $endpoint .= "&maxNps=" . /** @scrutinizer ignore-type */ $maxNps;
258
        if($minRating)              $endpoint .= "&minRating=" . /** @scrutinizer ignore-type */ $minRating;
259
        if($maxRating)              $endpoint .= "&maxRating=" . /** @scrutinizer ignore-type */ $maxRating;
260
        if($minDuration !== null)   $endpoint .= "&minDuration=" . /** @scrutinizer ignore-type */ $minDuration;
261
        if($maxDuration !== null)   $endpoint .= "&maxDuration=" . /** @scrutinizer ignore-type */ $maxDuration;
262
263
        return $this->getMaps($endpoint, $limit);
264
    }
265
266
    ////////////////
267
    /// Get User ///
268
    ////////////////
269
270
    /**
271
     * Private building response functions
272
     * @param string $endpoint
273
     * @return ResponseUser
274
     */
275
    private function getUser(string $endpoint): ResponseUser
276
    {
277
        $response = new ResponseUser();
278
279
        $apiResult = $this->callAPI($endpoint);
280
281
        if($apiResult === false || $apiResult == "Not Found") {
282
            $response->setErrorStatus(true)->setErrorStatus("[getMap] Something went wrong with the API call.");
0 ignored issues
show
Bug introduced by
'[getMap] Something went...ong with the API call.' of type string is incompatible with the type boolean expected by parameter $errorStatus of KriKrixs\object\response...ponse::setErrorStatus(). ( Ignorable by Annotation )

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

282
            $response->setErrorStatus(true)->setErrorStatus(/** @scrutinizer ignore-type */ "[getMap] Something went wrong with the API call.");
Loading history...
283
            return $response;
284
        }
285
286
        $response->setUser(new User(json_decode($apiResult)));
287
288
        return $response;
289
    }
290
291
    /**
292
     * Get user's infos by UserID
293
     * @param int $id User ID
294
     * @return ResponseUser
295
     */
296
    public function getUserByID(int $id): ResponseUser
297
    {
298
        return $this->getUser("/users/id/" . $id);
299
    }
300
301
    ////////////////////
302
    /// Download map ///
303
    ////////////////////
304
305
    /**
306
     * Download maps using id
307
     * @param array $ids
308
     * @param string $targetDir
309
     * @return ResponseDownload
310
     */
311
    public function downloadMapByIds(array $ids, string $targetDir): ResponseDownload
312
    {
313
        return $this->multiQuery->downloadMapZipAndCover($this->multiQuery->buildDownloadArray($ids, false), $targetDir);
314
    }
315
316
    /**
317
     * Download maps using hashes
318
     * @param array $hashes
319
     * @param string $targetDir
320
     * @return ResponseDownload
321
     */
322
    public function downloadMapByHashes(array $hashes, string $targetDir): ResponseDownload
323
    {
324
        return $this->multiQuery->downloadMapZipAndCover($this->multiQuery->buildDownloadArray($hashes, true), $targetDir);
325
    }
326
}