implement soundcloud search
This commit is contained in:
parent
f38e5a8b11
commit
dac08c1d3f
6 changed files with 105 additions and 11 deletions
|
@ -1,5 +1,6 @@
|
|||
import { Client, GuildMember, Message, TextChannel } from "discord.js";
|
||||
import { Player, Queue, Track } from "../src/index";
|
||||
import { QueryType } from "../src/types/types";
|
||||
import { config } from "./config";
|
||||
// use this in prod.
|
||||
// import { Player, Queue } from "discord-player";
|
||||
|
@ -51,6 +52,18 @@ client.on("message", async (message) => {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "soundcloud",
|
||||
description: "Plays a song from soundcloud",
|
||||
options: [
|
||||
{
|
||||
name: "query",
|
||||
type: "STRING",
|
||||
description: "The song you want to play",
|
||||
required: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "volume",
|
||||
description: "Sets music volume",
|
||||
|
@ -104,11 +117,16 @@ client.on("interaction", async (interaction) => {
|
|||
return void interaction.reply({ content: "You are not in my voice channel!", ephemeral: true });
|
||||
}
|
||||
|
||||
if (interaction.commandName === "play") {
|
||||
if (interaction.commandName === "play" || interaction.commandName === "soundcloud") {
|
||||
await interaction.defer();
|
||||
|
||||
const query = interaction.options.get("query")!.value! as string;
|
||||
const searchResult = (await player.search(query, interaction.user).catch(() => [])) as Track[];
|
||||
const searchResult = (await player
|
||||
.search(query, {
|
||||
requestedBy: interaction.user,
|
||||
searchEngine: interaction.commandName === "soundcloud" ? QueryType.SOUNDCLOUD_SEARCH : QueryType.AUTO
|
||||
})
|
||||
.catch(() => [])) as Track[];
|
||||
if (!searchResult.length) return void interaction.followUp({ content: "No results were found!" });
|
||||
|
||||
const queue = await player.createQueue(interaction.guild, {
|
||||
|
|
|
@ -2,10 +2,15 @@ import { Client, Collection, Guild, Snowflake, User } from "discord.js";
|
|||
import { TypedEmitter as EventEmitter } from "tiny-typed-emitter";
|
||||
import { Queue } from "./Structures/Queue";
|
||||
import { VoiceUtils } from "./VoiceInterface/VoiceUtils";
|
||||
import { PlayerEvents, PlayerOptions, QueryType } from "./types/types";
|
||||
import { PlayerEvents, PlayerOptions, QueryType, SearchOptions } from "./types/types";
|
||||
import Track from "./Structures/Track";
|
||||
import { QueryResolver } from "./utils/QueryResolver";
|
||||
import YouTube from "youtube-sr";
|
||||
import { Util } from "./utils/Util";
|
||||
// @ts-ignore
|
||||
import { Client as SoundCloud } from "soundcloud-scraper";
|
||||
|
||||
const soundcloud = new SoundCloud();
|
||||
|
||||
class DiscordPlayer extends EventEmitter<PlayerEvents> {
|
||||
public readonly client: Client;
|
||||
|
@ -75,11 +80,13 @@ class DiscordPlayer extends EventEmitter<PlayerEvents> {
|
|||
* @param {Discord.User} requestedBy The person who requested track search
|
||||
* @returns {Promise<Track[]>}
|
||||
*/
|
||||
async search(query: string | Track, requestedBy: User) {
|
||||
async search(query: string | Track, options: SearchOptions) {
|
||||
if (query instanceof Track) return [query];
|
||||
if (!options) throw new Error("DiscordPlayer#search needs search options!");
|
||||
if (!("searchEngine" in options)) options.searchEngine = QueryType.AUTO;
|
||||
|
||||
// @todo: add extractors
|
||||
const qt = QueryResolver.resolve(query);
|
||||
const qt = options.searchEngine === QueryType.AUTO ? QueryResolver.resolve(query) : options.searchEngine;
|
||||
switch (qt) {
|
||||
case QueryType.YOUTUBE_SEARCH: {
|
||||
const videos = await YouTube.search(query, {
|
||||
|
@ -93,7 +100,7 @@ class DiscordPlayer extends EventEmitter<PlayerEvents> {
|
|||
description: m.description,
|
||||
author: m.channel?.name,
|
||||
url: m.url,
|
||||
requestedBy: requestedBy,
|
||||
requestedBy: options.requestedBy,
|
||||
thumbnail: m.thumbnail?.displayThumbnailURL("maxresdefault"),
|
||||
views: m.views,
|
||||
fromPlaylist: false,
|
||||
|
@ -102,6 +109,35 @@ class DiscordPlayer extends EventEmitter<PlayerEvents> {
|
|||
});
|
||||
});
|
||||
}
|
||||
case QueryType.SOUNDCLOUD_TRACK:
|
||||
case QueryType.SOUNDCLOUD_SEARCH: {
|
||||
const result: any[] = QueryResolver.resolve(query) === QueryType.SOUNDCLOUD_TRACK ? [{ url: query }] : await soundcloud.search(query, "track").catch(() => {});
|
||||
if (!result || !result.length) return [];
|
||||
const res: Track[] = [];
|
||||
|
||||
for (const r of result) {
|
||||
const trackInfo = await soundcloud.getSongInfo(r.url).catch(() => {});
|
||||
if (!trackInfo) continue;
|
||||
|
||||
const track = new Track(this, {
|
||||
title: trackInfo.title,
|
||||
url: trackInfo.url,
|
||||
duration: Util.buildTimeCode(Util.parseMS(trackInfo.duration)),
|
||||
description: trackInfo.description,
|
||||
thumbnail: trackInfo.thumbnail,
|
||||
views: trackInfo.playCount,
|
||||
author: trackInfo.author.name,
|
||||
requestedBy: options.requestedBy,
|
||||
fromPlaylist: false,
|
||||
source: "soundcloud",
|
||||
engine: trackInfo
|
||||
});
|
||||
|
||||
res.push(track);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -54,8 +54,8 @@ class Queue<T = unknown> {
|
|||
|
||||
if (channel.type === "stage") await channel.guild.me.voice.setRequestToSpeak(true).catch(() => {});
|
||||
|
||||
this.connection.on("error", err => this.player.emit("error", this, err));
|
||||
this.connection.on("debug", msg => this.player.emit("debug", this, msg));
|
||||
this.connection.on("error", (err) => this.player.emit("error", this, err));
|
||||
this.connection.on("debug", (msg) => this.player.emit("debug", this, msg));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -158,7 +158,7 @@ class BasicStreamDispatcher extends EventEmitter<VoiceEvents> {
|
|||
}
|
||||
|
||||
get paused() {
|
||||
return [AudioPlayerStatus.AutoPaused, AudioPlayerStatus.Paused].includes(this.audioPlayer.state.status)
|
||||
return [AudioPlayerStatus.AutoPaused, AudioPlayerStatus.Paused].includes(this.audioPlayer.state.status);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -100,6 +100,7 @@ export interface ExtractorModelData {
|
|||
}
|
||||
|
||||
export enum QueryType {
|
||||
AUTO = "auto",
|
||||
YOUTUBE = "youtube",
|
||||
YOUTUBE_PLAYLIST = "youtube_playlist",
|
||||
SOUNDCLOUD_TRACK = "soundcloud_track",
|
||||
|
@ -112,7 +113,8 @@ export enum QueryType {
|
|||
VIMEO = "vimeo",
|
||||
ARBITRARY = "arbitrary",
|
||||
REVERBNATION = "reverbnation",
|
||||
YOUTUBE_SEARCH = "youtube_search"
|
||||
YOUTUBE_SEARCH = "youtube_search",
|
||||
SOUNDCLOUD_SEARCH = "soundcloud_search"
|
||||
}
|
||||
|
||||
export interface PlayerEvents {
|
||||
|
@ -149,3 +151,8 @@ export interface PlayOptions {
|
|||
/** If it should start playing provided track immediately */
|
||||
immediate?: boolean;
|
||||
}
|
||||
|
||||
export interface SearchOptions {
|
||||
requestedBy: User;
|
||||
searchEngine?: QueryType;
|
||||
}
|
||||
|
|
|
@ -1 +1,34 @@
|
|||
export {};
|
||||
import { TimeData } from "../types/types";
|
||||
|
||||
class Util {
|
||||
static durationString(durObj: object) {
|
||||
return Object.values(durObj)
|
||||
.map((m) => (isNaN(m) ? 0 : m))
|
||||
.join(":");
|
||||
}
|
||||
|
||||
static parseMS(milliseconds: number) {
|
||||
const round = milliseconds > 0 ? Math.floor : Math.ceil;
|
||||
|
||||
return {
|
||||
days: round(milliseconds / 86400000),
|
||||
hours: round(milliseconds / 3600000) % 24,
|
||||
minutes: round(milliseconds / 60000) % 60,
|
||||
seconds: round(milliseconds / 1000) % 60
|
||||
} as TimeData;
|
||||
}
|
||||
|
||||
static buildTimeCode(duration: TimeData) {
|
||||
const items = Object.keys(duration);
|
||||
const required = ["days", "hours", "minutes", "seconds"];
|
||||
|
||||
const parsed = items.filter((x) => required.includes(x)).map((m) => (duration[m as keyof TimeData] > 0 ? duration[m as keyof TimeData] : ""));
|
||||
const final = parsed
|
||||
.filter((x) => !!x)
|
||||
.map((x) => x.toString().padStart(2, "0"))
|
||||
.join(":");
|
||||
return final.length <= 3 ? `0:${final.padStart(2, "0") || 0}` : final;
|
||||
}
|
||||
}
|
||||
|
||||
export { Util };
|
||||
|
|
Loading…
Reference in a new issue