├── GoogleAuthManager.py ├── GoogleAuthV1.py ├── GoogleAuthV2.py ├── README.md ├── gDriveCopyDownloader.py ├── gDriveLibrary.py └── requirements.txt /GoogleAuthManager.py: -------------------------------------------------------------------------------- 1 | from pydrive.auth import GoogleAuth 2 | from pydrive.drive import GoogleDrive 3 | from os import path 4 | 5 | 6 | def create_credential(): 7 | from GoogleAuthV1 import auth_and_save_credential 8 | auth_and_save_credential() 9 | 10 | 11 | # Authentication + token creation 12 | def create_drive_manager(): 13 | gAuth = GoogleAuth() 14 | typeOfAuth = None 15 | if not path.exists("credentials.txt"): 16 | typeOfAuth = input("type save if you want to keep a credential file, else type nothing") 17 | bool = True if typeOfAuth == "save" or path.exists("credentials.txt") else False 18 | authorize_from_credential(gAuth, bool) 19 | drive: GoogleDrive = GoogleDrive(gAuth) 20 | return drive 21 | 22 | 23 | def authorize_from_credential(gAuth, isSaved): 24 | if not isSaved: #no credential.txt wanted 25 | from GoogleAuthV1 import auth_no_save 26 | auth_no_save(gAuth) 27 | if isSaved and not path.exists("credentials.txt"): 28 | create_credential() 29 | gAuth.LoadCredentialsFile("credentials.txt") 30 | if isSaved and gAuth.access_token_expired: 31 | gAuth.LoadCredentialsFile("credentials.txt") 32 | gAuth.Refresh() 33 | print("token refreshed!") 34 | gAuth.SaveCredentialsFile("credentials.txt") 35 | gAuth.Authorize() 36 | print("authorized access to google drive API!") 37 | -------------------------------------------------------------------------------- /GoogleAuthV1.py: -------------------------------------------------------------------------------- 1 | from pydrive.auth import GoogleAuth 2 | def auth_and_save_credential(): 3 | gAuth = GoogleAuth() 4 | gAuth.LocalWebserverAuth() 5 | gAuth.SaveCredentialsFile("credentials.txt") 6 | def auth_no_save(gAuth): 7 | gAuth.LocalWebserverAuth() -------------------------------------------------------------------------------- /GoogleAuthV2.py: -------------------------------------------------------------------------------- 1 | from pydrive.auth import GoogleAuth 2 | import webbrowser 3 | 4 | # https://realpython.com/flask-google-login/#creating-a-google-client 5 | from flask import Flask, redirect, request, url_for 6 | from flask_login import LoginManager 7 | from oauthlib.oauth2 import WebApplicationClient 8 | import requests 9 | 10 | GOOGLE_DISCOVERY_URL = ( 11 | "https://accounts.google.com/.well-known/openid-configuration" 12 | ) 13 | 14 | 15 | class FlaskModified(Flask): 16 | def run(self, host=None, port=None, debug=None, load_dotenv=True, **options): 17 | with self.app_context(): 18 | webbrowser.open('https://127.0.0.1:5000') #default url and port for Flask app 19 | pass 20 | super(FlaskModified, self).run(host=host, port=port, debug=debug, load_dotenv=load_dotenv, **options) 21 | 22 | 23 | app = FlaskModified(__name__) 24 | 25 | gAuth = GoogleAuth() 26 | auth_url = gAuth.GetAuthUrl() 27 | 28 | 29 | @app.route("/") 30 | def index(): 31 | return 'Google Login' 32 | 33 | 34 | @app.route("/login") 35 | def login(): 36 | return redirect(auth_url) 37 | 38 | 39 | @app.route("/login/callback") 40 | def callback(): 41 | code = request.args.get("code") 42 | print("code : " + code) 43 | gAuth.Auth(code) 44 | return redirect(url_for("index")) 45 | 46 | 47 | def auth_and_save_credential(): 48 | app.run(debug="true", ssl_context="adhoc") 49 | 50 | 51 | #app.run(debug="true", ssl_context="adhoc") 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # googledrive-copy-downloader 2 | This python script allow you to download google drive files even if the daily limit of download has excedeed using google drive API easily from the download link(s). 3 | It automatically copy the file to the google drive of the account that is provided, download the file and delete it, and it supports multiple links at once. 4 | 5 | 6 | ## How to use : 7 | First, you need to add a Google application credentials(clients_secrets.json) that can access the Google drive API from your account. 8 | 9 | To do that, the quickest way is to : 10 | * Go to : https://developers.google.com/drive/api/v3/quickstart/python 11 | * Click on enable drive API 12 | * Download client configuration 13 | * Rename this file client_secrets.json 14 | * Replace the one in the folder with the script (in the same place as the .exe) 15 | * (Optional) After the first launch, you can change settings in the config.ini file (download location, dowloading directly the links from the clipboard or (hasn't been tested) modify folderId with the ID of a folder on a google drive team) 16 | 17 | #### You can launch it directly using python : 18 | Use preferably python 3.6, install the requirement on the requirements.txt file with pip and launch gDriveCopyDownloader.py. 19 | #### You can use the release (Windows Only): 20 | You just need to download the .zip, and launch the exe. 21 | #### You can make yourself the .exe : 22 | Use auto-py-to-exe or pyinstaller, select gDriveCopyDownloader as the input. 23 | 24 | 25 | Then you can follow the instructions on the script. Careful if you store the credentials (to avoid log-in every time), it keeps them in plain text and it can give access to your google drive(If you don't store anything sensitive and you don't care if your google drive is hacked it isn't a problem). 26 | Also, the clipboard feature supports multiple hyperlinks. 27 | 28 | 29 | ### Common issues : 30 | * crash on startup : 31 | Check the How to use section, the client secret could be missing. 32 | * "This app has not been verified yet" or any other issues with client_secrets : 33 | Check the How to use section. 34 | * Space issues : 35 | the script could crash itself if there is not enough space on your google drive, be sure to check the bin. 36 | * Authentication problems : 37 | If you have any app that communicate through localhost:8080 (like Kodi), the authentication with google servers may not works. 38 | 39 | 40 | ### Known limitations : 41 | * clipboard feature only works on Windows. 42 | * There might be somme issues with linux(getting the default download folder for example). 43 | * Currently, the script only support links with that end with : /d/XXXX, /id=XXX and /folders/XXXX . 44 | * The script doesn't display the speed of download in real-time. 45 | * It doesn't work with google files(sheet, docs,...). 46 | 47 | 48 | -------------------------------------------------------------------------------- /gDriveCopyDownloader.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import GoogleAuthManager 4 | import gDriveLibrary 5 | import configparser 6 | import asyncio 7 | 8 | CONFIG = configparser.ConfigParser() 9 | CONFIG.optionxform=str 10 | DATABASE = CONFIG['DEFAULT'] 11 | 12 | def get_default_download_location(): 13 | if os.name == 'nt': # if windows 14 | import winreg 15 | sub_key = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders' 16 | downloads_guid = '{374DE290-123F-4565-9164-39C4925E467B}' 17 | with winreg.OpenKey(winreg.HKEY_CURRENT_USER, sub_key) as key: 18 | location = winreg.QueryValueEx(key, downloads_guid)[0] 19 | return location 20 | else: 21 | return os.path.join(os.path.expanduser('~'), 'downloads') 22 | 23 | def read_config(): 24 | CONFIG.read('config.ini') 25 | 26 | def write_config(): 27 | if 'ClipboardDetection' not in DATABASE: 28 | DATABASE['ClipboardDetection'] = "0" 29 | with open("config.ini", "w+") as file: 30 | CONFIG.write(file) 31 | 32 | def get_location(): 33 | read_config() 34 | if 'DownloadPath' in DATABASE and os.path.exists(DATABASE['DownloadPath'] ): 35 | return DATABASE['DownloadPath'] 36 | print("Default location is : " + get_default_download_location()) 37 | while 1: 38 | otherPath = input("\nif you want another location, write it here or else press enter\n") 39 | if os.path.exists(otherPath): 40 | break 41 | if otherPath == "": 42 | otherPath = get_default_download_location() 43 | break 44 | DATABASE['DownloadPath'] = otherPath 45 | write_config() 46 | return otherPath 47 | 48 | def get_folder_id(): 49 | if not 'FolderId' in DATABASE: 50 | DATABASE['FolderId'] = 'root' 51 | write_config() 52 | return DATABASE['FolderId'] 53 | 54 | def Copy_dwnld_from_links(links, drive): 55 | for fileID in gDriveLibrary.extract_files_id(links, drive): 56 | copiedFile = gDriveLibrary.copy_file(drive, fileID, get_folder_id()) 57 | gDriveLibrary.download_file(drive, copiedFile, get_location()) 58 | gDriveLibrary.delete_file(drive, copiedFile['id']) 59 | 60 | 61 | async def Check_clipboard_links(drive): #Only works for windows 62 | # Ressource : https://stackoverflow.com/questions/55698762/how-to-copy-html-code-to-clipboard-using-python 63 | if os.name == 'nt': 64 | import win32clipboard 65 | else: 66 | raise OSError("os isn't windows !") 67 | CF_HTML = win32clipboard.RegisterClipboardFormat("HTML Format") 68 | cacheClipboard = "" 69 | while 1: 70 | await asyncio.sleep(1) 71 | win32clipboard.OpenClipboard(0) 72 | try: 73 | src = win32clipboard.GetClipboardData(CF_HTML).decode("UTF-8") 74 | except TypeError:#if not html 75 | try: 76 | src = win32clipboard.GetClipboardData(win32clipboard.CF_TEXT).decode("UTF-8") 77 | except TypeError: 78 | src = "" 79 | win32clipboard.CloseClipboard() 80 | if(src != cacheClipboard): #avoid downloading infinite loop if still in clipboard 81 | cacheClipboard = src 82 | Copy_dwnld_from_links(src, drive) 83 | 84 | print(" Some infos : \n" 85 | "You can put the links directly from google drive ('https://drive.google.com') but also those behind a " 86 | "redirection(like the one from igg).\n" 87 | "Temporary files in google drive of your download will be stored on 'Temp folder for script', you can delete it " 88 | "after the downloads. \n" 89 | "Settings are stored in the config.ini file.\n" 90 | "If you want to put the google drive folder in a custom folder(to use your google team account), " 91 | "edit the FolderId field in config.ini and replace 'root' with the google drive team folder id.\n" 92 | "If you use Windows, you can go on config.ini and change ClipboardDetection to ClipboardDetection=1," 93 | "you just have to Ctrl+C the links to download and it should handle the rest.\n" 94 | "The download percentage status is updated about every 100 MB, so wait a little if it appears to be stuck.\n" 95 | "You can put multiple links at the same time\n") 96 | print("###############") 97 | print("\n Careful, if you choose to save the credentials : " 98 | " An access to your google drive will be/is stored on 'credentials.txt'. \n IT COULD BE USED BY SOMEONE ELSE" 99 | " TO ACCESS, DOWNLOAD OR DELETE FILES FROM YOUR GOOGLE DRIVE. \n I'm not responsible if anything bad happen" 100 | " in your drive.\n") 101 | print("###############") 102 | drive = GoogleAuthManager.create_drive_manager() 103 | get_location() # ask for path if not set 104 | get_folder_id() # create folder id txt file 105 | while 1: 106 | if 'ClipboardDetection' in DATABASE and DATABASE['ClipboardDetection'] == "1" and os.name == 'nt': 107 | print("The script now captures your links from your clipboard !") 108 | asyncio.run(Check_clipboard_links(drive)) 109 | else: 110 | links = input("paste the link(s) to download : \n") 111 | Copy_dwnld_from_links(links, drive) -------------------------------------------------------------------------------- /gDriveLibrary.py: -------------------------------------------------------------------------------- 1 | """ 2 | Documentation : 3 | explain client secret(how to replace it if needed) : https://developers.google.com/drive/api/v3/quickstart/python 4 | download : the ile is chucked with pieces of ~100MB, need to download at least this amount beore appearing on screen 5 | """ 6 | import re 7 | import os 8 | from googleapiclient.http import MediaIoBaseDownload 9 | from tqdm import tqdm 10 | 11 | 12 | # get download folder from user : 13 | # https://stackoverflow.com/questions/35851281/python-finding-the-users-downloads-folder 14 | 15 | 16 | def get_Gdrive_folder_id(drive, driveService, name, parent="root"): # return ID of folder, create it if missing 17 | body = {'title': name, 18 | 'mimeType': "application/vnd.google-apps.folder" 19 | } 20 | query = "title='Temp folder for script' and mimeType='application/vnd.google-apps.folder'" \ 21 | " and '" + parent + "' in parents and trashed=false" 22 | if parent != "root": 23 | query += "and driveId='" + parent + "' and includeItemsFromAllDrives=true and supportsAllDrives = true" 24 | listFolders = drive.ListFile({'q': query}) 25 | for subList in listFolders: 26 | if subList == []: # if folder doesn't exist, create it 27 | folder = driveService.files().insert(body=body).execute() 28 | break 29 | else: 30 | folder = subList[0] # if one folder with the correct name exist, pick it 31 | 32 | return folder['id'] 33 | 34 | 35 | def extract_file_ids_from_folder(drive, folderID): 36 | files = drive.ListFile({'q': "'" + folderID + "' in parents"}).GetList() 37 | fileIDs = [] 38 | for file in files : 39 | fileIDs.append(file['id']) 40 | return fileIDs 41 | 42 | 43 | def extract_files_id(links, drive): 44 | # copy of google drive file from google drive link : 45 | links = re.findall(r"\b(?:https?:\/\/)?(?:drive\.google\.com[-_&?=a-zA-Z\/\d]+)", 46 | links) # extract google drive links 47 | try: 48 | fileIDs = [re.search(r"(?<=/d/|id=|rs/).+?(?=/|$)", link)[0] for link in links] # extract the fileIDs 49 | for fileID in fileIDs: 50 | if drive.auth.service.files().get(fileId=fileID).execute()['mimeType'] == "application/vnd.google-apps.folder": 51 | fileIDs.extend(extract_file_ids_from_folder(drive, fileID)) 52 | fileIDs.remove(fileID) 53 | return fileIDs 54 | except Exception as error: 55 | print("error : " + str(error)) 56 | print("Link is probably invalid") 57 | print(links) 58 | 59 | 60 | def copy_file(drive, fileId, parentFolder = "root"): #if different parentFolder, input the folder ID 61 | fileOriginMetaData = drive.auth.service.files().get(fileId=fileId).execute() 62 | """remove 4 last characters of the original file name 63 | and add file extension(should be .rar) in case the file extension is missing from the name """ 64 | nameNoExtension = ".".join(fileOriginMetaData['originalFilename'].split(".")[:-1]) 65 | newFileName = nameNoExtension + "." + fileOriginMetaData['fileExtension'] 66 | print("Name of the file on your google drive and on the disk: " + newFileName) 67 | folderID = get_Gdrive_folder_id(drive, drive.auth.service, "Temp folder for script", parentFolder) 68 | copiedFileMetaData = {"parents": [{"id": str(folderID)}], 'title': newFileName} # ID of destination folder 69 | copiedFile = drive.auth.service.files().copy( 70 | fileId=fileId, 71 | body=copiedFileMetaData 72 | ).execute() 73 | return copiedFile 74 | 75 | 76 | def download_file(drive, file, destFolder): 77 | copiedFileMedia = drive.auth.service.files().get_media(fileId=file['id']) 78 | newFileName = file['title'] 79 | defaultPath = destFolder + "\\" + newFileName 80 | fullPath = generate_path_with_unique_filename(destFolder, newFileName) 81 | if defaultPath != fullPath : 82 | print("File already exist in the disk, new path: " + fullPath) 83 | print("Download in progress. File size: " + sizeof_file(int(file['fileSize']))) 84 | 85 | step = 104857600//1048576 86 | fsize = int(file['fileSize'])//1048576 87 | 88 | file = open(fullPath, "wb+") 89 | downloader = MediaIoBaseDownload(file, copiedFileMedia, chunksize=104857600) # change chunksize here 90 | done = False 91 | 92 | pbar = tqdm(desc='Downloading', unit='MB', total=fsize) 93 | while done is False: 94 | status, done = downloader.next_chunk() 95 | pbar.update(step) 96 | pbar.close() 97 | file.close() 98 | print("\nDownload completed : " + newFileName) 99 | 100 | def delete_file(drive, id): 101 | drive.auth.service.files().delete(fileId=id).execute() 102 | 103 | #https://stackoverflow.com/questions/1094841/reusable-library-to-get-human-readable-version-of-file-size 104 | def sizeof_file(num, suffix='B'): 105 | for unit in ['','K','M','G','Ti','Pi','Ei','Zi']: 106 | if abs(num) < 1024.0: 107 | return "%3.1f%s%s" % (num, unit, suffix) 108 | num /= 1024.0 109 | return "%.1f%s%s" % (num, 'Yi', suffix) 110 | 111 | def generate_path_with_unique_filename(folder, filename): 112 | fullpath = folder + "\\" + filename 113 | if not os.path.exists(fullpath): 114 | return fullpath 115 | fileNumber = 1 116 | while(os.path.exists(fullpath)): 117 | fullpath = folder + "\\" + str(fileNumber) + filename 118 | fileNumber+=1 119 | return fullpath -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | altgraph==0.17 2 | auto-py-to-exe==2.6.6 3 | bottle==0.12.18 4 | bottle-websocket==0.2.9 5 | cachetools==4.0.0 6 | certifi==2019.11.28 7 | cffi==1.13.2 8 | chardet==3.0.4 9 | Click==7.0 10 | cryptography==2.8 11 | Eel==0.11.0 12 | Flask==1.1.1 13 | Flask-Login==0.4.1 14 | future==0.18.2 15 | gevent==1.4.0 16 | gevent-websocket==0.10.1 17 | google-api-python-client==1.7.11 18 | google-auth==1.10.0 19 | google-auth-httplib2==0.0.3 20 | greenlet==0.4.15 21 | httplib2==0.15.0 22 | idna==2.8 23 | itsdangerous==1.1.0 24 | Jinja2==2.10.3 25 | MarkupSafe==1.1.1 26 | oauth2==1.9.0.post1 27 | oauth2client==4.1.3 28 | oauthlib==3.1.0 29 | pefile==2019.4.18 30 | pyasn1==0.4.8 31 | pyasn1-modules==0.2.7 32 | pycparser==2.19 33 | PyDrive==1.3.1 34 | PyInstaller==3.6 35 | pyOpenSSL==19.1.0 36 | pywin32==227 37 | pywin32-ctypes==0.2.0 38 | PyYAML==5.2 39 | requests==2.22.0 40 | rsa==4.0 41 | six==1.13.0 42 | tqdm==4.43.0 43 | uritemplate==3.0.1 44 | urllib3==1.25.7 45 | Werkzeug==0.16.0 46 | whichcraft==0.6.1 47 | --------------------------------------------------------------------------------