A. Masson
A. Masson

Reputation: 2477

List Google app script projects

I'm trying to list all my google script files using the Google Drive File API as documented here. However I always get back an empty [] list. I think my token and my scopes are fine as I'm getting back this from Google OAuth2:

{ "access_token": "xxxxx", "expires_in": 3600, "refresh_token": "yyyyy",
"scope": "https://www.googleapis.com/auth/drive.scripts https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive",
"token_type": "Bearer"
}

But then when I issue the query using mimeType filtering (I just want to get google app scripts list):

mimeType = 'application/vnd.google-apps.script'

I'm only getting back an empty items list ([]), even if I've just created a google script inside a Google Sheet:

{ "kind": "drive#fileList", "etag": "....", "selfLink": "https://www.googleapis.com/drive/v2/files?q=mimeType+%3D+'application/vnd.google-apps.script'", "incompleteSearch": false, "items": []}

I'm guessing that app-script means something else than google script code which is part of a Google Sheet... Any help appreciated :)

Upvotes: 2

Views: 2698

Answers (3)

Joe Jansen
Joe Jansen

Reputation: 771

As of 2025 the Apps Script API (or clasp) still doesn't provide a projects list endpoint, so it seems like this is still a cumbersome operation.

I found the best approach to a similar approach to Cooper's answer from 2020:

  1. Visit https://script.google.com/home/all and use the web search bar to query if you want to refine the project list.
  2. Scroll down and the page will keep inserting more rows into to the DOM for each project.
  3. Once you have all the projects you need loaded into the DOM, open up the element inspector and copy the entire HTML.
  4. Save that HTML in a .txt file.
  5. Run a script to extract all of the data-script-ids to a .json file.
const fs = require("fs"); // File System module

function extractDataScriptIds(filePath) {
  // Read file content as a string
  const content = fs.readFileSync(filePath, "utf8");
  // Regular expression to match data-script-id values
  const regex = /data-script-id="([^"]+)"/g;
  const scriptIds = [];
  let match;
  while ((match = regex.exec(content)) !== null) {
    scriptIds.push(match[1]);
  }
  return scriptIds;
}

// Get input file path from command line argument
const inputFilePath = process.argv[2];
if (!inputFilePath) {
  console.error("Usage: node extract.js <input-file> [output-file]");
  process.exit(1);
}

// Optional: get output file path, default to 'scriptIds.json'
const outputFilePath = process.argv[3] || "scriptIds.json";

const scriptIds = extractDataScriptIds(inputFilePath);

// Write the array of IDs as JSON to the output file
fs.writeFileSync(outputFilePath, JSON.stringify(scriptIds, null, 2), "utf8");
console.log(
  `Extracted ${scriptIds.length} data-script-id values saved to ${outputFilePath}`
);


Upvotes: 0

Cooper
Cooper

Reputation: 64042

A Patchwork Quilt of a Work Around to get a list of Standalone and Bound Project Ids

I posted this here because I ran across this page several times during the last couple of days and in case there is someone else who would really like to get a complete list of their projects and doesn't mind having to jump through a few hoops to get it then this might be useful to them.

I found a way to get all of the my Apps Script Project File Ids. It's not pretty but but I got all 1393 of them bound and standalone. I went into Google Developer Hub and noticed that my project id's could be found in an attribute name data-script-id="project id" and by continuing to page down to the bottom of the list I was able to get the page to keep seeking more pages until there were none left. Then I went into Chrome developers tools and I found the div that contained all the divs with the above attribute and for me this one was named <div class="uBvHbd" and I copied the entire div and tried pasting it into an ascii file on my Google Account but I found that to be a problem so I opened up my copy of UltraEdit and pasted it in there. I played around with the regex for a while and spent the day retraining myself on the UltraEdit Document Model and their version of the Javascript Engine and developed the following routines which allowed me to build a complete list of projectids.

UltraEdits Scripting Language:

function getDocumentPaths() {
  UltraEdit.outputWindow.write('File Paths:');
  for(var i=0;i<UltraEdit.document.length;i++) {
    UltraEdit.outputWindow.write('[' + i + ']: ' + UltraEdit.document[i].path);
  }
}

function getFileNames() {
  var nA=[];
  var fnToIdx={fnA:[]};
  for(var i=0;i<UltraEdit.document.length;i++) {
    var p=UltraEdit.document[i].path;
    var pA=p.split(/\\/);
    var fn=pA[pA.length-1].split('.')[0];
     fnToIdx.fnA.push(fn);
     fnToIdx[fn]=i;
  }
   UltraEdit.outputWindow.write('FileNames: \r\n' + fnToIdx.fnA.join('\r\n'));
   return fnToIdx;
}

