2

I'm trying to get the direct download link for a file in Google Drive using the Google Drive API (v3), but I'm also trying to do this without making the file publicly shared. Here is what I've tried:

https://www.googleapis.com/drive/v3/files/**FILE_ID**?alt=media&supportsAllDrives=True&includeItemsFromAllDrives=True&key=**API_KEY**

Now this works if the file is shared publicly. But when the file isn't shared publicly you get this message:

{'error': {'errors': [{'domain': 'global', 'reason': 'notFound', 'message': 'File not found: 10k0Qogwcz7k0u86m7W2HK-LO7bk8xAF8.', 'locationType': 'parameter', 'location': 'fileId'}], 'code': 404, 'message': 'File not found: 10kfdsfjDHJ38-UHJ34D82.'}}

After doing some googling I found a post on stack overflow saying that I need to add a request header with my access token, but this doesn't work and the application just hangs

Here is the full code:

### SETTING UP GOOGLE API
scopes = 'https://www.googleapis.com/auth/drive'
store = file.Storage('storage.json')
credentials = store.get()
if not credentials or credentials.invalid:
    flow = client.flow_from_clientsecrets('client_secret.json', scopes)
    credentials = tools.run_flow(flow, store)
accessToken = credentials.access_token
refreshToken = credentials.refresh_token
drive = build('drive', 'v3', credentials=credentials)


### SENDING REQUEST
    req_url = "https://www.googleapis.com/drive/v3/files/"+file_id+"?alt=media&supportsAllDrives=True&includeItemsFromAllDrives=True&key="+GOOGLE_API_KEY
    headers={'Authorization': 'Bearer %s' % accessToken}
    request_content = json.loads((requests.get(req_url)).content)
    print(request_content)

------------------ EDIT: ------------------

I've gotten really close to an answer, but I can't seem to figure out why this doesn't work.

So I've figured out previously that alt=media generates a download link for the file, but when the file is private this doesn't work.

I just discovered that you can add &access_token=.... to access private files, so I came up with this API call:

https://www.googleapis.com/drive/v3/files/**FILE_ID**?supportsAllDrives=true&alt=media&access_token=**ACCESS_TOKEN**&key=**API_KEY**

When I go to that url on my browser I get this message:

We're sorry...
... but your computer or network may be sending automated queries. To protect our users, we can't process your request right now.

I find this confusing because if I remove alt=media, I am able to call on that request and I get some metadata about the file.

Tanaike
  • 181,128
  • 11
  • 97
  • 165
eliasbenb
  • 169
  • 13

1 Answers1

1

I believe your goal as follows.

  • From I'm trying to get the direct download link for a file in Google Drive using the Google Drive API (v3),, I understand that you want to retrieve webContentLink.
  • The file that you want to retrieve the webContentLink is the files except for Google Docs files.
  • You have already been able to get the file metadata using Drive API. So your access token can be used for this.

Modification points:

  • When the file is not shared, the API key cannot be used. By this, https://www.googleapis.com/drive/v3/files/**FILE_ID**?alt=media&supportsAllDrives=True&includeItemsFromAllDrives=True&key=**API_KEY** returns File not found. I think that the reason of this issue is due to this.
  • When I saw your script in your question, it seems that you want to download the file content.
  • In your script, headers is not used. So in this case, the access token is not used.
  • In the method of "Files: get", there is no includeItemsFromAllDrives.
  • In your script, I think that an error occurs at credentials.access_token. How about this? If my understanding is correct, please try to modify to accessToken = credentials.token.
  • In Drive API v3, the default response values don't include webContentLink. So in this case, the field value is required to be set like fields=webContentLink.

When your script is modified, it becomes as follows.

Modified script:

file_id = '###'  # Please set the file ID.
req_url = "https://www.googleapis.com/drive/v3/files/" + file_id + "?supportsAllDrives=true&fields=webContentLink"
headers = {'Authorization': 'Bearer %s' % accessToken}
res = requests.get(req_url, headers=headers)
obj = res.json()
print(obj.get('webContentLink'))

Or, you can use drive = build('drive', 'v3', credentials=credentials) in your script, you can also use the following script.

file_id = '###'  # Please set the file ID.
drive = build('drive', 'v3', credentials=credentials)
request = drive.files().get(fileId=file_id, supportsAllDrives=True, fields='webContentLink').execute()
print(request.get('webContentLink'))

Note:

  • In this modified script,
    • When the file is in the shared Drive and you don't have the permissions for retrieving the file metadata, an error occurs.
    • When your access token cannot be used for retrieving the file metadata, an error occurs.

So please be careful above points.

  • When * is used for fields, all file metadata can be retrieved.

Reference:

Added:

  • You want to download the binary data from the Google Drive by the URL.
  • The file size is large like "2-10 gigabytes".

In this case, unfortunately, webContentLink cannot be used. Because in the case of the such large file, webContentLink is redirected. So I think that the method that the file is publicly shared and use the API key is suitable for achieving your goal. But, you cannot publicly shared the file.

