Google Apps Script - Typewriter Effect - javascript

I'm trying to add a sort of 'typewriter effect' on my google apps script for google docs. I want to make it type out text, in this case a wikipedia article, as if a user was typing it, so add a delay. Unfortunately the function appendText(), even if you use Utilities.sleep, it still just types the entire article out as soon as the script finishes. What function would I use to accomplish something like this?
function onOpen(e) {
DocumentApp.getUi().createAddonMenu()
.addItem('Start', 'myFunction')
.addToUi();
}
function onInstall(e) {
onOpen(e);
}
function myFunction() {
var body = DocumentApp.getActiveDocument().getBody();
var text = body.editAsText();
var response = UrlFetchApp.fetch('https://en.wikipedia.org/w/api.php?&format=json&action=query&generator=random&grnnamespace=0&prop=title&grnlimit=1');
var json = JSON.parse(response);
for (key in json.query.pages) {
var title = json.query.pages[key].title;
}
var url = 'https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&explaintext=&titles=' + title
var response2 = UrlFetchApp.fetch(url);
var json2 = JSON.parse(response2);
for (key in json2.query.pages) {
var content = json2.query.pages[key].extract;
}
//content = content.replace(/==.*==/, '====')
var all = title + '\n' + content;
text.appendText("Start\n");
Utilities.sleep(1000);
text.appendText(content);
}

