moocow 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +107 -0
- data/bin/rtm +113 -0
- data/ext/hash_array.rb +22 -0
- data/ext/string_camelize.rb +10 -0
- data/lib/moocow/auth.rb +58 -0
- data/lib/moocow/endpoint.rb +146 -0
- data/lib/moocow/moocow.rb +145 -0
- data/lib/moocow.rb +5 -0
- metadata +85 -0
data/README.rdoc
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
= Ruby Client for Remember The Milk
|
2
|
+
|
3
|
+
Author:: Dave Copeland (mailto:davetron5000 at g mail dot com)
|
4
|
+
Copyright:: Copyright (c) 2009 by Dave Copeland
|
5
|
+
License:: Distributes under the Apache License, see LICENSE.txt in the source distro
|
6
|
+
|
7
|
+
== Overview
|
8
|
+
|
9
|
+
This is a very lightweight and easy-to-use client for Remember[http://www.rememberthemilk.com] The[http://www.rememberthemilk.com] Milk[http://www.rememberthemilk.com]
|
10
|
+
|
11
|
+
rtm = RTM.new(Endpoint.new(your_api_key,your_secret))
|
12
|
+
rtm.token = user_token
|
13
|
+
response = rtm.tasks.getList
|
14
|
+
response.tasks.list.as_array.each do |list|
|
15
|
+
# this is an RTM list of taskseries
|
16
|
+
end
|
17
|
+
|
18
|
+
=== Authorization
|
19
|
+
|
20
|
+
Authorization is a bit tricky, but you only need to do it one time. This example shows
|
21
|
+
command line/desktop usage
|
22
|
+
|
23
|
+
rtm = RTM.new(Endpoint.new(your_api_key,your_secret))
|
24
|
+
auth = rtm.auth
|
25
|
+
url = auth.url
|
26
|
+
frob = auth.frob
|
27
|
+
# Send user to this URL
|
28
|
+
# They will authorize your app's api_key via the RTM website.
|
29
|
+
# store the frob somewhere, you will need it
|
30
|
+
#
|
31
|
+
# When they return...
|
32
|
+
auth.frob=frob
|
33
|
+
rtm.token=auth.get_token
|
34
|
+
# store the token for all time
|
35
|
+
|
36
|
+
rtm.tasks.getList
|
37
|
+
rtm.groups.delete
|
38
|
+
rtm.tasks.notes.add
|
39
|
+
|
40
|
+
And so forth. Basically, you can call the RTM methods as if they were real methods.
|
41
|
+
|
42
|
+
=== Responses
|
43
|
+
|
44
|
+
As shown above, you can simply navigate the XML returned by method names. This uses Hash.method_missing and can be dicey, so you can also navigate a bit safer via
|
45
|
+
|
46
|
+
response = rtm.tasks.getList
|
47
|
+
response['tasks']['list'].as_array.each do |list|
|
48
|
+
# this is an RTM list of taskseries
|
49
|
+
end
|
50
|
+
|
51
|
+
Note that for any element that you want to treat as a list, you must call as_array on it, to ensure that it gets turned into a list. The underlying HTTParty code doesn't know if an XML element is supposed to be a list or just a singular.
|
52
|
+
|
53
|
+
<i>Note that you can use Ruby-style conventions instead of camel-case, e.g. <tt>rtm.tasks.get_list</tt></i>
|
54
|
+
|
55
|
+
=== Timelines
|
56
|
+
|
57
|
+
Many methods require timelines; this is the way that RTM provides for undo-type features. If you don't really care about this, or want it done for you, you can specify auto timeline creation:
|
58
|
+
|
59
|
+
|
60
|
+
rtm.auto_timeline=true
|
61
|
+
rtm.tasks.add(:name => 'My new task')
|
62
|
+
timeline = rtm.last_timeline
|
63
|
+
|
64
|
+
This will create a timeline for you, and use it to create the task. The list of methods that <b>do not</b> require timelines is used to determine if this is done, and is based on the RTM documentation at the time of this writing.
|
65
|
+
|
66
|
+
== Basic Command Line
|
67
|
+
|
68
|
+
To verify and play with the RTM API, there is a command-line tool called <tt>rtm</tt>. It takes the following commands:
|
69
|
+
|
70
|
+
[any] executes any RTM method
|
71
|
+
[auth] Prints out the auth URL to authorize your api key/ruby client. Do not do this after getting a token
|
72
|
+
[chktoken] Checks that your token is valid
|
73
|
+
[help] Shows list of commands or help for one command
|
74
|
+
[ls] Lists incomplete tasks
|
75
|
+
[token] Gets your token; do this only once and put it in your api.yaml file
|
76
|
+
|
77
|
+
The easiest way to get started is to get your API Key and Secret from RTM and then:
|
78
|
+
|
79
|
+
rtm ls
|
80
|
+
|
81
|
+
This will init your api.yaml file
|
82
|
+
|
83
|
+
rtm auth
|
84
|
+
|
85
|
+
Go to this URL and authorize your API Key. Save the frob that is printed out
|
86
|
+
|
87
|
+
rtm token <<frob>>
|
88
|
+
|
89
|
+
This will authorize your app and print out your token. Edit <tt>api.yaml</tt> and add your token.
|
90
|
+
|
91
|
+
:token: <<your token>>
|
92
|
+
|
93
|
+
To verify:
|
94
|
+
|
95
|
+
rtm ls
|
96
|
+
|
97
|
+
This should list your incomplete tasks if all is well.
|
98
|
+
|
99
|
+
rtm any contacts.getList
|
100
|
+
|
101
|
+
Will print out a hashdump of what RTM returns for this method.
|
102
|
+
|
103
|
+
== Links
|
104
|
+
|
105
|
+
* http://github.com/davetron5000/rtm - Source code
|
106
|
+
* http://davetron5000.github.com/rtm - Rubydoc
|
107
|
+
* http://www.rememberthemilk.com/services/api/methods/ - RTM API documentation
|
data/bin/rtm
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/../ext')
|
4
|
+
require 'rubygems'
|
5
|
+
require 'gli'
|
6
|
+
require 'httparty'
|
7
|
+
require 'rtm'
|
8
|
+
require 'hash_array'
|
9
|
+
|
10
|
+
include GLI
|
11
|
+
|
12
|
+
arg_name 'method.call'
|
13
|
+
desc 'executes any RTM method. Do not include the "rtm." in the method name'
|
14
|
+
command :any do |c|
|
15
|
+
c.action do |global_options,options,args|
|
16
|
+
parts = args[0].split(/\./)
|
17
|
+
obj = $rtm.send(parts.shift.to_s)
|
18
|
+
response = obj
|
19
|
+
parts.each do |p|
|
20
|
+
response = response.send(p.to_sym)
|
21
|
+
end
|
22
|
+
puts response.inspect
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'Lists incomplete tasks'
|
27
|
+
arg_name 'list_name'
|
28
|
+
command :ls do |c|
|
29
|
+
c.action do |global_options,options,args|
|
30
|
+
params = { :filter => 'status:incomplete' }
|
31
|
+
params[:filter] += " AND list:#{args[0]}" if args.length > 0
|
32
|
+
response = $rtm.tasks.get_list(params)
|
33
|
+
tasks = Array.new
|
34
|
+
if response.tasks
|
35
|
+
response.tasks.list.as_array.each do |l|
|
36
|
+
l.taskseries.as_array.each do |task|
|
37
|
+
tasks << task
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
tasks.sort {|a,b| a.name.upcase <=> b.name.upcase }.each do |task|
|
42
|
+
printf "%10d - %s\n",task['id'],task.name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc 'Prints out the auth URL to authorize your api key/ruby client and the frob that was used. Do not do this after getting a token'
|
48
|
+
command :auth do |c|
|
49
|
+
c.action do |global_options,options,args|
|
50
|
+
auth = $rtm.auth
|
51
|
+
puts auth.url
|
52
|
+
puts auth.frob
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
desc 'Gets your token; do this only once and put it in your api.yaml file'
|
57
|
+
arg_name "frob"
|
58
|
+
command :token do |c|
|
59
|
+
c.action do |global_options,options,args|
|
60
|
+
if args.length == 1
|
61
|
+
auth = $rtm.auth
|
62
|
+
auth.frob = args[0]
|
63
|
+
puts "Use frob '#{args[0]}'?"
|
64
|
+
a = $stdin.gets.chomp
|
65
|
+
if a == 'yes'
|
66
|
+
puts auth.get_token
|
67
|
+
else
|
68
|
+
puts "You must provide your frob as the argument"
|
69
|
+
end
|
70
|
+
else
|
71
|
+
puts "You didn't say 'yes'"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
desc 'Checks that your token is valid'
|
77
|
+
command :chktoken do |c|
|
78
|
+
c.action do |global_options,options,args|
|
79
|
+
begin
|
80
|
+
$rtm.check_token
|
81
|
+
puts "Token good"
|
82
|
+
rescue RTM::InvalidTokenException
|
83
|
+
puts "Token bad"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
pre do |global_options,command,options,args|
|
89
|
+
if !command.nil? && command.name != :help
|
90
|
+
return_val = true
|
91
|
+
if File.exists? 'api.yaml'
|
92
|
+
api_info = File.open('api.yaml') { |file| YAML::load(file) }
|
93
|
+
if !(api_info[:api_key] && api_info[:secret])
|
94
|
+
puts "api.yaml isn't complete"
|
95
|
+
return_val = false;
|
96
|
+
else
|
97
|
+
ep = RTM::Endpoint.new(api_info[:api_key],api_info[:secret])
|
98
|
+
ep.token=api_info[:token]
|
99
|
+
$rtm = RTM::RTM.new(ep)
|
100
|
+
end
|
101
|
+
else
|
102
|
+
puts "api.yaml not found; this is a YAML hash of your API key, secret key, and token"
|
103
|
+
File.open('api.yaml','w') { |file| YAML::dump({:api_key => 'your api key', :secret => 'your secret'},file); }
|
104
|
+
puts "it has been created for you with dummy values"
|
105
|
+
return_val = false
|
106
|
+
end
|
107
|
+
return_val
|
108
|
+
else
|
109
|
+
true
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
GLI.run(ARGV)
|
data/ext/hash_array.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
# These give Hash and Array a common method that returns an array.
|
3
|
+
# HTTParty doesn't know if a particular XML element is expected to be
|
4
|
+
# a list, so when it gets one of that element, it returns it as a Hash.
|
5
|
+
# So, one can simply call as_array on any part of the XML navigation
|
6
|
+
# that is expected to be an array, as such:
|
7
|
+
#
|
8
|
+
# response['rsp']['tasks']['list'].as_array.each {|a| }
|
9
|
+
#
|
10
|
+
#
|
11
|
+
|
12
|
+
class Hash
|
13
|
+
def as_array; [self]; end
|
14
|
+
|
15
|
+
def method_missing(symbol)
|
16
|
+
return self[symbol.to_s]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Array
|
21
|
+
def as_array; self; end
|
22
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
|
2
|
+
class String
|
3
|
+
# Stolen from sequel; gives String a camelize method
|
4
|
+
def camelize(first_letter_in_uppercase = :lower)
|
5
|
+
s = gsub(/\/(.?)/){|x| "::#{x[-1..-1].upcase unless x == '/'}"}.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
|
6
|
+
s[0...1] = s[0...1].downcase unless first_letter_in_uppercase == :upper
|
7
|
+
s
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
data/lib/moocow/auth.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'string_camelize'
|
2
|
+
module RTM
|
3
|
+
|
4
|
+
# Implements authorization related tasks. These are to be used one-time only
|
5
|
+
# when the user first uses your application.
|
6
|
+
#
|
7
|
+
# auth = RTMAuth.new(api_key,secret)
|
8
|
+
# url = auth.get_auth_url
|
9
|
+
# # send user to url
|
10
|
+
# # When they have done so
|
11
|
+
# token = auth.get_token
|
12
|
+
# # object auth no longer needed; use token with RTM
|
13
|
+
class RTMAuth
|
14
|
+
def initialize(endpoint)
|
15
|
+
@endpoint = endpoint
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get the URL to allow the user to authorize the application
|
19
|
+
# [perms] the permissions you wish to get, either :read, :write, or :delete
|
20
|
+
# [application_type] if :desktop, a frob is gotten and the URL is suitable for a desktop application. if :web, a url suitable for the web is returned.
|
21
|
+
# [callback_url] the callback URL you want the user sent to after they authorize. This will have the frob and you must call frob= before get_token with the frob that was given to you.
|
22
|
+
def url(perms = :delete, application_type=:desktop, callback_url=nil)
|
23
|
+
@frob = get_frob if application_type == :desktop
|
24
|
+
params = {'perms' => perms.to_s}
|
25
|
+
params['frob'] = @frob if @frob
|
26
|
+
params['callbackURL'] = callback_url if callback_url
|
27
|
+
@endpoint.url_for(nil,{'frob' => @frob, 'perms' => 'delete'},'auth')
|
28
|
+
end
|
29
|
+
|
30
|
+
# After the user has authorized, gets the token
|
31
|
+
def get_token
|
32
|
+
response = @endpoint.call_method('rtm.auth.getToken', { 'frob' => @frob }, false)
|
33
|
+
response['auth']['token']
|
34
|
+
end
|
35
|
+
alias :getToken :get_token
|
36
|
+
|
37
|
+
def check_token
|
38
|
+
raise "checkToken should be called on the RTM object directly";
|
39
|
+
end
|
40
|
+
alias :checkToken :check_token
|
41
|
+
|
42
|
+
def get_frob
|
43
|
+
response = @endpoint.call_method('rtm.auth.getFrob',nil,false)
|
44
|
+
@frob = response['frob']
|
45
|
+
@frob
|
46
|
+
end
|
47
|
+
alias :getFrob :get_frob
|
48
|
+
|
49
|
+
# After a call to get_frob, this returns the frob that was gotten.
|
50
|
+
def frob
|
51
|
+
@frob
|
52
|
+
end
|
53
|
+
|
54
|
+
def frob=(frob)
|
55
|
+
@frob=frob
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module RTM
|
4
|
+
# Acts as the endopint to RTM actions, providing a means to separate the API's behavior
|
5
|
+
# with interaction with RTM.
|
6
|
+
class Endpoint
|
7
|
+
NO_TIMELINE = {
|
8
|
+
"rtm.contacts.getList" => true,
|
9
|
+
"rtm.groups.getList" => true,
|
10
|
+
"rtm.lists.getList" => true,
|
11
|
+
"rtm.reflection.getMethodInfo" => true,
|
12
|
+
"rtm.reflection.getMethods" => true,
|
13
|
+
"rtm.settings.getList" => true,
|
14
|
+
"rtm.tasks.getList" => true,
|
15
|
+
"rtm.test.echo" => true,
|
16
|
+
"rtm.test.login" => true,
|
17
|
+
"rtm.time.convert" => true,
|
18
|
+
"rtm.time.parse" => true,
|
19
|
+
"rtm.timelines.create" => true,
|
20
|
+
"rtm.timezones.getList" => true,
|
21
|
+
"rtm.transactions.undo" => true,
|
22
|
+
}
|
23
|
+
BASE_URL = 'http://www.rememberthemilk.com/services/'
|
24
|
+
|
25
|
+
# Create an endpoint to RTM, upon which methods may be called.
|
26
|
+
# [api_key] your api key
|
27
|
+
# [secret] your secret
|
28
|
+
# [http] a class that acts like HTTParty (omit; this is used for testing mostly)
|
29
|
+
def initialize(api_key,secret,http=HTTParty)
|
30
|
+
@api_key = api_key
|
31
|
+
@secret = secret
|
32
|
+
@http=http
|
33
|
+
raise "Cannot work with a secret key" if @secret.nil?
|
34
|
+
raise "Cannot work with a api_key key" if @api_key.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def auto_timeline=(auto)
|
38
|
+
@auto_timeline = auto
|
39
|
+
end
|
40
|
+
|
41
|
+
# Update the token used to access this endpoint
|
42
|
+
def token=(token)
|
43
|
+
@token = token
|
44
|
+
end
|
45
|
+
|
46
|
+
def last_timeline
|
47
|
+
@last_timeline
|
48
|
+
end
|
49
|
+
|
50
|
+
# Calls the RTM method with the given parameters
|
51
|
+
# [method] the full RTM method, e.g. rtm.tasks.getList
|
52
|
+
# [params] the parameters to pass, can be symbols. api_key, token, and signature not required
|
53
|
+
# [token_required] if false, this method will not require a token
|
54
|
+
def call_method(method,params={},token_required=true)
|
55
|
+
@last_timeline = nil
|
56
|
+
raise NoTokenException if token_required && (!@token || @token.nil?)
|
57
|
+
params = {} if params.nil?
|
58
|
+
params[:auth_token] = @token if !@token.nil?
|
59
|
+
|
60
|
+
if (@auto_timeline && !NO_TIMELINE[method])
|
61
|
+
response = @http.get(url_for('rtm.timelines.create',{'auth_token' => @token}));
|
62
|
+
if response['timeline']
|
63
|
+
@last_timeline = response['timeline']
|
64
|
+
params[:timeline] = @last_timeline
|
65
|
+
else
|
66
|
+
raise BadResponseException, "Expected a <timeline></timeline> type response, but got: #{response.body}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
params_no_symbols = Hash.new
|
71
|
+
params.each do |k,v|
|
72
|
+
params_no_symbols[k.to_s] = v
|
73
|
+
end
|
74
|
+
|
75
|
+
response = @http.get(url_for(method,params_no_symbols))
|
76
|
+
verify(response)
|
77
|
+
return response['rsp']
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get the url for a particular call, doing the signing and all that other stuff.
|
81
|
+
#
|
82
|
+
# [method] the RTM method to call
|
83
|
+
# [params] hash of parameters. The +method+, +api_key+, and +api_sig+ parameters should _not_ be included.
|
84
|
+
# [endpoint] the endpoint relate to BASE_URL at which this request should be made.
|
85
|
+
def url_for(method,params={},endpoint='rest')
|
86
|
+
params['api_key'] = @api_key
|
87
|
+
params['method'] = method if method
|
88
|
+
signature = sign(params)
|
89
|
+
url = BASE_URL + endpoint + '/' + params_to_url(params.merge({'api_sig' => signature}))
|
90
|
+
url
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# quick and dirty way to check the response we got back from RTM.
|
97
|
+
# Call this after every call to RTM. This will verify that you got <rsp> with a "stat='ok'"
|
98
|
+
# and, if not, make a best effort at raising the error message from RTM.
|
99
|
+
#
|
100
|
+
# [response] the response from HTTParty from RTM
|
101
|
+
def verify(response)
|
102
|
+
raise BadResponseException, "No response at all" if !response || response.nil?
|
103
|
+
raise BadResponseException, "Got response with no body" if !response.body
|
104
|
+
raise BadResponseException, "Didn't find an rsp element, response body was #{response.body}" if !response["rsp"]
|
105
|
+
raise BadResponseException, "Didn't find a stat in the <rsp> element, response body was #{response.body}" if !response["rsp"]["stat"]
|
106
|
+
if response['rsp']['stat'] != 'ok'
|
107
|
+
err = response['rsp']['err']
|
108
|
+
if err
|
109
|
+
code = err['code']
|
110
|
+
msg = err['msg']
|
111
|
+
raise VerificationException, "ERROR: Code: #{code}, Message: #{msg}"
|
112
|
+
else
|
113
|
+
raise VerificationException, "Didn't find an <err> tag in the response for stat #{response['rsp']['stat']}, response body was #{response.body}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Turns params into a URL
|
119
|
+
def params_to_url(params)
|
120
|
+
string = '?'
|
121
|
+
params.each do |k,v|
|
122
|
+
string += CGI::escape(k)
|
123
|
+
string += '='
|
124
|
+
string += CGI::escape(v)
|
125
|
+
string += '&'
|
126
|
+
end
|
127
|
+
string
|
128
|
+
end
|
129
|
+
|
130
|
+
# Signs the request given the params and secret key
|
131
|
+
#
|
132
|
+
# [params] hash of parameters
|
133
|
+
#
|
134
|
+
def sign(params)
|
135
|
+
raise "Something's wrong; @secret is nil" if @secret.nil?
|
136
|
+
sign_me = @secret
|
137
|
+
params.keys.sort.each do |key|
|
138
|
+
sign_me += key
|
139
|
+
raise "Omit params with nil values; key #{key} was nil" if params[key].nil?
|
140
|
+
sign_me += params[key]
|
141
|
+
end
|
142
|
+
return Digest::MD5.hexdigest(sign_me)
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'string_camelize'
|
3
|
+
require 'digest/md5'
|
4
|
+
require 'cgi'
|
5
|
+
require 'moocow/auth'
|
6
|
+
require 'moocow/endpoint'
|
7
|
+
|
8
|
+
module RTM
|
9
|
+
|
10
|
+
# Root access to RTM api.
|
11
|
+
# Most methods work just like the api demonstrates, however auth is a bit different:
|
12
|
+
#
|
13
|
+
# rtm = RTM.new(Endpoint.new(api_key,secret))
|
14
|
+
# auth_url = rtm.auth.url
|
15
|
+
# # Send user to url
|
16
|
+
# # When they click authorize and return to your app do:
|
17
|
+
# rtm.token=rtm.auth.get_token
|
18
|
+
#
|
19
|
+
# This is a one time thing, so cache the token somewhere and you are good to go:
|
20
|
+
#
|
21
|
+
# response = rtm.tasks.getList(:filter => 'location:Work and status:completed')
|
22
|
+
# response = rtm.groups.add(:group => 'My New Group')
|
23
|
+
#
|
24
|
+
# If you don't supply timelines, they will be created for you each time as needed.
|
25
|
+
# Also note that you can use Ruby-style method calls instead of filthy camel-case, e.g.
|
26
|
+
#
|
27
|
+
# response = rtm.tasks.get_list(:filter => 'location:Work and status:completed')
|
28
|
+
# # Same as
|
29
|
+
# response = rtm.tasks.getList(:filter => 'location:Work and status:completed')
|
30
|
+
#
|
31
|
+
class RTM
|
32
|
+
|
33
|
+
# Create access to RTM
|
34
|
+
#
|
35
|
+
# [endpoint] an Endpoint to RTM
|
36
|
+
def initialize(endpoint)
|
37
|
+
@endpoint = endpoint
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set the token
|
41
|
+
def token=(token)
|
42
|
+
@endpoint.token=token
|
43
|
+
end
|
44
|
+
|
45
|
+
def auto_timeline=(a)
|
46
|
+
@endpoint.auto_timeline=a
|
47
|
+
end
|
48
|
+
|
49
|
+
# Gets the last timeline that was used if in auto-timeline mode
|
50
|
+
def last_timeline
|
51
|
+
@endpoint.last_timeline
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get the auth method-space
|
55
|
+
def auth
|
56
|
+
RTMAuth.new(@endpoint)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Raises an InvalidTokenException if the token is not valid
|
60
|
+
def check_token
|
61
|
+
begin
|
62
|
+
response = @endpoint.call_method('rtm.auth.checkToken')
|
63
|
+
rescue VerificationException
|
64
|
+
raise InvalidTokenException
|
65
|
+
end
|
66
|
+
end
|
67
|
+
alias :checkToken :check_token
|
68
|
+
|
69
|
+
# Get the test method-space (Kernel defines a test method, making method_missing problematic)
|
70
|
+
def test
|
71
|
+
return RTMMethodSpace.new('test',@endpoint)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Gateway to all other method-spaces. Assumes you are making a valid call
|
75
|
+
# on RTM. Essentially, *any* method will give you an RTMMethodSpace object keyed to the
|
76
|
+
# method name, e.g. rtm.foobar will assume that RTM has a "foobar" methodspace.
|
77
|
+
# method names are converted to camelcase.
|
78
|
+
def method_missing(symbol,*args)
|
79
|
+
if !args || args.empty?
|
80
|
+
rtm_object = symbol.to_s.camelize
|
81
|
+
return RTMMethodSpace.new(rtm_object,@endpoint)
|
82
|
+
else
|
83
|
+
return super(symbol,*args)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
# Generic means of calling an RTM method. This is returned by RTM.method_missing and, in most cases, is
|
90
|
+
# the end point that calls an RTM method.
|
91
|
+
class RTMMethodSpace
|
92
|
+
|
93
|
+
# Create an RTMMethodSpace
|
94
|
+
#
|
95
|
+
# [name] the name of this method space, e.g. 'tasks'
|
96
|
+
# [endpoint] an endpoing to RTM
|
97
|
+
def initialize(name,endpoint)
|
98
|
+
@name = name
|
99
|
+
@endpoint = endpoint
|
100
|
+
raise "No endpoint" if @endpoint.nil?
|
101
|
+
end
|
102
|
+
|
103
|
+
# Calls the method on RTM in most cases. The only exception is if this RTMMethodSpace is 'tasks' and you
|
104
|
+
# call the 'notes' method on it: a new RTMMethodSpace is returned for the 'rtm.tasks.notes' method-space.
|
105
|
+
#
|
106
|
+
# This returns a response object as from HTTParty, dereferenced into <rsp>. So, for example, if you called
|
107
|
+
# the 'tasks.getList' method, you would get a hash that could be accessed via response['tasks'].
|
108
|
+
# This object is a Hash and Array structure that mimcs the XML returned by RTM. One quirk is that for methods that could return 1 or more
|
109
|
+
# of the same item (tasks.getList is a good example; it will return multilple <list> elements unless you restrict by list, in which case
|
110
|
+
# it returns only one <list> element). Because HTTParty doesn't understand this, you may find it convienient to convert such
|
111
|
+
# results to arrays. the to_array extension on Hash and Array accomplish this:
|
112
|
+
#
|
113
|
+
# response = rtm.tasks.getList(:filter => 'list:Work')
|
114
|
+
# response['tasks']['list'].as_array.each do |list|
|
115
|
+
# list['taskseries'].as_array.each do |task|
|
116
|
+
# puts task['name']
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# So, call to_array on anything you expect to be a list.
|
121
|
+
#
|
122
|
+
# This method raises either a BadResponseException if you got a bad or garbled response from RTM or a VerificationException
|
123
|
+
# if you got a non-OK response.
|
124
|
+
def method_missing(symbol,*args)
|
125
|
+
if (@name == 'tasks' && symbol.to_s == 'notes')
|
126
|
+
return RTMMethodSpace.new("tasks.notes",@endpoint)
|
127
|
+
else
|
128
|
+
rtm_method = "rtm.#{@name}.#{symbol.to_s.camelize}"
|
129
|
+
@endpoint.call_method(rtm_method,*args)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class VerificationException < Exception
|
135
|
+
end
|
136
|
+
|
137
|
+
class BadResponseException < Exception
|
138
|
+
end
|
139
|
+
|
140
|
+
class InvalidTokenException < Exception
|
141
|
+
end
|
142
|
+
|
143
|
+
class NoTokenException < Exception
|
144
|
+
end
|
145
|
+
end
|
data/lib/moocow.rb
ADDED
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: moocow
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Copeland
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-22 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: httparty
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.3.1
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: gli
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.5
|
34
|
+
version:
|
35
|
+
description:
|
36
|
+
email: davidcopeland@naildrivin5.com
|
37
|
+
executables:
|
38
|
+
- rtm
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README.rdoc
|
43
|
+
files:
|
44
|
+
- ext/hash_array.rb
|
45
|
+
- ext/string_camelize.rb
|
46
|
+
- lib/moocow/auth.rb
|
47
|
+
- lib/moocow/endpoint.rb
|
48
|
+
- lib/moocow/moocow.rb
|
49
|
+
- lib/moocow.rb
|
50
|
+
- bin/rtm
|
51
|
+
- README.rdoc
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://davetron5000.github.com/moocow
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options:
|
56
|
+
- --title
|
57
|
+
- Remember The Milk Ruby Client
|
58
|
+
- --main
|
59
|
+
- README.rdoc
|
60
|
+
- -ri
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
- ext
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "0"
|
70
|
+
version:
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: "0"
|
76
|
+
version:
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project: moocow
|
80
|
+
rubygems_version: 1.3.1
|
81
|
+
signing_key:
|
82
|
+
specification_version: 2
|
83
|
+
summary: Ruby Client for Remember The Milk
|
84
|
+
test_files: []
|
85
|
+
|