#!/usr/local/bin/perl
# Config

$version = "4.0";
use FindBin qw($RealBin);
$base_dir = $RealBin;
$db_file = "$base_dir/bckdl.db";
$dl_dir = "/stock/download/back_downloader/";
$db_source = $dl_dir."_src";
$log_file = "$base_dir/bckdl.log";

# MyJDownloader API
$jd_email    = 'jd@unix-scripts.org';
$jd_password = 'CcAVf8E*nXB';
$jd_appkey   = 'TAGADA';
$jd_device   = 'JDownloader@algalord';
$jd_base_url = 'https://api.jdownloader.org';
$jd_state    = undef;

$verbose = 0;
$daemon_interval = 12; # in seconds

#######################################
# back_downloader
#######################################
# 4.0 :
#		Merge jd.py into Perl (MyJDownloader API native)
#		Remove filejoker support
#		Remove Switch module (deprecated), use if/elsif
#		Keep _k2senable, _dfenable, _adenable flags
# 3.7 : jdownloader also used for alldebrid
# 3.5 : add jdownloader warper k2s df
# 3.3.1 : same for ad with _adenable
# 3.3 :
#		enable k2s links by creating a file _k2senable in download dir
# 3.2 :
#		Add plowdown for k2s
# 3.1 :
#		Multidepth destination directory
# 3.0 :
#		Check pydebrid login
#		Deamon Mode
# 2.7 :
# Add add command (or -a) to add a single link
# 2.6 :
# Add : Priority system, new db field <prioriti>, default : 3
#		 0 :  exceptional, 1 : very high, 2 : high, 3 : normal, 4 : low, 5 : very low
#		 priority=# or p=# in source file to set priority
#		 priority <id> # or p <id> # set priority command
#		 priority <id-id> # or p <id-id> # set priority command
# 2.5.2 :
# Add : command shortcuts
# 2.5.1 :
# Fix : display help on unkown command
# 2.5 :
# Add : check internet connectivity before downloading
# 2.4 :
# Add : range for remove/reset/dl params
# Add : change from single file source to directory source
#		 	- _dl.txt in directory source normaly parsed
#			- other .txt files use filename for base destination, unless specified by dest param
# Add : d= in sources files, shorcut for dest=param
# 2.3 :
# Added : delete incomplete files on failure
# Change : full download loop use the download subroutine, means remove duplicate code
# Change : download logs id first, easier reading of logs expected
# 2.2.2
# Fix : check if directory is writable when it exists
# Fix : ignore if source file does not exists
# 2.2
# Change : handle HUP in addition to TERM signal
# Add : new command run_bg and dl_bg, do the same as the old command
#	    but as a daemon.
# Add : source file can include an optional dest= parameter
#		links following this parameter will go to the >dest< subdirectory of >dl_dir<
# 2.1
# Add : handle TERM (kill) signal to exit
#
# 2.0
# Add : arguments to control how it works
##############################

use DBI;
use Proc::Daemon;
use Net::Ping;
use File::Path qw(make_path);
use Digest::SHA qw(sha256 hmac_sha256_hex);
use MIME::Base64 qw(encode_base64 decode_base64);
use Crypt::Rijndael;
use LWP::UserAgent;
use JSON;
use URI::Escape qw(uri_escape);

###########################
# Handle Term signal
my $ExitFlag = 0;
$SIG{HUP} = $SIG{TERM} = $SIG{INT} = sub { logit("TERM signal received"); $ExitFlag = 1 };

my @priorities = ('Exceptional', 'Very High', 'High', 'Normal', 'Low', 'Very Low');

