Skip to content

Commit

Permalink
feat: Added Bandlab Plugin (#3)
Browse files Browse the repository at this point in the history
Co-authored-by: Skick <[email protected]>
  • Loading branch information
LakhindarPal and skick1234 authored Oct 27, 2024
1 parent 4eebfc4 commit c2a4ef5
Show file tree
Hide file tree
Showing 12 changed files with 905 additions and 2 deletions.
37 changes: 37 additions & 0 deletions packages/bandlab/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<div align="center">
<p>
<a href="https://nodei.co/npm/@distube/bandlab"><img src="https://nodei.co/npm/@distube/bandlab.png?downloads=true&downloadRank=true&stars=true"></a>
</p>
<p>
<img alt="npm peer dependency version" src="https://img.shields.io/npm/dependency-version/@distube/bandlab/peer/distube?style=flat-square">
<img alt="npm" src="https://img.shields.io/npm/dt/@distube/bandlab?logo=npm&style=flat-square">
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/distubejs/extractor-plugins?logo=github&logoColor=white&style=flat-square">
<a href="https://discord.gg/feaDd9h"><img alt="Discord" src="https://img.shields.io/discord/732254550689316914?logo=discord&logoColor=white&style=flat-square"></a>
</p>
<p>
<a href='https://ko-fi.com/skick' target='_blank'><img height='48' src='https://storage.ko-fi.com/cdn/kofi3.png' alt='Buy Me a Coffee at ko-fi.com' /></a>
</p>
</div>

# @distube/bandlab

A DisTube playable extractor plugin for supporting Bandlab.

[_What is a playable extractor plugin?_](https://github.com/skick1234/DisTube/wiki/Projects-Hub#plugins)

## Installation

```sh
npm install @distube/bandlab@latest
```

## Usage

```ts
import { DisTube } from "distube";
import { BandlabPlugin } from "@distube/bandlab";

const distube = new DisTube(client, {
plugins: [new BandlabPlugin()],
});
```
65 changes: 65 additions & 0 deletions packages/bandlab/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"name": "@distube/bandlab",
"version": "0.0.1",
"author": "Skick (https://github.com/skick1234)",
"repository": {
"type": "git",
"url": "git+https://github.com/distubejs/extractor-plugins.git"
},
"main": "./dist/index.js",
"devDependencies": {
"@types/node": "^20.14.7",
"discord.js": "^14.15.3",
"distube": "^5.0.2",
"eslint": "^8.57.0",
"eslint-config-distube": "^1.7.0",
"prettier": "^3.3.2",
"tslib": "^2.6.3",
"tsup": "^8.1.0",
"typescript": "^5.5.2"
},
"exports": "./dist/index.js",
"bugs": {
"url": "https://github.com/distubejs/extractor-plugins/issues"
},
"description": "A DisTube playable extractor plugin using bandlab.",
"directories": {
"lib": "src"
},
"files": [
"dist",
"script"
],
"funding": "https://github.com/skick1234/DisTube?sponsor",
"homepage": "https://github.com/distubejs/extractor-plugins/tree/main/packages/bandlab#readme",
"keywords": [
"distube",
"plugin",
"discord",
"music"
],
"license": "MIT",
"nano-staged": {
"*.ts": [
"prettier --write",
"eslint"
],
"*.{json,yml,yaml}": [
"prettier --write"
]
},
"peerDependencies": {
"distube": "5"
},
"scripts": {
"format": "prettier --write \"**/*.{js,ts,json,yml,yaml,md}\"",
"lint": "eslint .",
"build": "tsup",
"type": "tsc --noEmit",
"prepack": "pnpm run build"
},
"typings": "./dist/index.d.ts",
"dependencies": {
"undici": "^6.19.2"
}
}
24 changes: 24 additions & 0 deletions packages/bandlab/src/API.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Pool } from "undici";
import type { AlbumResponse } from "./interfaces/Album";
import type { PlaylistResponse } from "./interfaces/Playlist";
import type { RevisionResponse } from "./interfaces/Revision";
import type { TrackResponse } from "./interfaces/Track";

const api = new Pool("https://www.bandlab.com");

const get = async (path: string): Promise<any> => {
const { body } = await api.request({
path,
method: "GET",
});

const data: any = await body.json();
if (data.errorCode === 400) throw new Error(data.modelState[0]);
return data;
};

export const getTrack = async (id: string): Promise<TrackResponse | RevisionResponse> => get(`/api/v1.3/posts/${id}`);

export const getAlbum = async (id: string): Promise<AlbumResponse> => get(`/api/v1.3/albums/${id}`);

