Skip to content

Commit

Permalink
chore(release): Release version 2.7.2 (#96)
Browse files Browse the repository at this point in the history
* chore(deps): bump winston from 3.3.2 to 3.3.3

Bumps [winston](https://github.com/winstonjs/winston) from 3.3.2 to 3.3.3.
- [Release notes](https://github.com/winstonjs/winston/releases)
- [Changelog](https://github.com/winstonjs/winston/blob/master/CHANGELOG.md)
- [Commits](winstonjs/winston@v3.3.2...v3.3.3)

Signed-off-by: dependabot[bot] <[email protected]>

* chore(deps-dev): bump eslint from 7.3.0 to 7.3.1

Bumps [eslint](https://github.com/eslint/eslint) from 7.3.0 to 7.3.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](eslint/eslint@v7.3.0...v7.3.1)

Signed-off-by: dependabot[bot] <[email protected]>

* chore(deps-dev): bump @types/node from 14.0.13 to 14.0.14

Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.0.13 to 14.0.14.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Signed-off-by: dependabot[bot] <[email protected]>

* chore(deps): Update ytdl-core to 3.1.2

* chore(docker): Cleanup Dockerfile

* chore(docker): Cleanup Dockerfile

* fix(HelpCommand): fix grammar

* fix: Pass token from build function instead

* feat(docker): Add volumes for cache

* feat(caching): Some improvements

* Add song length limiter for cache
* Fix #81 by using a finish marker

* feat: Make song titles clickable by using Markdown link

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
  • Loading branch information
Hazmi35 and dependabot[bot] authored Jul 12, 2020
1 parent 943f602 commit 06893e6
Show file tree
Hide file tree
Showing 17 changed files with 159 additions and 152 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ CONFIG_MAX_VOLUME=100 # Max volume allowed for music (default: 100)
CONFIG_DEFAULT_VOLUME=50 # Default volume for music (default: 50)
CONFIG_ALLOW_DUPLICATE=no # Whether to to all allow users to add duplicate of songs (yes or no) (default: no)
CONFIG_DELETE_QUEUE_TIMEOUT=180 # Timeout in seconds before queue is deleted when no one is in the voice channel (default: 180 <3 minutes>)
CONFIG_CACHE_YOUTUBE_DOWNLOADS=no # (EXPERIMENTAL) Whether to download and cache musics from youtube (yes or no) (default: no)
CONFIG_CACHE_YOUTUBE_DOWNLOADS=no # (EXPERIMENTAL) Whether to download and cache musics from youtube (yes or no) (default: no)
CONFIG_CACHE_MAX_LENGTH=5400 # (EXPERIMENTAL) Max song length in seconds that are allowed to cached (default: 5400 <1 hour 30 minutes>)
3 changes: 2 additions & 1 deletion .env.schema
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ CONFIG_MAX_VOLUME=
CONFIG_DEFAULT_VOLUME=
CONFIG_ALLOW_DUPLICATE=
CONFIG_DELETE_QUEUE_TIMEOUT=
CONFIG_CACHE_YOUTUBE_DOWNLOADS=
CONFIG_CACHE_YOUTUBE_DOWNLOADS=
CONFIG_CACHE_MAX_LENGTH=
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,4 @@ dist
.tern-port

# YTDL Caches
*.webm
cache/**
23 changes: 3 additions & 20 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,11 @@ LABEL maintainer "Hazmi35 <[email protected]>"
WORKDIR /usr/Jukebox

COPY . .
RUN echo [INFO] Starting to build Docker image... \
&& echo [INFO] Installing build deps... \
&& apk add --no-cache --virtual .build-deps python g++ make git curl \
&& echo [INFO] Build deps installed! \
&& echo [INFO] Installing 3rd party packages... \
RUN apk add --no-cache --virtual .build-deps python3 build-base git curl \
&& apk add --no-cache --virtual .third-party ffmpeg \
&& echo [INFO] 3rd party packages installed! \
&& echo [INFO] Node version: $(node --version) \
&& echo [INFO] npm version: $(npm --version) \
&& echo [INFO] Yarn version: $(yarn --version) \
&& echo [INFO] Git version: $(git --version) \
&& echo [INFO] Installing npm packages... \
&& yarn install \
&& echo [INFO] All npm packages installed! \
&& echo [INFO] Everything looks okay. \
&& echo [INFO] Building TypeScript project... \
&& echo Using TypeScript version: $(node -p "require('typescript').version") \
&& npm run build \
&& echo [INFO] Done building TypeScript project! \
&& echo [INFO] Pruning devDependencies... \
&& yarn run build \
&& yarn install --production \
&& apk del .build-deps \
&& echo [INFO] Done! Starting bot with node
&& apk del .build-deps

CMD ["node", "dist/main.js"]
3 changes: 3 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
},
"CONFIG_CACHE_YOUTUBE_DOWNLOADS": {
"description": "(EXPERIMENTAL) Whether to download and cache musics from youtube (yes or no) (default: no)"
},
"CONFIG_CACHE_MAX_LENGTH": {
"description": "(EXPERIMENTAL) Max song length in seconds that are allowed to cached (default: 5400 <1 hour 30 minutes>)"
}
},
"repository": "https://github.com/Hazmi35/jukebox",
Expand Down
17 changes: 9 additions & 8 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
version: '2.4'

services:
bot:
container_name: "jukebox-bot"
build:
services:
bot:
build:
context: ./
dockerfile: Dockerfile
restart: unless-stopped
env_file:
container_name: jukebox-bot
env_file:
- .env
restart: unless-stopped
volumes:
- "./cache:/usr/Jukebox/cache"
version: "2.4"
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@
"html-entities": "^1.3.1",
"opusscript": "^0.0.7",
"simple-youtube-api": "^5.2.1",
"winston": "^3.3.2",
"ytdl-core": "^2.1.7"
"winston": "^3.3.3",
"ytdl-core": "^3.1.2"
},
"devDependencies": {
"@types/node": "^14.0.13",
"@types/node": "^14.0.14",
"@typescript-eslint/eslint-plugin": "^3.4.0",
"@typescript-eslint/parser": "^3.4.0",
"eslint": "^7.3.0",
"eslint": "^7.3.1",
"rimraf": "^3.0.2",
"ts-node": "^8.10.2",
"typescript": "^3.9.5"
Expand Down
6 changes: 2 additions & 4 deletions src/bot.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import "dotenv/config";
import Client from "./structures/Jukebox";

const client = new Client({ disableMentions: "everyone", messageCacheMaxSize: Infinity, messageCacheLifetime: 540, messageSweepInterval: 180 })
.setToken(process.env.DISCORD_TOKEN!);

const client = new Client({ disableMentions: "everyone", messageCacheMaxSize: Infinity, messageCacheLifetime: 540, messageSweepInterval: 180 });

process.on("unhandledRejection", (e) => {
client.log.error("UNHANDLED_REJECTION: ", e);
Expand All @@ -15,4 +13,4 @@ process.on("uncaughtException", (e) => {
process.exit(1);
});

client.build();
client.build(process.env.DISCORD_TOKEN!);
2 changes: 1 addition & 1 deletion src/commands/HelpCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default class PlayCommand extends BaseCommand {
.setThumbnail(message.client.user!.displayAvatarURL())
.setDescription(message.client.commandsHandler.commands.map(c => `\`${c.help.name}\``).join(" "))
.setTimestamp()
.setFooter(`Use ${message.client.config.prefix}help <command> to get more info on specific command!`, "https://hzmi.xyz/assets/images/390511462361202688.png"));
.setFooter(`Use ${message.client.config.prefix}help <command> to get more info on a specific command!`, "https://hzmi.xyz/assets/images/390511462361202688.png"));
} else {
message.channel.send(new MessageEmbed()
.setTitle(`Help for ${command.help.name} command`)
Expand Down
2 changes: 1 addition & 1 deletion src/commands/NowPlayingCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ export default class NowPlayingCommand extends BaseCommand {

public execute(message: IMessage): any {
if (!message.guild!.queue) return message.channel.send(new MessageEmbed().setDescription("There is nothing playing.").setColor("#FFFF00"));
return message.channel.send(new MessageEmbed().setDescription(`▶ Now playing: **${message.guild!.queue.songs.first()!.title}**`).setColor("#00FF00"));
return message.channel.send(new MessageEmbed().setDescription(`▶ Now playing: **[${message.guild!.queue.songs.first()!.title}](${message.guild!.queue.songs.first()!.url})**`).setColor("#00FF00"));
}
}
16 changes: 8 additions & 8 deletions src/commands/PlayCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default class PlayCommand extends BaseCommand {
const playlist = await this.client.youtube.getPlaylist(url);
const videos = await playlist.getVideos();
let skikppedVideos = 0;
message.channel.send(new MessageEmbed().setDescription(`Adding all videos in playlist: **${playlist.title}**, Hang on...`).setColor("#00FF00"));
message.channel.send(new MessageEmbed().setDescription(`Adding all videos in playlist: **[${playlist.title}](${playlist.url})**, Hang on...`).setColor("#00FF00"));
for (const video of Object.values(videos)) {
if ((video as any).raw.status.privacyStatus === "private") {
skikppedVideos++;
Expand All @@ -55,7 +55,7 @@ export default class PlayCommand extends BaseCommand {
new MessageEmbed()
.setDescription(`${skikppedVideos >= 2 ? `${skikppedVideos} videos` : `${skikppedVideos} video`} are skipped because it's a private video`)
.setColor("#FFFF00"));
return message.channel.send(new MessageEmbed().setDescription(`All videos in playlist: **${playlist.title}**, has been added to the queue!`).setColor("#00FF00"));
return message.channel.send(new MessageEmbed().setDescription(`All videos in playlist: **[${playlist.title}](${playlist.url})**, has been added to the queue!`).setColor("#00FF00"));
} else {
try {
// eslint-disable-next-line no-var
Expand All @@ -67,7 +67,7 @@ export default class PlayCommand extends BaseCommand {
let index = 0;
const msg = await message.channel.send(new MessageEmbed()
.setAuthor("Song Selection") // TODO: Find or create typings for simple-youtube-api or wait for v6 released
.setDescription(`${videos.map((video: any) => `**${++index} -** ${this.cleanTitle(video.title)}`).join("\n")}\n` +
.setDescription(`${videos.map((video: any) => `**${++index} -** ${this.cleanTitle(video.title)}${video.url}`).join("\n")}\n` +
"*Type `cancel` or `c` to cancel song selection*")
.setThumbnail(message.client.user!.displayAvatarURL())
.setColor("#00FF00")
Expand Down Expand Up @@ -134,11 +134,11 @@ export default class PlayCommand extends BaseCommand {
if (!this.client.config.allowDuplicate && message.guild!.queue.songs.find(s => s.id === song.id)) return message.channel.send(new MessageEmbed()
.setTitle("Already queued.")
.setColor("#FFFF00")
.setDescription(`Song: ${song.title} is already queued, and this bot configuration disallow duplicated song in queue, `
.setDescription(`Song: **[${song.title}](${song.id})** is already queued, and this bot configuration disallow duplicated song in queue, `
+ `please use \`${this.client.config.prefix}repeat\` instead`));
message.guild!.queue.songs.addSong(song);
if (playlist) return;
return message.channel.send(new MessageEmbed().setDescription(`✅ Song **${song.title}** has been added to the queue`).setColor("#00FF00"));
return message.channel.send(new MessageEmbed().setDescription(`✅ Song **[${song.title}](${song.id})** has been added to the queue`).setColor("#00FF00"));
}
return message;
}
Expand All @@ -154,7 +154,7 @@ export default class PlayCommand extends BaseCommand {
}

serverQueue.connection!.voice.setSelfDeaf(true);
const SongData = await ytdl(song.url, { cache: this.client.config.cacheYoutubeDownloads });
const SongData = await ytdl(song.url, { cache: this.client.config.cacheYoutubeDownloads, cacheMaxLength: this.client.config.cacheMaxLengthAllowed });

if (SongData.cache) this.client.log.info(`${this.client.shard ? `[Shard #${this.client.shard.ids}]` : ""} Using cache for song "${song.title}" on ${guild.name}`);

Expand All @@ -165,12 +165,12 @@ export default class PlayCommand extends BaseCommand {
}).on("start", () => {
serverQueue.playing = true;
this.client.log.info(`${this.client.shard ? `[Shard #${this.client.shard.ids}]` : ""} Song: "${song.title}" on ${guild.name} started`);
serverQueue.textChannel!.send(new MessageEmbed().setDescription(`▶ Start playing: **${song.title}**`).setColor("#00FF00"));
serverQueue.textChannel!.send(new MessageEmbed().setDescription(`▶ Start playing: **[${song.title}](${song.url})**`).setColor("#00FF00"));
}).on("finish", () => {
this.client.log.info(`${this.client.shard ? `[Shard #${this.client.shard.ids}]` : ""} Song: "${song.title}" on ${guild.name} ended`);
if (serverQueue.loopMode === 0) serverQueue.songs.deleteFirst();
else if (serverQueue.loopMode === 2) { serverQueue.songs.deleteFirst(); serverQueue.songs.addSong(song); }
serverQueue.textChannel!.send(new MessageEmbed().setDescription(`⏹ Stop playing: **${song.title}**`).setColor("#00FF00"));
serverQueue.textChannel!.send(new MessageEmbed().setDescription(`⏹ Stop playing: **[${song.title}](${song.url})**`).setColor("#00FF00"));
this.play(guild).catch(e => {
serverQueue.textChannel!.send(new MessageEmbed().setDescription(`Error while trying to play music:\n\`${e}\``).setColor("#FF0000"));
serverQueue.connection!.dispatcher.end();
Expand Down
2 changes: 1 addition & 1 deletion src/commands/QueueCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default class VolumeCommand extends BaseCommand {
else {
const embed = new MessageEmbed().setTitle("**Song Queue**").setColor("#00FF00").setThumbnail(message.client.user!.avatarURL()!);
let num = 1;
const songs = message.guild!.queue.songs.map(s => `**${num++}.** **${s.title}**`);
const songs = message.guild!.queue.songs.map(s => `**${num++}.** [**${s.title}**](${s.url})`);
if (message.guild!.queue.songs.size > 12) {
const indexes: string[] = this.chunk(songs, 12);
let index = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/commands/SkipCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default class PlayCommand extends BaseCommand {
if (message.member!.voice.channel.id !== message.guild!.queue.voiceChannel!.id) return message.channel.send(
new MessageEmbed().setDescription("❗ You need to be in the same voice channel as mine").setColor("#FF0000"));

message.channel.send(new MessageEmbed().setDescription(`⏭ Skipped ${message.guild!.queue.songs.first()!.title}`).setColor("#00FF00"));
message.channel.send(new MessageEmbed().setDescription(`⏭ Skipped [**${message.guild!.queue.songs.first()!.title}**](${message.guild!.queue.songs.first()!.url}})`).setColor("#00FF00"));
message.guild!.queue.connection!.dispatcher.end();
}
}
3 changes: 2 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export const maxVolume = Number(process.env.CONFIG_MAX_VOLUME) || 100;
export const allowDuplicate: boolean = process.env.CONFIG_ALLOW_DUPLICATE! === "yes" || false;
export const deleteQueueTimeout: number = Number(process.env.CONFIG_DELETE_QUEUE_TIMEOUT) * 1000 || 180 * 1000;
export const cacheYoutubeDownloads: boolean = process.env.CONFIG_CACHE_YOUTUBE_DOWNLOADS! === "yes" || false;
export const cacheMaxLengthAllowed = Number(process.env.CONFIG_CACHE_MAX_LENGTH) || 5400;

export default { name, prefix, owners, totalShards, defaultVolume, maxVolume, allowDuplicate, deleteQueueTimeout, cacheYoutubeDownloads };
export default { name, prefix, owners, totalShards, defaultVolume, maxVolume, allowDuplicate, deleteQueueTimeout, cacheYoutubeDownloads, cacheMaxLengthAllowed };
14 changes: 2 additions & 12 deletions src/structures/Jukebox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,22 @@ import YouTube from "simple-youtube-api";
import "./Guild";

export default class Jukebox extends Client {
private _token = "n/a";
readonly config = config;
readonly log = new LogWrapper(config.name).logger;
readonly youtube = new YouTube(process.env.YT_API_KEY!, { cache: false, fetchAll: true });
readonly commandsHandler = new CommandsHandler(this, resolve(__dirname, "..", "commands"));
readonly eventsLaoder = new ClientEventsLoader(this, resolve(__dirname, "..", "events"));
constructor(opt: ClientOptions) { super(opt); }

public async build(): Promise<Jukebox> {
public async build(token: string): Promise<Jukebox> {
// NOTE: Will be removed when caching is not a experimental feature anymore
if (this.config.cacheYoutubeDownloads) this.log.warn(this.constructor.name, { stack: "cacheYoutubeDownloads is still a experimental feature"});
this.on("ready", () => this.commandsHandler.load());
this.eventsLaoder.load();
await this.login(this.getToken());
await this.login(token);
return this;
}

public setToken(token: string): Jukebox {
this._token = token;
return this;
}

public getToken(): string {
return this._token;
}

public async getGuildsCount(): Promise<number> {
if (!this.shard) return this.guilds.cache.size;
const size = await this.shard.broadcastEval("this.guilds.cache.size");
Expand Down
39 changes: 24 additions & 15 deletions src/utils/YoutubeDownload.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,48 @@
import { getInfo, downloadFromInfo, videoInfo, downloadOptions, videoFormat } from "ytdl-core";
import { Readable, PassThrough } from "stream";
import { resolve as resolvePath } from "path";
import { createReadStream, createWriteStream, existsSync } from "fs";
import { createReadStream, createWriteStream, existsSync, appendFileSync } from "fs";

// Inspired by ytdl-core-discord (https://github.com/amishshah/ytdl-core-discord) // 1048576 * 1 = 1MB
export default function playSong(YoutubeLink: string, options: IdownloadOptions = { filter: "audio", quality: "highestaudio", highWaterMark: 1048576 * 32 }): Promise<ISongData> {
return new Promise((resolve, reject) => {
getInfo(YoutubeLink, (err, info) => {
if (err) return reject(err);
const canDemux: boolean = info.formats.find(filter)! && Number(info.length_seconds) != 0;
getInfo(YoutubeLink).then((info) => {
const canDemux: boolean = info.formats.find(filter)! && Number(info.videoDetails.lengthSeconds) != 0;
options = canDemux ? { ...options, filter } : { ...options };
if (options.cache && info.formats.find(f => !f.live)) {
const path = resolvePath(process.cwd(), "cache", `${info.video_id}.webm`);
if (existsSync(resolvePath(path))) return resolve({ canDemux, info, stream: createReadStream(path), cache: true });
const data = downloadFromInfo(info, options);
const stream = new PassThrough();
const cache = createWriteStream(path);
data.on("data", (chunk) => { stream.write(chunk); cache.write(chunk); } );
data.on("end", () => { stream.end(); cache.end(); });
return resolve({ canDemux, info, stream, cache: false });
if (options.cache && info.formats.find(f => !f.live) && !(Number(info.videoDetails.lengthSeconds) >= options.cacheMaxLength!)) {
const path = resolvePath(process.cwd(), "cache");
const filePath = resolvePath(path, `${info.videoDetails.videoId}.webm`);
const finishMarkerPath = `${filePath}.jukeboxCacheFinish.marker`;
if (existsSync(resolvePath(filePath))) {
if (existsSync(resolvePath(finishMarkerPath))) return resolve({ canDemux, info, stream: createReadStream(filePath), cache: true });
return cache(info, options, canDemux, filePath, finishMarkerPath, resolve);
}
return cache(info, options, canDemux, filePath, finishMarkerPath, resolve);
}
return resolve({ canDemux, info, stream: downloadFromInfo(info, options), cache: false });
});
}).catch(reject);
});
}

function filter(f: videoFormat): boolean {
return f.codecs === "opus" && f.container === "webm" && Number(f.audioSampleRate) === 48000;
}

function cache(info: videoInfo, options: IdownloadOptions, canDemux: boolean, path: string, finishMarkerPath: string, resolve: any,): any {
const data = downloadFromInfo(info, options);
const stream = new PassThrough();
const cache = createWriteStream(path)
.on("close", () => appendFileSync(finishMarkerPath, ""));
data.on("data", (chunk) => { stream.write(chunk); cache.write(chunk); } );
data.on("end", () => { stream.end(); cache.end(); });
return resolve({ canDemux, info, stream, cache: false });
}

interface ISongData {
canDemux: boolean;
info: videoInfo;
stream: Readable;
cache: boolean;
}

interface IdownloadOptions extends downloadOptions { cache?: boolean }
interface IdownloadOptions extends downloadOptions { cache?: boolean; cacheMaxLength?: number }
Loading

0 comments on commit 06893e6

Please sign in to comment.