#!/bin/bash

VERSION="1.9.39"

HERE=$(dirname "$0")
SCRIPT=$(readlink -f "$0")
HELPERS="$HERE/helpers.py"
BASH_MIN_VER="3"
PYTHON_MIN_VER="2.6"

MEGA_API_URL="https://g.api.mega.co.nz"
OPENSSL_AES_CTR_128_DEC="openssl enc -d -aes-128-ctr"
OPENSSL_AES_CBC_128_DEC="openssl enc -a -A -d -aes-128-cbc"
OPENSSL_AES_CBC_256_DEC="openssl enc -a -A -d -aes-256-cbc"
OPENSSL_MD5="openssl md5"

if [ ! -d ".megadown" ]; then
	mkdir ".megadown"
fi

# 1:message_error
function showError {
	echo -e "\n$1\n" 1>&2
	exit 1
}

function showHelp {
	echo -e "\nmegadown $VERSION - https://github.com/tonikelope/megadown"
	echo -e "\ncli downloader for mega.nz and megacrypter"
	echo -e "\nSingle url mode:           megadown <'URL'> [OPTION]...\n"
	echo -e "\tOptions:"
	echo -e "\t-o,\t--output FILE_NAME    Store file with this name."
	echo -e "\t-s,\t--speed SPEED         Download speed limit (integer values: 500B, K, 2M)."
	echo -e "\t-p,\t--password PASSWORD   Password for MegaCrypter links."
	echo -e "\t-q,\t--quiet               Quiet mode."
	echo -e "\t-m,\t--metadata            Prints file metadata and exits. (File name is base64 encoded)."
	echo -e "\n\nMulti url mode:          megadown <-l URL_LIST_FILE> [OPTION]...\n"
	echo -e "\tOptions:"
	echo -e "\t-s,\t--speed SPEED         Download speed limit (integer values: 500B, 500K, 2M)."
	echo -e "\t-p,\t--password PASSWORD   Password for MegaCrypter links (same for every link in a list)."
	echo -e "\t-q,\t--quiet               Quiet mode."
	echo -e "\t-m,\t--metadata            Prints file metadata and exits. (File name is base64 encoded)."
	echo -e "\tFile line format:          URL [optional_file_name]\n"
}

function check_deps {

	local dep_error=0

	if [ -n "$(command -v curl 2>&1)" ]; then
		DL_COM="curl --fail -s"
		DL_COM_PDATA="--data"
	elif [ -n "$(command -v wget 2>&1)" ]; then
		DL_COM="wget -q -O -"
		DL_COM_PDATA="--post-data"
	else
		echo "wget OR curl is required and it's not installed"
		dep_error=1
	fi

	for i in openssl python pv; do

		if [ -z "$(command -v "$i" 2>&1)" ]; then

			echo "[$i] is required and it's not installed"
			dep_error=1

		else

			case "$i" in

				openssl)

					openssl_sup=$(openssl enc -ciphers 2>&1)

					for i in "aes-128-ctr" "aes-128-cbc" "aes-256-cbc"; do

						if [ -z "$(echo -n "$openssl_sup" | grep -o "$i" | head -n1)" ]; then

							echo "Your openssl binary does not support ${i}"
							dep_error=1

						fi

					done
				;;

				python)

					if [[ "$(python --version 2>&1 | grep -o -E '[0-9]\.[0-9]')" < "$PYTHON_MIN_VER" ]]; then

						echo "python >= ${PYTHON_MIN_VER} is required"
						dep_error=1

					fi
				;;
			esac
		fi

	done

	if [[ "$(echo -n "$BASH_VERSION" | grep -o -E "[0-9]+" | head -n1)" < "$BASH_MIN_VER" ]]; then
		echo "bash >= ${BASH_MIN_VER} is required"
		dep_error=1
	fi

	if [ $dep_error -ne 0 ]; then
		showError "ERROR: there are dependencies not present!"
	fi
}