You need to flush the document. The DocumentApp API does not have a flush method (like SpreadsheetApp) but you can still flush it by using saveAndClose and then re-opening the document (for example with document=DocumentApp.openById("myid")
saveAndClose is automatically called when a script finishes, but not after every change you make as Google batches those changes for performance.
https://developers.google.com/apps-script/reference/document/document

I have tried #ZigMandel suggestion. It appears to work but the text is being typed from the left out.
function onOpen(e) {
DocumentApp.getUi().createAddonMenu()
.addItem('Start', 'myFunction')
.addToUi();
}
function onInstall(e) {
onOpen(e);
}
function myFunction() {
var body = DocumentApp.getActiveDocument().getBody();
var text = body.editAsText();
var response = UrlFetchApp.fetch('https://en.wikipedia.org/w/api.php?&format=json&action=query&generator=random&grnnamespace=0&prop=title&grnlimit=1');
var json = JSON.parse(response);
for (key in json.query.pages) {
var title = json.query.pages[key].title;
}
var url = 'https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&explaintext=&titles=' + title
var response2 = UrlFetchApp.fetch(url);
var json2 = JSON.parse(response2);
for (key in json2.query.pages) {
var content = json2.query.pages[key].extract;
}
//format(content);
//var par1 = body.insertParagraph(0, title);
//par1.setAlignment(DocumentApp.HorizontalAlignment.CENTER);
var str = "Sphinx of black quartz, judge my vow."
var split = str.split("");
for (var i = split.length - 1; i >= 0; i--) {
text.appendText(split[i]);
DocumentApp.getActiveDocument().saveAndClose();
body = DocumentApp.getActiveDocument().getBody();
text = body.editAsText();
}
}
function format(txt) {
txt = '\n' + txt;
txt = txt.replace(/\===(.+?)\===/g, "").replace(/\==
(.+?)\==/g,"").replace(/\n+/g, "\n").replace(/\n/g, "\n" + " ");
return txt;
}

To get the script to print out each character with a delay between, you can put your sleep method into a for loop.
for(var i = 0; i < content.length; i++) {
text.appendText(content[i]);
Utilities.sleep(200);
}
I think this gives you the effect you are looking for.

Related

Functions Rerunning in GAS

I'm using GAS to send and receive texts. There is one function that send texts (sendTexts.gs) and one that receives (receiveTexts.gs). I have both of these functions linked to individual buttons on the sheet, but when I run one function, both are running (texts get sent every time). Is there a cache or something that needs to be cleared? The receiveTexts has no commands in the code that could send messages in it, and based on logger testing, I know that both are running when I only click one.
EDIT: This also occurs in the GAS "terminal". If I click run in the script editor both run.
Here is the code with personal/individual info (codes/phone numbers edited out):
function sendSms(to, body) {
var playerArray = getMeta();
Logger.log(playerArray);
var messages_url = "https://api.twilio.com/2010-04-01/Accounts/EDIT/Messages.json";
var payload = {
"To": to,
"Body" : body,
"From" : "EDIT"
};
var options = {
"method" : "post",
"payload" : payload
};
options.headers = {
"Authorization" : "Basic " + Utilities.base64Encode("EDIT")
};
UrlFetchApp.fetch(messages_url, options);
}
function sendAll() {
var spreadsheet = SpreadsheetApp.getActive();
var text = SpreadsheetApp.setActiveSheet(spreadsheet.getSheetByName('Meta')).getRange('B4').getValue();
var playerArray = getMeta();
Logger.log(text);
for (i=0; i<playerArray.length;i++) {
try {
var number = playerArray[i][1];
Logger.log(number);
response_data = sendSms(number, text);
status = "sent";
} catch(err) {
Logger.log(err);
status = "error";
}
Logger.log(status);
}
}
function sendTexts() {
sendAll();
}
Logger.log("ran send texts");
Here is the receive texts code with the same adjustments:
function receiveTexts() {
var spreadsheet = SpreadsheetApp.getActive();
var ACCOUNT_SID = "EDIT";
var ACCOUNT_TOKEN = "EDIT";
var toPhoneNumber = "+EDIT";
var sheet = spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Meta'), true);
var playerArray = getMeta();
var numberToRetrieve = playerArray.length;
var hoursOffset = 0;
var options = {
"method" : "get"
};
options.headers = {
"Authorization" : "Basic " + Utilities.base64Encode(ACCOUNT_SID + ":" + ACCOUNT_TOKEN)
};
var url="https://api.twilio.com/2010-04-01/Accounts/" + ACCOUNT_SID + "/Messages.json?To=" + toPhoneNumber + "&PageSize=" + numberToRetrieve;
var response = UrlFetchApp.fetch(url,options);
// -------------------------------------------
// Parse the JSON data and put it into the spreadsheet's active page.
// Documentation: https://www.twilio.com/docs/api/rest/response
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Meta'), true);
var numRounds = spreadsheet.getRange('B2').getValue();
var theSheet = SpreadsheetApp.setActiveSheet(spreadsheet.getSheetByName('Attacks'),true);
var theColumn = (numRounds*2)+1;
var dataAll = JSON.parse(response.getContentText());
for (i=0; i<dataAll.messages.length; i++){
var sentNumber = dataAll.messages[i].from;
Logger.log(sentNumber);
for (k=0; k<playerArray.length;k++){
Logger.log(playerArray[k][1]);
if (playerArray[k][1]==sentNumber){
var player = k;
Logger.log('Success');
Logger.log(player);
break;
}
}
var playerRow = playerArray[player][0];
Logger.log(playerRow);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Attacks'),true);
theSheet.getRange(playerRow, theColumn).setValue(dataAll.messages[i].body);
}
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Meta'), true);
sheet.getRange(2,2).setValue(numRounds +1);
}
Logger.log("texts received ran");
EDIT2: I separated out getMeta into a separate file. Now when I run getMeta, it also runs the other two scripts. Anytime any one script is run, all three run. This makes me think it's not related to the code, but related to some setting or something. Does the order of the scripts in the sidebar matter? I feel like that's not it because running any of the three causes all three to be run.

Add a paragraph or table etc. at Cursor

I have a function for adding the contents of a separate google document at the cursor point within the active document, but I haven't been able to get it to work. I keep getting the "Element does not contain the specified child element" exception, and then the contents gets pasted to the bottom of the document rather than at the cursor point!
function AddTable() {
//here you need to get document id from url (Example, 1oWyVMa-8fzQ4leCrn2kIk70GT5O9pqsXsT88ZjYE_z8)
var FileTemplateFileId = "1MFG06knf__tcwHWdybaBk124Ia_Mb0gBE0Gk8e0URAM"; //Browser.inputBox("ID der Serienbriefvorlage (aus Dokumentenlink kopieren):");
var doc = DocumentApp.openById(FileTemplateFileId);
var DocName = doc.getName();
//Create copy of the template document and open it
var docCopy = DocumentApp.getActiveDocument();
var totalParagraphs = doc.getBody().getParagraphs(); // get the total number of paragraphs elements
Logger.log(totalParagraphs);
var cursor = docCopy.getCursor();
var totalElements = doc.getNumChildren();
var elements = [];
for (var j = 0; j < totalElements; ++j) {
var body = docCopy.getBody();
var element = doc.getChild(j).copy();
var type = element.getType();
if (type == DocumentApp.ElementType.PARAGRAPH) {
body.appendParagraph(element);
} else if (type == DocumentApp.ElementType.TABLE) {
body.appendTable(element);
} else if (type == DocumentApp.ElementType.LIST_ITEM) {
body.appendListItem(element);
}
// ...add other conditions (headers, footers...
}
Logger.log(element.editAsText().getText());
elements.push(element); // store paragraphs in an array
Logger.log(element.editAsText().getText());
for (var el = 0; el < elements.length; el++) {
var paragraph = elements[el].copy();
var doc = DocumentApp.getActiveDocument();
var bodys = doc.getBody();
var cursor = doc.getCursor();
var element = cursor.getElement();
var container = element.getParent();
try {
var childIndex = body.getChildIndex(container);
bodys.insertParagraph(childIndex, paragraph);
} catch (e) {
DocumentApp.getUi().alert("There was a problem: " + e.message);
}
}
}
You want to copy the objects (paragraphs, tables and lists) from the document of 1MFG06knf__tcwHWdybaBk124Ia_Mb0gBE0Gk8e0URAM to the active Document.
You want to copy the objects to the cursor position on the active Document.
You want to achieve this using Google Apps Script.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Modification points:
In your script, appendParagraph, appendTable and appendListItem are used at the 1st for loop. I think that the reason that the copied objects are put to the last of the document is due to this.
var body = docCopy.getBody(); can be put to the out of the for loop.
In your case, I think that when the 1st for loop is modified, 2nd for loop is not required.
When above points are reflected to your script, it becomes as follows.
Modified script:
function AddTable() {
var FileTemplateFileId = "1MFG06knf__tcwHWdybaBk124Ia_Mb0gBE0Gk8e0URAM";
var doc = DocumentApp.openById(FileTemplateFileId);
var docCopy = DocumentApp.getActiveDocument();
var body = docCopy.getBody();
var cursor = docCopy.getCursor();
var cursorPos = docCopy.getBody().getChildIndex(cursor.getElement());
var totalElements = doc.getNumChildren();
for (var j = 0; j < totalElements; ++j) {
var element = doc.getChild(j).copy();
var type = element.getType();
if (type == DocumentApp.ElementType.PARAGRAPH) {
body.insertParagraph(cursorPos + j, element);
} else if (type == DocumentApp.ElementType.TABLE) {
body.insertTable(cursorPos + j, element);
} else if (type == DocumentApp.ElementType.LIST_ITEM) {
body.insertListItem(cursorPos + j, element);
}
}
}
It seems that DocName is not used in your script.
References:
insertParagraph()
insertTable()
insertListItem()
If I misunderstood your question and this was not the result you want, I apologize. At that time, can you provide the sample source Document? By this, I would like to confirm it.

Google script - parse HTML from Website Forum - and Write Data to Sheet

I'm getting HTML from a forum url, and parsing the post count of the user from their profile page. I don't know how to write the parsed number into the Google spreadsheet.
It should go account by account in column B till last row and update the column A with count.
The script doesn't give me any errors, but it doesn't set the retrieved value into the spreadsheet.
function msg(message){
Browser.msgBox(message);
}
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu("Update")
.addItem('Update Table', 'updatePosts')
.addToUi();
}
function getPostCount(profileUrl){
var html = UrlFetchApp.fetch(profileUrl).getContentText();
var sliced = html.slice(0,html.search('Posts Per Day'));
sliced = sliced.slice(sliced.search('<dt>Total Posts</dt>'),sliced.length);
postCount = sliced.slice(sliced.search("<dd> ")+"<dd> ".length,sliced.search("</dd>"));
return postCount;
}
function updatePosts(){
if(arguments[0]===false){
showAlert = false;
} else {
showAlert=true;
}
var spreadSheet = SpreadsheetApp.getActiveSpreadsheet();
var accountSheet = spreadSheet.getSheetByName("account-stats");
var statsLastCol = statsSheet.getLastColumn();
var accountCount = accountSheet.getLastRow();
var newValue = 0;
var oldValue = 0;
var totalNewPosts = 0;
for (var i=2; i<=accountCount; i++){
newValue = parseInt(getPostCount(accountSheet.getRange(i, 9).getValue()));
oldValue = parseInt(accountSheet.getRange(i, 7).getValue());
totalNewPosts = totalNewPosts + newValue - oldValue;
accountSheet.getRange(i, 7).setValue(newValue);
statsSheet.getRange(i,statsLastCol).setValue(newValue-todaysValue);
}
if(showAlert==false){
return 0;
}
msg(totalNewPosts+" new post found!");
}
function valinar(needle, haystack){
haystack = haystack[0];
for (var i in haystack){
if(haystack[i]==needle){
return true;
}
}
return false;
}
The is the first time I'm doing something like this and working from an example from other site.
I have one more question. In function getPostCount I send the function profileurl. Where do I declare that ?
Here is how you get the URL out of the spreadsheet:
function getPostCount(profileUrl){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var thisSheet = ss.getSheetByName("List1");
var getNumberOfRows = thisSheet.getLastRow();
var urlProfile = "";
var sliced = "";
var A_Column = "";
var arrayIndex = 0;
var rngA2Bx = thisSheet.getRange(2, 2, getNumberOfRows, 1).getValues();
for (var i = 2; i < getNumberOfRows + 1; i++) { //Start getting urls from row 2
//Logger.log('count i: ' + i);
arrayIndex = i-2;
urlProfile = rngA2Bx[arrayIndex][0];
//Logger.log('urlProfile: ' + urlProfile);
var html = UrlFetchApp.fetch(urlProfile).getContentText();
sliced = html.slice(0,html.search('Posts Per Day'));
var postCount = sliced.slice(sliced.search("<dd> ")+"<dd> ".length,sliced.search("</dd>"));
sliced = sliced.slice(sliced.search('<dt>Total Posts</dt>'),sliced.length);
postCount = sliced.slice(sliced.search("<dd> ")+"<dd> ".length,sliced.search("</dd>"));
Logger.log('postCount: ' + postCount);
A_Column = thisSheet.getRange(i, 1);
A_Column.setValue(postCount);
};
}
You're missing var in front of one of your variables:
postCount = sliced.slice(sliced.search("<dd> ")+"<dd> ".length,sliced.search("</dd>"));
That won't work. Need to put var in front. var postCount = ....
In this function:
function updatePosts(){
if(arguments[0]===false){
showAlert = false;
} else {
showAlert=true;
}
There is no array named arguments anywhere in your code. Where is arguments defined and how is it getting any values put into it?

Google Apps Script: How to find a listItem object in a google document and insert a item to it?

Following the documentation sample, I'm trying to create a function that searchs for a numerated list in a google document and, if finds it, adds a new item to that list. But I get this error: Cannot find method setListId(string). (line 21, file "test") or, if I change line 21 content (replacing elementContentfor newElement), I get the message: Preparing for execution... and nothing happens. How to fix it?
This is my code:
function test() {
var elementContent = "New item testing"; // a paragraph with its formating
var targetDocId = "1R2c3vo9oOOjjlDR_n5L6Tf9yb-luzt4IxpHwwZoTeLE";
var targetDoc = DocumentApp.openById(targetDocId);
var body = targetDoc.getBody();
for (var i = 0; i < targetDoc.getNumChildren(); i++) {
var child = targetDoc.getChild(i);
if (child.getType() == DocumentApp.ElementType.LIST_ITEM){
var listId = child.getListId();
var newElement = body.appendListItem(elementContent);
newElement.setListId(newElement);
Logger.log("child = " + child);
}
}
}
Following my comment, I tried to play with your script to see what happened and I came up with that code below...
I'm not saying it solves your issue and/or is the best way to achieve what you want but at least it gives a result that works as expected.
Please consider it as a "new playground" and keep experimenting on it to make it better ;-)
function test() {
var elementContent = "New item testing"; // a paragraph with its formating
var targetDocId = DocumentApp.getActiveDocument().getId();
var targetDoc = DocumentApp.openById(targetDocId);
var body = targetDoc.getBody();
var childIndex = 0;
for (var i = 0; i < targetDoc.getNumChildren(); i++) {
var child = targetDoc.getChild(i);
if (child.getType() == DocumentApp.ElementType.LIST_ITEM){
while(child.getType() == DocumentApp.ElementType.LIST_ITEM){
child = targetDoc.getChild(i)
childIndex = body.getChildIndex(child);
Logger.log(childIndex)
i++
}
child = targetDoc.getChild(i-2)
var listId = child.getListId();
Logger.log(childIndex)
var newElement = child.getParent().insertListItem(childIndex, elementContent);
newElement.setListId(child);
break;
}
}
}

