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 :)
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.
This has been reported as a future request. https://issuetracker.google.com/issues/111149037
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.
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:
Related
Our non-profit information display is a simple looping Google Slides presentation, which works well for static content. Our dynamic content are composed of some current weather textual charts sourced from a Google Sheet (populated using API). These charts are 'refreshed' using a Google Apps Script every minute. Why every minute? Since one of the dynamic charts is the current time -- and this is the only way we have found to maintain a real-time clock in a Google Slide display.
In any case, this works fine -- but after running an entire week, every minute (10000 calls?) the linked charts fail with an 'exclamation mark in a triangle' symbol (and the message 'Image could not be loaded'). Re-embedding the charts does not help -- it is as if the entire Google Slide presentation is now corrupted and unusable for this purpose. We 'fix' this by merely creating a replacement copy of the presentation -- which (after re-activating our triggers to refresh) works just fine. It would be great if we did not need to maintain our presentation this way every week. Help? Suggestions? Thanks!
UPDATE: Here is our Google Slides 'refresh' code:
function refreshCharts(){
var gotSlides = SlidesApp.getActivePresentation().getSlides();
for (var i = 0; i < gotSlides.length; i++) {
var slide = gotSlides[i];
var sheetsCharts = slide.getSheetsCharts();
for (var k = 0; k < sheetsCharts.length; k++) {
var shChart = sheetsCharts[k];
shChart.refresh();
}
}
}
Description
If you are interested to try batch update I put together a simple example script.
Code.gs
function updateCharts() {
try {
let id = SlidesApp.getActivePresentation().getId();
let chartIds = [];
let slides = SlidesApp.getActivePresentation().getSlides();
slides.forEach( slide => { let charts = slide.getSheetsCharts();
charts.forEach( chart => chartIds.push(chart.getObjectId()) );
}
);
console.log(chartIds);
let charts = chartIds.map( id => { return { refreshSheetsChart: { objectId: id } } } );
console.log(charts);
let requests = { requests: charts };
Slides.Presentations.batchUpdate(requests,id);
}
catch(err) {
console.log(err);
}
}
Execution log
6:54:40 AM Notice Execution started
6:54:40 AM Info [ 'g139f22bb9da_0_0' ]
6:54:40 AM Info [ { refreshSheetsChart: { objectId: 'g139f22bb9da_0_0' } } ]
6:54:42 AM Notice Execution completed
Reference
Slides.batchUpdate()
I found this code online, however it does not work when I "copy and paste" it into Google Apps Script:
https://webscraping.pro/scrape-google-app-script/
What changes must I make for it to work, or alternatively is there a straight-forward way to search for specific key words on a website and return the results in Google Sheets?
Try this code in a worksheet, then filter some unusable rows
function textOnly() {
var url='https://stackoverflow.com/questions/69148306/how-do-i-use-google-apps-script-to-scrape-a-website-for-specific-key-words?noredirect=1#comment122219344_69148306'
var sh=SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
sh.clear()
var data=UrlFetchApp.fetch(url).getContentText().replace(/(\r\n|\n|\r|\t)/gm," ").split('>')
data.forEach(function (part){
var text = part.split('<')[0]
if (text.replace(/([ ]+)/gm,"")!='') {
sh.appendRow([part.split('<')[0].replace(/([ ]{2,})/gm," ").replace(/(^ )/gm,"")])
}
})
}
You will then able to filter with your specific key words. To include these key words inside the script, assuming they are located in keywords range :
function textOnly() {
var url='https://stackoverflow.com/questions/69148306/how-do-i-use-google-apps-script-to-scrape-a-website-for-specific-key-words?noredirect=1#comment122219344_69148306'
var sh=SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
sh.clear()
var data=UrlFetchApp.fetch(url).getContentText().replace(/(\r\n|\n|\r|\t)/gm," ").split('>')
var list = SpreadsheetApp.getActiveSpreadsheet().getRangeByName('keywords').getValues().join('|').toLowerCase().split('|')
var n=0
data.forEach(function (part){
var text = part.split('<')[0]
var myText = part.split('<')[0].replace(/([ ]{2,})/gm," ").replace(/(^ )/gm,"")
if (text.replace(/([ ]+)/gm,"")!='') {
list.forEach(function (term){
if (myText.toLowerCase().includes(term)){
sh.appendRow([myText])
n++
}
})
}
})
SpreadsheetApp.getActive().toast(n+" item(s) found", "End of script !", 5);
}
I have a script that pulls fields from a Google Sheet and inserts them into an email template and sends the emails off. That works fine.
I recently wanted to include a PDF as an attachment to the emails. It would be the same PDF for every email. I uploaded the PDF into Google Drive. When I run the script, the first email is sent off fine with the attachment but the following emails are not sent because I encounter this error: "Cannot retrieve the next object: iterator has reached the end"
Pretty sure it has to deal with the attachment/file and me not handling the iteration correctly. Can someone help? Below is the code:
function send2Email()
{
var filename= 'even_overview2020.pdf';
var file = DriveApp.getFilesByName(filename);
var spread =SpreadsheetApp.getActiveSpreadsheet();
var contactSheet =spread.getSheetByName(contactSheetName);
var bodySheet =spread.getSheetByName(templateSheetName);
var contactData =contactSheet.getDataRange().getValues();
var bodyData =bodySheet.getDataRange().getValues();
var fname,company,sign,template,email,subject,body,sender,file;
for (var i =1;i<contactData.length;i++)
{
contactData[i][statusCol-1]="";
}
contactSheet.getDataRange().setValues(contactData);
for (var i =1;i<contactData.length;i++)
{
fname=trim_(contactData[i][fnameCol-1]);
company=trim_(contactData[i][companyCol-1]);
sign=trim_(contactData[i][signCol-1]);
template=trim_(contactData[i][templateCol-1]);
email=trim_(contactData[i][emailCol-1]);
sender=trim_(contactData[i][senderCol-1]);
Logger.log(email);
for(var j=1;j<bodyData.length;j++)
{
if(trim_(bodyData[j][tempRefCol-1]).toUpperCase()==String(template).toUpperCase())
{
body=bodyData[j][bodyCol-1];
subject=bodyData[j][subjectCol-1];
}
}
Logger.log(j+","+email+','+body+','+subject);
body=body.replace(/\n/g,"<br>");
body=body.replace("(w)",sign).replace("(x)",fname).replace("(y)",company).replace("(s)",sender.split(" ")[0]);
Logger.log(email+','+body+','+subject);
MailApp.sendEmail({to:email,subject:subject,name:sender,htmlBody:body,attachments: [file.next().getAs(MimeType.PDF)]});
contactSheet.getRange(i+1, statusCol).setValue('Y');
}
}
How about this modification?
Modification point:
In your script, attachments: [file.next().getAs(MimeType.PDF)]} is used in the loop. By this, the 1st loop works by file.next(). But after 2nd loop, an error occurs at file.next() because the file of filename is one file in Google Drive. I think that this is the reason of your issue.
In order to avoid this issue, how about the following modification?
Modified script:
From:
var file = DriveApp.getFilesByName(filename);
To:
var files = DriveApp.getFilesByName(filename);
var file;
if (files.hasNext()) {
file = files.next().getAs(MimeType.PDF);
} else {
throw new Error("No file");
}
And also, please modify as follows.
From:
MailApp.sendEmail({to:email,subject:subject,name:sender,htmlBody:body,attachments: [file.next().getAs(MimeType.PDF)]});
To:
MailApp.sendEmail({to:email,subject:subject,name:sender,htmlBody:body,attachments: [file]});
Reference:
Class FileIterator
My goal is to get a partial filename from a cell(B2) using a script and find the complete URL, put the resulting URL in cell B3 so I can run a series of queries in the data. I've spent more hours searching google than I care to admit on this. I'm sure the answer is there but my in-experience with google scripting is preventing me from seeing.
Thanks for any help.
Files of a Feather
The feather is the partial string which must be provided. You can use a prompt if you want it to be interactive with a user.
function getFilesOfAFeather(feather) {
var files=DriveApp.getFiles();
var fA=[];
while(files.hasNext()){
var file=files.next();
if(file.getName().indexOf(feather)>-1) {
fA.push({name: file.getName(), id: file.getId(), url: file.getUrl()});
}
}
return fA;
}
If fA.length=0 then no files were found.
If fA.length=1 then var url=fA[0].url;
If fA.length>1 then you have to pick which one you want
function findUrl() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var feather=sh.getRange('B2').getValue();
var f=getFilesOfAFeather(feather);
if(f.length==1) {
return f[0].url;
}else{
SpreadsheetApp.getUi().alert(Utilities.formatString('Number of files found: ',f.length));
}
}
Below my sample code
function getResponseData()
{
var service = googleOAuth();
var attachmentURL = https://sites.google.com/feeds/content/domainname/sitename?kind=attachment&start-index=1&max-results=500
if(service.hasAccess())
{
var dataResponse;
try
{
dataResponse = UrlFetchApp.fetch(attachmentURL, {method: 'get',headers: {Authorization: 'Bearer ' + service.getAccessToken()}});
}
catch(e)
{
Logger.log(e);
}
}
function googleOAuth()
{
var service = OAuth2.createService('sites')
.setAuthorizationBaseUrl('')
.setTokenUrl('').setClientId('').setClientSecret('').setProjectKey('').setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getScriptProperties()).setScope('')
.setParam('login_hint', Session.getActiveUser().getEmail()).setParam('access_type', 'offline')
.setParam('approval_prompt', 'force');
return service;
}
function authCallback(request)
{
var driveService = googleOAuth();
var isAuthorized = driveService.handleCallback(request);
if(isAuthorized)
{
return HtmlService.createHtmlOutput('Success! You can close this tab.');
}
else
{
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
I have attached the google sites page 1500 documents and but get the lastest attachment document below 1000. how to get all documents. please help me.I have get the 996 document only retrieve. how to another document retrieve.please help me.
I created a google site of my own, uploaded 1500 files and wrote google apps script (similar to yours) to replicate this issue. As it turns out, I too am not able to retrieve files after the first 1000. This is happening even if I change the start-index and max-results. The only reason I can think of is that this is an undocumented limitation. There must be a limit of 1000 for the number of attachments. I might be wrong.
The limits for attachments to a Google Site are mentioned in this link - Storage and file limits