if ($#ARGV > -1) {
	my $param = $ARGV[1];
	$param =~ /^(\d+)(-*)(\d*)$/;
	my $id = $1 + 0;
	my $lastId = $3 + 0;
	my $param2 = $ARGV[2];
	my $cmd = $ARGV[0];

	if    ($cmd eq 'help' || $cmd eq '-h') { help(); }

	elsif ($cmd eq 'import' || $cmd eq '-i') {
		InitDB(); ParseNew();
		my $pending = CountPending();
		print("  $pending links pending.\n");
	}

	elsif ($cmd eq 'add' || $cmd eq '-a') { InitDB(); AddLink($ARGV[1], $ARGV[2], $ARGV[3]); }

	elsif ($cmd eq 'list'        || $cmd eq '-l')  { InitDB(); ListLink(1); }
	elsif ($cmd eq 'list_run'    || $cmd eq '-lr') { InitDB(); ListLink(2); }
	elsif ($cmd eq 'list_failed' || $cmd eq '-lf') { InitDB(); ListLink(3); }
	elsif ($cmd eq 'list_skip'   || $cmd eq '-ls') { InitDB(); ListLink(4); }
	elsif ($cmd eq 'list_all'    || $cmd eq '-la') { InitDB(); ListLink(0); }

	elsif ($cmd eq 'reset_failed'  || $cmd eq '-rf') { InitDB(); ResetLink(0); }
	elsif ($cmd eq 'reset_skipped' || $cmd eq '-rs') { InitDB(); ResetLink(-1); }
	elsif ($cmd eq 'reset' || $cmd eq '-r') {
		if ($id > 0) { InitDB(); ResetLink($id, $lastId); } else { help(); }
	}

	elsif ($cmd eq 'priority' || $cmd eq '-p') {
		if (($id > 0) && ($param2 >= 1) && ($param2 <= 5)) { InitDB(); SetPriority($id, $lastId, $param2); } else { help(); }
	}

	elsif ($cmd eq 'remove_failed'   || $cmd eq '-rmfa') { InitDB(); RemoveLink(0); }
	elsif ($cmd eq 'remove_finished' || $cmd eq '-rmfi') { InitDB(); RemoveLink(-1); }
	elsif ($cmd eq 'remove' || $cmd eq '-rm') {
		if ($id > 0) { InitDB(); RemoveLink($id, $lastId); } else { help(); }
	}

	elsif ($cmd eq 'daemon' || $cmd eq 'deamon' || $cmd eq '-d') {
		print "Starting v$version Daemon Mode\n";
		$verbose = 1;
		DaemonMode();
	}
	elsif ($cmd eq 'daemon-bg' || $cmd eq 'daemon_bg' || $cmd eq 'deamon-bg' || $cmd eq 'deamon_bg' || $cmd eq '-dbg') {
		print "Starting v$version Daemon Mode to Background\n";
		Proc::Daemon::Init;
		DaemonMode();
	}

	elsif ($cmd eq 'run') { $verbose = 1; InitDB(); ParseNew(); Download(); }
	elsif ($cmd eq 'run_bg' || $cmd eq 'run-bg' || $cmd eq 'bg') {
		print "Going to background\n";
		Proc::Daemon::Init;
		InitDB(); ParseNew(); Download();
	}

	elsif ($cmd eq 'dl') {
		if ($id > 0) { InitDB(); Download($id, $lastId); } else { help(); }
	}
	elsif ($cmd eq 'dl_bg') {
		if ($id > 0) {
			print "Going to background\n";
			Proc::Daemon::Init;
			InitDB(); Download($id, $lastId);
		} else { help(); }
	}

	else { help(); }
}
else {
	help();
}

$dbh->disconnect() if $dbh;

#############################
# Daemon mode routine
sub DaemonMode {
	InitDB();
	while ($ExitFlag == 0) {
		print(" tic\n");
		ParseNew();
		my $req = "SELECT id FROM download WHERE downloaded=0";
		my $res = $dbh->prepare($req);
		$res->execute();
		if (my @row = $res->fetchrow_array()) {
			Download();
		}
		sleep($daemon_interval);
	}
}

#############################
# AddLink routine
sub AddLink {
	my ($link, $destination, $priority) = @_;
	if ($link =~ /^http/) {
		$priority += 0;
		if ($priority < 0 || $priority > 5) { $priority = 3; }
		my $sth = $dbh->prepare("INSERT INTO download (link,destination,priority) VALUES (?,?,?)");
		if (!$sth->execute($link, $destination, $priority)) {
			print "Error importing link : $link\n";
		} else {
			print "Link added successfully\n";
		}
	} else {
		print "Non HTTP link, ignoring.\n";
	}
}