# 1:b64_encoded_string
function urlb64_to_b64 {
	local b64=$(echo -n "$1" | tr '\-_' '+/' | tr -d ',')
	local pad=$(((4-${#1}%4)%4))

	for i in $(seq 1 $pad); do
		b64="${b64}="
	done

	echo -n "$b64"
}

# 1:mega://enc link
function decrypt_md_link {

	local data=$(regex_imatch "^.*?mega:\/\/enc[0-9]*?\?([a-z0-9_,-]+).*?$" "$1" 1)

	local iv="79F10A01844A0B27FF5B2D4E0ED3163E"

	if [ $(echo -n "$1" | grep 'mega://enc?') ]; then

		key="6B316F36416C2D316B7A3F217A30357958585858585858585858585858585858"

	elif [ $(echo -n "$1" | grep 'mega://enc2?') ];then

		key="ED1F4C200B35139806B260563B3D3876F011B4750F3A1A4A5EFD0BBE67554B44"
	fi

	echo -n "https://mega.nz/#"$(echo -n "$(urlb64_to_b64 "$data")" | $OPENSSL_AES_CBC_256_DEC -K "$key" -iv "$iv")
}

# 1:hex_raw_key
function hrk2hk {
	declare -A hk
	hk[0]=$(( 0x${1:0:16} ^ 0x${1:32:16} ))
	hk[1]=$(( 0x${1:16:16} ^ 0x${1:48:16} ))

	printf "%016x" ${hk[*]}
}

# 1:link
function get_mc_link_info {

	local MC_API_URL=$(echo -n "$1" | grep -i -E -o 'https?://[^/]+')"/api"

	local download_exit_code=1

	local info_link=$($DL_COM --header 'Content-Type: application/json' $DL_COM_PDATA "{\"m\":\"info\", \"link\":\"$1\"}" "$MC_API_URL")

	download_exit_code=$?

	if [ "$download_exit_code" -ne 0 ]; then
		echo -e "ERROR: Oooops, something went bad. EXIT CODE (${download_exit_code})"
		return 1
	fi

	if [ $(echo $info_link | grep '"error"') ]; then
		local error_code=$($HELPERS json_param "$info_link" error)
		echo -e "MEGACRYPTER ERROR $error_code"
		return 1
	fi

	local expire=$($HELPERS json_param "$info_link" expire)

	if [ "$expire" != "0" ]; then

		IFS='#' read -a array <<< "$expire"

		local no_exp_token=${array[1]}
	else
		local no_exp_token="$expire"
	fi

	local file_name=$(echo -n $($HELPERS json_param "$info_link" name) | base64 -w 0 -i 2>/dev/null)

	local path=$(echo -n $($HELPERS json_param "$info_link" path))

	if [ "$path" != "0" ]; then
		path=$(echo -n "$path" | base64 -w 0 -i 2>/dev/null)
	fi

	local mc_pass=$($HELPERS json_param "$info_link" pass)

	local file_size=$($HELPERS json_param "$info_link" size)

	local key=$($HELPERS json_param "$info_link" key)

	echo -n "${file_name}@${path}@${file_size}@${mc_pass}@${key}@${no_exp_token}"
}

# 1:file_name 2:file_size 3:formatted_file_size [4:md5_mclink]
function check_file_exists {

	if [ -f "$1" ]; then

		local actual_size=$(stat -c %s "$1")

		if [ "$actual_size" == "$2" ]; then

			if [ -n "$4" ] && [ -f ".megadown/${4}" ]; then
				rm ".megadown/${4}"
			fi

			showError "WARNING: File $1 exists. Download aborted!"
		fi

		DL_MSG="\nFile $1 exists but with different size (${2} vs ${actual_size} bytes). Downloading [${3}] ...\n"

	else

		DL_MSG="\nDownloading $1 [${3}] ...\n"

	fi
}

# 1:file_size
function format_file_size {

	if [ "$1" -ge 1073741824 ]; then
		local file_size_f=$(awk "BEGIN { rounded = sprintf(\"%.1f\", ${1}/1073741824); print rounded }")" GB"
	elif [ "$1" -ge 1048576 ];then
		local file_size_f=$(awk "BEGIN { rounded = sprintf(\"%.1f\", ${1}/1048576); print rounded }")" MB"
	else
		local file_size_f="${1} bytes"
	fi

	echo -ne "$file_size_f"
}

# 1:mc_pass_info 2:pass_to_check
function mc_pass_check {

	IFS='#' read -a array <<< "$1"

	local iter_log2=${array[0]}

	local key_check=${array[1]}

	local salt=${array[2]}

	local iv=${array[3]}

	local password=$(echo -n "$2" | base64 -w 0 -i 2>/dev/null)

	local mc_pass_hash=$($HELPERS pbkdf2 "$salt" "$password" $((2**$iter_log2)))

	mc_pass_hash=$(echo -n "$mc_pass_hash" | base64 -d -i 2>/dev/null | od -v -An -t x1 | tr -d '\n ')

	iv=$(echo -n "$iv" | base64 -d -i 2>/dev/null | od -v -An -t x1 | tr -d '\n ')

	if [ "$(echo -n "$key_check" | $OPENSSL_AES_CBC_256_DEC -K "$mc_pass_hash" -iv "$iv" 2>/dev/null | od -v -An -t x1 | tr -d '\n ')" != "$mc_pass_hash" ]; then
		echo -n "0"
	else
		echo -n "${mc_pass_hash}#${iv}"
	fi
}

#1:string
function trim {

	if [[ "$1" =~ \ *([^ ]|[^ ].*[^ ])\ * ]]; then
		echo -n "${BASH_REMATCH[1]}"
	fi
}

#1:pattern 2:subject 3:group
function regex_match {

	if [[ "$2" =~ $1 ]]; then
		echo -n "${BASH_REMATCH[$3]}"
	fi
}

#1:pattern 2:subject 3:group
function regex_imatch {

	shopt -s nocasematch

	if [[ "$2" =~ $1 ]]; then
		echo -n "${BASH_REMATCH[$3]}"
	fi

	shopt -u nocasematch
}

#MAIN STARTS HERE:
check_deps

if [ -z "$1" ]; then
	showHelp
	exit 1
fi

p1=$(trim "$1")

if [[ "$p1" =~ ^http ]] || [[ "$p1" =~ ^mega:// ]]; then
	link="$p1"
fi

eval set -- "$(getopt -o "l:p:k:o:s:qm" -l "list:,password:,key:,output:,speed:,quiet,metadata" -- "$@")"

while true; do
	case "$1" in
		-l|--list)     list="$2";     shift 2;;
		-p|--password) password="$2"; shift 2;;
		-o|--output)   output="$2";   shift 2;;
		-s|--speed)    speed="$2";    shift 2;;
		-q|--quiet)    quiet=true;    shift 1;;
        -m|--metadata) metadata=true; shift 1;;

		--) shift; break;;

		*)
			showHelp
			exit 1;;
	esac
