#!/bin/bash set -e ############################################################ # Help # ############################################################ help() { # Display Help echo "Send files or text with PairDrop via command-line interface." echo "Current domain: ${DOMAIN}" echo echo "Usage:" echo -e "Open PairDrop:\t\t$(basename "$0")" echo -e "Send files:\t\t$(basename "$0") file1/directory1 (file2/directory2 file3/directory3 ...)" echo -e "Send text:\t\t$(basename "$0") -t \"text\"" echo -e "Specify domain:\t\t$(basename "$0") -d \"https://pairdrop.net/\"" echo -e "Show this help text:\t$(basename "$0") (-h|--help)" echo echo "This pairdrop-cli version was released alongside v1.10.0" } openPairDrop() { url="$DOMAIN" if [[ -n $params ]];then url="${url}?${params}" fi if [[ -n $hash ]];then url="${url}#${hash}" fi echo "PairDrop is opening at $DOMAIN" if [[ $OS == "Windows" ]];then start "$url" elif [[ $OS == "Mac" ]];then open "$url" elif [[ $OS == "WSL" || $OS == "WSL2" ]];then powershell.exe /c "Start-Process ${url}" else xdg-open "$url" > /dev/null 2>&1 fi exit } setOs() { unameOut=$(uname -a) case "${unameOut}" in *Microsoft*) OS="WSL";; #must be first since Windows subsystem for linux will have Linux in the name too *microsoft*) OS="WSL2";; #WARNING: My v2 uses ubuntu 20.4 at the moment slightly different name may not always work Linux*) OS="Linux";; Darwin*) OS="Mac";; CYGWIN*) OS="Cygwin";; MINGW*) OS="Windows";; *Msys) OS="Windows";; *) OS="UNKNOWN:${unameOut}" esac } specifyDomain() { [[ ! $1 = http* ]] || [[ ! $1 = */ ]] && echo "Incorrect format. Specify domain like https://pairdrop.net/" && exit echo "DOMAIN=${1}" > "$config_path" echo -e "Domain is now set to:\n$1\n" } sendText() { params="base64text=hash" hash=$(echo -n "${OPTARG}" | base64) if [[ $(echo -n "$hash" | wc -m) -gt 32600 ]];then params="base64text=paste" if [[ $OS == "Windows" || $OS == "WSL" || $OS == "WSL2" ]];then echo -n "$hash" | clip.exe elif [[ $OS == "Mac" ]];then echo -n "$hash" | pbcopy else (echo -n "$hash" | xclip) || echo "You need to install xclip for sending bigger files from cli" fi hash= fi openPairDrop exit } escapePSPath() { local path=$1 # escape '[' and ']' with grave accent (`) character pathPS=${path//[/\`[} pathPS=${pathPS//]/\`]} # escape single quote (') with another single quote (') pathPS=${pathPS//\'/\'\'} # Convert GitHub bash path "/i/path" to Windows path "I:/path" if [[ $pathPS == /* ]]; then # Remove preceding slash pathPS="${pathPS#/}" # Convert drive letter to uppercase driveLetter=$(echo "${pathPS::1}" | tr '[:lower:]' '[:upper:]') # Put together absolute path as used in Windows pathPS="${driveLetter}:${pathPS:1}" fi echo "$pathPS" } sendFiles() { params="base64zip=hash" workingDir="$(pwd)" tmpDir="/tmp/pairdrop-cli-temp/" tmpDirPS="\$env:TEMP/pairdrop-cli-temp/" index=0 directoryBaseNamesUnix=() directoryPathsUnix=() filePathsUnix=() directoryCount=0 fileCount=0 pathsPS="" #create tmp folder if it does not exist already if [[ ! -d "$tmpDir" ]]; then mkdir "$tmpDir" fi for arg in "$@"; do echo "$arg" [[ ! -e "$arg" ]] && echo "The given path $arg does not exist." && exit # Remove trailing slash from directory arg="${arg%/}" # get absolute path and basename of file/directory absolutePath=$(realpath "$arg") baseName=$(basename "$absolutePath") directoryPath=$(dirname "$absolutePath") if [[ -d $absolutePath ]]; then # is directory ((directoryCount+=1)) # add basename and directory path to arrays directoryBaseNamesUnix+=("$baseName") directoryPathsUnix+=("$directoryPath") else # is file ((fileCount+=1)) absolutePathUnix=$absolutePath # append new path and separate paths with space filePathsUnix+=("$absolutePathUnix") fi # Prepare paths for PowerShell on Windows if [[ $OS == "Windows" ]];then absolutePathPS=$(escapePSPath "$absolutePath") # append new path and separate paths with commas pathsPS+="'${absolutePathPS}', " fi # set fileNames on first loop if [[ $index == 0 ]]; then baseNameU=${baseName// /_} # Prevent baseNameU being empty for hidden files by removing the preceding dot if [[ $baseNameU == .* ]]; then baseNameU=${baseNameU#.*} fi # only use trunk of basename "document.txt" -> "document" baseNameTrunk=${baseNameU%.*} # remove all special characters zipName=${baseNameTrunk//[^a-zA-Z0-9_]/} zipToSendAbs="${tmpDir}${zipName}_pairdrop.zip" wrapperZipAbs="${tmpDir}${zipName}_pairdrop_wrapper.zip" if [[ $OS == "Windows" ]];then zipToSendAbsPS="${tmpDirPS}${zipName}_pairdrop.zip" wrapperZipAbsPS="${tmpDirPS}${zipName}_pairdrop_wrapper.zip" fi fi ((index+=1)) # somehow ((index++)) stops the script done # Prepare paths for PowerShell on Windows if [[ $OS == "Windows" ]];then # remove trailing comma pathsPS=${pathsPS%??} fi echo "Preparing ${fileCount} files and ${directoryCount} directories..." # if arguments include files only -> zip files once so files it is unzipped by sending browser # if arguments include directories -> wrap first zip in a second wrapper zip so that after unzip by sending browser a zip file is sent to receiver # # Preferred zip structure: # pairdrop "d1/d2/d3/f1" "../../d4/d5/d6/f2" "d7/" "../d8/" "f5" # zip structure: pairdrop.zip # |-f1 # |-f2 # |-d7/ # |-d8/ # |-f5 # -> truncate (relative) paths but keep directories [[ -e "$zipToSendAbs" ]] && echo "Cannot overwrite $zipToSendAbs. Please remove first." && exit if [[ $OS == "Windows" ]];then # Powershell does preferred zip structure natively powershell.exe -Command "Compress-Archive -Path ${pathsPS} -DestinationPath ${zipToSendAbsPS}" else # Workaround needed to create preferred zip structure on unix systems # Create zip file with all single files by junking the path if [[ $fileCount != 0 ]]; then zip -q -b /tmp/ -j -0 -r "$zipToSendAbs" "${filePathsUnix[@]}" fi # Add directories recursively to zip file index=0 while [[ $index < $directoryCount ]]; do # workaround to keep directory name but junk the rest of the paths # cd to path above directory cd "${directoryPathsUnix[index]}" # add directory to zip without junking the path zip -q -b /tmp/ -0 -u -r "$zipToSendAbs" "${directoryBaseNamesUnix[index]}" # cd back to working directory cd "$workingDir" ((index+=1)) # somehow ((index++)) stops the script done fi # If directories are included send as zip # -> Create additional zip wrapper which will be unzipped by the sending browser if [[ "$directoryCount" != 0 ]]; then echo "Bundle as ZIP file..." # Prevent filename from being absolute zip path by "cd"ing to directory before zipping zipToSendDirectory=$(dirname "$zipToSendAbs") zipToSendBaseName=$(basename "$zipToSendAbs") cd "$zipToSendDirectory" [[ -e "$wrapperZipAbs" ]] && echo "Cannot overwrite $wrapperZipAbs. Please remove first." && exit if [[ $OS == "Windows" ]];then powershell.exe -Command "Compress-Archive -Path ${zipToSendBaseName} -DestinationPath ${wrapperZipAbsPS} -CompressionLevel Optimal" else zip -q -b /tmp/ -0 "$wrapperZipAbs" "$zipToSendBaseName" fi cd "$workingDir" # remove inner zip file and set wrapper as zipToSend (do not differentiate between OS as this is done via Git Bash on Windows) rm "$zipToSendAbs" zipToSendAbs=$wrapperZipAbs fi # base64 encode zip file if [[ $OS == "Mac" ]];then hash=$(base64 -i "$zipToSendAbs") else hash=$(base64 -w 0 "$zipToSendAbs") fi # remove zip file (do not differentiate between OS as this is done via Git Bash on Windows) rm "$zipToSendAbs" if [[ $(echo -n "$hash" | wc -m) -gt 1000 ]];then params="base64zip=paste" # Copy $hash to clipboard if [[ $OS == "Windows" || $OS == "WSL" || $OS == "WSL2" ]];then echo -n "$hash" | clip.exe elif [[ $OS == "Mac" ]];then echo -n "$hash" | pbcopy elif [ -n "$WAYLAND_DISPLAY" ]; then # Wayland if ! command -v wl-copy &> /dev/null; then echo -e "You need to install 'wl-copy' to send bigger filePathsUnix from cli" echo "Try: sudo apt install wl-clipboard" exit 1 fi # Workaround to prevent use of Pipe which has a max letter limit echo -n "$hash" > /tmp/pairdrop-cli-temp/pairdrop_hash_temp wl-copy < /tmp/pairdrop-cli-temp/pairdrop_hash_temp rm /tmp/pairdrop-cli-temp/pairdrop_hash_temp else # X11 if ! command -v xclip &> /dev/null; then echo -e "You need to install 'xclip' to send bigger filePathsUnix from cli" echo "Try: sudo apt install xclip" exit 1 fi echo -n "$hash" | xclip -sel c fi hash= fi openPairDrop exit } ############################################################ ############################################################ # Main program # ############################################################ ############################################################ script_path="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" pushd . > '/dev/null'; script_path="${BASH_SOURCE[0]:-$0}"; while [ -h "$script_path" ]; do cd "$( dirname -- "$script_path"; )"; script_path="$( readlink -f -- "$script_path"; )"; done cd "$( dirname -- "$script_path"; )" > '/dev/null'; script_path="$( pwd; )"; popd > '/dev/null'; config_path="${script_path}/.pairdrop-cli-config" [ ! -f "$config_path" ] && specifyDomain "https://pairdrop.net/" && [ ! -f "$config_path" ] && echo "Could not create config file. Add 'DOMAIN=https://pairdrop.net/' to a file called .pairdrop-cli-config in the same file as this 'pairdrop' bash file" [ ! -f "$config_path" ] || export "$(grep -v '^#' "$config_path" | xargs)" setOs ############################################################ # Process the input options. Add options as needed. # ############################################################ # Get the options # open PairDrop if no options are given [[ $# -eq 0 ]] && openPairDrop && exit # display help and exit if first argument is "--help" or more than 2 arguments are given [ "$1" == "--help" ] && help && exit while getopts "d:ht:*" option; do case $option in d) # specify domain - show help and exit if too many arguments [[ $# -gt 2 ]] && help && exit specifyDomain "$2" exit;; t) # Send text - show help and exit if too many arguments [[ $# -gt 2 ]] && help && exit sendText exit;; h | ?) # display help and exit help exit;; esac done # Send file(s) sendFiles "$@"