function getScriptIdsIndex() {
  for(var i=0;i<UltraEdit.document.length;i++) {
    if(UltraEdit.document[i].isName('ScriptIds')) {
      return i;
    }
  }
}

function getFileIndexByName(name) {
  var name=name||'ScriptIds';
  if(name) {
    for(var i=0;i<UltraEdit.document.length;i++) {
      if(UltraEdit.document[i].isName(name)) {
        return i;
      }
    }
  }else{
    UltraEdit.messageBox("Invalid or Missing Inputs at getFileIndexByName().","Alert")
  }
}

function getAllFileIndices(obj) {
  UltraEdit.outputWindow.write('File Indices:');
  var fnIndicesA=[]
  for(var j=0;j<obj.fnA.length;j++) {
    fnIndicesA.push({name:obj.fnA[j] , index:obj[obj.fnA[j]]})
  }
  var_dump(fnIndicesA);
}

function updateWorkingTabs(index) {
  UltraEdit.outputWindow.write('Index: ' + index);
  UltraEdit.document[index].selectAll();
  UltraEdit.document[index].copy();
  var workingTabsA=[];
  for(var i=0;i<UltraEdit.document.length;i++) {
    if(UltraEdit.document[i].path.slice(0,-1)=='Edit') {
      UltraEdit.document[i].paste();
      workingTabsA.push(i);
    }
  }
  return workingTabsA;
}

function findAllIds() {
  var fnToIndex=getFileNames();
  UltraEdit.document[fnToIndex['ScriptIds']].selectAll();
  UltraEdit.document[fnToIndex['ScriptIds']].copy();
  var s=UltraEdit.clipboardContent;
  var re=/data-script-id="[^"]+"/g;
  var matchA=s.match(re);
  UltraEdit.document[fnToIndex['FileIds']].selectAll();
  UltraEdit.document[fnToIndex['FileIds']].cut();
  for(var i=0;i<matchA.length;i++) {
    UltraEdit.document[fnToIndex['FileIds']].write(matchA[i].slice(16,-1)  + '\r\n');
  }
}

function removeDuplicates() {
  var fnToIndex=getFileNames();
  UltraEdit.document[fnToIndex['FileIds']].selectAll();
  UltraEdit.document[fnToIndex['FileIds']].copy();
  var fnA=UltraEdit.clipboardContent.split('\r\n');
  if(!fnA[fnA.length-1]) {
    fnA.pop();
  }
  var uA=[];
  for(var i=0;i<fnA.length;i++) {
    if(uA.indexOf(fnA[i])==-1) {
      uA.push(fnA[i]);
    }
  }
  var s='';  
  for(var i=0;i<uA.length;i++) {
    if(i>0){
      s+='\r\n';
    }
    s+=uA[i];
  }
  UltraEdit.document[fnToIndex['FileIds']].selectAll();
  UltraEdit.document[fnToIndex['FileIds']].cut();
  UltraEdit.document[fnToIndex['FileIds']].write(s);
 }

UltraEdit.outputWindow.clear();
//UltraEdit.open('E:\\Projects\\ScriptIds\\ScriptIds.txt');
//var ScriptIds_idx=getScriptIdsIndex();
//var wtA=updateWorkingTabs(ScriptIds_idx);

//findAllIds()

removeDuplicates();

The input file was 24 MB and the output list was 81KB and it took the script running on my laptop less then 5 seconds to strip out the id's and remove duplicates (didn't find any either).

I ran these two routines in Google Apps Script to get script info from Google Apps Script API so that I could figure out which were Standalone and which were container bound. And for the container bound I was able to get the Container Filename and id.

