Skip to content

Commit

Permalink
Merge pull request #12 from rui2015/BeatmapsIO-New-API
Browse files Browse the repository at this point in the history
Implement new BeatSaver API
  • Loading branch information
FranciscoRibeiro03 authored Aug 6, 2021
2 parents 36f65eb + 83a0e38 commit f55a2a1
Show file tree
Hide file tree
Showing 41 changed files with 361 additions and 453 deletions.
37 changes: 37 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Breaking Changes on v2.0.0

These are all the breaking changes on v2.0.0

## Method Changes

```diff
- api.getMapDetailsByKey('key').then(map => {}).catch(err => {});
+ api.getMapByID('id').then(map => {}).catch(err => {});

- api.getMapDetailsByHash('hash').then(map => {}).catch(err => {});
+ api.getMapByHash('hash').then(map => {}).catch(err => {});

- api.getMapsSortedByLatest(page?).then(searchResult => {}).catch(err => {});
+ api.getLatestMaps(automapper, before?).then(searchResult => {}).catch(err => {});

- api.getMapsSortedByHot(page?).then(searchResult => {}).catch(err => {});
- api.getMapsSortedByRating(page?).then(searchResult => {}).catch(err => {});
- api.getMapsSortedByDownloads(page?).then(searchResult => {}).catch(err => {});
- api.getMapsSortedByPlays(page?).then(searchResult => {}).catch(err => {});

- api.searchMap('string').then(searchResult => {}).catch(err => {});
+ api.searchMaps(searchOptions, page?).then(searchResult => {}).catch(err => {});

- api.downloadMapByKey('key', directory).then(fileLocation => {}).catch(err => {});
- api.downloadMapByHash('hash', directory).then(fileLocation => {}).catch(err => {});
```

## Downloading maps

The methods for downloading maps have been removed. You now have to handle downloading the files yourself.
To get the download URL for a map, you can use the following code:

```js
const requestedMap = await api.getMapByID('ID');
const downloadURL = requestedMap.versions[0].downloadURL;
```
46 changes: 5 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,56 +16,20 @@ const api = new BeatSaverAPI({
Version = '1.0.0'
});

api.getMapDetailsByKey('key').then(map => {}).catch(err => {});
api.getMapDetailsByHash('hash').then(map => {}).catch(err => {});
api.getMapsByUploader('userID', page?).then(searchResult => {}).catch(err => {});
api.getMapsSortedByHot(page?).then(searchResult => {}).catch(err => {});
api.getMapsSortedByRating(page?).then(searchResult => {}).catch(err => {});
api.getMapsSortedByLatest(page?).then(searchResult => {}).catch(err => {});
api.getMapsSortedByDownloads(page?).then(searchResult => {}).catch(err => {});
api.getMapsSortedByPlays(page?).then(searchResult => {}).catch(err => {});
api.getMapByID('ID').then(map => {}).catch(err => {});
api.getMapByHash('hash').then(map => {}).catch(err => {});
api.getMapsByUploader(userID, page?).then(searchResult => {}).catch(err => {});
api.getLatestMaps(automapper, before?).then(searchResult => {}).catch(err => {});

api.searchMap('string').then(searchResult => {}).catch(err => {});

