· 8 years ago · Feb 20, 2018, 11:34 AM
1#!/usr/bin/env ruby
2# Creates a weekly gtd report from your backpack account
3# Based on the sample wrapper script from
4# David Heinemeier Hansson, 37signals and
5# Gmail sample script from shunsuk
6# Requires that XmlSimple, RedCloth, tlsmail, and time
7# are installed.
8# Assumptions:
9# ** All personal/home pages start with a space ' '
10# ** All closed projects start with zzc
11# ** All waiting projects start with zw
12# Author: Ed Botzum
13
14require 'net/https'
15require 'rexml/document'
16require 'yaml'
17require 'rubygems'
18require 'RedCloth'
19require 'XmlSimple'
20require 'tlsmail'
21require 'time'
22
23include REXML
24
25class Gmail
26 attr_accessor :username, :password
27
28 def initialize(username, password)
29 @username, @password = username, password
30 end
31
32 def send_mail(recepient, subject, content)
33 msg = <<EOF
34From: #{@username}
35To: #{recepient}
36MIME-Version: 1.0
37Content-type: text/html
38Subject: #{subject}
39Date: #{Time.now.rfc2822}
40
41#{content}
42EOF
43
44 Net::SMTP.enable_tls(OpenSSL::SSL::VERIFY_NONE)
45 Net::SMTP.start('smtp.gmail.com', 587, 'gmail.com', @username, @password, :login) do |smtp|
46 smtp.send_message(msg, @username, recepient)
47 end
48 end
49end
50
51class Backpack
52 attr_accessor :username, :token, :ignore_pages
53
54 def initialize(username, token)
55 @username, @token = username, token
56 connect
57 end
58
59 def generate_gtd_report
60 report = <<EOF
61<style type='text/css' media='screen'>
62 li {font-size: .8em;}
63 h1 {border-bottom: solid thick black;padding-left: .3em;}
64 h2 {border-bottom: solid thin black}
65 h3 {border-bottom: solid thin black}
66 #active_projects h4 {border-bottom: solid thin black}
67</style>
68EOF
69
70 report << "h1. My Worklist\n" <<
71 "\nThis is your weekly GTD wrklist report.\n"
72
73 # List all reminders for the upcoming week
74 report << "\nh2. Reminders\n\n"
75 list_reminders.each do |reminder|
76 report << "* #{reminder['remind_at']}: #{reminder['content']}\n"
77 end
78
79 # Get the list of pages
80 projects = { :home => [], :work => [], :waiting => [], :closed => [] }
81 list_pages.each do |page|
82 page_info = {:title => page['title'], :id => page['id']}
83 # Split into groups of home, work, closed, and waiting
84 if page['title'][0,1] == ' ' then
85 projects[:home] << page_info
86 elsif page['title'][0,3] == 'zzc'
87 projects[:closed] << page_info
88 elsif page['title'][0,2] == 'zw'
89 projects[:waiting] << page_info
90 else
91 projects[:work] << page_info
92 end
93 end
94
95 report << "\nh2. Projects\n"
96
97 # List all work projects
98 report << "\n<div id=\"active_projects\">\n"
99 report << list_projects('work', 'Work Projects', projects[:work])
100 report << list_projects('home', 'Home Projects', projects[:home])
101 report << "\n</div>\n"
102 report << list_projects('waiting', 'Projects on Hold', projects[:waiting])
103 report << list_projects('closed', 'Completed Projects', projects[:closed])
104
105 # Generate the report
106 return RedCloth.new(report).to_html
107 end
108
109 private
110
111 # Backpack API Methods
112
113 def connect(use_ssl = false)
114 @connection = Net::HTTP.new("#{@username}.backpackit.com", use_ssl ? 443 : 80)
115 @connection.use_ssl = use_ssl
116 @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE if use_ssl
117 end
118
119 def request(path, parameters = {}, second_try = false)
120 parameters = { "token" => @token }.merge(parameters)
121 response = @connection.post("/ws/#{path}", parameters.to_yaml, "X-POST_DATA_FORMAT" => "yaml")
122
123 if response.code == "200"
124 result = XmlSimple.xml_in(response.body)
125 result.delete "success"
126 result.empty? ? true : result
127 elsif response.code == "302" && !second_try
128 connect(true)
129 request(path, parameters, true)
130 else
131 raise "Error occured (#{response.code}): #{response.body}"
132 end
133 end
134
135 def list_page_lists(page_id)
136 request("page/#{page_id}/lists/list")['lists'][0]['list']
137 end
138
139 def last_item_completed(page_id, list_id)
140 xs = request "page/#{page_id}/lists/#{list_id}/items/list"
141 items = Document.new XmlSimple.xml_out(xs)
142 completed_items = XPath.match( items, "//item[@completed='true']" )
143 return completed_items[completed_items.size-1]
144 end
145
146 def list_open_items(page_id, list_id)
147 xs = request "page/#{page_id}/lists/#{list_id}/items/list"
148 items = Document.new XmlSimple.xml_out(xs)
149 completed_items = XPath.match( items, "//item[@completed='false']" )
150 end
151
152 def list_pages
153 return request("pages/all")['pages'][0]['page']
154 end
155
156 def list_reminders
157 return request("reminders")['reminder']
158 end
159
160 # Reporting Methods
161
162 def list_items(page)
163 list = ''
164 # Get the lists on each page
165 begin
166 list_page_lists(page[:id]).each do |page_list|
167 list << "\nh5. #{page_list['name']}\n\n"
168
169 last_one = last_item_completed(page[:id], page_list['id'])
170 list <<"* [-#{last_one.text}-]\n" unless last_one == nil
171
172 count = 0
173 list_open_items(page[:id], page_list['id']).each do |item|
174 if count == 0 then
175 list << "* *%{color:green}#{item.text}%*\n"
176 elsif count < 3
177 list << "* #{item.text}\n"
178 end
179 count += 1
180 end
181 end
182 rescue
183 # There are no valid
184 list = "\n* No valid to-do exist for this project.\n"
185 end
186
187 return list
188 end
189
190 def list_projects(category, title, pages)
191 list = "\nh3. #{title}\n"
192 pages.each do |page|
193 unless @ignore_pages.include?(page[:title])
194 list << "\nh4. #{page[:title]}\n"
195 list << list_items(page) if category == 'work' or category == 'home'
196 end
197 end
198
199 return list
200 end
201end
202
203bp = Backpack.new 'user_name', 'token'
204bp.ignore_pages = ['Ignore Works', ' Ignore Home', 'zw - Ignore Waiting']
205report = bp.generate_gtd_report
206gmail = Gmail.new 'your_email@gmail.com', 'your_password'
207gmail.send_mail 'your_email@gmail.com', 'Test Report', report