done

if [ -z "$link" ]; then

	if [ -z "$list" ]; then

		showHelp

		showError "ERROR: MEGA/MC link or --list parameter is required"

	elif [ ! -f "$list" ]; then

		showHelp

		showError "ERROR: list file ${list} not found"
	fi

	if [ ! $quiet ]; then
		echo -ne "\n(Pre)reading mc links info..."
	fi

	link_count=0

	while IFS='' read -r line || [ -n "$line" ]; do

		if [ -n "$line" ] && ! [ $(echo -n "$line" | grep -E -o 'mega://enc') ];then

			link=$(regex_imatch "^.*?(https?\:\/\/[^\/]+\/[#!0-9a-z_-]+).*$" "$line" 1)

			if [ $(echo -n "$link" | grep -E -o 'https?://[^/]+/!') ]; then

				md5=$(echo -n "$link" | $OPENSSL_MD5 | grep -E -o '[0-9a-f]{32}')

				if [ ! -f ".megadown/${md5}" ];then

					mc_link_info=$(get_mc_link_info "$link")

					if ! [ "$?" -eq 1 ];then
						echo -n "$mc_link_info" >> ".megadown/${md5}"
					fi
				fi

				link_count=$((link_count + 1))
			fi
		fi

	done < "$list"

	echo -ne " OK(${link_count} MC links found)\n"

	while IFS='' read -r line || [ -n "$line" ]; do

		if [ -n "$line" ];then

			if [ $(echo -n "$line" | grep -E -o 'mega://enc') ]; then

				link=$(regex_imatch "^.*?(mega:\/\/enc\d*?\?[a-z0-9_-]+).*$" "$line" 1)

				output=$(regex_imatch "^.*?mega:\/\/enc\d*?\?[a-z0-9_-]+(.*)$" "$line" 1 1)


			elif [ $(echo -n "$line" | grep -E -o 'https?://') ]; then

				link=$(regex_imatch ".*?(https?\:\/\/[^\/]+\/[#!0-9a-z_-]+).*$" "$line" 1)

				output=$(regex_imatch "^.*?https?\:\/\/[^\/]+\/[#!0-9a-z_-]+(.*)$" "$line" 1 1)

			else
				continue
			fi

			$SCRIPT "$link" --output="$output" --password="$password" --speed="$speed"

		fi

	done < "$list"

	exit 0