Jquery modify html with each

I have a problem with jQuery. I want to write a function that dynamically replaces some content in my HTML.
This is the function:
function renderPage(datas, html) {
$(html).find("*").each(function(){
if ($(this).attr("data-func")) {
var df = $(this).attr("data-func");
var content = null;
eval("content = " + df + "($(this),datas);");
console.log(content);
}
});
}
The problem is, that it has no effect! I see in the log that the content variable is right, but the output is not different.
My function in eval:
function fill(element,data) {
element.html("BASSZA MEG");
var events = data.events;
var i = 0;
var n = events.length;
for (;i<n; i++) {
obj = events[i];
var tr = $("<tr>");
var nev = $("<td>");
var link = $("<a href='programbelso.html#id="+obj["event_id"]+"&logo="+obj["event_logo"]+"'>");
link.html(obj["event_name"]);
nev.append(link);
tr.append(nev);
element.append(tr);
}
console.log("element: " + element);
return element;
}
Firstly, do NOT loop over every single element in the document, and then use an if() statement. You can drastically improve the performance by using the right selector.
The following sample should achieve what you're trying to do without needing to use the evil eval() function, too:
function renderPage(datas, html) {
$('[data-func]').each(function() {
var df = $(this).attr("data-func");
var the_func = window[$(this).attr("data-func")];
if(typeof the_func === 'function') {
var content = the_func($(this), datas);
console.log(content);
}
});
}

Categories