Welcome Guest Login Register Member List
ExpressionEngine Forums
Advanced Search
Username: Password:
Remember Me? forgot password?
You are here: Forum Home  >  Usage  >  Tips and Tricks  >  Thread
   
 
Create Tasks in Tracks via Email using the RESTFul API
 
lkraven
Posted: 29 March 2008 12:27 PM   [ Ignore ]  
Newbie
Rank
Total Posts:  1
Joined  2008-03-29

First off, I have to confess that unless you already know what you’re doing, this is a pretty technical trick.

I have rigged my hosted Tracks installation to be able to receive emails into an Inbox context I can sort through later.

This allows me to go through my email (gmail, or otherwise) and forward emails to “mygtd@mydomain.com” and have it automatically create a to-do with the subject of the email as the description and the body of the email as a note.

Again, this is somewhat tricky, but if you have the background to do it, I am including source code.  In this case, all we have used is a perl script.

I can provide very little support, so hopefully you can get it working.  This is something I badly wanted, and found a way to do… I didn’t think I should hoarde the benefits to myself, but neither can I be as much assistance as I would like.

Requirements:

I. You have to run your own email server capable of receiving emails and piping it to a script.  If you don’t know what this means, you may not be able to do it.

II. You probably have to be running Tracks in a hosted environment.  I have not tried it with anything other than on a linux host.

Once you have created the email address, modify the following script per the comments, and pipe it to the script.  It works wonderfully.

#!/usr/bin/perl
# Quick and dirty hack to add tasks via an email.
# Install by adding an alias in /etc/aliases or /etc/valiases depending
# on yor system:
#
# mygtdaddress@mydomain.com: "|/home/directory/inboxgtd.pl"
#


use strict;
use 
WWW::Curl::Easy;
open (STDOUT, ">/dev/null");    # Redirect CURL output to null.  Some mail servers will bounce
open (STDERR, ">/dev/null");    # messages if there is any return.


# Replace with todos.xml url of your GTD installation
# my $url = "http://yourgtd.yourdomain.com/todos.xml";

my $url = "http://yourgtd.yourdomain.com/todos.xml";

# The contextid where you want the todo added.  Usually an inbox.
my $contextid = "1";

my $curl = WWW::Curl::Easy->new();
my $description = "";
my $note = "";

# Your authorization credentials in the format
# my $auth = "username:password";
#
my $auth = "username:password";

# Leave these tokens alone.  They are valid as of Tracks 1.5 RESTful API.
my $notetag = "todo[notes]";
my $contexttag = "todo[context_id]";
my $descriptiontag = "todo[description]";

# Dirty hack.  First blank line begins body text.
my $passedempty = 0;

my @mail = <STDIN>;  # Read in the piped email
my $line = "";

foreach 
$line (@mail)
{
    
if ( ($line =~ m/Subject: (.*)/) )       # If it's the subject line...
    
{
     $description 
= $1;                                        # ... use it for the todo description
    
}

    
if ( $line =~ m/^$/ )                                    # Checking for that blank line
    
{
     $passedempty 
= 1;
    
}

    
if ($passedempty)                                            # Once we see it ...
    
{
     $note 
= $note . $line;                                # Copy every blank line into the todo note.
    
}
}

# Here we munge the data together to post.

my $post_data = $contexttag . "=" . $contextid . "&" . $descriptiontag . "=" . $description . "&" . $notetag . "=" . $note;

# We execute curl to do the posting.

$curl->setopt(CURLOPT_URL, $url);
$curl->setopt(CURLOPT_POSTFIELDS, $post_data);
$curl->setopt(CURLOPT_USERPWD, $auth);
$curl->perform();

close STDOUT;
close STDERR; 
Profile
 