#############################
# ListLink routine
sub ListLink {
	my ($option) = @_;
	$option += 0;
	my @title = qw(Full Pending Running Failed Skipped);
	print $title[$option] . " list of links in database";
	my $req = "SELECT id, link, date, downloaded, destination, priority FROM download";
	my $nb = 0;
	if ($option) {
		$req .= " WHERE downloaded=";
		if ($option == 1) { $req .= "0"; }
		else              { $req .= "$option"; }
		print " \n";
	} else {
		print "   (P=pending, F=finished, R=running, E=error, S=skip)\nStatus\t";
	}
	$req .= " ORDER BY ";
	if ($option == 1) { $req .= "priority, "; }
	$req .= "date;";
	print "ID\tDate\t\t\tLink\t\t\t\t\tDestination\t\tPriority\n";
	my $res = $dbh->prepare($req);
	$res->execute();
	while (my @row = $res->fetchrow_array()) {
		if (!$option) {
			if    ($row[3] == 0) { print " P\t"; }
			elsif ($row[3] == 1) { print "  F\t"; }
			elsif ($row[3] == 2) { print "   R\t"; }
			elsif ($row[3] == 3) { print "    E\t"; }
			elsif ($row[3] == 4) { print "     S\t"; }
		}
		print "$row[0]\t$row[2]\t$row[1]\t$row[4]\t$priorities[$row[5]]\n";
		$nb++;
	}
	print "\nTotal : $nb links displayed\n";
}

##################################################
# SetPriority
sub SetPriority {
	my ($id, $lastId, $priority) = @_;
	$id += 0; $lastId += 0; $priority += 0;
	if ($id && !$lastId) {
		my $res = $dbh->do("UPDATE download SET priority=? WHERE id=?", undef, $priority, $id);
		if ($res > 0) { print "Priority of link $id set to $priorities[$priority] ($priority)\n"; }
		else          { print "Failed to change priority of link!\n"; }
	} elsif ($id && $lastId) {
		my $res = $dbh->do("UPDATE download SET priority=? WHERE id>=? AND id<=?", undef, $priority, $id, $lastId);
		if ($res > 0) { print "Priority of $res links set to $priorities[$priority] ($priority)\n"; }
		else          { print "Failed to change priority of links!\n"; }
	}
}

##################################################
# ResetLink
sub ResetLink {
	my ($id, $lastId) = @_;
	$id += 0; $lastId += 0;
	if (!$id) {
		my $res = $dbh->do("UPDATE download SET downloaded=0 WHERE downloaded=3");
		print "Changed status of $res links to pending\n";
	} elsif ($id == -1) {
		my $res = $dbh->do("UPDATE download SET downloaded=0 WHERE downloaded=4");
		print "Changed status of $res links to pending\n";
	} elsif ($lastId) {
		my $res = $dbh->do("UPDATE download SET downloaded=0 WHERE id>=? AND id<=?", undef, $id, $lastId);
		if ($res > 0) { print "Status of $res links reset successful\n"; }
		else          { print "Failed to change status of links!\n"; }
	} else {
		my $res = $dbh->do("UPDATE download SET downloaded=0 WHERE id=?", undef, $id);
		if ($res > 0) { print "Status of link reset successful\n"; }
		else          { print "Failed to change status of link with id $id!\n"; }
	}
}

############################################
# RemoveLink routine
sub RemoveLink {
	my ($id, $lastId) = @_;
	$id += 0; $lastId += 0;
	if ($id == 0) {
		my $res = $dbh->do("DELETE FROM download WHERE downloaded=3");
		print "Removed $res links from database\n";
	} elsif ($id == -1) {
		my $res = $dbh->do("DELETE FROM download WHERE downloaded=1");
		print "Removed $res links from database\n";
	} elsif ($id > 0 && $lastId) {
		my $res = $dbh->do("DELETE FROM download WHERE id>=? AND id<=?", undef, $id, $lastId);
		if ($res > 0) { print "$res Link(s) deleted successfully\n"; }
		else          { print "Failed to delete links\n"; }
	} elsif ($id > 0) {
		my $res = $dbh->do("DELETE FROM download WHERE id=?", undef, $id);
		if ($res > 0) { print "Link $id deleted successfully\n"; }
		else          { print "Failed to delete link with id $id!\n"; }
	}
}