fi

if [ $(echo -n "$link" | grep -E -o 'mega://enc') ]; then
	link=$(decrypt_md_link "$link")
fi

if [ ! $quiet ]; then
	echo -e "\nReading link metadata..."
fi

if [ $(echo -n "$link" | grep -E -o 'mega(\.co)?\.nz') ]; then

	#MEGA.CO.NZ LINK

	file_id=$(regex_match "^.*\/#.*?!(.+)!.*$" "$link" 1)

	file_key=$(regex_match "^.*\/#.*?!.+!(.+)$" "$link" 1)

	hex_raw_key=$(echo -n $(urlb64_to_b64 "$file_key") | base64 -d -i 2>/dev/null | od -v -An -t x1 | tr -d '\n ')

	if [ $(echo -n "$link" | grep -E -o 'mega(\.co)?\.nz/#!') ]; then

		mega_req_json="[{\"a\":\"g\", \"p\":\"${file_id}\"}]"

		mega_req_url="${MEGA_API_URL}/cs?id=&ak="

	elif [ $(echo -n "$link" | grep -E -o -i 'mega(\.co)?\.nz/#N!') ]; then

		mega_req_json="[{\"a\":\"g\", \"n\":\"${file_id}\"}]"

		folder_id=$(regex_match "###n\=(.+)$" "$link" 1)

		mega_req_url="${MEGA_API_URL}/cs?id=&ak=&n=${folder_id}"
	fi

	mega_res_json=$($DL_COM --header 'Content-Type: application/json' $DL_COM_PDATA "$mega_req_json" "$mega_req_url")

	download_exit_code=$?

	if [ "$download_exit_code" -ne 0 ]; then
		showError "Oooops, something went bad. EXIT CODE (${download_exit_code})"
	fi

	if [ $(echo -n "$mega_res_json" | grep -E -o '\[ *\-[0-9]+ *\]') ]; then
		showError "MEGA ERROR $(echo -n "$mega_res_json" | grep -E -o '\-[0-9]+')"
	fi

	file_size=$($HELPERS json_param "$mega_res_json" s)

	at=$($HELPERS json_param "$mega_res_json" at)

	hex_key=$(hrk2hk "$hex_raw_key")

	at_dec_json=$(echo -n $(urlb64_to_b64 "$at") | $OPENSSL_AES_CBC_128_DEC -K "$hex_key" -iv "00000000000000000000000000000000" -nopad | tr -d '\0')

	if [ ! $(echo -n "$at_dec_json" | grep -E -o 'MEGA') ]; then
		showError "MEGA bad link"
	fi

	if [ -z "$output" ]; then
		file_name=$($HELPERS json_param "$(echo -n "$at_dec_json" | grep -E -o '\{.+\}')" n)
	else
		file_name="$output"
	fi

	check_file_exists "$file_name" "$file_size" "$(format_file_size "$file_size")"

	if [ $(echo -n "$link" | grep -E -o 'mega(\.co)?\.nz/#!') ]; then
		mega_req_json="[{\"a\":\"g\", \"g\":\"1\", \"p\":\"$file_id\"}]"
	elif [ $(echo -n "$link" | grep -E -o -i 'mega(\.co)?\.nz/#N!') ]; then
		mega_req_json="[{\"a\":\"g\", \"g\":\"1\", \"n\":\"$file_id\"}]"
	fi

	mega_res_json=$($DL_COM --header 'Content-Type: application/json' $DL_COM_PDATA "$mega_req_json" "$mega_req_url")

	download_exit_code=$?

	if [ "$download_exit_code" -ne 0 ]; then
		showError "Oooops, something went bad. EXIT CODE (${download_exit_code})"
	fi

	dl_temp_url=$($HELPERS json_param "$mega_res_json" g)
