Skip to content

Commit

Permalink
bot: v3.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
nikosszzz authored May 8, 2024
1 parent 8d3b413 commit fa59edf
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 145 deletions.
7 changes: 2 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "music-bot",
"version": "3.1.2",
"version": "3.2.0",
"description": "A general music Discord bot.",
"main": "./src/bot.ts",
"repository": {
Expand All @@ -26,11 +26,8 @@
"opusscript": "^0.1.1",
"play-dl": "^1.9.7",
"sodium-native": "^4.1.1",
"spotify-url-info": "^3.2.13",
"string-progressbar": "^1.0.4",
"undici": "^6.15.0",
"utf-8-validate": "^6.0.3",
"youtube-sr": "^4.3.11"
"utf-8-validate": "^6.0.3"
},
"devDependencies": {
"@rollup/plugin-typescript": "^11.1.6",
Expand Down
40 changes: 0 additions & 40 deletions pnpm-lock.yaml

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

2 changes: 1 addition & 1 deletion rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function hash(): string {
export default defineConfig([{
input: "src/index.ts",
treeshake: true,
external: ["discord.js", "play-dl", "lyrics-finder", "spotify-url-info", "youtube-sr", "array-move", "string-progressbar", "undici", "@discordjs/voice", "chalk", "typescript", "node:util", "node:os", "node:process"],
external: ["discord.js", "play-dl", "lyrics-finder", "array-move", "string-progressbar", "@discordjs/voice", "chalk", "typescript", "node:util", "node:os", "node:process"],
cache: true,
output: {
name: "bundle.js",
Expand Down
3 changes: 2 additions & 1 deletion src/commands/info/changelogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export default {
- types: Typings fixes
`;

const whatsnew = `- bot: Updated and upgraded`;
const whatsnew = `- bot: Updated and upgraded
- internals(music): Rewritten to some extent and more efficient.`;

const UpdateEmbed = new EmbedBuilder()
.setColor("NotQuiteBlack")
Expand Down
2 changes: 1 addition & 1 deletion src/commands/music/nowplaying.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default {

const song = queue.songs[0];
const seek = queue.resource.playbackDuration / 1000;
const left = song.duration - seek;
const left = song.duration - seek

try {
const nowPlaying = new EmbedBuilder()
Expand Down
2 changes: 1 addition & 1 deletion src/commands/music/play.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default {

let song: Song;
try {
song = await Song.from({ url, search: url, interaction });
song = await Song.from({ search: url, interaction });
} catch (err: any | Error) {
interaction.editReply({ content: "An error occured in the Music system! Song was not added." });
return Logger.error({ type: "CMDS/PLAY", err: err });
Expand Down
9 changes: 5 additions & 4 deletions src/commands/music/playlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Logger } from "@components/Logger";
import { MusicQueue } from "@components/MusicQueue";
import { Playlist } from "@components/Playlist";
import type { Command } from "@common/types";
import { SoundCloudPlaylist, SpotifyPlaylist } from "play-dl";

export default {
data: new SlashCommandBuilder()
Expand Down Expand Up @@ -45,7 +46,7 @@ export default {

let playlist: Playlist;
try {
playlist = await Playlist.from({ url, search: url, interaction });
playlist = await Playlist.from({ search: url, interaction });
} catch (err: any | Error) {
Logger.error({ type: "INTERNAL:PLAYLIST", err: err });
return interaction.editReply({ content: "Playlist not found." }).catch((err) => Logger.error({ type: "MUSICCMDS", err: err }));
Expand All @@ -59,7 +60,7 @@ export default {
.setDescription("The following playlist is now playing:")
.addFields(
{
name: playlist.data.title ? playlist.data.title : "Spotify Playlist", value: "** **"
name: playlist.data instanceof SpotifyPlaylist || playlist.data instanceof SoundCloudPlaylist ? playlist.data.name : playlist.data.title!, value: "** **"
})
.setURL(playlist.data.url as string);

Expand All @@ -74,7 +75,7 @@ export default {
.setDescription("The following playlist has been added to the queue:")
.addFields(
{
name: playlist.data.title ? playlist.data.title : "Spotify Playlist", value: "** **"
name: playlist.data instanceof SpotifyPlaylist || playlist.data instanceof SoundCloudPlaylist ? playlist.data.name : playlist.data.title!, value: "** **"
})
.setURL(playlist.data.url as string);

Expand Down Expand Up @@ -103,7 +104,7 @@ export default {
.setDescription("The following playlist is now playing:")
.addFields(
{
name: playlist.data.title ? playlist.data.title : "Spotify Playlist", value: "** **"
name: playlist.data instanceof SpotifyPlaylist || playlist.data instanceof SoundCloudPlaylist ? playlist.data.name : playlist.data.title!, value: "** **"
})
.setURL(playlist.data.url as string);

Expand Down
2 changes: 1 addition & 1 deletion src/commands/music/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function generateQueueEmbed(interaction: ChatInputCommandInteraction<CacheType>,
let j = i;
k += 10;

const info = current.map(track => `${++j} - [${track.title}](${track.url}) - Requested by ${track.req}`).join("\n");
const info = current.map(track => `${++j} - [${track.title}](${track.url}) ${track.durationRaw ? `\`${track.durationRaw}\`` : ""} - Requested by ${track.req}`).join("\n");

const queueEmbed = new EmbedBuilder()
.setColor("NotQuiteBlack")
Expand Down
31 changes: 16 additions & 15 deletions src/components/Bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import type { Command } from "@common/types";
import { config } from "@components/config";
import { Manager } from "@manager";
import type { MusicQueue } from "@components/MusicQueue";
import { setToken } from "play-dl";
import { getFreeClientID, setToken } from "play-dl";
import { Logger } from "@components/Logger";

export class Bot extends Client {
public commands = new Collection<string, Command>();
public queues = new Collection<string, MusicQueue>();
public readonly debug: boolean = false;
public readonly version: string = "3.1.3";
public readonly version: string = "3.2.0";
public readonly branch: string;

constructor(options: ClientOptions) {
Expand All @@ -20,19 +20,20 @@ export class Bot extends Client {
this.branch = this.debug ? "development" : "stable";

/* Music Authentication */
setToken({
soundcloud: {
client_id: config.SOUNDCLOUD_CLIENT_ID
},
/* Spotify auth is not needed, you can delete this. */
spotify: {
client_id: config.SPOTIFY_CLIENT_ID,
client_secret: config.SPOTIFY_SECRET_ID,
/* Put your country's region code. example: "gr", "us", "uk" */
market: "",
refresh_token: config.SPOTIFY_REFRESH_TOKEN
}
});
getFreeClientID().then(id =>
setToken({
soundcloud: {
client_id: config.SOUNDCLOUD_CLIENT_ID.length ? config.SOUNDCLOUD_CLIENT_ID : id
},
/* Spotify auth is not needed, you can delete this. */
spotify: {
client_id: config.SPOTIFY_CLIENT_ID,
client_secret: config.SPOTIFY_SECRET_ID,
/* Put your country's region code. example: "gr", "us", "uk" */
market: "gr",
refresh_token: config.SPOTIFY_REFRESH_TOKEN
}
}));

/* Bot Manager */
this.login(this.debug ? config.DEVTOKEN : config.TOKEN).finally(() => new Manager(this));
Expand Down
86 changes: 42 additions & 44 deletions src/components/Playlist.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,64 @@
import { Playlist as youtubePlaylist, type Thumbnail, type Video, YouTube as youtube } from "youtube-sr";
import { config } from "@components/config";
import { Song } from "@components/Song";
import type { CacheType, ChatInputCommandInteraction } from "discord.js";
//@ts-expect-error
import SpotifyUrlInfo, { Tracks } from "spotify-url-info";
import { fetch } from "undici";
import { sp_validate, so_validate, yt_validate, search as playSearch, soundcloud, type SoundCloudPlaylist } from "play-dl";
import { sp_validate, so_validate, yt_validate, playlist_info, soundcloud, spotify, search as playSearch, type SoundCloudPlaylist, YouTubePlayList, YouTubeVideo, SpotifyPlaylist, SoundCloudTrack, video_basic_info, is_expired, refreshToken } from "play-dl";

type dataType = SpotifyPlaylist | YouTubePlayList | SoundCloudPlaylist;
type tracksType = YouTubeVideo[] | SoundCloudTrack[];

export class Playlist {
public readonly data: youtubePlaylist;
public readonly data: SpotifyPlaylist | YouTubePlayList | SoundCloudPlaylist;
public readonly videos: Song[];

public constructor({ playlist, interaction }: { playlist: youtubePlaylist; interaction: ChatInputCommandInteraction<CacheType>; }) {
this.data = playlist;
public constructor({ playlist, interaction }: { playlist: { data: dataType, tracks: tracksType }; interaction: ChatInputCommandInteraction<CacheType>; }) {
this.data = playlist.data;

this.videos = this.data.videos
.filter((video) => !["Private video", "Deleted video"].includes(video.title!))
this.videos = playlist.tracks
.slice(0, config.MAX_PLAYLIST_SIZE)
.map((video) => new Song({
title: video.title as string,
.map((video) => video instanceof YouTubeVideo ? new Song({
title: video.title!,
url: video.url,
duration: video.durationInSec,
durationRaw: video.durationRaw,
thumbnail: video.thumbnails[0].url,
req: interaction.user.tag
}) : new Song({
title: video.name,
url: video.url,
duration: video.duration / 1000,
thumbnail: (video.thumbnail as Thumbnail).url as string,
duration: video.durationInSec,
durationRaw: undefined,
thumbnail: video.thumbnail,
req: interaction.user.tag
}));
}
};

public static async from({ url = "", search = "", interaction }: { url: string; search: string; interaction: ChatInputCommandInteraction<CacheType> }): Promise<Playlist> {
const isYoutubeUrl = yt_validate(url) === "playlist";
const isSpotifyUrl = ["playlist", "album"].includes(sp_validate(url) as string);
const isSoundCloudUrl = await so_validate(url) === "playlist";
public static async from({ search, interaction }: { search: string; interaction: ChatInputCommandInteraction<CacheType> }): Promise<Playlist> {
const isYoutubeUrl = yt_validate(search) === "playlist";
const isSpotifyUrl = ["playlist", "album"].includes(sp_validate(search) as string);
const isSoundCloudUrl = await so_validate(search) === "playlist";

let playlist: youtubePlaylist;
let pl: { data: dataType, tracks: tracksType }
if (isSpotifyUrl) {
const playlistTrack = await SpotifyUrlInfo(fetch).getTracks(url);
const limitedPlaylistTrack = playlistTrack.slice(0, config.MAX_PLAYLIST_SIZE);
const spotifyPl = Promise.all(limitedPlaylistTrack.map(async (track: Tracks): Promise<Video> => await youtube.searchOne(`${track.name} - ${track.artists ? track.artists[0].name : ""}`, "video")));
playlist = new youtubePlaylist({ videos: (await spotifyPl).filter((song: Video) => song.title !== undefined || song.duration !== undefined) });
if (is_expired()) await refreshToken();
const playlist = await spotify(search) as SpotifyPlaylist

const ytVideos = await Promise.all((await playlist.all_tracks()).map(async track => (await video_basic_info((await playSearch(`${track.artists[0].name} - ${track.name}`, { source: { youtube: "video" } }))[0].url)).video_details));

pl = { data: playlist, tracks: ytVideos };
} else if (isYoutubeUrl) {
playlist = await youtube.getPlaylist(url);
const playlist = await playlist_info(search, { incomplete: true });

pl = { data: playlist, tracks: await playlist.all_videos() };
} else if (isSoundCloudUrl) {
const scPl = await soundcloud(url) as SoundCloudPlaylist;
const scPlTracks = (await scPl.all_tracks()).map((track) => ({
title: track.name,
url: track.permalink,
duration: track.durationInSec,
thumbnail: {
url: track.thumbnail
},
req: interaction.user.tag
}));
playlist = new youtubePlaylist({ videos: scPlTracks, title: scPl.name, url: scPl.url });
const playlist = await soundcloud(search) as SoundCloudPlaylist;

pl = { data: playlist, tracks: await playlist.all_tracks() };
} else {
const result = await playSearch(search, {
source: {
youtube: "playlist"
},
limit: 1
});
playlist = await youtube.getPlaylist(result[0].url as string);
const playlist = await playlist_info(search as string, { incomplete: true });

pl = { data: playlist, tracks: await playlist.all_videos() };
}

return new this({ playlist, interaction });
return new this({ playlist: { data: pl.data, tracks: pl.tracks }, interaction });
}
}
Loading

0 comments on commit fa59edf

Please sign in to comment.