Suddenly encountered one issue where Google DOCS service fails after making some calls to copy elements.
Funny thing is that it actually copies the first element, but when it goes for the second loop within the "for" it fails giving this error:
Service unavailable: Documents
This script used to work fine for a few months as we have it published within our company.
Suddenly last week it stopped working for all the users regardless of their browser etc...
It is a script linked to a Google Spreadsheet (where it gets the information to generate a Google Docs). But it also fails if I execute it, or debug it, from the Script console as well.
What I've tried after it started failing is to add a couple of lines to saveAndClose the document in case there was an issue with too many calls or buffering.
I've checked and the project does have access to Google Docs, Drive and Maps API.
The elements to be copied are correct, as I can do Logger.Log with them...
It just fails when it has to copy the actual element (appendParagraph, append ListItem, etc...)
I've enabled StackDriver logs and errors but it doesn't show any logs in the console, just "failed".
Any hint or directions will be very much appreciate it!
Thanks!
function copyDescription(ID,newDoc,newDocID){
var otherBody = DocumentApp.openById(ID).getBody();
newDoc.saveAndClose();
newDoc = DocumentApp.openById(newDocID);
var docbody = newDoc.getBody();
var totalElements = otherBody.getNumChildren();
//Run through template document and copies to the new one
for( var j = 0; j < totalElements; ++j ) {
var element = otherBody.getChild(j).copy();
var attributes = otherBody.getChild(j).getAttributes();
var type = element.getType();
if (type == DocumentApp.ElementType.PARAGRAPH) {
if (element.asParagraph().getNumChildren() != 0 && element.asParagraph().getChild(0).getType() == DocumentApp.ElementType.INLINE_IMAGE) {
var pictattr = element.asParagraph().getChild(0).asInlineImage().getAttributes();
var blob = element.asParagraph().getChild(0).asInlineImage().getBlob();
}
else {
docbody.appendParagraph(element);
}
}
else if( type == DocumentApp.ElementType.TABLE )
docbody.appendTable(element);
else if( type == DocumentApp.ElementType.LIST_ITEM )
docbody.appendListItem(element);
else
throw new Error("Unsupported element type: "+type);
newDoc.saveAndClose();
}
}
Related
A previously working solution that was resolved here by #tanaike suddenly returns an empty cell upon execution. I don't get an error message and in the google apps scripts edit page I get "Notice Execution completed".
It looks like it's working in the background but having trouble returning a value to the cell, my guess would be something wrong with the last line that may resolve it?
function pressReleases(code) {
var url = 'https://finance.yahoo.com/quote/' + code + '/press-releases'
var html = UrlFetchApp.fetch(url).getContentText().match(/root.App.main = ([\s\S\w]+?);\n/);
if (!html || html.length == 1) return;
var obj = JSON.parse(html[1].trim());
// --- I modified the below script.
const { _cs, _cr } = obj;
if (!_cs || !_cr) return;
const key = CryptoJS.algo.PBKDF2.create({ keySize: 8 }).compute(_cs, JSON.parse(_cr)).toString();
const obj2 = JSON.parse(CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(obj.context.dispatcher.stores, key)));
var res = obj2.StreamStore.streams["YFINANCE:" + code + ".mega"].data.stream_items[0].title;
// ---
return res || "No value";
}
The CryptoJS code saved as a script in google apps script is here
When I tested this script, at if (!_cs || !_cr) return;, I confirmed that the values of _cs and _cr are undefined. From this result, I understood that recently, the specification of the key for decrypting the data has been changed at the server side. When I saw this thread, I confirmed the same situation. In the thread, I noticed that, in the current stage, the key can be simply retrieved from the HTML data. So, as with the current script, how about the following modification?
Usage:
1. Get crypto-js.
Please access https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js. And, copy and paste the script to the script editor of Google Apps Script, and save the script.
2. Modify script.
function pressReleases(code) {
var url = 'https://finance.yahoo.com/quote/' + code + '/press-releases'
var html = UrlFetchApp.fetch(url).getContentText().match(/root.App.main = ([\s\S\w]+?);\n/);
if (!html || html.length == 1) return;
var obj = JSON.parse(html[1].trim());
var key = Object.entries(obj).find(([k]) => !["context", "plugins"].includes(k))[1];
if (!key) return;
const obj2 = JSON.parse(CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(obj.context.dispatcher.stores, key)));
var res = obj2.StreamStore.streams["YFINANCE:" + code + ".mega"].data.stream_items[0].title;
// console.log(res); // Check the value in the log.
return res || "No value";
}
When this script is run with code = "PGEN", the value of Precigen Provides Pipeline and Corporate Updates at the 41st Annual J.P. Morgan Healthcare Conference is obtained.
Note:
If you want to load crypto-js directly, you can also use the following script. But, in this case, the process cost becomes higher than that of the above flow. Please be careful about this.
const cdnjs = "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js";
eval(UrlFetchApp.fetch(cdnjs).getContentText());
I can confirm that this method can be used for the current situation (January 14, 2023). But, when the specification in the data and HTML is changed in the future update on the server side, this script might not be able to be used. Please be careful about this.
Reference:
crypto-js
I am trying to find a specific spreadsheet(Target) within each folder (A). These folders are within Folder (B) which are in turn within Folder (C). The current script i have retrieve folder (C), and search through each (B) for folder (A) then spreadsheet. However, because of large numbers of folder (B), i have placed a continuationtoken at the folder (A) level to track which folder (B) have been searched [or at least i believe that's what i am doing]. The issue I have is that the script always resume from the same first folder (B) instead of continuing from the last folder (B) that was searched.
Folder (C) = baseFolder
Folder (B) = studentFolder
Folder (A) = AssessmentFolder
Spreadsheet (Target) = any spreadsheet which includes the given searchkey
Following are snippets of the script that i am using.
if (continuationToken == null) {
// firt time execution, get all files from Drive
var allFolders = baseFolder.getFolders();
var Tokenalert = ui.alert('There is no token');
}
else {
// not the first time, pick up where we left off
var allFolders = DriveApp.continueFolderIterator(continuationToken);
}
while (allFolders.hasNext() && end.getTime() - start.getTime() <= maxTime) {
i++;
var studentFolder = allFolders.next();
var AssessmentFolder = studentFolder.getFoldersByName("02 Assessment");
if (AssessmentFolder.hasNext() == true){
Logger.log(studentFolder.getName());
progress.getRange(i,1).setValue(studentFolder.getName());
AssessmentFolder = AssessmentFolder.next();
if (AssessmentFolder.searchFiles(checkcode).hasNext() == true){
var filetochange = AssessmentFolder.searchFiles(checkcode);
var editfile = filetochange.next();
progress.getRange(i,2).setValue(editfile);
Logger.log(editfile);var fileid = editfile.getId();
var editss = SpreadsheetApp.openById(fileid);
Logger.log(editss.getName());
progress.getRange(i,3).setValue(fileid);
var editsheet = editss.getSheetByName(sheettoedit);
// remove protection from the sheet mentioned
// Protect the active sheet except B2:C5, then remove all other users from the list of editors.
var protection = editsheet.protect().setDescription('Test');
if (protectrange != 0) {
var unprotected = editsheet.getRange(protectrange);
}
if (protectrange2 != 0) {
var unprotected2 = editsheet.getRange(protectrange2);
}
else {
unprotected2 = unprotected;
}
if (protectrange3 != 0) {
var unprotected3 = editsheet.getRange(protectrange3);
}
else {
unprotected3 = unprotected2;
}
protection.setUnprotectedRanges([unprotected, unprotected2]);
// Ensure the current user is an editor before removing others. Otherwise, if the user's edit
// permission comes from a group, the script throws an exception upon removing the group.
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
progress.getRange(i,4).setValue("complete");
}
else {
progress.getRange(i,4).setValue("fail");
}
}
end = new Date()
}
// Save your place by setting the token in your user properties
if(allFolders.hasNext()){
var continuationToken = allFolders.getContinuationToken();
userProperties.setProperty('CONTINUATION_TOKEN', continuationToken);
progress.getRange(1,6).setValue(continuationToken);
} else {
i++;
progress.getRange(i,1).setValue("Completed")
// Delete the token
PropertiesService.getUserProperties().deleteProperty('CONTINUATION_TOKEN');
ss.deleteSheet("Progress");
var Completionalert = ui.alert('completed');
}
Pardon my messy code as I am new to coding.
I have checked and the continuation token is stored, and is retrieved. I have also ensured that the script does not end prematurely before the token is stored. The only issue i can think of is that either the way I enter the token again is wrong, or a wrong token is stored. But i am unsure of which. I have tried storing the token at B level and A level, but it doesn't make sense and doesn't work.
Also, I have read the following:
https://developers.google.com/apps-script/reference/drive/folder-iterator Howver it was not very helpful as it only shows how to get a token.
Google Apps Script: How to use ContinuationToken with recursive folder iterator but i do not understand the the term recursive.
It would be great if someone can explain how continuationtoken works. Does it just track the last folder and resume from there? or does it actually take the whole list of folder and gives the position?
To reiterate, the main issue is that the token is created and retrieved, but somehow the script is not resuming from the last folder checked.
Any offhand feedback on other portions of the script is welcomed^^ and thank you in advance!:)
There are script run-time limitations
regular user: 6 minutes
Business/Enterprise/Education: 30 mins
Take a brief look for detailed article on Quotas for Google Services
Take a look on Stackoverflow threat Google Apps Script: How to use ContinuationToken with recursive folder iterator, it has similarities to your question
Recursive function is the function which calls itself
Here is the little example of function which returns the base to the exponent power
function pow(a, b) {
if (b === 1) { return a }
else { return a * pow(a, b - 1) }
}
console.log(pow(3, 3)) // same as 3 * 3 * 3 = 27
P.S. This is just an example, use Math.pow() instead
Does it clarify your concerns?
After checking through my script, I realized that the error came from me declaring the variable allFolders twice, one more time before this chunk of script and the script is now working as it should after removing the first declaration of allFolders.
Therefore, there was nothing wrong with the script posted here.
I'm creating a google API append script. There's a google sheet with lots of google document links. I retrieve these links open the file, go through its content and append it to a new document. This needs to be done for all documents in this list. After struggling for a while I got all of it working except for positioned images. Either I don't get an image at all, or the image gets copied in, but from that point on is copied and pasted on every page of the document. I'm probably just failing somewhere in the coding process but can't seem to find my own flaw. Note that I work in IT but not as a developer so Yes the code is written very poorly :'(
I've tried and reeditted my code repeatedly to ensure i was picking up the positioned image properly (e.g. by putting text in instead of the image etc. and it all seems to work properly, however the positioned image keeps repeating all over the document.)
Note that below I've only included the bad function and the main i'm calling, not all the get stuff from the excel sheet as that's working properly.
UPDATE:
I've updated my code. It now successfully finds, sets layout etc. and places the positioned images.
However, from that point on, the positioned images that have been placed earlier in the merged document are repeated on the last paragraph of every appended document.
function main() {
var spreadsheetid = "Fill_this_with_spreadsheet_ID";
var DocIDS = [];
DocIDS = getDocIDFromSpreadsheet(spreadsheetid);
mergeGoogleDocs(DocIDS);
//remove_blank(DocIDS);
}
/*Function to append contents of document to another document
for (var i = 1; i < List.length; ++i ) {
var otherBody = DocumentApp.openById(List[i]).getActiveSection();
var totalElements = otherBody.getNumChildren();
baseDoc.saveAndClose()
var baseDoc = DocumentApp.openById(DocIDS[0]);
var body = baseDoc.getActiveSection();
for( var j = 0; j < totalElements; ++j ) {
var element = otherBody.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH ){
var positionedImages = element.getPositionedImages();
if ( positionedImages == "PositionedImage") {
//This is required because the positionedImage needs a "new" pragraph to anchor to...
var paragraph = body.appendParagraph("");
var tempvalue = positionedImages[0].getLayout();
var tempheight = positionedImages[0].getHeight();
var tempwidth = positionedImages[0].getWidth();
var tempblob = positionedImages[0].getBlob();
paragraph.addPositionedImage(tempblob);
var positionedImages2 = paragraph.getPositionedImages();
positionedImages2[0].setLayout(tempvalue);
positionedImages2[0].setLayout(tempvalue);
positionedImages2[0].setHeight(tempheight);
positionedImages2[0].setWidth(tempwidth);
//To-do, fix repeated multiplication of positioned Images on all following documents at the final paragraph.
}
else {
body.appendParagraph(element);
}
}
else if( type == DocumentApp.ElementType.TABLE ){
body.appendTable(element);}
else if( type == DocumentApp.ElementType.LIST_ITEM ) {
glyphType = element.getGlyphType();
body.appendListItem(element);
element.setGlyphType(glyphType);
}
else
throw new Error("Unknown element type: "+type);
}
body.appendPageBreak()
}
I expect to get all documents merged into one document. while it does merge everything, it goes wrong on positioned images. When I run the code I get the positioned images duplicated on every new appended document following the first time it was inserted. Where it only needs to be included once, not on every document.
I've been struggling for several evenings now and desperate for pointers and/or a proper fix.
I am trying to run a function every night that checks a list of dates and does work if it finds that a date has passed and all the checkboxes on that row are checked. Every day, though, I get an email saying
"Cannot call SpreadsheetApp.getUi() from this context. (line 172, file "Code")".
The weird thing is that I don't use getUi() anywhere in my CheckHireDates function and the line that it specifies is not even in the function that is supposed to run. Line 172 is in my onEdit function which also doesn't use getUi(). Can anybody help me understand why I'm getting this error?
I did use getUi in my onOpen function, so I commented it out when I started having this problem but it still didn't fix anything.
function CheckHireDates() {
var spreadsheet = SpreadsheetApp.getActive();
var PaycomSheet = spreadsheet.getSheetByName('Paycom');
var TechSheet = spreadsheet.getSheetByName("Tech Key");
var SoftwareTracker = spreadsheet.getSheetByName('Software Tracking');
var range = "R2:R";
var Hvals = PaycomSheet.getRange("H2:H").getValues();
var Hlast = Hvals.filter(String).length;
var data = PaycomSheet.getRange(range).getValues();
var today = new Date().toLocaleDateString();
for(var i = 0; i < Hlast;i++)
{
Hvals[i].toLocaleString();
if(Hvals[i] <= today && (PaycomSheet.getRange('R' + (i+2)).getValue() == true))
{
var fullName = PaycomSheet.getRange('D' + (i+2)).getValue();
var techRow = findMatchingRow(fullName, "Tech Key");
var softwareRow = findMatchingRow(fullName, "Software Tracking");
var position = PaycomSheet.getRange('G' + (i+2)).getValue();
TechSheet.getRange('E' + techRow).setValue(1);
SoftwareTracker.getRange('G' + softwareRow).setValue(1);
if (position == "Pre-Employment, Initial")
{
position = "Initial";
}
else if (position == "Pre-Employment, Ace")
{
position = "Route";
}
else if (position == "Pre-Employment, Expert")
{
position = "Route";
}
else
{
Logger.log("Position not known");
}
TechSheet.getRange('G' + techRow).setValue(position);
SoftwareTracker.getRange('D' + softwareRow).setValue(position);
SoftwareTracker.getRange('H' + softwareRow + ':M' + softwareRow).setValue(2);
if (position !== "Initial")
{
SoftwareTracker.getRange('N' + softwareRow).setValue(2);
}
}
}
}
I had that problem and found it involved another google script in the project set up by a different user. The error message had a mixture of details from my script and the other script:
myFunctionName Cannot call SpreadsheetApp.getUi() from this context.
(line 2, file "OtherScript")
Line 2 of the other script did use getUi()
var app = SpreadsheetApp.getUi();
It seemed that when my script ran (triggered by onChange event) then the other script would also get run (maybe also triggered by a change event). The other script set up some UI elements, some simple menus. Normally that caused no problem. However, SpreadsheetApp.getUi() only works in the context of there being a current instance of an open editor (see https://developers.google.com/apps-script/reference/base/ui). So if a change event happened without an open editor then it fails, causing the error.
I resolved the problem by slightly modifying the other script to catch the problem:
try {
app = SpreadsheetApp.getUi();
} catch (error) {}
if (app != null) {
//Use app...
}
A different approach that might also have worked is to speak with the person and how their script was triggered, and see if they'd change it to trigger by onOpen event that just occurs when someone opens a spreadsheet and hence there is a Ui context.
So, I think your problem would be coming from SpreadsheetApp.getUi() in a different script in the project. See if the error message mentions a different file, like mine did. When your script runs in the night, there would be no context, which explains why the error occurs at that time.
I needed a solution that copies into the current Doc the content of another set of Docs that are used as templates. (we need to retain the current docId). I wrote a simple Google Doc add-on that works like a charm... in some Google Apps domains.
I installed (and made screencasts) the IDENTICAL code in four different domains. In two of the domains the content copied from the selected "template" Doc gets (correctly) copied ONCE in the current Doc, however, in two other domains it gets injected TWICE!
All domains have identical settings (script run as admin, authorizations accepted, etc). I even installed a second copy in the faulty domains and that showed the same behaviour.
Anyone seen this behaviour?
function runInsert(template) {
var targetDoc = DocumentApp.getActiveDocument();
if( targetDoc.getHeader() == null) {
targetDoc.addHeader();
}/
if( targetDoc.getFooter() == null) {
targetDoc.addFooter();
}
var templateDoc = DocumentApp.openById(template);
// check for header, get elements and add to current doc
var templateHeader = templateDoc.getHeader();
if( templateHeader != null) {
var totalElementsHeader = templateHeader.getNumChildren();
for( var j = 0; j < totalElementsHeader; ++j ) {
var header = targetDoc.getHeader();
var element = templateHeader.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH ){
header.appendParagraph(element);
}
}
}
// check for body, get elements and add to current doc
var templateBody = templateDoc.getBody();
if( templateBody != null) {
var totalElementsBody = templateBody.getNumChildren();
for( var j = 0; j < totalElementsBody; ++j ) {
var body = targetDoc.getBody();
var element = templateBody.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH ){
body.appendParagraph(element);
Logger.log("j is " + j + " element contains " + element.getText());
}
else if( type == DocumentApp.ElementType.TABLE){
body.appendTable(element);
....
}
}
... copy footer
}
Found that the insert executed twice and fixed it in the sidebar javascript.
Does the script run off of installed triggers? I had a similar issue with one of my scripts where I accidentally installed a trigger twice which was leading to the script running twice for every event.
Check your triggers and see if multiples are installed for your project in the script editor under Resources > Current Project's Triggers.