Skip to content

Instantly share code, notes, and snippets.

@SKGleba
Last active May 28, 2024 07:27
Show Gist options
  • Save SKGleba/b281d0480ef70707acccbfa143829c63 to your computer and use it in GitHub Desktop.
Save SKGleba/b281d0480ef70707acccbfa143829c63 to your computer and use it in GitHub Desktop.
Google Drive API wrapper for basic ops
# !/bin/bash
# Google Drive API wrapper
# Create a project in Google Cloud Console, enable Drive API for chosen scopes, create OAuth 2.0 credentials, save them as secret.json in the same directory as this script
# and run this script with the 'init' argument to get the refresh token
SCRIPT_DIR=$(dirname $0)
RTOKEN_FILE="$SCRIPT_DIR/rtoken.json" # Refresh token file
TTOKEN_FILE="$SCRIPT_DIR/ttoken.json" # Temporary token file
SECRET_FILE="$SCRIPT_DIR/secret.json" # Secrets
RFILES_LIST="$SCRIPT_DIR/rfiles.csv" # remote files list
RUSERS_LIST="$SCRIPT_DIR/rusers.csv" # cached list of users and their permission ids
LAST_LOG="$SCRIPT_DIR/last.log" # last log file
USE_REMOTE_CACHE=0 # sync the remote cache with the local cache (rusers.csv)
REMOTE_CACHE_FILE="curling_cache.csv" # remote cache file
REMOTE_APEX_DIR="" # remote apex directory
DRIVE_ID="" # (shared) drive id
SCOPE="https://www.googleapis.com/auth/drive.file" # scopes, separated by space
function obtain_rt() {
[[ "$1" != "" ]] && SCOPE=$(echo "$1" | sed 's/,/ /g')
local client_id=$(jq -r '.web.client_id' $SECRET_FILE)
local client_secret=$(jq -r '.web.client_secret' $SECRET_FILE)
local scopes=$(echo $SCOPE | sed 's/ /%20/g')
echo "Go to the following URL and authorize Curling:"
echo "https://accounts.google.com/o/oauth2/auth?client_id=$client_id&redirect_uri=http://localhost&scope=$scopes&response_type=code&access_type=offline&prompt=consent"
echo "Enter the code (from the return uri):"
read code
curl -s -d "code=$code&client_id=$client_id&client_secret=$client_secret&redirect_uri=http://localhost&grant_type=authorization_code" "https://accounts.google.com/o/oauth2/token" > $RTOKEN_FILE
}
function revoke_rt() {
local refresh_token=$(jq -r '.refresh_token' $RTOKEN_FILE)
curl -s -d "token=$refresh_token" "https://accounts.google.com/o/oauth2/revoke"
rm $RTOKEN_FILE
rm $TTOKEN_FILE
}
function obtain_tt() {
local client_id=$(jq -r '.web.client_id' $SECRET_FILE)
local client_secret=$(jq -r '.web.client_secret' $SECRET_FILE)
local refresh_token=$(jq -r '.refresh_token' $RTOKEN_FILE)
curl -s -d "client_id=$client_id&client_secret=$client_secret&refresh_token=$refresh_token&grant_type=refresh_token" "https://accounts.google.com/o/oauth2/token" > $TTOKEN_FILE
}
function ensure_valid_ttoken() {
# obtain the temp token if its older than 3600 seconds
if [ ! -f $TTOKEN_FILE ]; then
obtain_tt
else
local now=$(date +%s)
local ttoken=$(ls -l --time-style=+%s $TTOKEN_FILE | awk '{print $6}')
if [ $((now - ttoken)) -gt 3598 ]; then
obtain_tt
fi
fi
if [ $(jq -r '.access_token' $TTOKEN_FILE) == "null" ]; then
echo "Error: Invalid token"
exit 1
fi
}
function get_file_list_raw() {
ensure_valid_ttoken
local access_token=$(jq -r '.access_token' $TTOKEN_FILE)
local sdrive_or=""
[[ ! -z $DRIVE_ID ]] && sdrive_or="&corpora=drive&driveId=$DRIVE_ID&includeItemsFromAllDrives=true&supportsAllDrives=true"
curl -s -H "Authorization: Bearer $access_token" "https://www.googleapis.com/drive/v3/files?fields=files(id,name,mimeType)$sdrive_or" | jq -r '.files[] | "\(.name),\(.id),\(.mimeType)"'
}
function list_directory_full() {
local directory_id=$1
[[ -z $directory_id ]] && directory_id="root"
local directory_name=$2 # optional for recursion
ensure_valid_ttoken
local access_token=$(jq -r '.access_token' $TTOKEN_FILE)
local sdrive_or=""
if [ ! -z $DRIVE_ID ]; then
sdrive_or="&corpora=drive&driveId=$DRIVE_ID&includeItemsFromAllDrives=true&supportsAllDrives=true"
[[ "$directory_id" == "root" ]] && directory_id=$DRIVE_ID
fi
local list_csv=$(curl -s -H "Authorization: Bearer $access_token" "https://www.googleapis.com/drive/v3/files?q='$directory_id'+in+parents&fields=files(id,name,mimeType)$sdrive_or" | jq -r '.files[] | "\(.name),\(.id),\(.mimeType)"')
local list=""
while IFS=, read -r name id mime_type; do
if [[ -z $name ]]; then
continue
fi
if [ "$mime_type" == "application/vnd.google-apps.folder" ]; then
list+="$directory_name$name/,$id,$mime_type"
local dir_content=$(list_directory_full $id "$directory_name$name/")
[[ ! -z $dir_content ]] && list+="\n$dir_content"
else
list+="$directory_name$name,$id,$mime_type"
fi
list+=$'\n'
done <<< "$list_csv"
echo -en "$list"
}
function refresh_file_list() {
local apex_dir=$1
local list=$(list_directory_full "root")
[[ ! -z $apex_dir ]] && list=$(echo "$list" | grep "^$apex_dir/")
echo -en "$list" > $RFILES_LIST
}
function upload_file() {
local file=$1
local remote_path=$2 # eg "folder1/folder2/file.txt"
local overrides=$3 # eval-ed overrides
echo "Uploading $file to $remote_path"
ensure_valid_ttoken
local access_token=$(jq -r '.access_token' $TTOKEN_FILE)
local mime_type=$(file --mime-type -b $file)
local folder=""
local folder_id=""
local file_name=""
local folder_id_param=""
if [ ! -z $remote_path ]; then
file_name=$(basename $remote_path)
folder=$(dirname $remote_path)
folder_id=$(cat $RFILES_LIST | grep "^$folder/," | cut -d, -f2)
[[ ! -z $folder_id ]] && folder_id_param=", parents: ['$folder_id']"
fi
[[ -z $file_name ]] && file_name=$(basename $file)
if [ "$folder_id_param" == "" ] && [ ! -z $DRIVE_ID ]; then
folder_id_param=", parents: ['$DRIVE_ID']"
fi
local additional_params=""
local api_addparams=""
if [ ! -z "$overrides" ]; then
eval "$overrides"
fi
#echo "overrides: $overrides"
curl -s -X POST -H "Authorization: Bearer $access_token" -F "metadata={name : '$file_name'$folder_id_param$additional_params};type=application/json;charset=UTF-8" -F "file=@$file;type=$mime_type" "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true$api_addparams" >> $LAST_LOG
}
function download_file() {
local file_id=$1
local file_name=$2
local local_file=$3 # xd
local overrides=$4
[[ -z $local_file ]] && local_file=$file_name
ensure_valid_ttoken
local access_token=$(jq -r '.access_token' $TTOKEN_FILE)
local get_type="?alt=media"
if [ ! -z "$overrides" ]; then
eval "$overrides"
fi
get_type+="&supportsAllDrives=true"
curl -s -H "Authorization: Bearer $access_token" "https://www.googleapis.com/drive/v3/files/$file_id$get_type" > $file_name
}
function delete_file() {
local file_id=$1
ensure_valid_ttoken
local access_token=$(jq -r '.access_token' $TTOKEN_FILE)
curl -s -X DELETE -H "Authorization: Bearer $access_token" "https://www.googleapis.com/drive/v3/files/$file_id?supportsAllDrives=true" >> $LAST_LOG
}
function update_file() {
local file_id=$1
local file=$2
local remote_path=$3 # eg "folder1/folder2/file.txt"
local overrides=$4 # eval-ed overrides
ensure_valid_ttoken
local access_token=$(jq -r '.access_token' $TTOKEN_FILE)
local mime_type=$(file --mime-type -b $file)
local file_name=""
if [ ! -z $remote_path ]; then
file_name=$(basename $remote_path)
fi
[[ -z $file_name ]] && file_name=$(basename $file)
local additional_params=""
local api_addparams=""
if [ ! -z "$overrides" ]; then
eval "$overrides"
fi
#echo "overrides: $overrides"
curl -s -X PATCH -H "Authorization: Bearer $access_token" -F "metadata={name : '$file_name'$additional_params};type=application/json;charset=UTF-8" -F "file=@$file;type=$mime_type" "https://www.googleapis.com/upload/drive/v3/files/$file_id?uploadType=multipart&supportsAllDrives=true$api_addparams" >> $LAST_LOG
}
function create_directory() {
local folder_path=$1
ensure_valid_ttoken
local access_token=$(jq -r '.access_token' $TTOKEN_FILE)
local folder=""
local pfolder=""
local pfolder_id=""
local pfolder_id_param=""
folder=$(basename $folder_path)
pfolder=$(dirname $folder_path)
if [ "$pfolder" != "." ]; then
pfolder_id=$(cat $RFILES_LIST | grep "^$pfolder/," | cut -d, -f2)
[[ ! -z $pfolder_id ]] && pfolder_id_param=", parents: ['$pfolder_id']"
elif [ ! -z $DRIVE_ID ]; then
pfolder_id_param=", parents: ['$DRIVE_ID']"
fi
local sdrive_or=""
[[ ! -z $DRIVE_ID ]] && sdrive_or="?supportsAllDrives=true&driveId=$DRIVE_ID"
curl -s -X POST -H "Authorization: Bearer $access_token" -H "Content-Type: application/json" -d "{ name : '$folder', mimeType : 'application/vnd.google-apps.folder'$pfolder_id_param }" "https://www.googleapis.com/drive/v3/files$sdrive_or" >> $LAST_LOG
}
function delete_directory() {
local folder_id=$1
ensure_valid_ttoken
local access_token=$(jq -r '.access_token' $TTOKEN_FILE)
curl -s -X DELETE -H "Authorization: Bearer $access_token" "https://www.googleapis.com/drive/v3/files/$folder_id?supportsAllDrives=true" >> $LAST_LOG
}
function get_file_acl_raw() {
local file_id=$1
ensure_valid_ttoken
local access_token=$(jq -r '.access_token' $TTOKEN_FILE)
curl -s -H "Authorization: Bearer $access_token" "https://www.googleapis.com/drive/v3/files/$file_id/permissions?supportsAllDrives=true" | jq
}
function get_file_acl() {
local file_id=$1
ensure_valid_ttoken
local permissions=$(get_file_acl_raw $file_id | jq -r '.permissions[] | "\(.id),\(.role),\(.type)"')
# translate permission id to email
echo "$permissions" | while IFS=, read -r id role type; do
local email=$(cat $RUSERS_LIST | grep $id | cut -d, -f1)
[[ -z $email ]] && email="unknown"
echo "$email,$id,$role,$type"
done
}
function share_file() {
local file_id=$1
local email=$2
local role=$3 # reader, writer, commenter, owner
[[ -z $role ]] && role="reader"
local type=$4 # user, group, domain, anyone
[[ -z $type ]] && type="user"
ensure_valid_ttoken
local access_token=$(jq -r '.access_token' $TTOKEN_FILE)
local resp=$(curl -s -X POST -H "Authorization: Bearer $access_token" -H "Content-Type: application/json" -d "{ role : '$role', type : '$type', emailAddress : '$email' }" "https://www.googleapis.com/drive/v3/files/$file_id/permissions?supportsAllDrives=true")
echo $resp >> $LAST_LOG
# we dont have access to the emailAddress, so we cache the email and permission id
local permission_id=$(echo $resp | jq -r '.id')
if [ $(cat $RUSERS_LIST | grep -c $email) -eq 0 ]; then
echo "$email,$permission_id" >> $RUSERS_LIST
else
sed -i "s/$email/$email,$permission_id/" $RUSERS_LIST
fi
}
function unshare_file_direct() {
local file_id=$1
local permission_id=$2
ensure_valid_ttoken
local access_token=$(jq -r '.access_token' $TTOKEN_FILE)
curl -s -X DELETE -H "Authorization: Bearer $access_token" "https://www.googleapis.com/drive/v3/files/$file_id/permissions/$permission_id?supportsAllDrives=true"
sed -i "s/,$permission_id//" $RUSERS_LIST
}
function unshare_file() {
local file_id=$1
local email=$2
local permissions=$(get_file_acl $1)
local permission_id=$(echo "$permissions" | grep $email | cut -d, -f2)
[[ -z $permission_id ]] && echo "Error: Permission not found for $email" && exit 1
unshare_file_direct $file_id $permission_id
}
function list_shared_drives() {
ensure_valid_ttoken
local access_token=$(jq -r '.access_token' $TTOKEN_FILE)
curl -sH "Authorization: Bearer $access_token" "https://www.googleapis.com/drive/v3/drives" | jq -r '.drives[] | "\(.name),\(.id)"'
}
# $1: op
# $2: file
function main() {
local op=$1
local file=$2
local remote_path=$3
local opt_overrides=$4
echo "On main with args: $@" >> $LAST_LOG
if [ -z $op ]; then
echo "Usage: $0 [op] <args>"
echo "ops:"
echo " init : intialize oauth, run it once"
echo " list <folder> : list files"
echo " upload [file] <dest> <overrides> : upload a file"
echo " upload_csv [file] <dest> : upload a csv file and convert it to google sheets"
echo " download [file] <dest> <overrides> : download a file"
echo " download_csv [file] <dest> : download a google sheet and convert it to csv"
echo " delete [file] : delete a file"
echo " update [file] <dest> <overrides> : update a file"
echo " update_csv [file] <dest> : update a google sheet from a csv file"
echo " share [file] [email] <role> <type> : share a file"
echo " unshare [file] [email] : unshare a file"
echo " get_acl [file] : get acl of a file"
echo " sync <in/out> : sync the remote cache with the local cache"
echo " mkdir [folder] : create a folder"
echo " rmdir [folder] : delete a folder"
exit 1
fi
if [ "$op" == "init" ]; then
[[ -f $RTOKEN_FILE ]] && revoke_rt
obtain_rt $2
[[ "$REMOTE_APEX_DIR" != "" ]] && create_directory $REMOTE_APEX_DIR
exit 0
fi
if [ ! -f $SECRET_FILE ]; then
echo "Error: Secret file not found"
exit 1
fi
if [ ! -f $RTOKEN_FILE ]; then
echo "Error: Refresh token file not found, run '$0 init' first"
exit 1
fi
refresh_file_list $REMOTE_APEX_DIR
if [ "$op" == "list" ] && [ "$file" == "" ]; then
cat $RFILES_LIST | awk -F, '{print $1}' | grep -v "^$REMOTE_APEX_DIR/$" | sed "s|^$REMOTE_APEX_DIR/||"
exit 0
fi
if [ "$op" == "sync" ]; then
if [ "$2" == "in" ]; then
main "download" $RUSERS_LIST $REMOTE_CACHE_FILE
elif [ "$2" == "out" ]; then
main "update" $RUSERS_LIST $REMOTE_CACHE_FILE
else
echo "Error: Invalid sync operation"
exit 1
fi
exit 0
fi
# Ułaga: wycina z nazwy pliku rozszerzenie
if [ "$op" == "upload_csv" ]; then
opt_overrides="mime_type=\"text/csv\"; api_addparams=\"&convert=true\"; additional_params=\", mimeType: 'application/vnd.google-apps.spreadsheet'\""
op="upload"
elif [ "$op" == "download_csv" ]; then
opt_overrides="get_type=\"/export?mimeType=text/csv\""
op="download"
elif [ "$op" == "update_csv" ]; then
opt_overrides="mime_type=\"text/csv\"; api_addparams=\"&convert=true\"; additional_params=\", mimeType: 'application/vnd.google-apps.spreadsheet'\""
op="update"
fi
# non-file ops
if [ "$op" == "list_shared_drives" ]; then
list_shared_drives
exit 0
elif [ "$op" == "list_raw" ]; then
get_file_list_raw
exit 0
fi
if [ "$3" == "" ] || [ "$op" == "share" ] || [ "$op" == "unshare" ]; then
remote_path=$file
[[ "$op" == "upload" ]] && remote_path=$(basename $file)
fi
[[ "$REMOTE_APEX_DIR" != "" ]] && remote_path="$REMOTE_APEX_DIR/$remote_path"
local file_id=$(cat $RFILES_LIST | grep "^$remote_path," | cut -d, -f2)
if [ -z $file_id ]; then
if [ "$op" == "update" ]; then
echo "File does not exist, uploading"
op="upload"
fi
if [ "$op" != "upload" ] && [ "$op" != "mkdir" ]; then
echo "Error: File not found : $remote_path"
exit 1
fi
else
if [ "$op" == "upload" ]; then
echo "File already exists, updating"
op="update"
fi
fi
case $op in
"upload")
upload_file $file $remote_path "$opt_overrides"
;;
"download")
download_file $file_id $file $remote_path "$opt_overrides"
;;
"delete")
delete_file $file_id
;;
"update")
update_file $file_id $file $remote_path "$opt_overrides"
;;
"share")
[[ $USE_REMOTE_CACHE -eq 1 ]] && main "download" $RUSERS_LIST $REMOTE_CACHE_FILE
share_file $file_id $3 $4 $5
[[ $USE_REMOTE_CACHE -eq 1 ]] && main "update" $RUSERS_LIST $REMOTE_CACHE_FILE
;;
"unshare_direct")
[[ $USE_REMOTE_CACHE -eq 1 ]] && main "download" $RUSERS_LIST $REMOTE_CACHE_FILE
unshare_file_direct $file_id $3
[[ $USE_REMOTE_CACHE -eq 1 ]] && main "update" $RUSERS_LIST $REMOTE_CACHE_FILE
;;
"get_acl_raw")
[[ $USE_REMOTE_CACHE -eq 1 ]] && main "download" $RUSERS_LIST $REMOTE_CACHE_FILE
get_file_acl_raw $file_id
;;
"get_acl")
[[ $USE_REMOTE_CACHE -eq 1 ]] && main "download" $RUSERS_LIST $REMOTE_CACHE_FILE
get_file_acl $file_id
;;
"unshare")
[[ $USE_REMOTE_CACHE -eq 1 ]] && main "download" $RUSERS_LIST $REMOTE_CACHE_FILE
unshare_file $file_id $3
[[ $USE_REMOTE_CACHE -eq 1 ]] && main "update" $RUSERS_LIST $REMOTE_CACHE_FILE
;;
"mkdir")
create_directory $remote_path
;;
"list")
cat $RFILES_LIST | grep -v "^$remote_path," | grep "^$remote_path" | sed "s|^$remote_path||" | awk -F, '{print $1}'
;;
"rmdir")
delete_directory $file_id
;;
*)
echo "Error: Invalid operation"
exit 1
;;
esac
return 0
}
echo "Running on: $(date)" > $LAST_LOG
main $@
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment