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

BeatSaverAPI::downloadMapByKeys()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
}