else

	#MEGACRYPTER LINK

	MC_API_URL=$(echo -n "$1" | grep -i -E -o 'https?://[^/]+')"/api"

	md5=$(echo -n "$link" | $OPENSSL_MD5 | grep -E -o '[0-9a-f]{32}')

	if [ -f ".megadown/${md5}" ];then
		mc_link_info=$(cat ".megadown/${md5}")
	else
		mc_link_info=$(get_mc_link_info "$link")

		if [ "$?" -eq 1 ];then
			echo -e "$mc_link_info"
			exit 1
		fi

		echo -n "$mc_link_info" >> ".megadown/${md5}"
	fi

	IFS='@' read -a array <<< "$mc_link_info"

	if [ -z "$output" ];then
		file_name=$(echo -n "${array[0]}" | base64 -d -i 2>/dev/null)
	else
		file_name="$output"
	fi

	path=${array[1]}

	if [ "$path" != "0" ]; then
		path=$(echo -n "$path" | base64 -d -i 2>/dev/null)
	fi

	file_size=${array[2]}

	mc_pass=${array[3]}

	key=${array[4]}

	no_exp_token=${array[5]}

	if [ "$path" != "0" ] && [ "$path" != "" ]; then

		if [ ! -d "$path" ]; then

			mkdir -p "$path"
		fi

		file_name="${path}${file_name}"
	fi

	if [ "$mc_pass" != "0" ]; then

		echo -ne "\nLink is password protected. "

		if [ -n "$password" ]; then

			pass_hash=$(mc_pass_check "$mc_pass" "$password")

		fi

		if [ -z "$pass_hash" ] || [ "$pass_hash" == "0" ]; then

			echo -ne "\n\n"

			read -e -p "Enter password: " pass

			pass_hash=$(mc_pass_check "$mc_pass" "$pass")

			until [ "$pass_hash" != "0" ]; do
				read -e -p "Wrong password! Try again: " pass
				pass_hash=$(mc_pass_check "$mc_pass" "$pass")
			done
		fi

		echo -ne "\nPassword is OK. Decrypting metadata...\n"

		IFS='#' read -a array <<< "$pass_hash"

		pass_hash=${array[0]}

		iv=${array[1]}

		hex_raw_key=$(echo -n "$key" | $OPENSSL_AES_CBC_256_DEC -K "$pass_hash" -iv "$iv" | od -v -An -t x1 | tr -d '\n ')

		if [ -z "$output" ]; then
			file_name=$(echo -n "$file_name" | $OPENSSL_AES_CBC_256_DEC -K "$pass_hash" -iv "$iv")
		fi
	else
		hex_raw_key=$(echo -n $(urlb64_to_b64 "$key") | base64 -d -i 2>/dev/null | od -v -An -t x1 | tr -d '\n ')
	fi

	check_file_exists "$file_name" "$file_size" "$(format_file_size "$file_size")" "$md5"

	hex_key=$(hrk2hk "$hex_raw_key")

	dl_link=$($DL_COM --header 'Content-Type: application/json' $DL_COM_PDATA "{\"m\":\"dl\", \"link\":\"$link\", \"noexpire\":\"$no_exp_token\"}" "$MC_API_URL")

	download_exit_code=$?

	if [ "$download_exit_code" -ne 0 ]; then
		showError "Oooops, something went bad. EXIT CODE (${download_exit_code})"
	fi

	if [ $(echo $dl_link | grep '"error"') ]; then

		error_code=$($HELPERS json_param "$dl_link" error)

		showError "MEGACRYPTER ERROR $error_code"
	fi

	dl_temp_url=$($HELPERS json_param "$dl_link" url)

	if [ "$mc_pass" != "0" ]; then

		iv=$(echo -n $($HELPERS json_param "$dl_link" pass) | base64 -d -i 2>/dev/null | od -v -An -t x1 | tr -d '\n ')

		dl_temp_url=$(echo -n "$dl_temp_url" | $OPENSSL_AES_CBC_256_DEC -K "$pass_hash" -iv "$iv")
	fi