From this situation, as a workaround, I would like to propose to use this method. This method is "One Time Download for Google Drive". At Google Drive, when the publicly shared file is downloaded, even when the permission of file is deleted under the download, the download can be run. This method uses this.

Flow

In this sample script, the API key is used.

  1. Request to Web Apps with the API key and the file ID you want to download.
  2. At Web Apps, the following functions are run.
    • Permissions of file of the received file ID are changed. And the file is started to be publicly shared.
    • Install a time-driven trigger. In this case, the trigger is run after 1 minute.
      • When the function is run by the time-driven trigger, the permissions of file are changed. And sharing file is stopped. By this, the shared file of only one minute can be achieved.
  3. Web Apps returns the endpoint for downloading the file of the file ID.
    • After you got the endpoint, please download the file using the endpoint in 1 minute. Because the file is shared for only one minute.

Usage:

1. Create a standalone script

In this workaround, Google Apps Script is used as the server side. Please create a standalone script. If you want to directly create it, please access to https://script.new/. In this case, if you are not logged in Google, the log in screen is opened. So please log in to Google. By this, the script editor of Google Apps Script is opened.

2. Set sample script of Server side

Please copy and paste the following script to the script editor. At that time, please set your API key to the variable of key in the function doGet(e).

Here, please set your API key in the function of doGet(e). In this Web Apps, when the inputted API key is the same, the script is run.

function deletePermission() {
  const forTrigger = "deletePermission";
  const id = CacheService.getScriptCache().get("id");
  const triggers = ScriptApp.getProjectTriggers();
  triggers.forEach(function(e) {
    if (e.getHandlerFunction() == forTrigger) ScriptApp.deleteTrigger(e);
  });
  const file = DriveApp.getFileById(id);
  file.setSharing(DriveApp.Access.PRIVATE, DriveApp.Permission.NONE);
}

function checkTrigger(forTrigger) {
  const triggers = ScriptApp.getProjectTriggers();
  for (var i = 0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() == forTrigger) {
      return false;
    }
  }
  return true;
}

function doGet(e) {
  const key = "###"; // <--- API key. This is also used for checking the user.
  
  const forTrigger = "deletePermission";
  var res = "";
  if (checkTrigger(forTrigger)) {
    if ("id" in e.parameter && e.parameter.key == key) {
      const id = e.parameter.id;
      CacheService.getScriptCache().put("id", id, 180);
      const file = DriveApp.getFileById(id);
      file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
      var d = new Date();
      d.setMinutes(d.getMinutes() + 1);
      ScriptApp.newTrigger(forTrigger).timeBased().at(d).create();
      res = "https://www.googleapis.com/drive/v3/files/" + id + "?alt=media&key=" + e.parameter.key;
    } else {
      res = "unavailable";
    }
  } else {
    res = "unavailable";
  }
  return ContentService.createTextOutput(res);
}

3. Deploy Web Apps

  1. On the script editor, Open a dialog box by "Publish" -> "Deploy as web app".
  2. Select "Me" for "Execute the app as:".
  3. Select "Anyone, even anonymous" for "Who has access to the app:". This is a test case.
    • If Only myself is used, only you can access to Web Apps. At that time, please use your access token.
  4. Click "Deploy" button as new "Project version".
  5. Automatically open a dialog box of "Authorization required".
    1. Click "Review Permissions".
    2. Select own account.
    3. Click "Advanced" at "This app isn't verified".
    4. Click "Go to ### project name ###(unsafe)"
    5. Click "Allow" button.
  6. Click "OK"

4. Test run: Client side

This is a sample script of python. Before you test this, please confirm the above script is deployed as Web Apps. And please set the URL of Web Apps, the file ID and your API key.

import requests
url1 = "https://script.google.com/macros/s/###/exec"
url1 += "?id=###fileId###&key=###your API key###"
res1 = requests.get(url1)
url2 = res1.text
res2 = requests.get(url2)
with open("###sampleFilename###", "wb") as f:
    f.write(res2.content)
  • In this sample script, at first, it requests to the Web Apps using the file ID and API key, and the file is shared publicly in 1 minute. And then, the file can be downloaded. After 1 minute, the file is not publicly shared. But the download of the file can be kept.

Note:

  • When you modified the script of Web Apps, please redeploy the Web Apps as new version. By this, the latest script is reflected to the Web Apps. Please be careful this.

