I'm new to Google Apps Script and I'm trying to make a script for a spreadsheet where I'll store all the email addresses found by .getFrom() method in the sheet and ignore the same email addresses so that I get only one email address instead of multiple times. So far storing is working successfully but ignoring same emails is not working. I get same emails multiple times in my sheet's column.
Here's my code:
var n=threads.length;
var messages=thread.getMessages();
var getfrom = 0;
var allMails = [];
for (var i=0; i<n; i++)
{
for (var j=0; j<messages.length; j++)
{
var message=messages[j];
getfrom = message.getFrom();
var first_name = getfrom.substring(0, getfrom.indexOf(" "));
var last_name = getfrom.substring(getfrom.indexOf(" ")+1, getfrom.indexOf(" <"));
var email_address = 0;
if (first_name == '' && last_name == '')
{
email_address = getfrom;
} else {
email_address = getfrom.substring(getfrom.indexOf("<")+1, getfrom.indexOf(">"));
}
// This is how I check if I already have the email address or not
if (email_address == my_email || email_address[j] == email_address[j-1])
{
continue;
}
}
allMails.push([email_address]);
}
Logger.log(allMails);
sheet1.getRange(2, 3, n, 1).setValues(allMails);
Browser.msgBox("Operation complete");
How can I ignore duplicate values and get one email address instead of multiple times?
You can either ensure uniqueness before adding emails to the list, or build the full list first and remove duplicates later.
Option 1: Pre-filter
This example builds a one-dimensional array of addresses; because it's a simple array we can use the JavaScript built-in .indexOf() method to check for uniqueness. After all threads have been examined, the simple array is converted to a two-dimensional array for storage in the spreadsheet, using another Array built-in, map(). Before that though, the array gets sorted - just because we can. You might want to do other filtering, such as removing "no-reply" addresses.
function getUniqueFromAddresses1() {
var my_email = Session.getActiveUser().getEmail();
var threads = GmailApp.getInboxThreads();
var n=threads.length;
var allMails = [];
for (var i=0; i<n; i++)
{
var thread = threads[i];
var messages=thread.getMessages();
for (var j=0; j<messages.length; j++)
{
var message=messages[j];
var getfrom = message.getFrom();
// Use RegEx to extract just email address
var email_address = getfrom.match(/[^<> ]*\#[^> ]*/)[0];
// Skip messages I sent or addresses already collected
var index = allMails.indexOf(email_address);
if (email_address !== my_email && allMails.indexOf(email_address) == -1) {
allMails.push(email_address);
}
}
}
// Could do further filtering & sorting of allEmails here
allMails = allMails.sort()
Logger.log(JSON.stringify(allMails));
// convert allMails array to two-dimensional array
allMails = allMails.map( function(item){
return [item];
});
Logger.log(JSON.stringify(allMails));
// Store in spreadsheet; use dimensions of array to avoid mismatching range size
sheet1.getRange(2, 3, allMails.length, allMails[0].length).setValues(allMails);
debugger; // Pause in debugger
Browser.msgBox("Operation complete");
}
Option 2: Post-filter
Here's the alternate approach, removing duplicates after the array is built. The JavaScript magic here was lifted from this answer. We still use a one-dimensional array to collect and filter addresses. There's also an extra step required to remove our own address from the list.
Performance: This should be faster than approach 1, as there will be fewer comparisons required. HOWEVER, the bulk of the time used in the whole operation is tied up in accessing messages, so time savings in native JavaScript are negligible.
function getUniqueFromAddresses2() {
var my_email = Session.getActiveUser().getEmail();
var threads = GmailApp.getInboxThreads();
var n=threads.length;
var allMails = [];
for (var i=0; i<n; i++)
{
var thread = threads[i];
var messages=thread.getMessages();
for (var j=0; j<messages.length; j++)
{
var message=messages[j];
var getfrom = message.getFrom();
// Use RegEx to extract just email address
var email_address = getfrom.match(/[^<> ]*\#[^> ]*/)[0];
// Save the address
allMails.push(email_address);
// Skip messages I sent or addresses already collected
var index = allMails.indexOf(email_address);
if (email_address !== my_email && allMails.indexOf(email_address) == -1) {
allMails.push(email_address);
}
}
}
// Remove duplicates - https://stackoverflow.com/a/32533637/1677912
allMails = allMails.sort().reduce(function(a, b){ if (b != a[0]) a.unshift(b); return a }, []);
// Remove my address
if ((mine=allMails.indexOf(my_email)) > -1) allMails.splice(mine,1);
// Could do further filtering & sorting of allEmails here
allMails = allMails.sort()
Logger.log(JSON.stringify(allMails));
// convert allMails array to two-dimensional array
allMails = allMails.map( function(item){ return [item]; });
Logger.log(JSON.stringify(allMails));
sheet1.getRange(2, 3, n, 1).setValues(allMails);
debugger; // Pause in debugger
Browser.msgBox("Operation complete");
}
How did you get the email addresses?
The original function took several steps to identify an email address in the string returned by message.getFrom(). It's tricky, because that string can contain just an email address, or a name and an address. The operation can be simplified by using a regular expression to match just the email address, and ignore whatever other text is in the string.
// Use RegEx to extract just email address
var email_address = getfrom.match(/[^<> ]*\#[^> ]*/)[0];
The expression looks for # and the text immediately before and after it, bordered by a space or angle braces. You can try this out in an online demo.
/[^<> ]*\#[^> ]*/
[^<> ]* match a single character not present in the list below
Quantifier: * Between zero and unlimited times, as many times as possible, giving back as needed [greedy]
<> a single character in the list "<> " literally (case sensitive)
\# matches the character # literally
You need to cross check your allMails array for a given email address to ensure it's not in the list, however you can't easily check against allMails directly because it is a two-dimensional array.
I would add a single dimensional array purely for the purpose of cross-checking.
var n=threads.length;
var messages=thread.getMessages();
var getfrom = 0;
var allMails = [];
var cross_check = [];
for (var i=0; i<n; i++)
{
for (var j=0; j<messages.length; j++)
{
var message=messages[j];
getfrom = message.getFrom();
var first_name = getfrom.substring(0, getfrom.indexOf(" "));
var last_name = getfrom.substring(getfrom.indexOf(" ")+1, getfrom.indexOf(" <"));
var email_address = 0;
if (first_name == '' && last_name == '')
{
email_address = getfrom;
} else {
email_address = getfrom.substring(getfrom.indexOf("<")+1, getfrom.indexOf(">"));
}
if(email_address != my_email && cross_check.indexOf(email_address) == -1){
cross_check.push(email_address);
allMails.push([email_address]);
}
}
}
Logger.log(allMails);
sheet1.getRange(2, 3, n, 1).setValues(allMails);
Browser.msgBox("Operation complete");
See the documentation for the indexOf function, which explains why we check against -1, here:
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
Also check the Gmail Extractor - it saves the email addresses from Gmail in a Google spreadsheet.
Related
I am trying to send an automatic email on a reoccurring basis. My Google-App-script is working fine with the exception that it does not email the 1st email, i.e. first row. It starts the looping process of emails from the second row on. How do I tweak this issue in my code?
function sendemail() {
var spreadSheet = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var secondSheet = spreadSheet[1];
var dataRange = secondSheet.getDataRange();
var data = dataRange.getValues();
for (var i = 1; i < data.length; i++) {
(function(val) {
var row = data[i];
var emailAddress = row[0];
var message = 'Test Email';
MailApp.sendEmail(emailAddress, message);
})(i);
}
}
It only emails 'email2' and 'email3' but not to 'email1' in this case. How do I get it to send emails to all emails?
Your for-loop should start with zero. When you start with 1, you skip the first email in the array.
for (var i = 0; i < data.length; i++) { ... }
From Mozilla:
JavaScript arrays are zero-indexed. The first element of an array is at index 0, and the last element is at the index value equal to the value of the array's length property minus 1.
getValues() returns an array, so you need to be careful to use the correct index. This is different from the range indexes used by Google, but is noted in the documentation.
Remember that while a range index starts at 1, 1, the JavaScript array is indexed from [0][0].
I'm trying to check which person to assign to each job using GAS. The function runs against an array that contains both: the person responsible for the client and his client list.
The code works fine when running for the first Client in the array, it adds the member and everything, so I know it's working. The problem is it only runs once, so if the client is "PR", it will add "lucasfogolin" as a member, but if its CLC, it won't check.
var clients = [{actor:'lucasfogolin',clients:'PR,CLC,TrĂvia,Smart,MC,TTWO'},
{actor:'alfredorocha',clients:'FDV,IAB,IMDiva'}
]
My sorting function is below:
function sortActors(notification) {
//When a card is moved to the "Sorting list"
var card = new Notification(notification).movedCard('Sorting');
//Gets the cards name
var cardName = card.name();
for (var i = 0; i < clients.length; i+=1) {
//Creates an array from splitting the clients
var arrayClients = clients[i].clients.split(',');
for (var j = 0; j < arrayClients.length; j+=1) {
//Creates a REGEX to run against the card's name, not sensitive
var regex = new RegExp(arrayClients[j],'gi');
//Checks if the name of the client is in the name of the card
if(cardName.match(regex)[0] == arrayClients[j]) {
//Function that adds the actor to the card
addMembers(card,clients[i].actor)
}
}
}
return false;
}
addMembers function
function addMembers(card,members) {
//Makes an array from the members cited (if more than one is to be added)
var array = members.split(',');
//Runs a loop to add all members
for (var i = 0; i < array.length; i+=1) {
card.addMember(new Member({username: array[i]}));
}
}
change the code from
if(cardName.match(regex)[0] == arrayClients[j])
to this
if(cardName.match(regex) != null && cardName.match(regex)[0] == arrayClients[j])
In this part
//Checks if the name of the client is in the name of the card
if(cardName.match(regex)[0] == arrayClients[j]) {
You are checking element 0 of a regex string match, that's ok when you have a match but when you don't have a match cardName.match(regex) will return null and it is not possible to access element 0 of a null value. That could create problems.
GOOGLE SCRIPT not just normal javascript.
I am trying to put together a google sheets to show the rankings for an online videogame, Cube 2, Sauerbraten. I already have a way to get a list of players on the server at any given moment. However since people join and leave clans all the time, it would be very disappointing if when one leaves and/or joins a new clan, he has to start over in the rankings. So I quickly came up with the below code in hopes to scan a player's name, and if there is a clan tag at the beginning of his name, it gets rid of it. clansPre is an array of some of the clan tags. The idea is that if va|P1, RED|P2, .rC|P3, !s]P4, [RUSS]P5, Crowd>P6, oo|P7, and P08 played a game together, their stats would be recorded under P1, P2, P3, P4, P5, P6, P7, and P08. However, the limited testing I've been able to do has shown that it always removes the first 3 characters, regardless of name. Also, cName was my attempt to get name in string form, back when I was trying to use startWith().
var clansPre = ["va|", "RED|", ".rC|", "!s]", "vaQ'", "[RUSS]", "cm|", "Crowd>", "oo|"];
var numClans = clansPre.length;
function nameWoClan(name){
var cName = name.substring(0, name.length)
for(var cC = 0; cC<numClans; cC++) {
if(cName.search(clansPre[cC]) == 0) {
return cName.substring(clansPre[cC].length, name.length)
}
}
return cName;
}
It might be that I'm completely stupid, but in defence, the only programming language I learned that I didn't teach myself was Java, which was last year, and I haven't really needed to use Java since then, so I'm a little rusty. Anyway, thank you!
You were on a right track with your attempt.
However given your code.
var cName = name.substring(0, name.length) seems redundant.
Your saying you want the subscription of name from the start index of 0 to name.length that would be the same as
var cName = name;
Now I have written a more complex example that determines if the clan name is part of the name. If it is then removes the clan name but also returns an object with a clan name. Now this method is case insensitive (as it lowers the text). So if you want case sensitive matching then remove the two calls toLowerCase()
function nameWoClan(name) {
if (!name)
return null;
var clanName = '';
for (var i = 0; i < clansPre.length; i++) {
if (name.toLowerCase().startsWith(clansPre[i].toLowerCase())) {
clanName = name.substring(0, clansPre[i].length);
name = name.substring(clansPre[i].length);
break;
}
}
return {
clanName: clanName,
playerName: name
}
}
I have done a simple example below.
$('#extract-name').on('click', function() {
var names = $('#player-names').val().split('\n');
$('#names-list-body').html(''); //clear the table
names.forEach(function(item, index) {
var name = nameWoClan(item);
if (name) {
$('#names-list-body').append($('<tr><td>' + item + '</td><td>' + name.clanName + '</td><td>' + name.playerName + '</td></tr>'));
}
});
});
var clansPre = ["va|", "RED|", ".rC|", "!s]", "vaQ'", "[RUSS]", "cm|", "Crowd>", "oo|"];
function nameWoClan(name) {
if (!name)
return null;
var clanName = '';
for (var i = 0; i < clansPre.length; i++) {
if (name.toLowerCase().startsWith(clansPre[i].toLowerCase())) {
clanName = name.substring(0, clansPre[i].length);
name = name.substring(clansPre[i].length);
break;
}
}
return {
clanName: clanName,
playerName: name
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>
One full name per line
</p>
<textarea id="player-names" rows="5" cols="65">
john
.rC|sally
[RUSS]Superman
oo|Batman
RED|MAN
RED|va|
</textarea>
<br/>
<button type="button" id="extract-name">
Extract Name
</button>
<table id="names-list">
<thead>
<tr>
<th>Original Name</th>
<th>Clan Name</th>
<th>Player Name</th>
</tr>
</thead>
<tbody id="names-list-body"></tbody>
</table>
JSFiddle
EDIT AFTER REVIEW
After reviewing the Google Spreadsheet Script (never seen it before). Found we need a few extra bits. First of the startsWith does not appear to be supported. Not sure why. So i wrote a simple starts with function.
function myStartsWith(name, clanName){
name = name.toLowerCase();
clanName = clanName.toLowerCase();
var cNameLen = clanName.length;
var nameLen = name.length;
return cNameLen < nameLen && name.substring(0, cNameLen) === clanName;
}
Now I created a sample copy Copy of Pasta Ranks and have updated the script.
function myStartsWith(name, clanName){
name = name.toLowerCase();
clanName = clanName.toLowerCase();
var cNameLen = clanName.length;
var nameLen = name.length;
return cNameLen < nameLen && name.substring(0, cNameLen) === clanName;
}
function nameWoClan(name) {
if (!name)
return null;
var clanName = '';
for (var i = 0; i < clansPre.length; i++) {
var match = myStartsWith(name, clansPre[i]);
if (match) {
clanName = name.substring(0, clansPre[i].length);
name = name.substring(clansPre[i].length);
break;
}
}
return {
clanName: clanName,
playerName: name
}
}
function testing(){
//get the sheet to query
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[2];
//get the last row index
var lastRow = sheet.getLastRow();
//get the player names range O2:O18 //removes header
var range = sheet.getRange(2, 15, lastRow);
//get the values for the range
var data = range.getValues()
var names = [];
//iterate the range getting the records. note index [0] as values is a multi-dimension name
for(var i = 0;i< lastRow;i++){
var name = nameWoClan(data[i][0]);
if(name)
{
names.push(name);
}
}
var n = names;
}
Its not the pretties but if you debug the testing function and set a breakpoint on the final var n = names you will see names contains a list of player names and clan names.
Also worth noting that none of the Player names actually had a clan name so I added evil. as a test case.
function removeClanTag(name) {
var index = name.search(/>|\]|\||'/g);
return name.substring(index+1);
}
var u = [
"tag>username",
"tag|username",
"tag'username",
"tag]username",
];
for (var i in u) {
console.log(removeClanTag(u[i]));
}
You could try scanning for the locations of all the tag endings and removing the text from +1 after that point.
If you want to do something more complicated you could create a regex to match specific clan tag patterns like
/[.*]/g
Which will match anything between braces
My problem is as follows: I am trying to take data as formatted in the 'names' variable in the snippet below, convert the string to array, then reorganize the array so the text is in the correct order. I am able to get the pieces I have put together to properly sort the first or last instance of a first & last name, but am seeking guidance on how to go about processing multiple names. The snippet below will return the last instance of the first & last name in the correct order. At this point, I am only looking to have the data returned as a properly sorted array, e.g.
if the input string is
names = "Bond, James & Banner, Bruce";
once processed should return: ['James', 'Bond,', '&', 'Bruce', 'Banner,']
As always I appreciate all the help I can get, thanks in advance!
Array.prototype.move = function(from,to){
this.splice(to,0,this.splice(from,1)[0]);
return this;
};
var names ="Bond, James & Banner, Bruce";
var namesArr = names.split(' ');
var idx;
// search for a comma (only last names have commas with them)
for(var i = 0; i < namesArr.length; i++) {
if(namesArr[i].indexOf(',') != -1) {
idx = i;
}
}
namesArr.move(idx, idx+1);
console.log(namesArr);
You were close but this solution should work for you. Basically you need to update in the loop and increment the index i to account for the switch. Otherwise you will end up revisiting the first last name you switch.
Array.prototype.move = function(from,to){
this.splice(to,0,this.splice(from,1)[0]);
return this;
};
var names ="Bond, James & Banner, Bruce & Guy, Other";
var namesArr = names.split(' ');
var idx;
// search for a comma (only last names have commas with them)
for(var i = 0; i < namesArr.length; i++) {
if(namesArr[i].indexOf(',') != -1) {
namesArr.move(i, i+1);
i++;
}
}
console.log(namesArr);
Another solution could be by using String.prototype.match() and a regular expression \w+ to match the names:
var names = "Bond, James & Banner, Bruce & Licoln, Anna"; // ...
var arr_names = names.match(/\w+/g); // Match names
var res = [];
for (var i = 0; i < arr_names.length; i += 2) { // Step by 2
res.push(arr_names[i + 1]); // Push the name before
res.push(arr_names[i]); // Push the current name
res.push("&"); // Add "&"
}
res.splice((res.length - 1), 1); // Remove last "&"
console.log(res);
I am using the twitter stream api. I am currently using follow & track parameters but the API combines them with an OR and I want to combine them with an AND. The only way logically to do this is to manually check that the tweet contains a userID# in my array of userIDs to follow and the tweet text contains a keyword from my array of keywords to track.
I have converted the tweet object and tweet.text object into a JSON string like this:
var tweetText = JSON.stringify(tweet.text);
var tweetObject = JSON.stringify(tweet);
I want an if statement like this:
if tweetObject == a value in tracked ids array && tweetText == a value in tracked words array
do the rest of my code
How can I achieve this? I tried to use .indexOf() but that takes only one parameter so I could say:
if(tweetObject.indexOf("12345678") > -1 && tweetText.indexOf("spotify") > -1) {
do my code
}
But this is NOT what I want, I want it to go through the array and see if tweetObject and tweetText contain any of the array elements and if so do the code
this is what I have:
// start twitter
t.stream(
"statuses/filter",
{follow: trackedHandles, track: trackedWords, lang: "en" },
function(stream) {
stream.on('data', function(tweet) {
//convert tweet.text& tweet.entities into two strings
var tweetText = JSON.stringify(tweet.text);
var userName = JSON.stringify(tweet.entities);
var tweetObject = JSON.stringify(tweet);
var flag = false;
for (var x = 0; x < trackedHandles.length; x++) {
//console.log(trackedHandles[x].toString());
var searchParam = tweetObject.indexOf(trackedHandles[x].toString());
if(searchParam != -1) {
flag = true;
//console.log(searchParam);
//console.log(trackedHandles[x].toString());
//incriment the keywords, and store the keywords as keys in redis (as they appear in feed)
for (var i = 0; i < trackedWords.length; i++) {
if(tweetText.indexOf(trackedWords[i]) > - 1) {
// incriments added value to the word
console.log(trackedWords[i]);
redisClient.incr(trackedWords[i]);
}
}
//if tweetText does not contains "RT" & "#", print tweet to console.
if(tweetText.indexOf("RT") == -1 && tweetText.indexOf("#") == -1) {
//console.log(tweetText + "\r\n ");
//console.log(screen_name + "\r\n ");
//console.log(tweet);
}
}
}
});
}
);
Please help guys, this is for my senior project for my undergrad degree. I have most of the app it self complete, I just need this one piece because What I am trying to accomplish is to only stream tweets from specific users based on specific keywords. If you can think of a more appropriate title for this please let me know. Thanks in advance for any help!
Use a for or forEach loop
var tweetObjArr= [1,2,3,4,5];
var tweetIds = [7,3]
var flag = false;
for (var i = 0; i < tweetIds .length; i++) {
if(tweetObjArr.indexOf(tweetIds [i]) != -1) {
flag=true;
break;
}
}
Might be you want jQuery.inArray function like
if(jQuery.inArray("12345678", tweetObject)!==-1)