fi

if [ $metadata ]; then
	echo -n $(echo -n "$file_name" | base64 -i)"|${file_size}"
	exit 0
fi

if [ -z "$speed" ]; then
	DL_COMMAND="$DL_COM"
else
	DL_COMMAND="$DL_COM --limit-rate $speed"
fi

if [ "$output" == "-" ]; then

	hex_iv="${hex_raw_key:32:16}0000000000000000"

	$DL_COMMAND "$dl_temp_url" | $OPENSSL_AES_CTR_128_DEC -K "$hex_key" -iv "$hex_iv"

	exit 0
fi

if [ ! $quiet ]; then
	echo -e "$DL_MSG"
fi

if [ ! $quiet ]; then
	PV_CMD="pv"
else
	PV_CMD="pv -q"
fi

download_exit_code=1

until [ "$download_exit_code" -eq 0 ]; do

	if [ -f "${file_name}.temp" ]; then

		echo -e "(Resuming previous download ...)\n"

		temp_size=$(stat -c %s "${file_name}.temp")

		offset=$(($temp_size-$(($temp_size%16))))

		iv_forward=$(printf "%016x" $(($offset/16)))

		hex_iv="${hex_raw_key:32:16}$iv_forward"

		truncate -s $offset "${file_name}.temp"

		$DL_COMMAND "$dl_temp_url/$offset" | $PV_CMD -s $(($file_size-$offset)) | $OPENSSL_AES_CTR_128_DEC -K "$hex_key" -iv "$hex_iv" >> "${file_name}.temp"
	else
		hex_iv="${hex_raw_key:32:16}0000000000000000"

		$DL_COMMAND "$dl_temp_url" | $PV_CMD -s $file_size | $OPENSSL_AES_CTR_128_DEC -K "$hex_key" -iv "$hex_iv" > "${file_name}.temp"
	fi

	download_exit_code=${PIPESTATUS[0]}

	if [ "$download_exit_code" -ne 0 ]; then
		showError "Oooops, download failed! EXIT CODE (${download_exit_code})"
	fi
done

if [ ! -f "${file_name}.temp" ]; then
	showError "ERROR: FILE COULD NOT BE DOWNLOADED :(!"
fi

mv "${file_name}.temp" "${file_name}"

if [ -f ".megadown/${md5}" ];then
	rm ".megadown/${md5}"
fi

if [ ! $quiet ]; then
	echo -e "\nFILE DOWNLOADED!\n"
fi

exit 0