References:

Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • This works for its intention, but for my specific use case this doesn't work. I need to get a download link for the file (but I don't need to actually download the file). – eliasbenb Aug 12 '20 at 08:44
  • I have gotten really close the an answer, I'll be making an edit on my post about that. – eliasbenb Aug 12 '20 at 08:44
  • 1
    @eliasbenb Thank you for replying. I apologize for the inconvenience. I saw your updated question. After January, 2020, the access token cannot be used for the private file with the query parameter of `access_token=###`. Although now it seems that `access_token=###` can be used for retrieving the file metadata, from this reason, I proposed above modified script that the access token is included in the request header. [Ref](https://stackoverflow.com/q/60076883/7108653) I apologize for this. For example, if you don't want to use the headers for requesting, how about using Web Apps? – Tanaike Aug 12 '20 at 09:02
  • I'm perfectly fine with using headers for requesting. Again, your method worked, but not for my use case (basically I need a link which can be played by an external video player, e.g. VLC) – eliasbenb Aug 12 '20 at 09:12
  • 1
    @eliasbenb Thank you for replying. I could understand that you confirmed that my proposal script worked. But I cannot understand about `but not for my use case (basically I need a link which can be played by an external video player, e.g. VLC)`. Can I ask you about the detail of this? I apologize for my poor English skill. – Tanaike Aug 12 '20 at 23:41
  • so basically, I'm trying to play Google Drive videos on external video players (but without making the video publicly shared), for example [VLC](https://www.videolan.org/index.html), these video players don't accept the link generated by the method you suggested. Thank you for all your help btw. – eliasbenb Aug 13 '20 at 06:15
  • 1
    @eliasbenb Thank you for replying. For example, in that case, when the file is publicly shared, can the link of `webContentLink` be used for your situation? – Tanaike Aug 13 '20 at 06:18
  • `webContentLink` doesn't work for this situation, but I believe I could web scrape the page generated by it and get the download link using beautifulsoup and requests – eliasbenb Aug 13 '20 at 06:27
  • 1
    @eliasbenb Thank you for replying. I have to apologize for my poor English skill. In that case, can the URL of `webContentLink` be used for your situation? – Tanaike Aug 13 '20 at 06:34
  • not directly, but I'm trying it now. I'll reply in 10 minutes maybe. – eliasbenb Aug 13 '20 at 06:36
  • it doesn't work unfortunately, because you actually still need the file to be shared publicly to access the page. (you can't open the page unless you are logged in as the owner account) – eliasbenb Aug 13 '20 at 06:41
  • 1
    @eliasbenb Thank you for replying. I have to apologize for my poor English skill. At first, in order to confirm whether `webContentLink` can be used for your situation, I had asked `For example, in that case, when the file is publicly shared, can the link of webContentLink be used for your situation?` at [this replying](https://stackoverflow.com/questions/63363012/im-trying-to-get-the-direct-download-link-for-a-file-using-the-google-drive-api/63367293?noredirect=1#comment112088193_63367293). – Tanaike Aug 13 '20 at 07:19
  • no. Using `webContentLink` won't work even if the file is public. But this works with a public file `https://www.googleapis.com/drive/v3/files/**FILE_ID**?supportsAllDrives=true&alt=media&key=**API_KEY**` – eliasbenb Aug 13 '20 at 11:25
  • 1
    @eliasbenb Thank you for it. I could understand about it. In your case, the URL which can directly download the binary data is required. Here, I have a question. How much file size do you use? If it's large size, `webContentLink` is used, it is redirected even when the file is publicly shared. [Ref](https://stackoverflow.com/a/48133859/7108653) I thought that the reason of current issue might be this. – Tanaike Aug 13 '20 at 11:54
  • I think you might be right, most of the files would be 2-10 gigabytes – eliasbenb Aug 13 '20 at 11:55
  • 1
    @eliasbenb Thank you. I think that the reason of the current issue of `webContentLink` is that. But in this case, now the access token cannot be used with the query parameter like `access_token=###`. So I think that the method that the file is publicly shared and use the API key is suitable for your situation. So when you cannot publicly share the file, I cannot resolve your issue soon. So when you give me a time to think of other workaround, I'm glad. I deeply apologize for my poor skill. When I found it, I would like to tell you here. How about this? – Tanaike Aug 13 '20 at 11:59
  • 1
    don't feel obliged to find an answer, this is my problem not yours. Thank you again for trying to help, you have been really helpful – eliasbenb Aug 13 '20 at 12:01
  • 1
    Thank you for replying. When I found the workaround, I would like to tell you! Thank you, too. – Tanaike Aug 13 '20 at 12:02
  • 1
    @eliasbenb I came up with a workaround for achieving your goal. So I updated my answer. Could you please confirm it? If that was not the direction you expect, I apologize. – Tanaike Aug 14 '20 at 00:32
  • your method worked. Thank you for coming up with it! I will continue to look for a simpler method, but for the meantime I will use your method. Thank you so much! – eliasbenb Aug 15 '20 at 08:49
  • @eliasbenb Thank you for replying. I thought that after the query parameter of `access_token=###` couldn't be used, this is better way. But I deeply apologize that my proposal was not useful for your situation. This is due to my poor skill. I deeply apologize for this again. I would like to study more. – Tanaike Aug 15 '20 at 09:08
  • no no, your method is extremely helpful and it works well, I am using it right now actually. – eliasbenb Aug 15 '20 at 09:23
  • 1
    @eliasbenb Thank you for your response. – Tanaike Aug 16 '20 at 23:00