Last active
March 1, 2020 03:47
-
-
Save adamwolf/d2fe318c9de36ea3692cb2d3ea0f5952 to your computer and use it in GitHub Desktop.
Integrate Gmail and Beeminder using Google Apps Script. Beemind the age of your oldest email in your inbox, or the number of unread threads.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Beemind Gmail, by Adam Wolf | |
2019/07/26 | |
This is a Google Apps Script that lets you beemind the oldest message in your Gmail inbox in “days old”, as well as the | |
unread thread count. | |
You can share it to multiple Gmail accounts of yours, and each one will beemind to a different goal. | |
This is not user-friendly right now (more on this later), but it does the job for me. | |
## Getting Started | |
1. Go to https://script.google.com/home, and setup Google Apps Script if you haven’t. Create a new project. I called | |
mine BeemindGmail. Copy this script into the file it creates. | |
2. Get your Beeminder username and auth token. Your username and auth token can be found at | |
https://www.beeminder.com/api/v1/auth_token.json from a browser where you have logged into Beeminder. You should see | |
some JSON, with two keys and two values. The two values are your username and auth token. They do not include the | |
quotation marks. Don't share your auth token with anyone that you wouldn't give your password. | |
3. Put the username and auth token in the Script Properties as BEEMINDER_USER and BEEMINDER_TOKEN. You can do this | |
in File > Project Properties. | |
4. Run the function copyScriptCredentialsToUserProperties. You can do this in the top bar. Hit Save, and then there’s | |
a function dropdown. Select copyScriptCredentialsToUserProperties, and then hit the play button. This pulls the | |
username and token from the Script properties, which get copied along when you share, to User properties, which do not. | |
(You can delete the Script properties now, if you want, or you can wait until after we’ve done some testing!) | |
5. Configure the userGoalMapping. This ties the Gmail user to the Beeminder goal, and the type of datapoint. | |
I use this on multiple gmail accounts, so mine looks like: | |
var userGoalMapping = {'[email protected]': | |
{'oldestMessage': 'oldest_msg_in_gmail_inbox', | |
'readThreads': 'mail_unread_count'}, | |
'[email protected]': | |
{'oldestMessage': 'cbl_oldest_msg_in_inbox', | |
'readThreads': 'cbl_mail_unread_count'} | |
} | |
If you only want oldestMessage or readThreads, only include those. Remember to create the goal first! | |
6. Try it out! Run the function BeemindGmail using the top bar. You can see the logs in View > Logs. | |
7. Set this to run automatically. I have it run hourly. Do this in Edit > Current project's Triggers. I also set | |
mine to email me immediately if there is a script issue. | |
8. Delete the Script Properties if you haven’t already. This ensures that if you share this script with a buddy, you | |
aren’t sharing your auth token by accident. | |
Feel free to file an issue, contact me with problems, or even submit a fix :) | |
One idea is to have this submit datapoints via email, rather than the API! It would be much more user friendly. | |
However, this will never be able to be super user friendly, as Google has really locked down the Gmail API and requires | |
a pretty strict security audit for anything that goes through all your email like this. (This is good, and I'm not | |
complaining.) | |
If lots of people want this, rather than polish this up, we should talk to Beeminder themselves and let them know that | |
folks are interested in these metrics! | |
*/ | |
var userGoalMapping = {'[email protected]': {'oldestMessage': 'oldest_msg_in_gmail_inbox', 'readThreads': 'mail_unread_count'}, | |
'[email protected]': {'oldestMessage': 'cbl_oldest_msg_in_inbox', 'readThreads': 'cbl_mail_unread_count'} | |
} | |
function getAllInboxThreads() | |
{ | |
var out = []; | |
var index = 0; | |
const perPage = 50; | |
do { | |
page = GmailApp.getInboxThreads(index, perPage); | |
out = out.concat(page); | |
index += page.length; | |
} while (page.length == perPage); | |
return out; | |
} | |
function getOldestMessageInInbox() { | |
// may return null, if the inbox is empty. | |
//Get the oldest thread in the inbox, and get the newest message in it. | |
var threads = getAllInboxThreads(); | |
if (threads.length == 0) | |
{ | |
return null; | |
} | |
var messages = GmailApp.getMessagesForThread(threads[threads.length-1]); | |
var message = messages[messages.length-1]; | |
return message; | |
} | |
function getReadThreadsInInbox() | |
{ | |
var threads = getAllInboxThreads(); | |
var messages = GmailApp.getMessagesForThreads(threads); | |
var readThreads = []; //list of subjects | |
for (var thread_index = 0 ; thread_index < messages.length; thread_index++) { | |
for (var message_index = 0; message_index < messages[thread_index].length; message_index++) { | |
var message = messages[thread_index][message_index]; | |
var subject = message.getSubject(); | |
var inInbox = message.isInInbox(); | |
var isUnread = message.isUnread(); | |
if (inInbox && !isUnread) | |
{ | |
readThreads.push(subject); | |
break; // go to next thread | |
} | |
} | |
} | |
return readThreads; | |
} | |
function beemindGmail() | |
{ | |
var userEmail = Session.getEffectiveUser().getEmail(); | |
Logger.log("Beeminding Gmail for: "+ userEmail); | |
var userTimeZone = CalendarApp.getDefaultCalendar().getTimeZone(); | |
if (!(userEmail in userGoalMapping)) { | |
throw new Error( "User " + userEmail + " not in goal mapping." ); | |
} else | |
{ | |
if ('oldestMessage' in userGoalMapping[userEmail]) | |
{ | |
Logger.log("Beeminding oldest message."); | |
oldestMessage = getOldestMessageInInbox(); | |
var days_old = 0; | |
var comment; | |
if (oldestMessage) | |
{ | |
Logger.log("Oldest message in inbox: " + oldestMessage.getDate() + ", " + oldestMessage.getSubject()); | |
var d = oldestMessage.getDate().getTime(); | |
var now = Date.now(); | |
var diff = now - d; | |
comment = oldestMessage.getSubject(); | |
days_old = ((now - d)/ 1000 / 60 / 60 / 24); | |
} else | |
{ | |
Logger.log("No messages in inbox. Nice work."); | |
comment = ""; | |
} | |
createDatapoint(userGoalMapping[userEmail]['oldestMessage'], days_old, comment); | |
} | |
if ('readThreads' in userGoalMapping[userEmail]) | |
{ | |
Logger.log("Beeminding read threads."); | |
readThreads = getReadThreadsInInbox(); | |
Logger.log("Read threads in inbox: " + readThreads.length + " (" + readThreads + ")"); | |
createDatapoint(userGoalMapping[userEmail]['readThreads'], readThreads.length); | |
} | |
} | |
Logger.log("Done."); | |
} | |
function copyScriptCredentialsToUserProperties() | |
{ | |
//Run this function manually to copy the proper | |
var userProperties = PropertiesService.getUserProperties(); | |
var scriptProperties = PropertiesService.getScriptProperties(); | |
userProperties.setProperty("BEEMINDER_USER", scriptProperties.getProperty("BEEMINDER_USER")); | |
userProperties.setProperty("BEEMINDER_TOKEN", scriptProperties.getProperty("BEEMINDER_TOKEN")); | |
} | |
function createDatapoint(slug, value, comment) { | |
//TODO don't post duplicates! | |
var userProperties = PropertiesService.getUserProperties(); | |
var url = "https://www.beeminder.com/api/v1/users/" + userProperties.getProperty("BEEMINDER_USER") + "/goals/" + slug + "/datapoints.json"; | |
var data = {"value": value} | |
if (comment) | |
{ | |
data["comment"] = comment; | |
} | |
Logger.log("Posting datapoint with data: " + JSON.stringify(data) + "to URL: " + url); | |
data["auth_token"] = userProperties.getProperty('BEEMINDER_TOKEN'); | |
var payload = JSON.stringify(data); | |
var headers = { "Accept":"application/json", | |
"Content-Type":"application/json", | |
}; | |
var options = {"method":"POST", | |
"contentType" : "application/json", | |
"headers": headers, | |
"payload" : payload | |
}; | |
Logger.log(data); | |
var response = UrlFetchApp.fetch(url, options); | |
Logger.log(response); | |
} |
Haha, yes, I wasn't fired for slow response time or anything! :p I got a fellowship that paid the bills better, and a colleague is now doing the job better than I was, so a good outcome all around I think.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I hope things are going well, but I guess I get to check that off the list :)