#############################
# Database initialisation
sub InitDB {
	$dbh = DBI->connect("dbi:SQLite:dbname=$db_file", "", "") or die $DBI::errstr . "\n DB file = $db_file";
	$dbh->do(qq{
		CREATE TABLE IF NOT EXISTS 'download'(
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			link TEXT,
			date TEXT DEFAULT (datetime('now','localtime')),
			downloaded INTEGER DEFAULT 0,
			destination TEXT DEFAULT '',
			priority INTEGER DEFAULT 3)
	}) or die "Error creating table : $DBI::errstr";
}

############################
# CheckDestination
sub CheckDestination {
	my ($destination) = @_;
	$destination =~ s/[^a-zA-Z0-9 ._\/-]//g;
	$destination =~ s/\.\.\///g;
	my $fullpath = $dl_dir . "/" . $destination;
	if (-d $fullpath && -w $fullpath) {
		return $destination;
	} elsif (-e $fullpath) {
		my $i = 1;
		while (!(-d $fullpath . ".$i" && -w $fullpath . ".$i") && -e $fullpath . ".$i") {
			$i++;
		}
		$destination .= ".$i";
	}
	$fullpath = $dl_dir . "/" . $destination;
	if (!-d $fullpath) {
		make_path($fullpath, {error => \my $err});
		if ($err && @$err) { return ""; }
	}
	return $destination;
}

##################################
# Download loop
sub Download {
	my ($id, $lastId) = @_;
	$id += 0; $lastId += 0;
	if (!$id)          { logit("Session started : downloading all pending links"); }
	elsif (!$lastId)   { logit("Session started : downloading link with id ($id)"); }
	else               { logit("Session started : downloading links ($id) to ($lastId)"); }

	my $finished = 0;
	my $nb_dl = 0;
	my $nb_er = 0;
	my $req = "SELECT id, link, date, downloaded, destination FROM download WHERE downloaded=0";
	if ($id && $lastId) { $req .= " AND id>=$id AND id<=$lastId"; }
	elsif ($id)         { $req .= " AND id=$id"; }
	$req .= " ORDER BY priority, id LIMIT 1";

	while (!$finished && !$ExitFlag) {
		my $res = $dbh->prepare($req);
		$res->execute();
		if (my @row = $res->fetchrow_array()) {
			if (DownloadLink($row[0])) { $nb_dl++; }
			else                       { $nb_er++; }
		} else {
			$finished = 1;
		}
		$res->finish();
	}
	if ($ExitFlag) { logit("Interrupted by TERM signal"); }
	logit("Finished ! Successfully downloaded $nb_dl files, $nb_er failed!");
}

####################################
# DownloadLink
# return 1 on success, 0 on failure
sub DownloadLink {
	if (!checkInternet()) { logit("Error : check internet connection"); sleep(3); return 0; }
	my ($id) = @_;
	my $success = 0;
	my $sth = $dbh->prepare("SELECT id, link, date, downloaded, destination FROM download WHERE id=?");
	$sth->execute($id);
	if (my @row = $sth->fetchrow_array()) {
		my $link        = $row[1];
		my $destination = CheckDestination($row[4]);

		# Check enable flags per link type
		if ($link =~ /(keep2share|k2s|k2share|keep2s)\.cc/i) {
			if (!k2s_enable()) {
				logit("($id) Skip k2s link");
				$dbh->do("UPDATE download SET downloaded=4 WHERE id=?", undef, $id);
				return 0;
			}
		} elsif ($link =~ /(depositfiles|dfiles)\.(com|eu|org)/i) {
			if (!df_enable()) {
				logit("($id) Skip depositfiles link");
				$dbh->do("UPDATE download SET downloaded=4 WHERE id=?", undef, $id);
				return 0;
			}
		} else {
			if (!ad_enable()) {
				logit("($id) Skip ad link");
				$dbh->do("UPDATE download SET downloaded=4 WHERE id=?", undef, $id);
				return 0;
			}
		}

		$dbh->do("UPDATE download SET downloaded=2 WHERE id=?", undef, $id);
		logit("($id) Starting download of $link > $destination");

		my $dest_path = $dl_dir . "/" . $destination;
		my $rc = JDAddLink($link, $dest_path);

		if ($rc == 0) {
			logit("($id) Download sent to JDownloader successfully");
			$dbh->do("UPDATE download SET downloaded=1 WHERE id=?", undef, $id);
			$success = 1;
		} else {
			logit("($id) Error sending $link to JDownloader (code $rc)");
			$dbh->do("UPDATE download SET downloaded=3 WHERE id=?", undef, $id);
			$success = 0;
		}
	}
	$sth->finish();
	return $success;
}