lucky13cxc
Posted: 08 April 2008 09:39 PM   [ Ignore ]   [ # 1 ]  
Newbie
Rank
Total Posts:  2
Joined  2008-04-08

Fantastic! I’ve made some modifications to allow it to handle a context and due date in the format:

@<context> <due date>

I’ll post my modified version later tonight if I get the chance.

Profile
 
Reinier Balt
Posted: 10 April 2008 09:03 AM   [ Ignore ]   [ # 2 ]  
Sr. Member
RankRankRankRank
Total Posts:  551
Joined  2006-10-05

looks good! I’ve done something similar with the XMLRPC interface of tracks [1]. I’m using the rich_api which you can use to put an action in context and project like:  {{{ description of task @context > project }}}. I’m not sure if you can use the rich api with REST… I’m using the perl to parse the email (package) so that I can fill the notes field from attachements if necessary.

[1] http://www.rousette.org.uk/projects/forums/viewthread/75/

Profile
 
emkay
Posted: 22 April 2008 09:02 AM   [ Ignore ]   [ # 3 ]  
Newbie
Rank
Total Posts:  4
Joined  2008-04-22

For those who are not running a mail server, I have written this script in python that polls a pop3 server for a specific email address and creates a tracks action named as the subject of the email (The action is put into a hardcoded context… inbox in my case).
Here is the code, it is coded for gmail collection to my inbox context, but the comments show which bits to change (marked with ‘CHANGE’)
This can be run in a bat file on windows as a Scheduled Task (mine runs every 10 minutes) or a cron job on Unix.

import poplib
import urllib
import re
import pycurl
, sys

re_from        
= re.compile( "^From: (.*)" )
re_to          = re.compile( "^To: (.*)" )
re_subject     = re.compile( "^Subject: (.*)" )


def email2tracks(me,p, trlp, cid):
    
host ='pop.gmail.com'   # CHANGE this to be the address of your POP3 server
    
print me, p, host
    pop 
= poplib.POP3_SSL(host)  # CHANGE Remove the SSL if your POP3 server doesn't use SSL

    
try:
        
pop.user(me)
        
pop.pass_(p)
        
msgCount, msgTotalSize = pop.stat()
        print 
msgCount, msgTotalSize
        emptyHdr 
= {
        
"From" : "",
        
"To" : "",
        
"Subject" : "",
        
}
        matchREs 
= [
        
( re_from, "From" ),
        ( 
re_to, "To" ),
        ( 
re_subject, "Subject" ),
        
]

        
for n in range( msgCount ):
            
msgnum = n+1 # msg nums are 1-based, not 0-based

            
response, headerLines, bytes = pop.top(msgnum, 0)

            
hdr = emptyHdr.copy()
            
hdr["msgnum"] = msgnum
            hdr[
"size"] = bytes
            
for line in headerLines:
                for 
reExpr,hdrField in matchREs:
                    
match = reExpr.match( line )
                    if 
match:
                        
hdr[ hdrField ] = match.group(1).strip('"')

            print 
hdr['Subject']
            curl 
= pycurl.Curl()
            
curl.setopt(pycurl.URL, "http://127.0.0.1:8000/todos.xml")  # CHANGE if not running tracks on same machine as the script, and CHANGE port
            
curl.setopt(pycurl.POSTFIELDS, "todo[description]="+urllib.quote(hdr['Subject'])+"&todo;[context_id]"+cid)
            
curl.setopt(pycurl.USERPWD, trlp)
            
curl.perform()
            
curl.close()

            
pop.dele(msgnum)
        

    
except poplib.error_proto, detail:
        print 
"POP3 error:", detail


    finally
:
        
pop.quit()


# CHANGE
# Parameters are: POP3 user, POP3 passwd, tracks user:pass, then the context number you wish the email to go to (prefixed with =)
email2tracks('popuser','poppass',"tuser:tpass","=5") 

 

You will need python and pycurl installed.  For windows that is: http://python.org/ftp/python/2.5.2/python-2.5.2.msi for Python and http://pycurl.sourceforge.net/download/pycurl-ssl-7.16.4.win32-py2.5.exe for pycurl.

Hope this is useful to some,

Martin

Profile
 
lucky13cxc
Posted: 18 September 2008 09:42 PM   [ Ignore ]   [ # 4 ]  
Newbie
Rank
Total Posts:  2
Joined  2008-04-08

By request of someone on the forum, I thought I could post my updates to the code that allowed for a context and date to be provided.

#!/usr/bin/perl
# Quick and dirty hack to add tasks via an email.
# Install by adding an alias in /etc/aliases or /etc/valiases depending
# on yor system:
#
# mygtdaddress@mydomain.com: "|/home/directory/inboxgtd.pl"
#

#use strict;
#use WWW::Curl::Easy;

use XML::Simple;
use 
LWP::UserAgent;
use 
URI::Escape;

# Redirect any output to null
#open (STDOUT, ">/dev/null");
#open (STDERR, ">/dev/null");

# Replace with todos.xml and contexts.xml urls of your GTD installation
# my $url = "http://yourgtd.yourdomain.com/todos.xml";
# my $contextUrl = "http://yourgtd.yourdomain.com/contexts.xml";

my $url = "http://yourgtd.yourdomain.com/todos.xml";
my $contextUrl = "http://yourgtd.yourdomain.com/contexts.xml";

# The default contextid where you want the todo added
my $contextid = "10";

#my $curl = WWW::Curl::Easy->new();

# Initialize the post variables
my $description = "";
my $note = "";
my $duedate = "";

# Your authorization credentials in the format
# my $user = "username";
# my $password = "password";

my $user = "username";
my $password = "password";

# Leave these tokens alone.  They are valid as of Tracks 1.5 RESTful API.
my $notetag = "todo[notes]";
my $contexttag = "todo[context_id]";
my $descriptiontag = "todo[description]";
my $duetag = "todo[due]";

# Get the context legend in order to match by name
$ua = new LWP::UserAgent;
my $req = new HTTP::Request 'GET',$contextUrl;
$req->authorization_basic($user,$password);
my $res = $ua->request($req);
my $contexts = XMLin($res->content);

# Dirty hack.
my $passedempty = "0";

my @mail = <STDIN>;  # Read in the piped email
my $line = "";

foreach 
$line (@mail)
{
    
if ( ($line =~ m/^\@(.*)/) )
    
{
    
# Split the action string out to grab date if provided
        
@newline = split(/ /,$line);

    
# If a date was provided, grab the first element of the array for the context
    
if (@newline > 1){
        $cleancontext 
= ucfirst(substr($newline[0],1));
    
# if not, just use the matched expression above
    
} else {
        $cleancontext 
= ucfirst($1);
    
}

    
# Grab the appropriate contextid and the due date
    
$contextid = $contexts->{context}->{$cleancontext}->{id}->{content};
    
$duedate = $newline[1];

    
# debugging
    #$note .= "Context:" . $newline[0] . "\n";
    #$note .= "Date:" . $newline[1] . "\n";
    #$note .= "ContextName:" . ucfirst(substr($newline[0],1));
    #$note .= @newline;    

    # Minor input validation
    
if ($contextid !~ m/(\d+)/){
        $contextid 
= "10";
    
}

    
if ($duedate !~ m/(\d+)\/(\d+)\/(\d+)/) {
        $duedate 
= "";
    
}
    
    
# Don't include the action line in the notes
    
$line = "";
    
}
    
    
# Set the subject to the task description (strip the Fw:)
    
if ( ($line =~ m/Subject: (.*)/) )
    
{
     $description 
= $1;
     
$description =~ s/Fw: //;
    
}

    
# Hack to find the end of the e-mail; this was needed due to a large standard footer being added to the e-mail
    # may not be needed if this is not the case for your e-mails being forwarded
    # Start section -->
        
if ( ($line =~ m/(.*)tax advice contained in the body(.*)/) )
        
{
         $passedempty 
= "2";
        
}
    
        
# If we're passed the text/plain, but before the disclaimer, grab the line for notes
        
if ($passedempty eq "1")
        
{
         $note 
.= $line;
        
}

    
# Start grabbing after we've hit the plain text portion of the email
    
if ( ($line =~ m/(.*)Subject$/) && $passedempty eq "0")
    
{
     $passedempty 
= "1";
    
}
    
# <-- End Section

}
# Trim the notes
$note = substr($note,0,5000);
$note =~ s/<.*?>//g;

# Here we munge the data together to post.

my $post_data = $contexttag . "=" . $contextid . "&" . $descriptiontag . "=" . uri_escape($description) . "&" . $notetag . "=" . uri_escape($note) . "&" . $duetag . "=" . uri_escape($duedate);

# We execute curl to do the posting.
#$curl->setopt(CURLOPT_URL, $url);
#$curl->setopt(CURLOPT_POSTFIELDS, $post_data);
#$curl->setopt(CURLOPT_USERPWD, $auth);
#$curl->perform();

# Use LWP to do the posting ($ua was created earlier)
#$ua = new LWP::UserAgent;
$req = new HTTP::Request 'POST',$url;
$req->content_type('application/x-www-form-urlencoded');
$req->content($post_data);
$req->authorization_basic($user,$password);

$res = $ua->request($req);

close STDOUT;
close STDERR; 
Profile
 
desrod
Posted: 02 December 2008 01:37 AM   [ Ignore ]   [ # 5 ]  
Newbie
Avatar
Rank
Total Posts:  7
Joined  2008-11-24

Here is a cleaned-up, refactored, faster version, leveraging the use of upstream modules to do the heavy lifting:

#!/usr/bin/perl
#        _._   
#       /_ _`.      (c) 2008, David A. Desrosiers
#       (.(.)|      setuid@gmail.com
#       |\_/'|   
#       )____`\     Email to Tracks interface
#      //_V _\ \ 
#     ((  |  `(_)   If you find this useful, please drop me 
#    / \> '   / \   an email, or send me bug reports if you find
#    \  \.__./  /   problems with it. I accept PayPal too! =-)
#     `-'    `-' 
#
##############################################################################
# 
# License
#
##############################################################################
#
# This script is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
# 
# This script is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
# 
# You should have received a copy of the GNU General Public License along   
# with this program; if not, write to the Free Software Foundation, Inc., 59
# Temple Place
#
# - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
#
# You'll need to set this up as an alias in your MTA configuration (typically
# /etc/aliases), as follows:
#
# tracks@your-site.tld: "|/path/to/gtd-tracks-mail.pl"
#
# Don't forget to run 'newaliases(1)' after you've modified this so it can
# update the aliases table with the new entry.
#
# Email format can be any of the following:
#
#       Subject: The main description of the Task
#
#       @Context  
#       01/02/2003
#       This is my note text
#
# or:
# 
#       Subject: Default Task description
#
#       c: @context  
#       d: 2008/12/31
#       n: The note text to insert
#
# Most valid date formats are accepted, and this will do its best to   
# "correct" and normalize them. You can also prefix your lines with the
# modifiers above, or read the regexes below for more. For example: 
# c:, context:, cxt:, con:, ct: are all valid prefixes for "context"
# n: and note: are all valid prefixes for "notes", and so on.
#
##############################################################################
use strict;
use 
XML::Simple;
use 
LWP::UserAgent;
use 
URI::Escape;
use 
Email::Abstract;
use 
Date::Manip;
use 
Date::Parse;
use 
Data::Dumper; 

my $url                 = "http://your-site.tld/todos.xml";
my $contextUrl          = "http://your-site.tld/contexts.xml";

# The default contextid where you want the todo added
# SELECT id,name FROM contexts;
my $contextid           = "8";

my $user                = "yourname";
my $password            = "yourpass";

# Leave these tokens alone.  They are valid as of Tracks 1.5 RESTful API.
my %todo = map { +($_ => "todo[$_]") } qw(notes context_id description due);

# Get the context legend in order to match by name
my $ua                  = new LWP::UserAgent;
my $req                 = new HTTP::Request 'GET',$contextUrl;
$req->authorization_basic($user,$password);
my $res                 = $ua->request($req);
my $contexts            = XMLin($res->content);

# Split apart the email into Subject and Body
my $message             = do { local $/; <STDIN> };
my $email               = Email::Abstract->new($message);
my $subject             = $email->get_header("Subject");
my $body                = $email->get_body;

# These can probably be cleaned up a bit
my ($context_line)      = $body =~ /^(?:c:|ct:|cxt:|con:|context:|@)\s*(.+)$/mi;
my ($date_line)         = $body =~ /^(?:d:|date:)\s*(\d.*)$/m;
$date_line              = UnixDate(ParseDate("today"), "%g") if (length($date_line) == 0);
my $time                = str2time($date_line);
my $due_date            = UnixDate(scalar gmtime($time), "%m/%d/%Y");
my ($note_line)         = $body =~ /^(?:n:|note:)\s*(.*?)$/mi; 

# Concatenate the data here before we send POST to the Tracks server
my $post_data = 
        
$todo{'context_id'} . "=" . $contextid . "&" . 
        
$todo{'description'}. "=" . uri_escape($subject) . "&" .
        
$todo{'notes'} . "=" . uri_escape($note_line) . "&" .  
        
$todo{'due'} . "=" . uri_escape($due_date);

# Use LWP to do the posting ($ua was created earlier)
$req = new HTTP::Request 'POST',$url;
$req->content_type('application/x-www-form-urlencoded');
$req->content($post_data);
$req->authorization_basic($user,$password);
$res = $ua->request($req); 
Profile
 
   
 
 
‹‹ Agenda context      Applescript and the API ››

Powered By ExpressionEngine
Template Design By Sonnenvogel.com
Select a theme:

ExpressionEngine Discussion Forum - Version 2.1.2 (20091002)
Script Executed in 0.1471 seconds

Atom Feed
RSS 2.0