2023-04-10 14:48:47 +02:00
import { SlashCommandBuilder , EmbedBuilder , ActionRowBuilder , StringSelectMenuBuilder } from 'discord.js' ;
2022-06-17 01:25:05 +02:00
import { exec } from 'node:child_process' ;
import fs from 'node:fs' ;
import os from 'node:os' ;
2022-06-17 07:31:58 +02:00
import utils from '../../utils/videos.js' ;
2022-06-17 01:25:05 +02:00
2022-08-28 17:03:15 +02:00
let client ;
let cleanUp ;
2023-04-11 20:31:25 +02:00
let maxFileSize ;
2022-08-28 17:03:15 +02:00
2022-06-17 01:25:05 +02:00
export default {
2022-06-16 09:18:39 +02:00
data : new SlashCommandBuilder ( )
. setName ( 'download' )
. setDescription ( 'Download a video.' )
. addStringOption ( option =>
option . setName ( 'url' )
2022-06-20 08:34:18 +02:00
. setDescription ( 'url of the video you want to download.' )
2022-06-16 09:18:39 +02:00
. setRequired ( true ) )
. addBooleanOption ( option =>
2022-08-22 20:23:27 +02:00
option . setName ( 'format' )
2022-06-16 09:18:39 +02:00
. setDescription ( 'Choose the quality of the video.' )
2022-08-22 20:23:27 +02:00
. setRequired ( false ) )
. addBooleanOption ( option =>
option . setName ( 'compress' )
. setDescription ( 'Compress the video?' )
2022-06-16 09:18:39 +02:00
. setRequired ( false ) ) ,
2022-08-28 17:03:15 +02:00
category : 'utility' ,
2022-08-30 23:29:57 +02:00
alias : [ 'dl' ] ,
2022-08-28 17:03:15 +02:00
async execute ( interaction , args , c ) {
client = c ;
2022-09-01 01:43:59 +02:00
const url = args . url ;
const format = args . format ;
2023-04-11 20:31:25 +02:00
maxFileSize = await utils . getMaxFileSize ( interaction . guild ) ;
2022-09-01 01:43:59 +02:00
interaction . doCompress = args . compress ;
2023-04-11 20:31:25 +02:00
2022-08-28 17:03:15 +02:00
if ( interaction . cleanUp ) {
cleanUp = interaction . cleanUp ;
}
2022-06-16 09:18:39 +02:00
await interaction . deferReply ( { ephemeral : false } ) ;
2022-08-28 17:03:15 +02:00
if ( interaction . isMessage ) {
interaction . delete ( ) ;
}
2022-06-16 09:18:39 +02:00
2022-06-20 08:34:18 +02:00
if ( ! await utils . stringIsAValidurl ( url ) ) {
console . error ( ` Not a url!!! ${ url } ` ) ;
return interaction . editReply ( { content : '❌ This does not look like a valid url!' , ephemeral : true } ) ;
2022-06-16 23:11:01 +02:00
}
2022-08-28 17:03:15 +02:00
if ( format ) {
2022-06-16 09:18:39 +02:00
let qualitys = await new Promise ( ( resolve , reject ) => {
2022-07-06 18:52:44 +02:00
exec ( ` ./bin/yt-dlp " ${ url } " --print "%()j" ` , ( err , stdout , stderr ) => {
2022-06-16 09:18:39 +02:00
if ( err ) {
reject ( stderr ) ;
}
if ( stderr ) {
console . error ( stderr ) ;
}
resolve ( stdout ) ;
} ) ;
} ) ;
2023-04-20 19:54:21 +02:00
qualitys = JSON . parse ( qualitys ) ;
2022-06-16 09:18:39 +02:00
const options = [ ] ;
qualitys . formats . forEach ( f => {
2022-09-10 09:35:52 +02:00
if ( f . format . includes ( 'storyboard' ) ) return ;
2022-06-16 09:18:39 +02:00
options . push ( {
2022-07-06 18:52:44 +02:00
label : f . resolution ? f . resolution : 'Unknown format' ,
2022-06-16 14:28:49 +02:00
description : ` ${ f . format } V: ${ f . vcodec } A: ${ f . acodec } ` ,
2022-06-16 09:18:39 +02:00
value : f . format _id ,
} ) ;
} ) ;
2022-07-06 18:52:44 +02:00
if ( options . length < 2 ) {
await interaction . deleteReply ( ) ;
return interaction . followUp ( { content : '❌ There is no other quality option for this video!' , ephemeral : true } ) ;
}
2022-06-16 09:18:39 +02:00
if ( options . length > 25 ) {
// Reverse so the higher quality formats are first
options . reverse ( ) ;
while ( options . length > 25 ) {
// Remove the lower quality formats
options . pop ( ) ;
}
// Reverse again so the lower quality appears first
options . reverse ( ) ;
}
2022-08-28 17:03:15 +02:00
const row = new ActionRowBuilder ( )
2022-06-16 09:18:39 +02:00
. addComponents (
2023-04-10 14:48:47 +02:00
new StringSelectMenuBuilder ( )
. setCustomId ( ` downloadQuality ${ interaction . user . id } ${ interaction . id } ` )
2022-06-16 09:18:39 +02:00
. setPlaceholder ( 'Nothing selected' )
2022-06-16 14:36:38 +02:00
. setMinValues ( 1 )
. setMaxValues ( 2 )
2022-06-16 09:18:39 +02:00
. addOptions ( options ) ,
) ;
await interaction . deleteReply ( ) ;
await interaction . followUp ( { content : 'Which quality do you want?' , ephemeral : true , components : [ row ] } ) ;
2022-09-10 09:35:52 +02:00
client . on ( 'interactionCreate' , async ( interactionMenu ) => {
if ( interaction . user !== interactionMenu . user ) return ;
2022-06-16 09:18:39 +02:00
if ( ! interactionMenu . isSelectMenu ( ) ) return ;
2023-04-10 14:48:47 +02:00
if ( interactionMenu . customId === ` downloadQuality ${ interaction . user . id } ${ interaction . id } ` ) {
2022-06-16 09:18:39 +02:00
await interactionMenu . deferReply ( { ephemeral : false } ) ;
2023-04-20 19:54:21 +02:00
const aproxFileSize = await utils . getVideoSize ( url , interactionMenu . values [ 0 ] ) ;
if ( aproxFileSize > 100 && ! args . compress ) {
await interaction . followUp ( 'Uh oh! The video you tried to download is larger than 100 mb! Try again with compression.' , { ephemeral : true } ) ;
}
else if ( aproxFileSize > 500 ) {
await interaction . followUp ( 'Uh oh! The video you tried to download is larger than 500 mb!' , { ephemeral : true } ) ;
}
2022-08-22 20:23:27 +02:00
download ( url , interactionMenu , interaction ) ;
2022-06-16 09:18:39 +02:00
}
} ) ;
return ;
}
2023-04-20 19:54:21 +02:00
const aproxFileSize = await utils . getVideoSize ( url ) ;
if ( aproxFileSize > 100 && ! args . compress ) {
await interaction . followUp ( 'Uh oh! The video you tried to download is larger than 100 mb! Try again with compression.' , { ephemeral : true } ) ;
}
else if ( aproxFileSize > 500 ) {
await interaction . followUp ( 'Uh oh! The video you tried to download is larger than 500 mb!' , { ephemeral : true } ) ;
}
2022-06-16 09:18:39 +02:00
download ( url , interaction ) ;
} ,
} ;
2022-08-22 20:23:27 +02:00
async function download ( url , interaction , originalInteraction ) {
2022-06-16 09:18:39 +02:00
let format = 'bestvideo*+bestaudio/best' ;
2023-04-11 20:31:25 +02:00
2022-08-28 17:03:15 +02:00
const Embed = new EmbedBuilder ( )
2022-09-08 16:56:15 +02:00
. setColor ( interaction . member ? interaction . member . displayHexColor : 'Navy' )
2023-09-13 20:56:06 +02:00
. setAuthor ( { name : ` Downloaded by ${ interaction . user . username } ` , iconURL : interaction . user . displayAvatarURL ( ) , url : url } )
. setFooter ( { text : ` You can get the original video by clicking on the "Downloaded by ${ interaction . user . username } " message! ` } ) ;
2022-06-16 09:18:39 +02:00
2023-04-11 20:31:25 +02:00
2022-09-14 11:31:19 +02:00
if ( interaction . customId === ` downloadQuality ${ interaction . user . id } ` ) {
2022-06-16 09:18:39 +02:00
format = interaction . values [ 0 ] ;
2022-06-16 14:36:38 +02:00
if ( interaction . values [ 1 ] ) format += '+' + interaction . values [ 1 ] ;
2022-06-16 09:18:39 +02:00
}
2022-06-17 01:25:05 +02:00
utils . downloadVideo ( url , interaction . id , format )
2022-06-16 09:18:39 +02:00
. then ( async ( ) => {
const file = fs . readdirSync ( os . tmpdir ( ) ) . filter ( fn => fn . startsWith ( interaction . id ) ) ;
2023-03-20 03:57:52 +01:00
let output = ` ${ os . tmpdir ( ) } / ${ file } ` ;
2022-06-16 09:18:39 +02:00
2022-08-22 20:23:27 +02:00
const compressInteraction = originalInteraction ? originalInteraction : interaction ;
2022-08-28 17:03:15 +02:00
if ( compressInteraction . doCompress ) {
2023-03-20 03:57:52 +01:00
const presets = [ 'Social 8 MB 3 Minutes 360p30' , 'Social 50 MB 10 Minutes 480p30' , 'Social 50 MB 5 Minutes 720p30' , 'Social 100 MB 5 Minutes 1080p30' ] ;
2022-08-22 20:23:27 +02:00
const options = [ ] ;
presets . forEach ( p => {
options . push ( {
label : p ,
value : p ,
} ) ;
} ) ;
2022-08-28 17:03:15 +02:00
const row = new ActionRowBuilder ( )
2022-08-22 20:23:27 +02:00
. addComponents (
2023-04-10 14:48:47 +02:00
new StringSelectMenuBuilder ( )
. setCustomId ( ` preset ${ interaction . user . id } ${ interaction . id } ` )
2022-08-22 20:23:27 +02:00
. setPlaceholder ( 'Nothing selected' )
. addOptions ( options ) ,
) ;
await interaction . deleteReply ( ) ;
await interaction . followUp ( { content : 'Which compression preset do you want?' , ephemeral : true , components : [ row ] } ) ;
2022-09-10 09:35:52 +02:00
client . on ( 'interactionCreate' , async ( interactionMenu ) => {
if ( interaction . user !== interactionMenu . user ) return ;
2022-08-22 20:23:27 +02:00
if ( ! interactionMenu . isSelectMenu ( ) ) return ;
2023-04-10 14:48:47 +02:00
if ( interactionMenu . customId === ` preset ${ interaction . user . id } ${ interaction . id } ` ) {
2022-08-22 20:23:27 +02:00
await interactionMenu . deferReply ( { ephemeral : false } ) ;
compress ( file , interactionMenu , Embed ) ;
2022-09-10 10:53:21 +02:00
if ( interaction . isMessage ) cleanUp ( ) ;
2022-08-22 20:23:27 +02:00
}
} ) ;
return ;
}
2022-06-16 09:18:39 +02:00
2023-03-20 03:57:52 +01:00
// If the video format is not one compatible with Discord, reencode it.
const bannedFormats = [ 'hevc' ] ;
const codec = await utils . getVideoCodec ( output ) ;
if ( bannedFormats . includes ( codec ) ) {
const oldOutput = output ;
output = ` ${ os . tmpdir ( ) } /264 ${ file } ` ;
await utils . ffmpeg ( ` -i ${ oldOutput } -vcodec libx264 -acodec aac ${ output } ` ) ;
}
2023-04-11 20:31:25 +02:00
const fileStat = fs . statSync ( output ) ;
const fileSize = fileStat . size / 1000000.0 ;
Embed . setAuthor ( { name : ` ${ Embed . data . author . name } ( ${ fileSize . toFixed ( 2 ) } MB) ` , iconURL : Embed . data . author . icon _url , url : Embed . data . author . url } ) ;
2022-06-16 09:18:39 +02:00
if ( fileSize > 100 ) {
await interaction . deleteReply ( ) ;
await interaction . followUp ( 'Uh oh! The video you tried to download is too big!' , { ephemeral : true } ) ;
}
2023-04-11 20:31:25 +02:00
else if ( fileSize > maxFileSize ) {
2022-06-20 08:34:18 +02:00
const fileurl = await utils . upload ( output )
2022-06-16 09:18:39 +02:00
. catch ( err => {
console . error ( err ) ;
} ) ;
2023-04-11 20:31:25 +02:00
await interaction . editReply ( { content : ` File was bigger than ${ maxFileSize } mb. It has been uploaded to an external site. ` , embeds : [ Embed ] , ephemeral : false } ) ;
2022-06-20 08:34:18 +02:00
await interaction . followUp ( { content : fileurl , ephemeral : false } ) ;
2022-06-16 09:18:39 +02:00
}
else {
await interaction . editReply ( { embeds : [ Embed ] , files : [ output ] , ephemeral : false } ) ;
}
2022-09-10 10:53:21 +02:00
if ( interaction . isMessage ) cleanUp ( ) ;
2022-06-16 09:18:39 +02:00
} )
. catch ( async err => {
console . error ( err ) ;
await interaction . deleteReply ( ) ;
await interaction . followUp ( { content : 'Uh oh! An error has occured!' , ephemeral : true } ) ;
} ) ;
2022-06-16 10:25:45 +02:00
return ;
2022-06-16 09:18:39 +02:00
}
2022-08-22 20:23:27 +02:00
async function compress ( input , interaction , embed ) {
const output = ` compressed ${ input } .mp4 ` ;
2023-03-20 03:57:52 +01:00
// Delete the file as it apparently don't overwrite?
2023-04-11 20:31:25 +02:00
if ( fs . existsSync ( output ) ) {
fs . rmSync ( output ) ;
}
2022-08-22 20:23:27 +02:00
utils . compressVideo ( ` ${ os . tmpdir ( ) } / ${ input } ` , output , interaction . values [ 0 ] )
. then ( async ( ) => {
const fileStat = fs . statSync ( ` ${ os . tmpdir ( ) } / ${ output } ` ) ;
const fileSize = fileStat . size / 1000000.0 ;
2023-04-11 20:31:25 +02:00
embed . setAuthor ( { name : ` ${ embed . data . author . name } ( ${ fileSize . toFixed ( 2 ) } MB) ` , iconURL : embed . data . author . icon _url , url : embed . data . author . url } ) ;
if ( fileSize > maxFileSize ) {
await interaction . editReply ( { content : ` File was bigger than ${ maxFileSize } mb. It has been uploaded to an external site. ` , ephemeral : false } ) ;
2022-08-22 20:23:27 +02:00
}
else {
await interaction . editReply ( { embeds : [ embed ] , files : [ ` ${ os . tmpdir ( ) } / ${ output } ` ] , ephemeral : false } ) ;
}
} ) ;
}