####################################
# Parsing sub
sub ParseNew {
	if (!-d $db_source) {
		logit("Parser : Could not find source directory");
		return;
	} elsif (!-r $db_source || !-x $db_source) {
		logit("Parser : Could not open source directory");
		return;
	}

	my $DIR;
	if (opendir($DIR, $db_source)) {
		my @files = grep { /^[^\.].*(\.txt)$/ && -f "$db_source/$_" } readdir($DIR);
		closedir $DIR;

		my $countParsed = 0;
		foreach my $curFile (@files) {
			my $priority    = 3;
			my $destination = "";
			if ($curFile eq "_dl.txt") {
				$destination = "";
				logit("Parser : _dl.txt found");
			} else {
				$curFile =~ /^(.*)(\.txt)$/;
				$destination = $1;
				logit("Parser : $curFile found");
			}

			my $fhi;
			if (!open($fhi, "<:encoding(utf8)", "$db_source/$curFile")) {
				print "Could not open $curFile, file not parsed\n";
				next;
			}
			my $added = 0;
			while (my $line = <$fhi>) {
				$line =~ s/\r|\n//g;
				next if $line =~ /^$/;
				if ($line =~ /^(?:d|dest)=(.*)$/i) {
					$destination = $1;
				} elsif ($line =~ /^(?:p|priority)=([0-5])$/i) {
					$priority = $1;
				} elsif ($line =~ /^http/) {
					my $sth = $dbh->prepare("INSERT INTO download (link,destination,priority) VALUES (?,?,?)");
					if (!$sth->execute($line, $destination, $priority)) {
						print "Error importing link : $line\n";
					} else {
						$added++;
					}
				}
			}
			close($fhi);
			my $time = time();
			logit("Parser : Added $added link(s) to download database.");
			if (!rename("$db_source/$curFile", "$db_source/.$curFile.$time")) {
				logit("Parser : WARNING : Could not rename $curFile, file will be parsed again next time");
			}
			$countParsed++;
		}

		if ($countParsed > 0) {
			logit("Parser : $countParsed file(s) parsed");
		} else {
			my $pending = CountPending();
			logit("Nothing done, $pending links pending.");
		}
	} else {
		logit("Parser : Error opening source directory");
	}
}

sub CountPending {
	my $res = $dbh->prepare("SELECT COUNT(id) FROM download WHERE downloaded=0");
	$res->execute();
	if (my @row = $res->fetchrow_array()) {
		$res->finish();
		return $row[0];
	}
	return 0;
}

####################################
# Enable flag checks
sub k2s_enable { return -e "$dl_dir/_k2senable" ? 1 : 0; }
sub df_enable  { return -e "$dl_dir/_dfenable"  ? 1 : 0; }
sub ad_enable  { return -e "$dl_dir/_adenable"  ? 1 : 0; }

####################################
# Internet check
sub checkInternet {
	my $p = Net::Ping->new("tcp", 2);
	$p->service_check(1);
	$p->port_number(80);
	my $stop_time = time() + 2;
	my $return = 0;
	while ($stop_time > time()) {
		$return = $p->ping("www.google.com");
		sleep(1) unless $return;
	}
	undef($p);
	return $return;
}