export const getPlaylist = async (id: string): Promise<PlaylistResponse> => get(`/api/v1.3/collections/${id}`);
136 changes: 136 additions & 0 deletions packages/bandlab/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { getAlbum, getPlaylist, getTrack } from "./API";
import { DisTubeError, PlayableExtractorPlugin, Playlist, Song } from "distube";
import type { ResolveOptions } from "distube";
import type { RevisionResponse } from "./interfaces/Revision";
import type { TrackResponse } from "./interfaces/Track";
import type { AlbumResponse } from "./interfaces/Album";
import type { PlaylistResponse } from "./interfaces/Playlist";

const BASE_URL = "https://www.bandlab.com";

const REGEX =
/https?:\/\/(?:www\.)?bandlab\.com\/(?:[\w\d_]+\/)?(collections|albums|track|post|songs)\/([a-f0-9-]+)(?:_|\?|$)/;

const SUPPORTED_TYPES = ["collections", "albums", "track", "post", "songs"];

const parseURL = (url: string): { type?: string; id?: string } => {
const [, type, id] = url.match(REGEX) ?? [];
return { type, id };
};

export class BandlabPlugin extends PlayableExtractorPlugin {
validate(url: string) {
if (typeof url !== "string" || !url.includes("bandlab.com")) return false;
const { type, id } = parseURL(url);
if (!type || !id || !SUPPORTED_TYPES.includes(type)) return false;
return true;
}

async resolve<T>(url: string, options: ResolveOptions<T>): Promise<Song<T> | Playlist<T>> {
const { type, id } = parseURL(url);
if (!type || !id) throw new DisTubeError("BANDLAB_PLUGIN_INVALID_URL", `Invalid Bandlab url: ${url}`);

const api = type === "collections" ? getPlaylist(id) : type === "albums" ? getAlbum(id) : getTrack(id);
const data: any = await api.catch(e => {
throw new DisTubeError("BANDLAB_PLUGIN_API_ERROR", e.message);
});

if (data.type === "Track") {
return new BandlabTrack(this, data, options);
} else if (data.type === "Revision") {
return new BandlabRevision(this, data, options);
} else {
return new BandlabPlaylist(this, data, url, options);
}
}

async getStreamURL(song: Song): Promise<string> {
if (!song.url) {
throw new DisTubeError("BANDLAB_PLUGIN_INVALID_SONG", "Cannot get stream url from invalid song.");
}
const data = await getTrack(song.id).catch(e => {
throw new DisTubeError("BANDLAB_ERROR", `${e.message || e}`);
});
return data.type === "Revision"
? (data as RevisionResponse).revision.mixdown.file
: (data as TrackResponse).track.sample.audioUrl;
}

getRelatedSongs() {
return [];
}
}

class BandlabTrack<T> extends Song<T> {
constructor(plugin: BandlabPlugin, data: TrackResponse, options: ResolveOptions<T> = {}) {
super(
{
plugin,
source: "bandlab",
playFromSource: true,
id: data.id.toString(),
name: data.track.name,
url: `${BASE_URL}/track/${data.id}`,
thumbnail: data.track.picture.url,
uploader: {
name: data.creator.name,
url: `${BASE_URL}/${data.creator.username}`,
},
likes: data.counters.likes,
views: data.counters.plays,
ageRestricted: data.isExplicit,
duration: data.track.sample.duration,
},
options,
);
}
}

class BandlabRevision<T> extends Song<T> {
constructor(plugin: BandlabPlugin, data: RevisionResponse, options: ResolveOptions<T> = {}) {
super(
{
plugin,
source: "bandlab",
playFromSource: true,
id: data.id.toString(),
name: data.revision.song.name,
url: `${BASE_URL}/track/${data.id}`,
thumbnail: data.revision.song.picture.url,
uploader: {
name: data.creator.name,
url: `${BASE_URL}/${data.creator.username}`,
},
likes: data.revision.counters.likes,
views: data.revision.counters.plays,
ageRestricted: data.isExplicit,
duration: data.revision.mixdown.duration,
},
options,
);
}
}

class BandlabPlaylist<T> extends Playlist<T> {
constructor(
plugin: BandlabPlugin,
data: PlaylistResponse | AlbumResponse,
url: string,
options: ResolveOptions<T> = {},
) {
super(
{
source: "bandlab",
id: data.id.toString(),
name: data.name,
url,
thumbnail: data.picture.url,
songs:
data.posts[0].type === "Track"
? data.posts.map((s: any) => new BandlabTrack(plugin, s, options))
: data.posts.map((s: any) => new BandlabRevision(plugin, s, options)),
},
options,
);
}
}
Loading

0 comments on commit c2a4ef5

Please sign in to comment.