Or how to achieve tracking of almost everything with no data re-entry using Firefox, Greasemonkey, Remember The Milk (RTM henceforth), Project Hamster for general time tracking, Trac and it’s Timing and Estimation and Ticket2RTM plugins for ticket & hour tracking and RTM integration, and Git or SVN for version control…or any combination of these desired. My condolences if you’re using Windoze or IE. I don’t use either and the following assumes Firefox and something Linuxy.
Project Hamster is a wonderfully easy and robust time tracking system written in python (cross-platform) and has a nice applet for the gnome toolbar among other features. I already use Trac and it’s Timing and Estimation plugin to track time related to code, tickets, and changesets, but not everything I want to track can be related to such things. I use Remember The Milk for pretty much everything I need to do aside from sleeping, eating, and various other things I can’t or don’t care to have on a “to-do” list – the Trac plugin Ticket2RTM ensures tickets are also in RTM, RTM becoming the base center of operations for my life.
Hamster, while I used it religiously for a time some years ago, fell by the wayside as just another task after the initial “wow!” realizations on how I spent my time. Recently I needed Hamster to step in and fill the gaps…but only the gaps. Re-entry of data is a very big no-no in my world, so I set about brainstorming a way to get data from Remember The Milk into Hamster. After all, if I already wrote down what I need to do in RTM or Trac, no sense rewriting it for Hamster when I’m doing it.
Enter Greasemonkey, a Firefox plugin that lets you add and alter javascript on any webpage. Using a Greasemonkey script I added a “copy” link to tasks in RTM. The original idea of just clicking a link and having it go directly to Hamster failed due to Dbus issues with scripts, but I find I prefer the copy method as it lets me edit tasks before I enter them into Hamster (usually by prepending -10 because I’ve been working on something different for the last ten minutes and not noticed).
On to the Greasemonkey code!
// ==UserScript==
// @name Copy Links for Hamster Tracking
// @namespace http://jaciss.wordpress.com
// @description Adds a copy button next to each task for starting Project Hamster tracking via a local python script
// @include https://www.rememberthemilk.com/home/*
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js
// ==/UserScript==
/*
jQuery inclusion code courtesy of: http://joanpiedra.com/jquery/greasemonkey/
jQuery is extreme overkill here, but it's (obviously) a quick hack at this stage.
!NOTE: The below are just examples to show what can be done! I use Trac (ticket/bug tracking) and the timingandestimation (track hours by commit messages, now generated by these scripts) and ticket2rtm plug-ins for my work - ignore, remove, or edit to suit your system(s) of choice where necessary.
*/
var $;
// Add jQuery
(function() {
if (typeof unsafeWindow.jQuery == 'undefined') {
var GM_Head = document.getElementsByTagName('head')[0] || document.documentElement,
GM_JQ = document.createElement('script');
GM_JQ.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js';
GM_JQ.type = 'text/javascript';
GM_JQ.async = true;
GM_Head.insertBefore(GM_JQ, GM_Head.firstChild);
}
GM_wait();
})();
// Check if jQuery's loaded
function GM_wait() {
if (typeof unsafeWindow.jQuery == 'undefined') {
window.setTimeout(GM_wait, 100);
} else {
$ = unsafeWindow.jQuery.noConflict(true);
letsJQuery();
}
}
// All your GM code must be inside this function
function letsJQuery() {
//alert($); // check if the dollar (jquery) function works
//alert($().jquery); // check jQuery version
var lists = new Array();
function compileLists() {
$.each(unsafeWindow.listList.list.entries,
function() {
lists[parseInt(this[0])] = this[1];
return true;
});
}
function htmlEntities(str) {
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '/quote');
}
function addCopyLinksToTasks() {
var entries = unsafeWindow.taskList.list.hashMap;
var locs = unsafeWindow.locationList.list.hashMap;
$('#tasks table.xtable tr.xtr').each(function() {
var entry_id = parseInt(/_(\d+)$/.exec($(this).find('td.xtd_check form').attr('id'))[1]);
if (entry_id > 0) {
var entry = entries[entry_id];
var loc = unsafeWindow.locationMgr.getTaskLocation(entry.series_id);
var tags = unsafeWindow.tagMgr.getTagsForTaskSeries(entry.series_id);
var url = entry.url == null ? '': entry.url;
var name = entry.name;
var src = entry.location_id;
var list = lists[entry.list_id];
url = url.replace('http:\/\/', '');
#I preface projects with p- for the GTD RTM script
#project = list.replace('p-', '');
name = htmlEntities(name);
name = name.replace(/\'/g, '\\\'');
tagstr = '';
if (tags.length > 0) {
for (i = 0; i < tags.length; i++) {
tagstr += ' #' + tags[i];
}
}
if (loc == 'none') loc = 'computer';
#if it's a trac ticket
if (url.match("trac")) {
pathparts = url.split('/');
ticket = pathparts[pathparts.length - 1];
ticket = ' #working ticket ' + ticket;
} else ticket = '';
text = 'python /usr/share/pyshared/myhamster.py "' + project + '@' + loc + ', ' + name + tagstr + ticket + """;
if ($(this).html().match('copier')) {
$('copier_' + entry_id).replaceWith('<td id="copier_' + entry_id + '"><a onclick="netscape.security.PrivilegeManager.enablePrivilege(\'UniversalXPConnect\');Components.classes[\'@mozilla.org/widget/clipboardhelper;1\'].getService(Components.interfaces.nsIClipboardHelper).copyString(\'' + text + '\')">copy </a></td>');
} else {
$(this).append('<td id="copier_' + entry_id + '"><a onclick="netscape.security.PrivilegeManager.enablePrivilege(\'UniversalXPConnect\');Components.classes[\'@mozilla.org/widget/clipboardhelper;1\'].getService(Components.interfaces.nsIClipboardHelper).copyString(\'' + text + '\')">copy </a></td>');
}
}
});
};
compileLists();
setInterval(addCopyLinksToTasks, 3000);
}
So this adds a copy link that will alert you to the danger of allowing “outside” sources access to your clipboard. When clicked, it copies something like
python /usr/share/pyshared/myhamster.py "webpresence@computer, write blog about greasemonkey key to tying hamster & rtm together #focus #na"
Where, for me, webpresence is both a list in RTM and a project/activity in Hamster (having a positive web presence is a goal of mine). Now we paste this text onto a command line and it calls a script (myhamster.py) which looks something like this:
import sys
import re
import hamster.client
from decimal import Decimal
#where to add commit log entries
commit_file = '/path/to/commit.txt'
storage = hamster.client.Storage()
facts = storage.get_todays_facts()
last_fact = facts[len(facts)-1]
#is there an ongoing task?
if last_fact['end_time'] == None:
try:
#is it a trac ticket?
for tag in last_fact['tags']:
if "#" in tag:
hours = last_fact['delta'].seconds
hours = Decimal(hours/60)
hours = Decimal(hours/60)
commit_str = "%s closes %s (%f), " % (last_fact['description'],tag,hours)
#print "Ticket was being worked on, adding line to commit.txt"
print commit_str
cf = open(commit_file,'a')
cf.write(commit_str)
cf.close()
except KeyError:
print "no tags"
else:
print "No active activity!"
new_fact=' '.join(sys.argv[1:99])
new_fact = new_fact.replace('/quote','"')
if "#" in new_fact:
reg = re.compile('ticket\s\d+')
tractag = reg.findall(new_fact);
if len(tractag)>0:
print "Trac tag initiated."
tractag = tractag[0]
tractag = tractag.replace('ticket ','#')
else: tractag=''
reg2 = re.compile('#[a-zA-Z0-9]+')
tags = reg2.findall(new_fact)
i=0
for tag in tags:
tags[i]=tags[i].replace('#','')
i=i+1
if(len(tractag)>0): tags.append(tractag)
else:
tags=''
print new_fact
#print tags
print storage.add_fact(new_fact,tags)
Note that the above script writes Timing and Estimation formatted lines (for tracking hours/ticket in Trac) to a commit.txt file if it detects a # in the tag. I left this in as an example of what can be done with this sort of system. I usually have to edit the commit file before use, but I rarely forget to add things anymore, and certainly don’t have to re-enter the data, which was the point of this exercise.
Filed under: how-to, firefox, greasemonkey, hamster, projecthamster, rememberthemilk, RTM, ticket2rtm, timingandestimation, trac