###############################
# Logging
sub logit {
	my ($logline) = @_;
	open(my $log, '>>', $log_file);
	my $date = localtime();
	print $log "$date : $$: $logline\n";
	close($log);
	if ($verbose) { print($logline . "\n"); }
}

######################################
# MyJDownloader API
######################################


sub _jd_make_secret {
	my ($email, $password, $domain) = @_;
	return sha256(lc($email) . $password . lc($domain));
}

sub _aes_cbc {
	my ($token, $data, $mode) = @_;
	my $iv  = substr($token, 0, 16);
	my $key = substr($token, 16, 16);
	my $cipher = Crypt::Rijndael->new($key, Crypt::Rijndael::MODE_CBC());
	$cipher->set_iv($iv);
	return $mode eq 'enc' ? $cipher->encrypt(_pkcs7_pad($data)) : _pkcs7_unpad($cipher->decrypt($data));
}

sub _pkcs7_pad {
	my ($data) = @_;
	my $pad = 16 - (length($data) % 16);
	return $data . chr($pad) x $pad;
}

sub _pkcs7_unpad {
	my ($data) = @_;
	my $pad = ord(substr($data, -1));
	return substr($data, 0, length($data) - $pad);
}

sub _jd_aes_decrypt {
	my ($token, $b64data) = @_;
	return _aes_cbc($token, decode_base64($b64data), 'dec');
}

sub _jd_aes_encrypt {
	my ($token, $plaintext) = @_;
	return encode_base64(_aes_cbc($token, $plaintext, 'enc'), '');
}

sub _jd_update_tokens {
	my ($login_secret, $device_secret, $session_token_hex) = @_;
	my $session_bin  = pack('H*', $session_token_hex);
	my $server_token = sha256($login_secret  . $session_bin);
	my $device_token = sha256($device_secret . $session_bin);
	return ($server_token, $device_token);
}

sub _jd_sign {
	my ($key, $data) = @_;
	return lc(hmac_sha256_hex($data, $key));
}

sub JDConnect {
	$jd_ua //= LWP::UserAgent->new(timeout => 30);
	my $rid          = time();
	my $login_secret  = _jd_make_secret($jd_email, $jd_password, 'server');
	my $device_secret = _jd_make_secret($jd_email, $jd_password, 'device');

	my $query = "/my/connect?email=" . uri_escape($jd_email)
	          . "&appkey=" . uri_escape($jd_appkey)
	          . "&rid=$rid";
	my $sig   = _jd_sign($login_secret, $query);
	$query   .= "&signature=$sig";

	my $resp = $jd_ua->get($jd_base_url . $query);
	unless ($resp->is_success) {
		logit("JD Connect HTTP error : " . $resp->status_line);
		return 0;
	}

	my $json = decode_json(_jd_aes_decrypt($login_secret, $resp->content));
	unless ($json->{sessiontoken}) {
		logit("JD Connect : no session token in response");
		return 0;
	}

	my ($server_enc_tok, $device_enc_tok) =
		_jd_update_tokens($login_secret, $device_secret, $json->{sessiontoken});

	$jd_state = {
		rid             => $rid + 1,
		session_token   => $json->{sessiontoken},
		server_enc_tok  => $server_enc_tok,
		device_enc_tok  => $device_enc_tok,
	};
	return 1;
}

sub JDGetDeviceId {
	my $rid   = ++$jd_state->{rid};
	my $query = "/my/listdevices?sessiontoken=$jd_state->{session_token}&rid=$rid";
	my $sig   = _jd_sign($jd_state->{server_enc_tok}, $query);
	$query   .= "&signature=$sig";

	my $resp = $jd_ua->get($jd_base_url . $query);
	unless ($resp->is_success) {
		logit("JD listdevices HTTP error : " . $resp->status_line);
		return undef;
	}

	my $json = decode_json(_jd_aes_decrypt($jd_state->{server_enc_tok}, $resp->content));
	for my $dev (@{ $json->{list} }) {
		if ($dev->{name} eq $jd_device) {
			return $dev->{id};
		}
	}
	logit("JD : device '$jd_device' not found");
	return undef;
}

