383 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Bash
		
	
	
	
			
		
		
	
	
			383 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Bash
		
	
	
	
#!/bin/bash
 | 
						|
set -e
 | 
						|
 | 
						|
# PairDrop version when this file was last changed
 | 
						|
version="v1.10.4"
 | 
						|
 | 
						|
############################################################
 | 
						|
# 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 ${version}"
 | 
						|
}
 | 
						|
 | 
						|
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"
 | 
						|
 | 
						|
# If config file does not exist, try to create it. If it fails log error message and exit
 | 
						|
[ ! -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 (${script_path})" &&
 | 
						|
  exit
 | 
						|
 | 
						|
# Read config variables
 | 
						|
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 "$@"
 |