api.downloadMapByKey('key', directory).then(fileLocation => {}).catch(err => {});
api.downloadMapByHash('hash', directory).then(fileLocation => {}).catch(err => {});
api.searchMaps(searchOptions, page?).then(searchResult => {}).catch(err => {});
```
You need to make sure that the App Version on the `BeatSaverAPI` constructor is valid [SemVer](https://semver.org/)
## Important Notes
- You can't use the `downloadMapByKey` and the `downloadMapByHash` functions if you're using this library on a browser.
- While using this library with Node.js, all requests made to BeatSaver are made with the User-Agent `AppName/Version` (so, in the example case, the User-Agent sent to BeatSaver will be `Application Name/1.0.0`). However, if you're using this library on a browser, the requests will use your browser's User-Agent.
## Important for users using webpack
If you try to use [webpack](https://webpack.js.org/) for front-end use with this library, it will throw two errors, due to the fact that the `fs` module is not available.
To stop these errors, you need to add one of the following to your `webpack.config.js` file:
```js
// Classic
module.exports = {
...
node: {
fs: "empty"
}
}
```
```js
// Symfony
// You edit the end of the file with the following
let config = Encore.getWebpackConfig();
config.node = {
fs: "empty"
}
module.exports = config
```
Thank you [KriKrixs](https://github.com/KriKrixs) for this
## Help
If you need help using this module or if you found an error with it, you can always contact me on my [Discord Server](https://discord.gg/qjKhqA3)
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "beatsaver-api",
"version": "1.1.0",
"version": "2.0.0",
"description": "A BeatSaver API Wrapper",
"main": "./lib/BeatSaverAPI.js",
"types": "./lib/BeatSaverAPI.d.ts",
Expand Down
61 changes: 13 additions & 48 deletions src/BeatSaverAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,8 @@ import axios, { AxiosInstance } from 'axios';
import { valid } from 'semver';
import isNode from 'detect-node';

import getMapDetailsByHash from './api/getMapDetailsByHash';
import getMapDetailsByKey from './api/getMapDetailsByKey';
import getMapsByUploader from './api/getMapsByUploader';
import getMapsSortedByDownloads from './api/getMapsSortedByDownloads';
import getMapsSortedByHot from './api/getMapsSortedByHot';
import getMapsSortedByLatest from './api/getMapsSortedByLatest';
import getMapsSortedByPlays from './api/getMapsSortedByPlays';
import getMapsSortedByRating from './api/getMapsSortedByRating';
import searchMap from './api/searchMap';
import { getMapByID, getMapByHash, getMapsByUploader, getLatestMaps } from './api/maps';
import { searchMaps, SearchOptions } from './api/search';

interface BeatSaverAPIOptions {
AppName: string;
Expand All @@ -30,7 +23,7 @@ class BeatSaverAPI {
this.appVersion = appVersionSemVer;

this.axiosInstance = axios.create({
baseURL: 'https://beatsaver.com/api',
baseURL: 'https://api.beatsaver.com',
headers: !isNode
? {}
: {
Expand All @@ -39,52 +32,24 @@ class BeatSaverAPI {
});
}

public async getMapDetailsByKey(key: string) {
return getMapDetailsByKey(key, this.axiosInstance);
public async getMapByID(id: string) {
return getMapByID(this.axiosInstance, id);
}

public async getMapDetailsByHash(hash: string) {
return getMapDetailsByHash(hash, this.axiosInstance);
public async getMapByHash(hash: string) {
return getMapByHash(this.axiosInstance, hash);
}

public async getMapsByUploader(userID: string, page: number = 0) {
return getMapsByUploader(userID, this.axiosInstance, page);
public async getMapsByUploader(userID: number, page: number = 0) {
return getMapsByUploader(this.axiosInstance, userID, page);
}

public async getMapsSortedByDownloads(page: number = 0) {
return getMapsSortedByDownloads(this.axiosInstance, page);
public async getLatestMaps(automapper: boolean, before?: string) {
return getLatestMaps(this.axiosInstance, automapper, before);
}

public async getMapsSortedByHot(page: number = 0) {
return getMapsSortedByHot(this.axiosInstance, page);
}

public async getMapsSortedByLatest(page: number = 0) {
return getMapsSortedByLatest(this.axiosInstance, page);
}

public async getMapsSortedByPlays(page: number = 0) {
return getMapsSortedByPlays(this.axiosInstance, page);
}

public async getMapsSortedByRating(page: number = 0) {
return getMapsSortedByRating(this.axiosInstance, page);
}

public async searchMap(searchString: string, page: number = 0) {
return searchMap(searchString, this.axiosInstance, page);
}

public async downloadMapByHash(hash: string, directory: string) {
if (!isNode) return "downloadMapByHash function can't be used in a browser";
const { default: downloadMapByHash } = await import('./api/downloadMapByHash');
return downloadMapByHash(hash, directory, this.axiosInstance);
}

public async downloadMapByKey(key: string, directory: string) {
if (!isNode) return "downloadMapByKey function can't be used in a browser";
const { default: downloadMapByKey } = await import('./api/downloadMapByKey');
return downloadMapByKey(key, directory, this.axiosInstance);
public async searchMaps(searchOptions: SearchOptions, page: number = 0) {
return searchMaps(this.axiosInstance, searchOptions, page);
}
}

Expand Down
68 changes: 19 additions & 49 deletions src/__tests__/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import BeatSaverAPI from '../BeatSaverAPI';
// Song Info for Key abcd (map: Keep it up)
const knownKey1 = 'abcd';
const knownHash1 = '2f3e8fcd9df83062343ee2a8ba4ba78bb4d83ed0';
const knownUploaderID1 = '5cff0b7698cc5a672c854703';
const knownUploaderUsername1 = 'epictroller';
const knownUploaderID1 = 4285821;
const knownUploaderUsername1 = 'EpicTroller';

// Song Info for Key 2144 (map: Shut Up and Dance)
const knownKey2 = '2144';
const knownHash2 = '89cf8bb07afb3c59ae7b5ac00337d62261c36fb4';
const knownUploaderID2 = '5cff0b7298cc5a672c84e98d';
const knownUploaderID2 = 30311;
const knownUploaderUsername2 = 'bennydabeast';

const bsapi = new BeatSaverAPI({
Expand All @@ -19,73 +19,43 @@ const bsapi = new BeatSaverAPI({
});

test(`Test Map with Key ${knownKey1}`, async () => {
const map = await bsapi.getMapDetailsByKey(knownKey1);
const map = await bsapi.getMapByID(knownKey1);
expect(map).not.toBeNull();
if (!map) throw new Error('Map is null');
expect(map.key).toBe(knownKey1);
expect(map.hash).toBe(knownHash1);
expect(map.versions[0].key).toBe(knownKey1);
expect(map.versions[0].hash).toBe(knownHash1);
if (!map.uploader) throw new Error('Map Uploader is null');
expect(map.uploader._id).toBe(knownUploaderID1);
expect(map.uploader.username).toBe(knownUploaderUsername1);
expect(map.uploader.id).toBe(knownUploaderID1);
expect(map.uploader.name).toBe(knownUploaderUsername1);
});

test(`Test Map with Key ${knownKey2}`, async () => {
const map = await bsapi.getMapDetailsByKey(knownKey2);
const map = await bsapi.getMapByID(knownKey2);
expect(map).not.toBeNull();
if (!map) throw new Error('Map is null');
expect(map.key).toBe(knownKey2);
expect(map.hash).toBe(knownHash2);
expect(map.versions[0].key).toBe(knownKey2);
expect(map.versions[0].hash).toBe(knownHash2);
if (!map.uploader) throw new Error('Map Uploader is null');
expect(map.uploader._id).toBe(knownUploaderID2);
expect(map.uploader.username).toBe(knownUploaderUsername2);
expect(map.uploader.id).toBe(knownUploaderID2);
expect(map.uploader.name).toBe(knownUploaderUsername2);
});

test(`Test Maps by Uploader ${knownUploaderUsername1}`, async () => {
const result = await bsapi.getMapsByUploader(knownUploaderID1);
if (!result) throw new Error('Result is null');
expect(result.docs.length).toBeGreaterThan(0);
expect(result.prevPage).toBeNull();
expect(result.totalDocs).toBeGreaterThan(0);

for (const map of result.docs) {
if (!map.uploader) throw new Error('Map Uploader is null');
expect(map.uploader._id).toBe(knownUploaderID1);
expect(map.uploader.username).toBe(knownUploaderUsername1);
expect(map.uploader.id).toBe(knownUploaderID1);
expect(map.uploader.name).toBe(knownUploaderUsername1);
}
});

test('Test Pages', async () => {
const resultPage0 = await bsapi.getMapsSortedByHot();
if (!resultPage0) throw new Error('Page 0 is null');
expect(resultPage0.prevPage).toBeNull();
expect(resultPage0.nextPage).not.toBeNull();
expect(resultPage0.lastPage).not.toBeNull();

const resultPage1 = await bsapi.getMapsSortedByHot(1);
if (!resultPage1) throw new Error('Page 1 is null');
expect(resultPage1.prevPage).toBe(0);
expect(resultPage1.nextPage).toBe(2);
});

test('Test Map Equality', async () => {
const map1ByKey = await bsapi.getMapDetailsByKey(knownKey1);
const map1ByHash = await bsapi.getMapDetailsByHash(knownHash1);
const map2ByKey = await bsapi.getMapDetailsByKey(knownKey2);
const map1ByKey = await bsapi.getMapByID(knownKey1);
const map1ByHash = await bsapi.getMapByHash(knownHash1);
const map2ByKey = await bsapi.getMapByID(knownKey2);
expect(map1ByKey).toStrictEqual(map1ByHash);
expect(map2ByKey).not.toEqual(map1ByKey)
expect(map2ByKey).not.toEqual(map1ByKey);
});

jest.setTimeout(30000);

test('Test Map Downloading', async () => {
const fileLocationByKey = await bsapi.downloadMapByKey(knownKey1, './');
const fileLocationByHash = await bsapi.downloadMapByHash(knownHash1, './');

const fileBytesByKey = readFileSync(fileLocationByKey);
const fileBytesByHash = readFileSync(fileLocationByHash);

expect(fileBytesByKey).toEqual(fileBytesByHash);

if (existsSync(fileLocationByKey)) unlinkSync(fileLocationByKey);
if (existsSync(fileLocationByHash)) unlinkSync(fileLocationByHash);
});
22 changes: 0 additions & 22 deletions src/api/downloadMapByHash.ts

This file was deleted.

22 changes: 0 additions & 22 deletions src/api/downloadMapByKey.ts

This file was deleted.

17 changes: 0 additions & 17 deletions src/api/getMapDetailsByHash.ts

This file was deleted.

17 changes: 0 additions & 17 deletions src/api/getMapDetailsByKey.ts

This file was deleted.

Loading

0 comments on commit f55a2a1

Please sign in to comment.