I can't figure out why the following code isn't working properly in case of special characters:
[...]
// get strings (names) from spreadsheet
var persons = SpreadsheetApp.getActiveSheet().getRange(2, 1, 31, 4).getValues();
// for each row
for (var row in persons) {
// build the filename
var myFile = persons[row][1] + "_" + persons[row][0] + "_20180124.txt";
[...]
// handle from Google Drive only file
var driveFiles = DriveApp.getFilesByName(myFile);
while (driveFiles.hasNext()) {
var file = driveFiles.next();
if(driveFiles.getName() == myFile) {
/* write to Log
Saša_KLANJŠČEK_20180124.txt not written
Peter_MARINČIČ_20180124.txt not written
Peter_KLANJŠČEK_20180124.txt not written
Niko_ČERNIC_20180124.txt not written
Tjaša_KOGOJ_20180124.txt written
*/
Logger.log(myFile + "\n");
[...]
Strings with uppercase unicode characters causes the conditional statement to fail. I tried with toString("UTF-8") method, but it still doesn't work. Is it an encoding problem?
Change:
if(driveFiles.getName() == myFile) {
into
if(file.getName() == myFile) {
Related
So, I never ever programmed JavaScript and never did anything with Google Script before either. I have a fairly good understanding of Visual Basic and macros in Excel and Word. Trying to make a fairly basic program: Plow through a list of variables in a spreadsheet, make a new sheet for each value, insert a formula in this new sheet, cell (1,1).
Debug accepts my program, no issues - however, nothing at all is happening when I run the program:
function kraft() {
var rightHere =
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange("A1:A131");
var loopy;
var goshDarn = "";
for (loopy = 1; loopy < 132; loopy++) {
celly = rightHere.getCell(loopy,1);
vaerdi = celly.getValue();
fed = celly.getTextStyle();
console.log(vaerdi & " - " & fed);
if (vaerdi != "" && fed.isBold == false) {
SpreadsheetApp.getActiveSpreadsheet().insertSheet(vaerdi);
var thisOne = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(vaerdi);
thisOne.deleteRows(500,500);
thisOne.deleteColumns(5, 23);
thisOne.getRange(1,1).setFormula("=ArrayFormula(FILTER('Individuelle varer'!A16:D30015,'Individuelle varer'!A16:A30015=" & Char(34) & vaerdi & Char(34) & ")))");
}
}
}
activeSheet could be called by name, so could activeSpreadsheet, I guess. But range A1:A131 has a ton of variables - some times there are empty lines and new headers (new headers are bold). But basically I want around 120 new sheets to appear in my spreadsheet, named like the lines here. But nothing happens. I tried to throw in a log thingy, but I cannot read those values anywhere.
I must be missing the most total basic thing of how to get script connected to a spreadsheet, I assume...
EDIT: I have tried to update code according to tips from here and other places, and it still does a wonderful nothing, but now looks like this:
function kraft() {
var rightHere = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange("A1:A131");
var loopy;
var goshDarn = "";
for (loopy = 1; loopy < 132; loopy++) {
celly = rightHere.getCell(loopy,1);
vaerdi = celly.getValue();
fed = celly.getFontWeight();
console.log(vaerdi & " - " & fed);
if (vaerdi != "" && fed.isBold == false) {
SpreadsheetApp.getActiveSpreadsheet().insertSheet(vaerdi);
var thisOne = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(vaerdi);
thisOne.deleteRows(500,500);
thisOne.deleteColumns(5, 23);
thisOne.getRange(1,1).setFormula("=ArrayFormula(FILTER('Individuelle varer'!A16:D30015,'Individuelle varer'!A16:A30015=" + "\"" + vaerdi + "\"" + ")))");
}
}
}
EDIT2: Thanks to exactly the advice I needed, the problem is now solved, with this code:
function kraft() {
var rightHere = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange("A1:A131");
var loopy;
for (loopy = 1; loopy < 132; loopy++) {
celly = rightHere.getCell(loopy,1);
vaerdi = celly.getValue();
fed = celly.getFontWeight()
console.log(vaerdi & " - " & fed);
if (vaerdi != "" && fed != "bold") {
SpreadsheetApp.getActiveSpreadsheet().insertSheet(vaerdi);
var thisOne = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(vaerdi);
thisOne.deleteRows(500,499);
thisOne.deleteColumns(5, 20);
thisOne.getRange(1,1).setFormula("=ArrayFormula(FILTER('Individuelle varer'!A16:D30015;'Individuelle varer'!A16:A30015=" + "\"" + vaerdi + "\"" + "))");
}
}
}
There are multiple issues with your script, but the main one is that you never actually call the isBold() function in your 'if' statement.
if (value && format.isBold() == false) {
//do something
}
Because you omitted the parentheses in 'fed.isBold', the expression never evaluates to 'true'. 'isBold' (without the parentheses) is of type Object as it's a function.
There are other issues that prevent the script from running properly:
Not using the 'var' keyword to declare variables and polluting the global scope. As a result, all variables you declare within your 'for' loop are not private to your function. Instead, they are attached to the global object and are accessible outside the function. https://prntscr.com/kjd8s5
Not using the built-in debugger. Running the function is not debugging. You should set the breakpoints and click the debug button to execute your function step-by-step and examine all values as it's being executed.
Deleting the non-existent columns. When you create the new sheet, you call the deleteColums(). There are 26 columns in total. The 1st parameter is the starting column while the 2nd one specifies how many columns must be deleted. Starting from column 5 and telling the script to remove 23 columns will throw an exception. Always refer to the documentation to avoid such errors.
console.log doesn't exist within the context of the Script Editor. You are NOT executing the scripts inside your browser, so Browser object model is not available. Use Logger.log(). Again, this is detailed in the documentation.
Your formula is not formatted properly.
JS is a dynamically typed language that's not easy to get used to. If you don't do at least some research prior to writing code, you'll be in for a lot of pain.
I need to convert a path this UNC. I have searched and search and cannot piece anything together.
"\\NAS_01\GlobalShare\Docs\Customers\2017\S\Smith, John\photo1.jpg"
I need to remove the "\NAS_01\GlobalShare\Docs\Customers\" portion of the path and also "photo1.jpg" and end up with:
2017\S\Smith, John\
so that I can pass it to the following function:
function getDriveFolderNoCreate(path, rootFolder) {
var name, folder, search, fullpath;
// Remove extra slashes and trim the path
fullpath = path.replace(/^\/*|\/*$/g, '').replace(/^\s*|\s*$/g, '').split("/");
// Always start with the main Drive folder
folder = rootFolder;
for (var subfolder in fullpath) {
name = fullpath[subfolder];
search = folder.getFoldersByName(name);
if (search.hasNext()) {
var folder = search.next;
var folderID = folder.getId();
return folderID;
}
}
}
My intention is to return a url to open the Google Drive folder with the same path.
I ended up with a multi-part solution that works very well.
I paste the fill UNC path to cell B2.
This formula is in B3 =Index(split(B2, "\"), 0, 8)
It returns the exact folder name i need.
Then in my gs file:
function findDriveFolder() {
var pFId = "XYZ1233333333333";
var input = SpreadsheetApp.getActive().getRange("B3").getValue();
var folders = DriveApp.getFoldersByName(input);
Logger.log("folders: " + folders[0]);
while (folders.hasNext()) {
var folder = folders.next();
var url = folder.getUrl();
showAnchor("View Folder", url);
}
}
function showAnchor(name,url) {
var html = '<html><body>'+name+'</body></html>';
var ui = HtmlService.createHtmlOutput(html)
SpreadsheetApp.getUi().showModelessDialog(ui,"Files Folder");
}
I have not implemented the searchFolders part yet that I hope will speed it up. At least it's working for now.
Apps Script needs your input backslashes to be escaped if you are writing the string yourself (i.e. testing input).
wrong:
input = "\\NAS_01\GlobalShare\Docs\Customers\2017\S\Smith, John\photo1.jpg"
right:
input = "\\\\NAS_01\\GlobalShare\\Docs\\Customers\\2017\\S\\Smith, John\\photos1.jpg"
In Apps Script then, I am able to get the matching portion with the following regex:
/\d{4}\\[A-Z]\\.+\\/
i.e:
function unc2uri(input) {
const forwardSlash = String.fromCharCode(47);
const backSlash = String.fromCharCode(92);
if(!input)
input = '\\\\NAS_01\\GlobalShare\\Docs\\Customers\\2017\\S\\Smith, John\\photo1.jpg';
// Should show \\NAS_01\GlobalShare\Docs\Customers\2017\S\Smith, John\photo1.jpg
Logger.log(input);
const matcher = /\d{4}\\[A-Z]\\.+\\/;
const arrayOfMatches = input.match(matcher);
// Should show [2017\S\Smith, John\].
Logger.log(arrayOfMatches);
}
To verify, ask for the input string from someplace else (example, Browser.inputBox) and pass that to the above as input:
function readInput() {
unc2uri(Browser.inputBox("What's the path?"));
}
In the inputBox, you would enter the string you expect to be sent, as we view it, i.e. \\NAS_01\GlobalShare\Docs\Customers\2017\S\Smith, John\photo1.jpg
So I am goofing off and wrote something that first queries other websites and loads all of the pertinent stock exchange symbols and the tries to iterate through the symbols and load Yahoo's server for the latest stock price. For some reason no matter what I try to search by using HtmlAgilityPack, I can't seem to get any pertinent info back. I don't think there's an issue with some javascript running after the page is loaded (but I could be wrong).
The following is a generalized routine that can be tinkered with to try and get the percentage change of the stock symbol back from Yahoo:
string symbol = "stock symbol"
string link = #"http://finance.yahoo.com/q?uhb=uh3_finance_vert&s=" + symbol;
string data = String.Empty;
try
{
// keep in this scope so wedget and doc deconstruct themselves
var webget = new HtmlWeb();
var doc = webget.Load(link);
string percGrn = doc.FindInnerHtml("//span[#id='yfs_pp0_" + symbol + "']//b[#class='yfi-price-change-up']");
string percRed = doc.FindInnerHtml("//span[#id='yfs_pp0_" + symbol + "']//b[#class='yfi-price-change-down']");
// create somthing we'll nuderstand later
if ((String.IsNullOrEmpty(percGrn) && String.IsNullOrEmpty(percRed)) ||
(!String.IsNullOrEmpty(percGrn) && !String.IsNullOrEmpty(percRed)))
throw new Exception();
// adding string to empty gives string
string perc = percGrn + percRed;
bool isNegative = String.IsNullOrEmpty(percGrn);
double percDouble;
if (double.TryParse(Regex.Match(perc, #"\d+([.])?(\d+)?").Value, out percDouble))
data = (isNegative ? 0 - percDouble : percDouble).ToString();
}
catch (Exception ex) { }
finally
{
// now we need to check what we have and load into the datgridView
if (!newData_d.ContainsKey(symbol)) newData_d.Add(symbol, data);
else MessageBox.Show("ERROR: Duplicate stock Symbols for: " + symbol);
}
And here is the extended method for FindInnerHtml:
// this is for the html agility class
public static string FindInnerHtml( this HtmlAgilityPack.HtmlDocument _doc, string _options)
{
var node = _doc.DocumentNode.SelectSingleNode(_options);
return (node != null ? node.InnerText.ToString() : String.Empty);
}
Any help with getting something back would be appreciated, thanks!
///////////////////////////////////////
EDIT:
///////////////////////////////////////
I highlighted the span id and then check out line 239 for where I saw 'yfi-price-change-up' reference:
The following XPath successfully find the target <span> which contains percentage of increase (or decrease) :
var doc = new HtmlWeb().Load("http://finance.yahoo.com/q?uhb=uh3_finance_vert&s=msft");
var xpath = "//span[contains(#class,'time_rtq_content')]/span[2]";
var span = doc.DocumentNode.SelectSingleNode(xpath);
Console.WriteLine(span.InnerText);
output :
(0.60%)
i am new to Google apps script, i want to create string of random characters in the code given below in variable body2.
function myfunction() {
var files = DriveApp.getFiles();
while (files.hasNext(`enter code here`)) {
Logger.log(files.next().getName());
}
var recipient = Session.getActiveUser().getEmail();
var subject = 'A list of files in your Google Drive';
var body1 = Logger.getLog();
var body2;
for(var i=0;i<6;i++)
{
body2[i]=BigNumber.tostring("Math.floor(Math.random()*11)");
}
body=body1+body2;
MailApp.sendEmail(recipient, subject, body);
};
but when i run this function, it says "TypeError: Cannot find function tostring in object 0. (line 12, file "Code") " i can't understand how to solve this error?
Why we have to multiply random by 11 , can it be multiplied with any integer number?
what if i want that string in only capital letters.!
Some other question
1) i don't have enough knowledge of JavaScript, is it good to learn GAS directly?
2) i can't find proper written material or documentation for GAS , the material available at Google's official site is seems to be updating time by time , what to do then ? any link to material would help me .!
I guess I just figured
function randomStr(m) {
var m = m || 15; s = '', r = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i=0; i < m; i++) { s += r.charAt(Math.floor(Math.random()*r.length)); }
return s;
};
Hope someone finds it helpful.
As for a random string use this its better:
Math.random().toString(36). 36 is the base thus will use letters and numbers in the string.
As for gas documentation, the official page is pretty complete. It changes because it constantly improves and adds new services.
I have this charIdGeneration() in my GAS library
function charIdGenerator()
{
var charId ="";
for (var i = 1; i < 10 ; i++)
{
charId += String.fromCharCode(97 + Math.random()*10);
}
//Logger.log(charId)
return charId;
}
I was interested in writing a twitter bot to help out some friends at a local ski resort. I found this tutorial from Amit Agarwal which gave me enough to get started (it did take me more than 5 minutes since I did a lot of modifying). I host the script on google docs.
FIRST I think this is javascript (my understanding is that google apps script uses javascript...) and when I have had problems with the code so far, google searches for javascript-such-and-such have been helpful, but if this is not actually javascript, please let me know so I can update the tag accordingly!
I have no prior experience with javascript, so I am pretty happy that it's actually working. But I want to see if I'm doing this right.
The start function initiates the trigger, which kicks off the fetchTweets() function every interval (30 minutes). In order to avoid duplicates (the first errors I encountered) & potentially being flagged as spam, I needed a way to ensure that I was not posting the same tweets over and over again. Within the start() function, the initial since_id value is assigned:
ScriptProperties.setProperty("SINCE_TWITTER_ID", "404251049889759234");
Within the fetchTweet() function, I think I am updating this property with the statement:
ScriptProperties.setProperty("SINCE_TWITTER_ID", lastID + '\n');
Is this a good way to do this? Or is there a better/more reliable way? And if so, how can I be sure it's updating the property? (I can check the log file and it seems to be doing it, so I probably just need to create a permanent text file for the logger).
Any help is greatly appreciated!!
/** A S I M P L E T W I T T E R B O T **/
/** ======================================= **/
/** Written by Amit Agarwal #labnol on 03/08/2013 **/
/** Modified by David Zemens #agnarchy on 11/21/2013 **/
/** Tutorial link: http://www.labnol.org/?p=27902 **/
/** Live demo at http://twitter.com/DearAssistant **/
/** Last updated on 09/07/2013 - Twitter API Fix **/
function start() {
Logger.log("start!" + '\n')
// REPLACE THESE DUMMY VALUES
// https://script.google.com/macros/d/18DGYaa-jbaAK9rEv0HZ2cMcWjFGgkvVcvr6TfksMNbbu2Brk3gZeZ46R/edit
var TWITTER_CONSUMER_KEY = "___REDACTED___";
var TWITTER_CONSUMER_SECRET = "___REDACTED___";
var TWITTER_HANDLE = "___REDACTED___";
var SEARCH_QUERY = "___REDACTED___" + TWITTER_HANDLE;
// Store variables
ScriptProperties.setProperty("TWITTER_CONSUMER_KEY", TWITTER_CONSUMER_KEY);
ScriptProperties.setProperty("TWITTER_CONSUMER_SECRET", TWITTER_CONSUMER_SECRET);
ScriptProperties.setProperty("TWITTER_HANDLE", TWITTER_HANDLE);
ScriptProperties.setProperty("SEARCH_QUERY", SEARCH_QUERY);
ScriptProperties.setProperty("SINCE_TWITTER_ID", "404251049889759234");
// Delete exiting triggers, if any
var triggers = ScriptApp.getScriptTriggers();
for(var i=0; i < triggers.length; i++) {
ScriptApp.deleteTrigger(triggers[i]);
}
// Setup trigger to read Tweets every 2 hours
ScriptApp.newTrigger("fetchTweets")
.timeBased()
.everyMinutes(30)
//.everyHours(2)
.create();
}
function oAuth() {
//Authentication
var oauthConfig = UrlFetchApp.addOAuthService("twitter");
oauthConfig.setAccessTokenUrl("https://api.twitter.com/oauth/access_token");
oauthConfig.setRequestTokenUrl("https://api.twitter.com/oauth/request_token");
oauthConfig.setAuthorizationUrl("https://api.twitter.com/oauth/authorize");
oauthConfig.setConsumerKey(ScriptProperties.getProperty("TWITTER_CONSUMER_KEY"));
oauthConfig.setConsumerSecret(ScriptProperties.getProperty("TWITTER_CONSUMER_SECRET"));
}
function fetchTweets() {
oAuth();
// I put this line in to monitor whether the property is getting "stored" so as to avoid
// reading in duplicate tweets.
Logger.log("Getting tweets since " + ScriptProperties.getProperty("SINCE_TWITTER_ID"))
var twitter_handle = ScriptProperties.getProperty("TWITTER_HANDLE");
var search_query = ScriptProperties.getProperty("SEARCH_QUERY")
Logger.log("searching tweets to " + search_query + '\n');
// form the base URL
// restrict to a certain radius ---:
//var search = "https://api.twitter.com/1.1/search/tweets.json?count=5&geocode=42.827934,-83.564306,75mi&include_entities=false&result_type=recent&q=";
// unrestricted radius:
var search = "https://api.twitter.com/1.1/search/tweets.json?count=5&include_entities=false&result_type=recent&q=";
search = search + encodeString(search_query) + "&since_id=" + ScriptProperties.getProperty("SINCE_TWITTER_ID");
var options =
{
"method": "get",
"oAuthServiceName":"twitter",
"oAuthUseToken":"always"
};
try {
var result = UrlFetchApp.fetch(search, options);
var lastID = ScriptProperties.getProperty("SINCE_TWITTER_ID");
if (result.getResponseCode() === 200) {
var data = Utilities.jsonParse(result.getContentText());
if (data) {
var tweets = data.statuses;
//Logger.log(data.statuses);
for (var i=tweets.length-1; i>=0; i--) {
// Make sure this is a NEW tweet
if (tweets[i].id > ScriptProperties.getProperty("SINCE_TWITTER_ID")) {
lastID = (tweets[i].id_str);
var answer = tweets[i].text.replace(new RegExp("\#" + twitter_handle, "ig"), "").replace(twitter_handle, "");
// I find this TRY block may be necessary since a failure to send one of the tweets
// may abort the rest of the loop.
try {
Logger.log("found >> " + tweets[i].text)
Logger.log("converted >> " + answer + '\n');
sendTweet(tweets[i].user.screen_name, tweets[i].id_str, answer.substring(0,140));
// Update the script property to avoid duplicates.
ScriptProperties.setProperty("SINCE_TWITTER_ID", lastID);
Logger.log("sent to #" + tweets[i].user.screen_name + '\n');
} catch (e) {
Logger.log(e.toString() + '\n');
}
}
}
}
}
} catch (e) {
Logger.log(e.toString() + '\n');
}
Logger.log("Last used tweet.id: " + lastID + + "\n")
}
function sendTweet(user, reply_id, tweet) {
var options =
{
"method": "POST",
"oAuthServiceName":"twitter",
"oAuthUseToken":"always"
};
var status = "https://api.twitter.com/1.1/statuses/update.json";
status = status + "?status=" + encodeString("RT #" + user + " " + tweet + " - Thanks\!");
status = status + "&in_reply_to_status_id=" + reply_id;
try {
var result = UrlFetchApp.fetch(status, options);
Logger.log("JSON result = " + result.getContentText() + '\n');
}
catch (e) {
Logger.log(e.toString() + '\n');
}
}
// Thank you +Martin Hawksey - you are awesome
function encodeString (q) {
// Update: 09/06/2013
// Google Apps Script is having issues storing oAuth tokens with the Twitter API 1.1 due to some encoding issues.
// Henc this workaround to remove all the problematic characters from the status message.
var str = q.replace(/\(/g,'{').replace(/\)/g,'}').replace(/\[/g,'{').replace(/\]/g,'}').replace(/\!/g, '|').replace(/\*/g, 'x').replace(/\'/g, '');
return encodeURIComponent(str);
// var str = encodeURIComponent(q);
// str = str.replace(/!/g,'%21');
// str = str.replace(/\*/g,'%2A');
// str = str.replace(/\(/g,'%28');
// str = str.replace(/\)/g,'%29');
// str = str.replace(/'/g,'%27');
// return str;
}
When you use ScriptProperties.setProperty("KEY", "VALUE");, internally Script Properties will overwrite a duplicate key (i.e., if an old Property has the same key, your new one will replace it). So in your case, since you are using the same identifier for the key (SINCE_TWITTER_ID), it will replace any previous Script Property that is that key.
Furthermore, you can view Script Properties via File -> Project properties -> Project properties (tab). Imo Google didn't name that very well. User properties as specific to Google users. Script properties as specific to the Script Project you are working under.
Also, it probably isn't a good idea to include \n in your value when you set the property. That will lead to all sorts of bugs down the road, because you'll have to compare with something like the following:
var valToCompare = "My value\n";
instead of:
var valToCompare = "My value";
because the value in SINCE_TWITTER_ID will actually be "some value\n" after you call your fetchTweet() function.
Of course, one seems more logical I think, unless you really need the line breaks (in which case you should be using them somewhere else, for this application).
Its ok like that thou I dont know why you are adding \n at fhe end. Might confuse other code. You can see script properties in the script's file menu+ properties