How to get the file URL from file name in Google S

2020-02-16 05:38发布

问题:

I would like to create a custom function that pulls a Drive URL from a file name in Google Sheets.

So, using the code below:

  1. If I have a valid file name in cell A1
  2. The function =getFile(A1) would return the URL

    • When I run my script from within the script editor, the return value works.
    • When I run the function getFile() from within my sheet, I get the error below.

My code:

function getFile(cell) {

  var filename = encodeURI(cell);

  var url = "https://www.googleapis.com/drive/v3/files?fields=files(id,name)&q=name+contains+'" + filename + "' and trashed=false";
  var params = {
    method: "GET",
    headers: {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
    muteHttpExceptions: true
  };
  var res = UrlFetchApp.fetch(url, params).getContentText();
  var json = JSON.parse(res);
  return res; // outputs response below
  if(json){
    var objFiles = json.files[0];
    var fileID = objFiles.id
    var resURL = "https://docs.google.com/spreadsheets/d/" + fileID;
    Logger.log(resURL);
    //return resURL; // only works when run within script editor
  }

}

Error:

"{
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "authError",
    "message": "Invalid Credentials",
    "locationType": "header",
    "location": "Authorization"
   }
  ],
  "code": 401,
  "message": "Invalid Credentials"
 }
}
"

I'm guessing something's wrong with my Auth token. Can someone direct me to resolving this? Thanks in advance!

回答1:

Custom functions runs as if run by a anonymous animal(user). ScriptApp.getOAuthToken will return a anonymous token without the required scopes. What you're attempting is not possible, unless the file in question is public.

References:

  • Custom functions permissions
  • Custom functions access services


回答2:

This may be a solution for some needs.

My particular need was: loop through a column of file names and pull the Google Docs URL at a set interval. The code below just loops through filenames in "Column A" of "My Sheet" and returns the value into the adjacent cell of "Column B" (starting at row 2 because I had column headers). I'm not concerned about security because I'm only referencing internal organization files.

To get the code below to work you need to:

  1. Google Sheet Doc Nav > Tools > Script Editor
  2. Create a .gs file & input code below (Referencing The Respective Sheet
  3. Within Script Editor > Edit > Current Project’s Triggers > Name Your Project
  4. Within Script Editor > Edit > Current Project’s Triggers > Click on modal link “No triggers set up. Click here to add one now” > set your time-based trigger (reference replaceFileColumn in the select field within that modal)

My mistake was: thinking that I needed to use a custom function in each cell to do so. (I still don't fully understand the auth reasons why this wouldn't work, so if anyone could explain in lay-man's terms that would be fabulous; my solution is just a workaround for expediency's sake).

In my spreadsheet I have a time-driven trigger calling replaceFileColumn()

Hope this helps someone!

function getMyFile(cell) {
  var filename = encodeURI(cell);
  var files = DriveApp.getFilesByName(cell);
  while (files.hasNext()) {
    var file = files.next();
    if(file){
      var fileValue = file.getUrl();
      return(fileValue);
    };
  };
}

function replaceFileColumn() {
  var spreadsheet = SpreadsheetApp.getActive().getSheetByName('My Sheet');
  var range = spreadsheet.getRange("A2:A");
  var range_update = spreadsheet.getRange("B2:B");
  var values = range.getValues();
  for (var i = 0; i < values.length; i++) {
    var fileName = values[i];
    var getFileUrl = getMyFile(fileName);
    values[i][0] = getFileUrl;
  }
  range_update.setValues(values);
}


回答3:

@I'-'I's answer is correct. Although I'm not sure whether this is what you want, how about this workaround? I have also experienced the same issue. At that time, I had used the following workaround.

  • Set and get access token using PropertiesService.

The flow is as follows.

Flow:

  1. Set access token every 1 hour by the time-driven trigger.
    • By this, the access token is updated every 1 hour. Because the expiration time of access token is 1 hour.
  2. When the custom function is run, it gets the access token using PropertiesService.
    • By this, the access token can be used.

Modified script:

Please install this function as the time-driven trigger. Of course, you can run manually this function.

function setAccessToken() {
  PropertiesService.getScriptProperties().setProperty("accessToken", ScriptApp.getOAuthToken());
}

In your script, please modify as follows.

From:
var params = {
  method: "GET",
  headers: {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
  muteHttpExceptions: true
};
To:
var params = {
  method: "GET",
  headers: {"Authorization": "Bearer " + PropertiesService.getScriptProperties().getProperty("accessToken")},
  muteHttpExceptions: true
};

Note:

  • In this case, the owner of access token is the owner of project.
  • I think that this can be also used by CacheService.

Reference:

  • PropertiesService