sub _jd_device_action {
	my ($device_id, $api_path, $params) = @_;
	my $rid = ++$jd_state->{rid};

	my @adapted = map { ref($_) ? encode_json($_) : $_ } @$params;

	my $payload = encode_json({
		apiVer => 1,
		url    => $api_path,
		params => \@adapted,
		rid    => $rid,
	});

	my $encrypted = _jd_aes_encrypt($jd_state->{device_enc_tok}, $payload);
	my $url = $jd_base_url . "/t_$jd_state->{session_token}_$device_id" . $api_path;

	my $resp = $jd_ua->post($url,
		'Content-Type' => 'application/aesjson-jd; charset=utf-8',
		Content        => $encrypted,
	);
	unless ($resp->is_success) {
		logit("JD device action $api_path HTTP error : " . $resp->status_line);
		return undef;
	}
	my $json = decode_json(_jd_aes_decrypt($jd_state->{device_enc_tok}, $resp->content));
	return $json->{data};
}

# Main entry point : connect, cleanup, add link
# Returns 0 on success, non-zero on error
sub JDAddLink {
	my ($url, $dest_path) = @_;

	my @parts    = split('/', $dest_path);
	my $pkg_name = pop(@parts) || 'download';
	my $dest_dir = join('/', @parts);

	unless (JDConnect()) {
		logit("JD : connection failed");
		return 1;
	}

	my $device_id = JDGetDeviceId();
	unless ($device_id) {
		logit("JD : could not get device id");
		return 2;
	}

	# Cleanup finished downloads
	_jd_device_action($device_id, '/downloadsV2/cleanup',
		[ [], [], 'DELETE_FINISHED', 'REMOVE_LINKS_ONLY', 'ALL' ]
	);

	# Add link
	my $result = _jd_device_action($device_id, '/linkgrabberv2/addLinks', [{
		autostart              => JSON::true,
		links                  => $url,
		packageName            => $pkg_name,
		destinationFolder      => $dest_dir,
		priority               => 'DEFAULT',
		overwritePackagizerRules => JSON::false,
	}]);

	return defined($result) ? 0 : 3;
}

##################################
# help
sub help {
	print qq(Background Downloader $version
	help, -h		display this help

	run			import new links and start the download
	import, -i		only import links into database

	daemon, -d		Start daemon mode (foreground)
	daemon-bg, -dbg		Start daemon mode (background)

	add <link> [dest] [priority]
	-a  <link> [dest] [priority]

	list, -l		list links to be downloaded
	list_run, -lr		list currently running downloads
	list_failed, -lf	list failed downloads
	list_all, -la		list all links (including already downloaded)
	list_skip, -ls		list skipped links

	reset_failed, -rf	reset failed downloads to a new attempt
	reset_skipped, -rs	reset skipped downloads
	reset <id>, -r		reset download with id <id>
	reset <id1>-<id2>	reset downloads from <id1> to <id2>

	remove_failed, -rmfa	remove all failed downloads
	remove_finished, -rmfi	remove all finished downloads
	remove <id>, -rm	remove link with id <id> from database
	remove <id1>-<id2>	remove links from <id1> to <id2>

	priority <id> #, -p	set priority of <id> to #
	priority <id>-<id2> #	set priority of range to #
				Priority levels 1:very high 2:high
				3:normal 4:low 5:very low

	dl <id>			send link with id <id> to JDownloader
	dl <id1>-<id2>		send links from <id1> to <id2>

Enable flags (create file in $dl_dir to activate) :
	_k2senable		enable keep2share / k2s links
	_dfenable		enable depositfiles links
	_adenable		enable all other links (alldebrid, etc.)

Use dest=<dest_dir> or d=<dest_dir> in source file to set subdirectory destination.
Use priority=# or p=# in source file to set priority (0-5).

If TERM or HUP signal is received, application waits for current operation to complete before exiting.
);
	exit;
}
