Last active
May 28, 2024 07:27
-
-
Save SKGleba/b281d0480ef70707acccbfa143829c63 to your computer and use it in GitHub Desktop.
Google Drive API wrapper for basic ops
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# !/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