2023-07-31 23:07:33 -04:00
import asyncio
import re
2023-07-31 21:35:15 -04:00
import discord
2023-08-01 00:03:00 -04:00
import os
2023-08-01 22:09:10 -04:00
import sqlite3
2023-07-31 23:07:33 -04:00
from yt_dlp import YoutubeDL
2023-07-31 21:35:15 -04:00
from redbot . core import commands , checks , Config , data_manager
class MusicDownloader ( commands . Cog ) :
def __init__ ( self , bot ) :
self . bot = bot
self . config = Config . get_conf ( self , identifier = 475728338 )
self . config . register_global (
2023-08-01 00:03:00 -04:00
save_directory = str ( data_manager . cog_data_path ( ) ) + f " { os . sep } MusicDownloader "
2023-07-31 21:35:15 -04:00
)
2023-08-01 22:09:10 -04:00
class UserBlacklisted ( Exception ) :
def __init__ ( self , message = " The user is blacklisted from using this command. " ) :
super ( ) . __init__ ( message )
def create_table ( self ) :
data_path = str ( data_manager . cog_data_path ( ) ) + f " { os . sep } MusicDownloader { os . sep } Data "
if not os . path . isdir ( data_path ) :
os . makedirs ( data_path )
db_path = os . path . join ( data_path , " database.db " )
if not os . path . isfile ( db_path ) :
con = sqlite3 . connect ( data_path )
cur = con . cursor ( )
cur . execute ( '''
CREATE TABLE IF NOT EXISTS blacklist_log (
user_id INTEGER PRIMARY KEY ,
reason STRING DEFAULT NULL
) ;
''' )
con . commit ( )
con . close ( )
def blacklist_checker ( self , user_id ) :
data_path = str ( data_manager . cog_data_path ( ) ) + f " { os . sep } MusicDownloader { os . sep } Data "
db_path = os . path . join ( data_path , " database.db " )
con = sqlite3 . connect ( db_path )
cur = con . cursor ( )
cur . execute ( f " SELECT user_id, reason FROM blacklist_log WHERE user_id = ?; " , ( user_id , ) )
result = cur . fetchone ( )
con . close ( )
if result :
user_id , reason = result
raise self . UserBlacklisted ( reason )
2023-07-31 21:35:15 -04:00
@commands.command ( )
2023-08-01 15:04:07 -04:00
@checks.is_owner ( )
2023-08-01 15:08:51 -04:00
async def change_data_path ( self , ctx : commands . Context , * , data_path : str = None ) :
2023-08-01 17:27:50 -04:00
""" This command changes the data path the `[p]download` command outputs to. """
2023-07-31 21:35:15 -04:00
old_path = await self . config . save_directory ( )
2023-08-01 15:04:36 -04:00
if not data_path :
await ctx . send ( f " The current data path is ` { old_path } `. " )
2023-08-01 15:07:54 -04:00
return
2023-08-01 00:03:00 -04:00
if os . path . isdir ( data_path ) :
2023-07-31 21:35:15 -04:00
await self . config . save_directory . set ( data_path )
embed = discord . Embed ( color = await self . bot . get_embed_color ( None ) , description = f " The save directory has been set to ` { data_path } `. \n It was previously set to ` { old_path } `. " )
await ctx . send ( embed = embed )
2023-08-01 00:03:00 -04:00
elif os . path . isfile ( data_path ) :
2023-07-31 21:35:15 -04:00
await ctx . send ( " The path you ' ve provided leads to a file, not a directory! " )
2023-08-01 00:03:00 -04:00
elif os . path . exists ( data_path ) is False :
2023-07-31 21:35:15 -04:00
await ctx . send ( " The path you ' ve provided doesn ' t exist! " )
2023-07-31 23:07:33 -04:00
@commands.command ( aliases = [ " dl " ] )
2023-08-01 13:28:11 -04:00
async def download ( self , ctx : commands . Context , url : str , delete : bool = False , subfolder : str = None ) :
2023-08-01 17:27:50 -04:00
""" This command downloads a YouTube Video as an `m4a` and uploads the file to discord.
If you ' re considered a bot owner, you will be able to save downloaded files to the data path set in the `[p]change_data_path` command.
2023-08-01 18:01:28 -04:00
* * Arguments * *
2023-08-01 17:27:50 -04:00
- The ` url ` argument is just the url of the YouTube Video you ' re downloading.
- The ` delete ` argument will automatically delete the audio file after uploading it to Discord . If set to False , it will only save the file if you are a bot owner .
- The ` subfolder ` argument only does anything if ` delete ` is set to False , but it allows you to save to a subfolder in the data path you ' ve set previously without having to change said data path manually. " " "
2023-08-01 22:09:10 -04:00
try :
self . blacklist_checker ( ctx . author . id )
except self . UserBlacklisted as e :
await ctx . send ( f " You are blacklisted from running this command! \n Reason: ` { e } ` " )
return
2023-07-31 23:07:33 -04:00
def youtube_download ( self , url : str , path : str , message : discord . Message ) :
""" This function does the actual downloading of the YouTube Video. """
class Logger :
def debug ( self , msg ) :
if msg . startswith ( ' [debug] ' ) :
pass
else :
self . info ( msg )
def info ( self , msg ) :
pass
def warning ( self , msg ) :
pass
def error ( self , msg ) :
print ( msg )
message . edit ( msg )
ydl_opts = {
' logger ' : Logger ( ) ,
' format ' : ' m4a/bestaudio/best ' ,
' postprocessors ' : [ { ' key ' : ' FFmpegExtractAudio ' , ' preferredcodec ' : ' m4a ' , } ] ,
2023-08-01 12:27:22 -04:00
' paths ' : { ' home ' : path } ,
' verbose ' : True
2023-07-31 23:07:33 -04:00
}
with YoutubeDL ( ydl_opts ) as ydl :
2023-08-01 13:24:15 -04:00
info = ydl . extract_info ( url = url , download = False )
2023-08-01 00:03:48 -04:00
title = info [ ' title ' ]
2023-08-01 00:15:26 -04:00
id = info [ ' id ' ]
2023-08-01 12:38:45 -04:00
filename = title + f ' [ { id } ].m4a '
2023-08-01 13:22:47 -04:00
full_filename = os . path . join ( data_path , filename )
if os . path . isfile ( full_filename ) :
previously_existed = True
else :
with YoutubeDL ( ydl_opts ) as ydl :
error_code = ydl . download ( url )
previously_existed = False
return filename , previously_existed
2023-07-31 23:07:33 -04:00
data_path = await self . config . save_directory ( )
2023-08-01 13:26:41 -04:00
if subfolder and await self . bot . is_owner ( ctx . author ) :
2023-08-01 00:03:00 -04:00
data_path = os . path . join ( data_path , subfolder )
2023-07-31 23:07:33 -04:00
illegal_chars = r ' <>: " / \ |?* '
if any ( char in illegal_chars for char in subfolder ) :
pattern = " [ " + re . escape ( illegal_chars ) + " ] "
modified_subfolder = re . sub ( pattern , r ' __** \ g<0>**__ ' , subfolder )
await ctx . send ( f " Your subfolder contains illegal characters: ` { modified_subfolder } ` " )
return
2023-08-01 00:03:00 -04:00
elif os . path . isfile ( data_path ) :
2023-07-31 23:07:33 -04:00
await ctx . send ( " Your ' subfolder ' is a file, not a directory! " )
return
2023-08-01 00:03:00 -04:00
elif os . path . exists ( data_path ) is False :
2023-07-31 23:07:33 -04:00
message = await ctx . send ( " Your subfolder does not exist yet, would you like to continue? It will be automatically created. " )
def check ( message ) :
return message . author == ctx . author and message . content . lower ( ) in [ ' yes ' , ' ye ' , ' y ' ]
try :
await self . bot . wait_for ( ' message ' , check = check , timeout = 60 ) # Timeout after 60 seconds
except asyncio . TimeoutError :
await message . edit ( " You took too long to respond. " )
else :
await message . edit ( " Confirmed! " )
try :
2023-08-01 00:03:00 -04:00
os . makedirs ( data_path )
2023-07-31 23:07:33 -04:00
except OSError as e :
await message . edit ( f " Encountered an error attempting to create the subfolder! \n ` { e } ` " )
2023-07-31 23:24:28 -04:00
msg = message . edit
2023-07-31 23:22:59 -04:00
else :
2023-07-31 23:24:28 -04:00
msg = ctx . send
message = await msg ( " YouTube Downloader started! " )
2023-08-01 13:22:47 -04:00
ytdlp_output = youtube_download ( self , url , data_path , message )
2023-08-01 13:25:07 -04:00
full_filename = os . path . join ( data_path , ytdlp_output [ 0 ] )
2023-08-01 12:36:07 -04:00
while not os . path . isfile ( full_filename ) :
2023-08-01 12:40:08 -04:00
await asyncio . sleep ( 0.5 )
2023-08-01 00:15:26 -04:00
if os . path . isfile ( full_filename ) :
with open ( full_filename , ' rb ' ) as file :
2023-08-01 17:31:21 -04:00
try :
complete_message = await ctx . send ( content = " YouTube Downloader completed! \n Downloaded file: " , file = discord . File ( file , ytdlp_output [ 0 ] ) )
except ValueError :
complete_message = await ctx . send ( content = " YouTube Downloader completed, but the audio file was too large to upload. " )
return
2023-08-01 13:09:09 -04:00
file . close ( )
2023-08-01 13:26:41 -04:00
if delete is True or await self . bot . is_owner ( ctx . author ) is False :
2023-08-01 13:25:07 -04:00
if ytdlp_output [ 1 ] is False :
2023-08-01 13:22:47 -04:00
os . remove ( full_filename )
2023-08-01 13:23:45 -04:00
await complete_message . edit ( content = " YouTube Downloader completed! \n File has been deleted from Galaxy. \n Downloaded file: " )
2023-08-01 22:09:10 -04:00
@commands.group ( name = " dl-blacklist " , invoke_without_command = True )
async def blacklist ( self , ctx , user : discord . User = None ) :
""" Group command for managing the blacklist. """
data_path = str ( data_manager . cog_data_path ( ) ) + f " { os . sep } MusicDownloader { os . sep } Data "
db_path = os . path . join ( data_path , " database.db " )
if user is None :
await ctx . send ( " Please provide a user to check in the blacklist. " )
return
con = sqlite3 . connect ( db_path )
cur = con . cursor ( )
cur . execute ( " SELECT user_id, reason FROM blacklist_log WHERE user_id = ?; " , ( user . id , ) )
result = cur . fetchone ( )
if result :
user_id , reason = result
await ctx . send ( f " { user . mention } is in the blacklist for the following reason: { reason } " )
else :
await ctx . send ( f " { user . mention } is not in the blacklist. " )
con . close ( )
@blacklist.command ( name = ' add ' )
async def blacklist_add ( self , ctx , user : discord . User , * , reason : str = None ) :
data_path = str ( data_manager . cog_data_path ( ) ) + f " { os . sep } MusicDownloader { os . sep } Data "
db_path = os . path . join ( data_path , " database.db " )
con = sqlite3 . connect ( db_path )
cur = con . cursor ( )
cur . execute ( " SELECT user_id FROM blacklist_log WHERE user_id = ?; " , ( user . id , ) )
result = cur . fetchone ( )
if result :
await ctx . send ( " User is already in the blacklist. " )
con . close ( )
return
cur . execute ( " INSERT INTO blacklist_log (user_id, reason) VALUES (?, ?); " , ( user . id , reason ) )
con . commit ( )
con . close ( )
await ctx . send ( f " { user . mention } has been added to the blacklist with the reason: { reason or ' No reason provided. ' } " )
@blacklist_add.command ( name = ' remove ' )
async def blacklist_remove ( self , ctx , user : discord . User ) :
data_path = str ( data_manager . cog_data_path ( ) ) + f " { os . sep } MusicDownloader { os . sep } Data "
db_path = os . path . join ( data_path , " database.db " )
con = sqlite3 . connect ( db_path )
cur = con . cursor ( )
cur . execute ( " SELECT user_id FROM blacklist_log WHERE user_id = ?; " , ( user . id , ) )
result = cur . fetchone ( )
if not result :
await ctx . send ( " User is not in the blacklist. " )
con . close ( )
return
cur . execute ( " DELETE FROM blacklist_log WHERE user_id = ?; " , ( user . id , ) )
con . commit ( )
con . close ( )
await ctx . send ( f " { user . mention } has been removed from the blacklist. " )