function getInfo(projectId) {
  var projectId=projectId||pid;
  var rObj={'ProjectId':projectId};
  var params = {muteHttpExceptions:true,headers: {"Authorization": "Bearer " + ScriptApp.getOAuthToken()}};
  try {
    var url=Utilities.formatString('https://script.googleapis.com/v1/projects/%s',projectId);
  }
  catch(e) {
    return rObj['Error']=e;
  }
  var resp=UrlFetchApp.fetch(url,params);
  var data=JSON.parse(resp.getContentText());
  if(data.hasOwnProperty('parentId')) {
    var pFile=DriveApp.getFileById(data.parentId)
    var parentName=pFile.getName();
    var pfldrA=[];
    var pFolders=pFile.getParents();
    while(pFolders.hasNext()) {
      var folder=pFolders.next();
      pfldrA.push({id:folder.getId(),name:folder.getName()});
    }
    rObj['ProjectName']=data.title;
    rObj['ParentName']=parentName;
    rObj['ParentId']=data.parentId;
    rObj['ParentFolders']='';
    rObj['ParentFolderIds']='';
  //var html=Utilities.formatString('<br /><b>Project Name:</b>%s<br /><b>ParentName:</b> %s<br /><b>ParentId:</b> %s',data.title,parentName,data.parentId);
  //html+=Utilities.formatString('<br /><b>Parent Folders:</b>');
    for(var i=0;i<pfldrA.length;i++) {
      if(i>0) {
        rObj.ParentFolders+=', ';
        rObj.ParentFolderIds+=', ';
      }
      //html+=Utilities.formatString('<br /><b>Name:</b> %s <b>Id:</b> %s',pfldrA[i].name,pfldrA[i].id);
      rObj.ParentFolders+=pfldrA[i].name;
      rObj.ParentFolderIds+=pfldrA[i].id;
    }
  }else{
    rObj['ProjectName']=data.title;
    //var html=Utilities.formatString('<br /><b>StandAlone Project</b><br /><b>Project Name:</b>%s<br /><b>ProjectId:</b>%s',data.title,projectId);
  }
  //Logger.log(data);
  //var userInterface=HtmlService.createHtmlOutput(html);
  //SpreadsheetApp.getUi().showModelessDialog(userInterface, "Project Info");
  return rObj;
}

function updateProjects() {
  var ss=SpreadsheetApp.getActive();
  var sh=ss.getSheetByName('Projects');
  var idx={hA:[],hr:1}
  idx.hA=sh.getRange(1,1,1,sh.getLastColumn()).getValues()[0];
  idx.hA.forEach(function(e,i){idx[e]=i;});
  var lr=sh.getLastRow();
  var sr=getColumnHeight(1,sh,ss) + 1;
  var cnt=25;
  setGlobal('count',cnt);
  if(lr-sr+1<cnt) {
    cnt=lr-sr+1;
  }
  if(sr>=lr) {
    setGlobal('count',0);
    return;
  }
  //Item    ProjectId   ProjectName ParentId    ParentName  ParentFolders   ParentFolderIds
  var rg=sh.getRange(sr,1,cnt,sh.getLastColumn());
  var vA=rg.getValues();
  for(var i=0;i<vA.length;i++) {
    if(!vA[i][idx.Item] && vA[i][idx.ProjectId]) {
      var pObj=getInfo(vA[i][idx.ProjectId]);
      if(!pObj.hasOwnProperty('Error') && pObj.hasOwnProperty('ParentId')) {
        vA[i][idx.Item]=i+sr;
        vA[i][idx.ProjectName]=pObj.ProjectName;
        vA[i][idx.ParentId]=pObj.ParentId;
        vA[i][idx.ParentName]=pObj.ParentName;
        vA[i][idx.ParentFolders]=pObj.ParentFolders;
        vA[i][idx.ParentFolderIds]=pObj.ParentFolderIds;
      }else if(!pObj.hasOwnProperty('Error') && !pObj.hasOwnProperty('ParentId')){
        vA[i][idx.Item]=i+sr;
        vA[i][idx.ProjectName]=pObj.ProjectName;
        vA[i][idx.ParentName]='Standalone Project';
      }else{
        vA[i][idxItem]=i+sr;
        vA[i][idx.ProjectName]=(pObj.hasOwnProperty('Error'))?pObj.Error:"Unknown Problem";
      }
      Utilities.sleep(1000);
    }
  }
  rg.setValues(vA);
}

I'll come back later and put some comments in the code. It's not pretty but I now have a list of all my projects and where to find them and the names of their containers if they have one. I have to thank @Tanaike for the Apps Script library. It really helped me to figure out how access the Apps Script API which turned out to be a lot less difficult than I thought it would be.

A Glimpse at a portion of the final list:

enter image description here

Upvotes: 3

Tanaike
Tanaike

Reputation: 201338

About retrieving the file list of projects of the container-bound script type and exporting each script from the project, please check the following answer.

Retrieve file list of projects of container-bound script type

  • Unfortunately, the file list of projects of the container-bound script type cannot be retrieved yet, while the file list of projects of the standalone type can be retrieved using drive.files.list of Drive API.

Export scripts in a project

  • When you want to export each script from a project of the containter-bound script type and the standalone type, you can do it using Google Apps Script API.
    • This might become a GAS sample script for your situation. here.
    • Also you can do it using a GAS library like this.

Upvotes: 3

Related Questions