Skip to content

Commit

Permalink
Added progress bar
Browse files Browse the repository at this point in the history
  • Loading branch information
Struck713 committed Sep 18, 2023
1 parent 4e1831f commit 121e829
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 63 deletions.
5 changes: 4 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Client, Events, GatewayIntentBits } from "discord.js";
import { TOKEN, BRUH } from '../config.json';
import { YOUTUBE, TOKEN, BRUH } from '../config.json';
import { Commands } from "./lib/command";
import { VoiceManager } from "./lib/voice";
import { StateManager } from "./lib/state";
import { BruhListener } from "./lib/listeners/bruh";

import YTDlpWrap from "yt-dlp-wrap";
export const ytdl = new YTDlpWrap(YOUTUBE.BINARY);

export const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent ] });
export const voiceManager = new VoiceManager();
export const stateManager = new StateManager();
Expand Down
2 changes: 1 addition & 1 deletion src/commands/voice/play.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const Play: Command = {
connection = voiceManager.join(user.voice.channel);
connection.play(metadata);

await Embeds.send(interaction, embed => embed.setAuthor({ name: "Now playing "})
await Embeds.send(interaction, embed => embed.setAuthor({ name: "Now Playing"})
.setTitle(metadata.getTitle())
.setURL(metadata.getUrl())
.setDescription(`by ${metadata.getAuthor()}`)
Expand Down
10 changes: 9 additions & 1 deletion src/commands/voice/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Colors, EmbedBuilder, SlashCommandBuilder } from "discord.js";
import { voiceManager } from "../../app";
import { Command } from "../../lib/command";
import { Embeds } from "../../lib/utils/embeds";
import { Time } from "../../lib/utils/misc";

export const Queue: Command = {
data: new SlashCommandBuilder()
Expand All @@ -28,15 +29,22 @@ export const Queue: Command = {

let { playing, queue } = connection;
if (playing) {

let bar = Time.progress(15, connection.resource?.playbackDuration, playing.getDuration() * 1000);
let embed = Embeds.create()
.setAuthor({ name: 'Now Playing' })
.setTitle(playing.getTitle())
.setURL(playing.getUrl())
.setDescription(`by ${playing.getAuthor()}`)
.setDescription(`
by ${playing.getAuthor()} \n
${bar} ${Time.format(connection.resource?.playbackDuration)}/${Time.format(playing.getDuration(), "seconds")}
`)
.setThumbnail(playing.getThumbnailUrl())
.addFields({ name: '\u200B', value: 'Next in the queue:' })

if (queue.length > 0) embed.addFields(queue.slice(0, Math.min(8, queue.length)).map((metadata, index) => ({ name: `${index + 2}. ${metadata.getTitle()}`, value: `by ${metadata.getAuthor()}` })));
else embed.addFields({ name: 'There is nothing next in the queue.', value: '\u200B' })

await Embeds.send(interaction, () => embed);
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/voice/skip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const Skip: Command = {
}

let metadata = connection.skip();
await Embeds.send(interaction, embed => embed.setAuthor({ name: "Skipped - Now playing"})
await Embeds.send(interaction, embed => embed.setAuthor({ name: "Skipped - Now Playing"})
.setTitle(metadata.getTitle())
.setURL(metadata.getUrl())
.setDescription(`by ${metadata.getAuthor()}`)
Expand Down
33 changes: 0 additions & 33 deletions src/lib/utils/html.ts

This file was deleted.

64 changes: 64 additions & 0 deletions src/lib/utils/misc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { join } from "path";

export namespace Entities {

export enum HTML_ENTITIES {
nbsp = ' ',
cent = '¢',
pound = '£',
yen = '¥',
euro = '€',
copy = '©',
reg = '®',
lt = '<',
gt = '>',
quot = '"',
amp = '&',
apos = '\''
};

export const decodeEntities = (str: string) => {
return str.replace(/\&([^;]+);/g, (entity, entityCode) => {
var match;
if (entityCode in HTML_ENTITIES) {
return HTML_ENTITIES[entityCode as keyof typeof HTML_ENTITIES];
} else if (match = entityCode.match(/^#x([\da-fA-F]+)$/)) {
return String.fromCharCode(parseInt(match[1], 16));
} else if (match = entityCode.match(/^#(\d+)$/)) {
return String.fromCharCode(~~match[1]);
} else {
return entity;
}
});
};

}

export namespace Time {

export const PROGRESS_UNFILLED = ":white_large_square:";
export const PROGRESS_FILLED = ":yellow_square:";

export const decode = (str: string): number => {
let [minutes, seconds] = str.split(":");
return (parseInt(minutes) * 60000) + (parseInt(seconds) * 1000);
}

export const format = (ms?: number, type: "seconds" | "milliseconds" = "milliseconds"): string => {
if (!ms) return "0:00";
if (type === "milliseconds") ms = Math.floor(ms / 1000);

let minutes = Math.floor(ms / 60);
let seconds = (ms % 60).toFixed(0);
return `${minutes}:${seconds.padStart(2, '0')}`;
}

export const progress = (segments: number, min?: number, max?: number) => {
if (!min || !max) return Array(segments).fill(PROGRESS_UNFILLED),join("");

let percent = Math.floor(((min / max) * segments));
let overall = segments - percent;
return Array(percent).fill(PROGRESS_FILLED).concat(Array(overall).fill(PROGRESS_UNFILLED)).join("");
}

}
40 changes: 21 additions & 19 deletions src/lib/utils/youtube.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Axios from 'axios';
import { YOUTUBE } from "../../../config.json";
import { Entities } from './html';
import { Entities, Time } from './misc';
import { ytdl } from '../../app';

const YOUTUBE_SHORTENED_URL = "https://youtu.be/";
const YOUTUBE_URL_REGEX = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube(-nocookie)?\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|live\/|v\/)?)([\w\-]+)(\S+)?$/;
Expand All @@ -10,44 +11,41 @@ const search = async (query: string): Promise<YoutubeMetadata | undefined> => {
const res = await Axios.get(url).then(res => res.data).catch(_ => undefined);
if (!res) return undefined;

let videoData = res.items[0];
return new YoutubeMetadata(
videoData.snippet.title,
videoData.snippet.channelTitle,
`${YOUTUBE_SHORTENED_URL}${videoData.id.videoId}`,
`https://i3.ytimg.com/vi/${videoData.id.videoId}/maxresdefault.jpg`
);
}
let { id: { videoId } } = res.items[0];
return getMetadata(createShareUrl(videoId));
}

const getVideoIdFromUrl = (url: string) => {
let capture = YOUTUBE_URL_REGEX.exec(url);
return capture ? capture[6] : undefined;
return capture ? capture[6] : "";
}

const getMetadata = async (url: string): Promise<YoutubeMetadata | undefined> => {
let shortenedUrl = createShareUrl(url);
const urlMetadata: string = `https://www.youtube.com/oembed?url=${shortenedUrl}&format=json`;
const urlThumbnail: string = `https://i3.ytimg.com/vi/${getVideoIdFromUrl(url)}/maxresdefault.jpg`;
const res = await Axios.get(urlMetadata).then(res => res.data).catch(_ => undefined);
if (res) return new YoutubeMetadata(res.title, res.author_name, shortenedUrl, urlThumbnail);
let res = await ytdl.getVideoInfo(url).catch(_ => undefined);
if (res) return new YoutubeMetadata(res.fulltitle, res.channel, createShareUrl(res.display_id), res.thumbnail, res.duration);
else return undefined;
}

const createShareUrl = (url: string): string => {
return `${YOUTUBE_SHORTENED_URL}${ getVideoIdFromUrl(url) }`;
}
const getThumbnailUrl = (id: string): string => `https://i3.ytimg.com/vi/${id}/maxresdefault.jpg`;
const createShareUrl = (id: string): string => `${YOUTUBE_SHORTENED_URL}${id}`;

export class YoutubeMetadata {

private title: string;
private author: string;
private url: string;
private duration: number;
private thumbnailUrl: string;

constructor(title: string, author: string, url: string, thumbnailUrl: string) {
constructor(title: string,
author: string,
url: string,
thumbnailUrl: string,
duration: number) {
this.title = Entities.decodeEntities(title);
this.author = Entities.decodeEntities(author);
this.url = url;
this.duration = duration;
this.thumbnailUrl = thumbnailUrl;
}

Expand All @@ -66,6 +64,10 @@ export class YoutubeMetadata {
getThumbnailUrl(): string {
return this.thumbnailUrl;
}

getDuration(): number {
return this.duration;
}

}

Expand Down
9 changes: 2 additions & 7 deletions src/lib/voice.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { YOUTUBE } from "../../config.json";

import { AudioPlayer, AudioPlayerStatus, AudioResource, createAudioPlayer, createAudioResource, getVoiceConnection, joinVoiceChannel } from "@discordjs/voice";
import { VoiceBasedChannel } from "discord.js";
import { voiceManager } from "../app";
import { voiceManager, ytdl } from "../app";
import { YoutubeMetadata } from "./utils/youtube";

import YTDlpWrap from "yt-dlp-wrap";
const ytdl = new YTDlpWrap(YOUTUBE.BINARY);

export class VoiceManager {

private connections: VoiceConnection[];
Expand Down Expand Up @@ -52,10 +47,10 @@ export class VoiceConnection {
channelId: string;

playing?: YoutubeMetadata;
resource?: AudioResource;
queue: YoutubeMetadata[];

private timeout?: NodeJS.Timeout;
private resource?: AudioResource;

constructor(guildId: string, channelId: string) {
this.guildId = guildId;
Expand Down

0 comments on commit 121e829

Please sign in to comment.