teambox-client 0.1.1
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/History +2 -0
- data/License +20 -0
- data/Notes.md +32 -0
- data/README.md +29 -0
- data/Rakefile +46 -0
- data/TODO.example.rb +327 -0
- data/VERSION +1 -0
- data/examples/activities.rb +24 -0
- data/examples/helpers/config_store.rb +38 -0
- data/examples/users.rb +10 -0
- data/lib/teambox-client/base.rb +84 -0
- data/lib/teambox-client/httpauth.rb +39 -0
- data/lib/teambox-client/request.rb +71 -0
- data/lib/teambox-client.rb +152 -0
- data/teambox-client.gemspec +64 -0
- metadata +131 -0
data/History
ADDED
data/License
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Pablo Villalba, John Nunemaker
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Notes.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Status codes
|
2
|
+
==============================================================================
|
3
|
+
|
4
|
+
http://apiwiki.twitter.com/REST+API+Documentation
|
5
|
+
|
6
|
+
200 OK: everything went awesome.
|
7
|
+
304 Not Modified: there was no new data to return.
|
8
|
+
400 Bad Request: your request is invalid, and we'll return an error message that tells you why. This is the status code returned if you've exceeded the rate limit (see below).
|
9
|
+
401 Not Authorized: either you need to provide authentication credentials, or the credentials provided aren't valid.
|
10
|
+
403 Forbidden: we understand your request, but are refusing to fulfill it. An accompanying error message should explain why.
|
11
|
+
404 Not Found: either you're requesting an invalid URI or the resource in question doesn't exist (ex: no such user).
|
12
|
+
500 Internal Server Error: we did something wrong. Please post to the group about it and the Twitter team will investigate.
|
13
|
+
502 Bad Gateway: returned if Twitter is down or being upgraded.
|
14
|
+
503 Service Unavailable: the Twitter servers are up, but are overloaded with requests. Try again later.
|
15
|
+
|
16
|
+
Errors
|
17
|
+
-------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
An example would be:
|
20
|
+
|
21
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
22
|
+
<hash>
|
23
|
+
<request>/direct_messages/destroy/456.xml</request>
|
24
|
+
<error>No direct message with that ID found.</error>
|
25
|
+
</hash>
|
26
|
+
|
27
|
+
Rate Limit Headers
|
28
|
+
-------------------------------------------------------------------------------
|
29
|
+
|
30
|
+
X-RateLimit-Limit the current limit in effect
|
31
|
+
X-RateLimit-Remaining the number of hits remaining before you are rate limited
|
32
|
+
X-RateLimit-Reset the time the current rate limiting period ends (in epoch time, number of seconds since 1970-01-01 00:00:00)
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
This is a Ruby wrapper for Teambox API.
|
3
|
+
|
4
|
+
Largely inspired by @jnunemaker's Twitter gem.
|
5
|
+
This is currently in development, and is missing many methods.
|
6
|
+
|
7
|
+
Get started
|
8
|
+
-------------------------------------------------------------------------------
|
9
|
+
|
10
|
+
First, install the *teambox-client* gem:
|
11
|
+
|
12
|
+
gem install teambox-client
|
13
|
+
|
14
|
+
Now, run `irb -rubygems` and this snippet to get the list of activities:
|
15
|
+
|
16
|
+
require 'teambox-client'
|
17
|
+
httpauth = Teambox::HTTPAuth.new(your_username, your_password)
|
18
|
+
client = Teambox::Base.new(httpauth)
|
19
|
+
puts client.activities
|
20
|
+
|
21
|
+
Examples
|
22
|
+
-------------------------------------------------------------------------------
|
23
|
+
|
24
|
+
First, create a file on $HOME/.teambox with these values:
|
25
|
+
|
26
|
+
username: your_teambox_username_or_email
|
27
|
+
password: password
|
28
|
+
|
29
|
+
See the examples directory.
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "teambox-client"
|
8
|
+
gem.summary = "A ruby gem wrapper for Teambox API"
|
9
|
+
gem.description = "Provides methods to read and write to Teambox for ruby apps"
|
10
|
+
gem.email = "pablo@teambox.com"
|
11
|
+
gem.homepage = "http://github.com/micho/teambox-ruby"
|
12
|
+
gem.authors = ["Pablo Villalba", "John Nunemaker"]
|
13
|
+
|
14
|
+
gem.add_dependency("hashie", "~> 0.2.0")
|
15
|
+
gem.add_dependency("httparty", "~> 0.5.0")
|
16
|
+
gem.add_dependency("yajl-ruby", "~> 0.7.0")
|
17
|
+
|
18
|
+
#gem.add_development_dependency ""
|
19
|
+
end
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
Rake::TestTask.new(:test) do |test|
|
26
|
+
test.libs << 'lib' << 'test'
|
27
|
+
test.pattern = 'test/**/*_test.rb'
|
28
|
+
test.verbose = true
|
29
|
+
end
|
30
|
+
|
31
|
+
task :test => :check_dependencies
|
32
|
+
task :default => :test
|
33
|
+
|
34
|
+
require 'rake/rdoctask'
|
35
|
+
Rake::RDocTask.new do |rdoc|
|
36
|
+
if File.exist?('VERSION')
|
37
|
+
version = File.read('VERSION')
|
38
|
+
else
|
39
|
+
version = ""
|
40
|
+
end
|
41
|
+
|
42
|
+
rdoc.rdoc_dir = 'rdoc'
|
43
|
+
rdoc.title = "teambox-ruby #{version}"
|
44
|
+
rdoc.rdoc_files.include('README*')
|
45
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
46
|
+
end
|
data/TODO.example.rb
ADDED
@@ -0,0 +1,327 @@
|
|
1
|
+
# This are methods that we didn't implement on Teambox, but sound like good ideas
|
2
|
+
# or reference methods
|
3
|
+
|
4
|
+
def friendship_destroy(id)
|
5
|
+
perform_post("/friendships/destroy/#{id}.json")
|
6
|
+
end
|
7
|
+
|
8
|
+
def verify_credentials
|
9
|
+
perform_get("/account/verify_credentials.json")
|
10
|
+
end
|
11
|
+
|
12
|
+
def users(*ids_or_usernames)
|
13
|
+
ids, usernames = [], []
|
14
|
+
ids_or_usernames.each do |id_or_username|
|
15
|
+
if id_or_username.is_a?(Integer)
|
16
|
+
ids << id_or_username
|
17
|
+
elsif id_or_username.is_a?(String)
|
18
|
+
usernames << id_or_username
|
19
|
+
end
|
20
|
+
end
|
21
|
+
query = {}
|
22
|
+
query[:user_id] = ids.join(",") unless ids.empty?
|
23
|
+
query[:screen_name] = usernames.join(",") unless usernames.empty?
|
24
|
+
perform_get("/users/lookup.json", :query => query)
|
25
|
+
end
|
26
|
+
|
27
|
+
def direct_message_destroy(id)
|
28
|
+
perform_post("/direct_messages/destroy/#{id}.json")
|
29
|
+
end
|
30
|
+
|
31
|
+
# Options: id, user_id, screen_name
|
32
|
+
# Could be adapted to Teambox as "people in my projects"
|
33
|
+
def friend_ids(query={})
|
34
|
+
perform_get("/friends/ids.json", :query => query)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Options: in_reply_to_status_id
|
38
|
+
def update(status, query={})
|
39
|
+
perform_post("/statuses/update.json", :body => {:status => status}.merge(query))
|
40
|
+
end
|
41
|
+
|
42
|
+
# Options: since_id, max_id, count, page
|
43
|
+
# options: count, page, ids_only
|
44
|
+
# Options: id, user_id, screen_name, page
|
45
|
+
|
46
|
+
# :per_page = max number of statues to get at once
|
47
|
+
# :page = which page of tweets you wish to get
|
48
|
+
def list_timeline(list_owner_username, slug, query = {})
|
49
|
+
perform_get("/#{list_owner_username}/lists/#{slug}/statuses.json", :query => query)
|
50
|
+
end
|
51
|
+
|
52
|
+
def list_create(list_owner_username, options)
|
53
|
+
perform_post("/#{list_owner_username}/lists.json", :body => {:user => list_owner_username}.merge(options))
|
54
|
+
end
|
55
|
+
|
56
|
+
def enable_notifications(id)
|
57
|
+
perform_post("/notifications/follow/#{id}.json")
|
58
|
+
end
|
59
|
+
|
60
|
+
def disable_notifications(id)
|
61
|
+
perform_post("/notifications/leave/#{id}.json")
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def rate_limit_status
|
66
|
+
perform_get("/account/rate_limit_status.json")
|
67
|
+
end
|
68
|
+
|
69
|
+
# One or more of the following must be present:
|
70
|
+
# name, email, url, location, description
|
71
|
+
def update_profile(body={})
|
72
|
+
perform_post("/account/update_profile.json", :body => body)
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
require 'pp'
|
85
|
+
module Teambox
|
86
|
+
class Search
|
87
|
+
include HTTParty
|
88
|
+
include Enumerable
|
89
|
+
base_uri "search.teambox.com/search"
|
90
|
+
format :json
|
91
|
+
|
92
|
+
attr_reader :result, :query
|
93
|
+
|
94
|
+
def initialize(q=nil, options={})
|
95
|
+
@options = options
|
96
|
+
clear
|
97
|
+
containing(q) if q && q.strip != ""
|
98
|
+
endpoint_url = options[:api_endpoint]
|
99
|
+
endpoint_url = "#{endpoint_url}/search" if endpoint_url && !endpoint_url.include?("/search")
|
100
|
+
self.class.base_uri(endpoint_url) if endpoint_url
|
101
|
+
end
|
102
|
+
|
103
|
+
def user_agent
|
104
|
+
@options[:user_agent] || "Ruby Teambox Gem"
|
105
|
+
end
|
106
|
+
|
107
|
+
def from(user, exclude=false)
|
108
|
+
@query[:q] << "#{exclude ? "-" : ""}from:#{user}"
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
def to(user, exclude=false)
|
113
|
+
@query[:q] << "#{exclude ? "-" : ""}to:#{user}"
|
114
|
+
self
|
115
|
+
end
|
116
|
+
|
117
|
+
def referencing(user, exclude=false)
|
118
|
+
@query[:q] << "#{exclude ? "-" : ""}@#{user}"
|
119
|
+
self
|
120
|
+
end
|
121
|
+
alias :references :referencing
|
122
|
+
alias :ref :referencing
|
123
|
+
|
124
|
+
def containing(word, exclude=false)
|
125
|
+
@query[:q] << "#{exclude ? "-" : ""}#{word}"
|
126
|
+
self
|
127
|
+
end
|
128
|
+
alias :contains :containing
|
129
|
+
|
130
|
+
# adds filtering based on hash tag ie: #teambox
|
131
|
+
def hashed(tag, exclude=false)
|
132
|
+
@query[:q] << "#{exclude ? "-" : ""}\##{tag}"
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
# Search for a phrase instead of a group of words
|
137
|
+
def phrase(phrase)
|
138
|
+
@query[:phrase] = phrase
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
142
|
+
# lang must be ISO 639-1 code ie: en, fr, de, ja, etc.
|
143
|
+
#
|
144
|
+
# when I tried en it limited my results a lot and took
|
145
|
+
# out several tweets that were english so i'd avoid
|
146
|
+
# this unless you really want it
|
147
|
+
def lang(lang)
|
148
|
+
@query[:lang] = lang
|
149
|
+
self
|
150
|
+
end
|
151
|
+
|
152
|
+
# popular|recent
|
153
|
+
def result_type(result_type)
|
154
|
+
@query[:result_type] = result_type
|
155
|
+
self
|
156
|
+
end
|
157
|
+
|
158
|
+
# Limits the number of results per page
|
159
|
+
def per_page(num)
|
160
|
+
@query[:rpp] = num
|
161
|
+
self
|
162
|
+
end
|
163
|
+
|
164
|
+
# Which page of results to fetch
|
165
|
+
def page(num)
|
166
|
+
@query[:page] = num
|
167
|
+
self
|
168
|
+
end
|
169
|
+
|
170
|
+
# Only searches tweets since a given id.
|
171
|
+
# Recommended to use this when possible.
|
172
|
+
def since(since_id)
|
173
|
+
@query[:since_id] = since_id
|
174
|
+
self
|
175
|
+
end
|
176
|
+
|
177
|
+
# From the advanced search form, not documented in the API
|
178
|
+
# Format YYYY-MM-DD
|
179
|
+
def since_date(since_date)
|
180
|
+
@query[:since] = since_date
|
181
|
+
self
|
182
|
+
end
|
183
|
+
|
184
|
+
# From the advanced search form, not documented in the API
|
185
|
+
# Format YYYY-MM-DD
|
186
|
+
def until_date(until_date)
|
187
|
+
@query[:until] = until_date
|
188
|
+
self
|
189
|
+
end
|
190
|
+
|
191
|
+
# Ranges like 25km and 50mi work.
|
192
|
+
def geocode(lat, long, range)
|
193
|
+
@query[:geocode] = [lat, long, range].join(",")
|
194
|
+
self
|
195
|
+
end
|
196
|
+
|
197
|
+
def max(id)
|
198
|
+
@query[:max_id] = id
|
199
|
+
self
|
200
|
+
end
|
201
|
+
|
202
|
+
# Clears all the query filters to make a new search
|
203
|
+
def clear
|
204
|
+
@fetch = nil
|
205
|
+
@query = {}
|
206
|
+
@query[:q] = []
|
207
|
+
self
|
208
|
+
end
|
209
|
+
|
210
|
+
def fetch(force=false)
|
211
|
+
if @fetch.nil? || force
|
212
|
+
query = @query.dup
|
213
|
+
query[:q] = query[:q].join(" ")
|
214
|
+
perform_get(query)
|
215
|
+
end
|
216
|
+
|
217
|
+
@fetch
|
218
|
+
end
|
219
|
+
|
220
|
+
def each
|
221
|
+
results = fetch()['results']
|
222
|
+
return if results.nil?
|
223
|
+
results.each {|r| yield r}
|
224
|
+
end
|
225
|
+
|
226
|
+
def next_page?
|
227
|
+
!!fetch()["next_page"]
|
228
|
+
end
|
229
|
+
|
230
|
+
def fetch_next_page
|
231
|
+
if next_page?
|
232
|
+
s = Search.new(nil, :user_agent => user_agent)
|
233
|
+
s.perform_get(fetch()["next_page"][1..-1])
|
234
|
+
s
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
protected
|
239
|
+
|
240
|
+
def perform_get(query)
|
241
|
+
response = self.class.get("#{self.class.base_uri}.json", :query => query, :format => :json, :headers => {"User-Agent" => user_agent})
|
242
|
+
@fetch = Teambox.mash(response)
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
|
250
|
+
|
251
|
+
|
252
|
+
|
253
|
+
|
254
|
+
|
255
|
+
|
256
|
+
|
257
|
+
|
258
|
+
|
259
|
+
|
260
|
+
|
261
|
+
|
262
|
+
|
263
|
+
|
264
|
+
module Teambox
|
265
|
+
class OAuth
|
266
|
+
extend Forwardable
|
267
|
+
|
268
|
+
def_delegators :access_token, :get, :post, :put, :delete
|
269
|
+
|
270
|
+
attr_reader :ctoken, :csecret, :consumer_options, :api_endpoint, :signing_endpoint
|
271
|
+
|
272
|
+
# Options
|
273
|
+
# :sign_in => true to just sign in with teambox instead of doing oauth authorization
|
274
|
+
# (http://apiwiki.teambox.com/Sign-in-with-Teambox)
|
275
|
+
def initialize(ctoken, csecret, options={})
|
276
|
+
@ctoken, @csecret, @consumer_options = ctoken, csecret, {}
|
277
|
+
@api_endpoint = options[:api_endpoint] || 'http://api.teambox.com'
|
278
|
+
@signing_endpoint = options[:signing_endpoint] || 'http://api.teambox.com'
|
279
|
+
if options[:sign_in]
|
280
|
+
@consumer_options[:authorize_path] = '/oauth/authenticate'
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def consumer
|
285
|
+
@consumer ||= ::OAuth::Consumer.new(@ctoken, @csecret, {:site => api_endpoint}.merge(consumer_options))
|
286
|
+
end
|
287
|
+
|
288
|
+
def signing_consumer
|
289
|
+
@signing_consumer ||= ::OAuth::Consumer.new(@ctoken, @csecret, {:site => signing_endpoint, :request_endpoint => api_endpoint }.merge(consumer_options))
|
290
|
+
end
|
291
|
+
|
292
|
+
def set_callback_url(url)
|
293
|
+
clear_request_token
|
294
|
+
request_token(:oauth_callback => url)
|
295
|
+
end
|
296
|
+
|
297
|
+
# Note: If using oauth with a web app, be sure to provide :oauth_callback.
|
298
|
+
# Options:
|
299
|
+
# :oauth_callback => String, url that teambox should redirect to
|
300
|
+
def request_token(options={})
|
301
|
+
@request_token ||= signing_consumer.get_request_token(options)
|
302
|
+
end
|
303
|
+
|
304
|
+
# For web apps use params[:oauth_verifier], for desktop apps,
|
305
|
+
# use the verifier is the pin that teambox gives users.
|
306
|
+
def authorize_from_request(rtoken, rsecret, verifier_or_pin)
|
307
|
+
request_token = ::OAuth::RequestToken.new(signing_consumer, rtoken, rsecret)
|
308
|
+
access_token = request_token.get_access_token(:oauth_verifier => verifier_or_pin)
|
309
|
+
@atoken, @asecret = access_token.token, access_token.secret
|
310
|
+
end
|
311
|
+
|
312
|
+
def access_token
|
313
|
+
@access_token ||= ::OAuth::AccessToken.new(signing_consumer, @atoken, @asecret)
|
314
|
+
end
|
315
|
+
|
316
|
+
def authorize_from_access(atoken, asecret)
|
317
|
+
@atoken, @asecret = atoken, asecret
|
318
|
+
end
|
319
|
+
|
320
|
+
private
|
321
|
+
|
322
|
+
def clear_request_token
|
323
|
+
@request_token = nil
|
324
|
+
end
|
325
|
+
|
326
|
+
end
|
327
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'teambox')
|
2
|
+
require File.join(File.dirname(__FILE__), 'helpers', 'config_store')
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
config = ConfigStore.new("#{ENV['HOME']}/.teambox")
|
6
|
+
httpauth = Teambox::HTTPAuth.new(config['username'], config['password'])
|
7
|
+
|
8
|
+
client = Teambox::Base.new(httpauth)
|
9
|
+
|
10
|
+
client.activities.each do |activity|
|
11
|
+
next unless activity["target"]
|
12
|
+
case activity["target"]["type"]
|
13
|
+
when "Comment"
|
14
|
+
pp activity["target"]["comment"]["body"]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
client.activities_from_project("teambox").each do |activity|
|
19
|
+
next unless activity["target"]
|
20
|
+
case activity["target"]["type"]
|
21
|
+
when "Comment"
|
22
|
+
pp activity["target"]["comment"]["body"]
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class ConfigStore
|
2
|
+
attr_reader :file
|
3
|
+
|
4
|
+
def initialize(file)
|
5
|
+
@file = file
|
6
|
+
end
|
7
|
+
|
8
|
+
def load
|
9
|
+
@config ||= YAML::load(open(file))
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](key)
|
14
|
+
load
|
15
|
+
@config[key]
|
16
|
+
end
|
17
|
+
|
18
|
+
def []=(key, value)
|
19
|
+
@config[key] = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def delete(*keys)
|
23
|
+
keys.each { |key| @config.delete(key) }
|
24
|
+
save
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def update(c={})
|
29
|
+
@config.merge!(c)
|
30
|
+
save
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def save
|
35
|
+
File.open(file, 'w') { |f| f.write(YAML.dump(@config)) }
|
36
|
+
self
|
37
|
+
end
|
38
|
+
end
|
data/examples/users.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'teambox')
|
2
|
+
require File.join(File.dirname(__FILE__), 'helpers', 'config_store')
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
httpauth = Teambox::HTTPAuth.new(TEAMBOX_USERNAME, TEAMBOX_PASSWORD)
|
6
|
+
client = Teambox::Base.new(httpauth)
|
7
|
+
|
8
|
+
pp client.user("pablo")
|
9
|
+
|
10
|
+
# TODO: Get a non-existant user
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Teambox
|
2
|
+
class Base
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def_delegators :client, :get, :post, :put, :delete
|
6
|
+
|
7
|
+
attr_reader :client
|
8
|
+
|
9
|
+
def initialize(client)
|
10
|
+
@client = client
|
11
|
+
end
|
12
|
+
|
13
|
+
# Teambox method
|
14
|
+
def activities(query={})
|
15
|
+
perform_get("/activities.json", :query => query)[:activities]
|
16
|
+
end
|
17
|
+
|
18
|
+
def activities_from_project(project_id, query={})
|
19
|
+
perform_get("/projects/#{project_id}/activities.json", :query => query)[:activities]
|
20
|
+
end
|
21
|
+
|
22
|
+
def user(id, query={})
|
23
|
+
perform_get("/users/#{id}.json", :query => query)[:user]
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def self.mime_type(file)
|
29
|
+
case
|
30
|
+
when file =~ /\.jpg/ then 'image/jpg'
|
31
|
+
when file =~ /\.gif$/ then 'image/gif'
|
32
|
+
when file =~ /\.png$/ then 'image/png'
|
33
|
+
else 'application/octet-stream'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def mime_type(f) self.class.mime_type(f) end
|
38
|
+
|
39
|
+
CRLF = "\r\n"
|
40
|
+
|
41
|
+
def self.build_multipart_bodies(parts)
|
42
|
+
boundary = Time.now.to_i.to_s(16)
|
43
|
+
body = ""
|
44
|
+
parts.each do |key, value|
|
45
|
+
esc_key = CGI.escape(key.to_s)
|
46
|
+
body << "--#{boundary}#{CRLF}"
|
47
|
+
if value.respond_to?(:read)
|
48
|
+
body << "Content-Disposition: form-data; name=\"#{esc_key}\"; filename=\"#{File.basename(value.path)}\"#{CRLF}"
|
49
|
+
body << "Content-Type: #{mime_type(value.path)}#{CRLF*2}"
|
50
|
+
body << value.read
|
51
|
+
else
|
52
|
+
body << "Content-Disposition: form-data; name=\"#{esc_key}\"#{CRLF*2}#{value}"
|
53
|
+
end
|
54
|
+
body << CRLF
|
55
|
+
end
|
56
|
+
body << "--#{boundary}--#{CRLF*2}"
|
57
|
+
{
|
58
|
+
:body => body,
|
59
|
+
:headers => {"Content-Type" => "multipart/form-data; boundary=#{boundary}"}
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_multipart_bodies(parts) self.class.build_multipart_bodies(parts) end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def perform_get(path, options={})
|
68
|
+
Teambox::Request.get(self, path, options)
|
69
|
+
end
|
70
|
+
|
71
|
+
def perform_post(path, options={})
|
72
|
+
Teambox::Request.post(self, path, options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def perform_put(path, options={})
|
76
|
+
Teambox::Request.put(self, path, options)
|
77
|
+
end
|
78
|
+
|
79
|
+
def perform_delete(path, options={})
|
80
|
+
Teambox::Request.delete(self, path, options)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Teambox
|
2
|
+
class HTTPAuth
|
3
|
+
include HTTParty
|
4
|
+
|
5
|
+
format :plain
|
6
|
+
|
7
|
+
attr_reader :username, :password, :options
|
8
|
+
|
9
|
+
def initialize(username, password, options={})
|
10
|
+
@username, @password = username, password
|
11
|
+
@options = {:ssl => false}.merge(options)
|
12
|
+
options[:api_endpoint] ||= "teambox.com"
|
13
|
+
self.class.base_uri "http#{'s' if options[:ssl]}://#{options[:api_endpoint]}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(uri, headers={})
|
17
|
+
self.class.get(uri, :headers => headers, :basic_auth => basic_auth)
|
18
|
+
end
|
19
|
+
|
20
|
+
def post(uri, body={}, headers={})
|
21
|
+
self.class.post(uri, :body => body, :headers => headers, :basic_auth => basic_auth)
|
22
|
+
end
|
23
|
+
|
24
|
+
def put(uri, body={}, headers={})
|
25
|
+
self.class.put(uri, :body => body, :headers => headers, :basic_auth => basic_auth)
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete(uri, body={}, headers={})
|
29
|
+
self.class.delete(uri, :body => body, :headers => headers, :basic_auth => basic_auth)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def basic_auth
|
35
|
+
@basic_auth ||= {:username => @username, :password => @password}
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Teambox
|
2
|
+
class Request
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def self.get(client, path, options={})
|
6
|
+
new(client, :get, path, options).perform
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.post(client, path, options={})
|
10
|
+
new(client, :post, path, options).perform
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.put(client, path, options={})
|
14
|
+
new(client, :put, path, options).perform
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.delete(client, path, options={})
|
18
|
+
new(client, :delete, path, options).perform
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :client, :method, :path, :options
|
22
|
+
|
23
|
+
def_delegators :client, :get, :post, :put, :delete
|
24
|
+
|
25
|
+
def initialize(client, method, path, options={})
|
26
|
+
@client, @method, @path, @options = client, method, path, options
|
27
|
+
end
|
28
|
+
|
29
|
+
def uri
|
30
|
+
@uri ||= begin
|
31
|
+
uri = URI.parse(path)
|
32
|
+
|
33
|
+
if options[:query] && options[:query] != {}
|
34
|
+
uri.query = to_query(options[:query])
|
35
|
+
end
|
36
|
+
|
37
|
+
uri.to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def perform
|
42
|
+
Teambox.make_friendly(send("perform_#{method}"))
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def perform_get
|
48
|
+
get(uri, options[:headers])
|
49
|
+
end
|
50
|
+
|
51
|
+
def perform_post
|
52
|
+
post(uri, options[:body], options[:headers])
|
53
|
+
end
|
54
|
+
|
55
|
+
def perform_put
|
56
|
+
put(uri, options[:body], options[:headers])
|
57
|
+
end
|
58
|
+
|
59
|
+
def perform_delete
|
60
|
+
delete(uri, options[:headers])
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_query(options)
|
64
|
+
options.inject([]) do |collection, opt|
|
65
|
+
collection << "#{opt[0]}=#{opt[1]}"
|
66
|
+
collection
|
67
|
+
end * '&'
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "hashie"
|
3
|
+
require "httparty"
|
4
|
+
require "yajl"
|
5
|
+
|
6
|
+
module Teambox
|
7
|
+
include HTTParty
|
8
|
+
API_VERSION = "1".freeze
|
9
|
+
format :json
|
10
|
+
|
11
|
+
class TeamboxError < StandardError
|
12
|
+
attr_reader :data
|
13
|
+
|
14
|
+
def initialize(data)
|
15
|
+
@data = data
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class RateLimitExceeded < TeamboxError; end
|
21
|
+
class Unauthorized < TeamboxError; end
|
22
|
+
class General < TeamboxError; end
|
23
|
+
|
24
|
+
class Unavailable < StandardError; end
|
25
|
+
class InformTeambox < StandardError; end
|
26
|
+
class NotFound < StandardError; end
|
27
|
+
|
28
|
+
def self.api_endpoint
|
29
|
+
# @api_endpoint ||= "api.teambox.com/#{API_VERSION}"
|
30
|
+
@api_endpoint ||= "teambox.com"
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.api_endpoint=(value)
|
34
|
+
@api_endpoint = value
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.firehose(options = {})
|
38
|
+
perform_get("/statuses/public_timeline.json")
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.user(id,options={})
|
42
|
+
perform_get("/users/show/#{id}.json")
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.status(id,options={})
|
46
|
+
perform_get("/statuses/show/#{id}.json")
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.friend_ids(id,options={})
|
50
|
+
perform_get("/friends/ids/#{id}.json")
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.follower_ids(id,options={})
|
54
|
+
perform_get("/followers/ids/#{id}.json")
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.timeline(id, options={})
|
58
|
+
perform_get("/statuses/user_timeline/#{id}.json", :query => options)
|
59
|
+
end
|
60
|
+
|
61
|
+
# :per_page = max number of statues to get at once
|
62
|
+
# :page = which page of tweets you wish to get
|
63
|
+
def self.list_timeline(list_owner_username, slug, query = {})
|
64
|
+
perform_get("/#{list_owner_username}/lists/#{slug}/statuses.json", :query => query)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def self.perform_get(uri, options = {})
|
70
|
+
base_uri self.api_endpoint
|
71
|
+
make_friendly(get(uri, options))
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.make_friendly(response)
|
75
|
+
raise_errors(response)
|
76
|
+
data = parse(response)
|
77
|
+
# Don't mash arrays of integers
|
78
|
+
if data && data.is_a?(Array) && data.first.is_a?(Integer)
|
79
|
+
data
|
80
|
+
else
|
81
|
+
mash(data)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.raise_errors(response)
|
86
|
+
case response.code.to_i
|
87
|
+
when 400
|
88
|
+
data = parse(response)
|
89
|
+
raise RateLimitExceeded.new(data), "(#{response.code}): #{response.message} - #{data['error'] if data}"
|
90
|
+
when 401
|
91
|
+
data = parse(response)
|
92
|
+
raise Unauthorized.new(data), "(#{response.code}): #{response.message} - #{data['error'] if data}"
|
93
|
+
when 403
|
94
|
+
data = parse(response)
|
95
|
+
raise General.new(data), "(#{response.code}): #{response.message} - #{data['error'] if data}"
|
96
|
+
when 404
|
97
|
+
raise NotFound, "(#{response.code}): #{response.message}"
|
98
|
+
when 500
|
99
|
+
raise InformTeambox, "Teambox had an internal error. Please let them know in the group. (#{response.code}): #{response.message}"
|
100
|
+
when 502..503
|
101
|
+
raise Unavailable, "(#{response.code}): #{response.message}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.parse(response)
|
106
|
+
Yajl::Parser.parse(response.body)
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.mash(obj)
|
110
|
+
if obj.is_a?(Array)
|
111
|
+
obj.map{|item| make_mash_with_consistent_hash(item)}
|
112
|
+
elsif obj.is_a?(Hash)
|
113
|
+
make_mash_with_consistent_hash(obj)
|
114
|
+
else
|
115
|
+
obj
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Lame workaround for the fact that mash doesn't hash correctly
|
120
|
+
def self.make_mash_with_consistent_hash(obj)
|
121
|
+
m = Hashie::Mash.new(obj)
|
122
|
+
def m.hash
|
123
|
+
inspect.hash
|
124
|
+
end
|
125
|
+
return m
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
module Hashie
|
131
|
+
class Mash
|
132
|
+
|
133
|
+
# Converts all of the keys to strings, optionally formatting key name
|
134
|
+
def rubyify_keys!
|
135
|
+
keys.each{|k|
|
136
|
+
v = delete(k)
|
137
|
+
new_key = k.to_s.underscore
|
138
|
+
self[new_key] = v
|
139
|
+
v.rubyify_keys! if v.is_a?(Hash)
|
140
|
+
v.each{|p| p.rubyify_keys! if p.is_a?(Hash)} if v.is_a?(Array)
|
141
|
+
}
|
142
|
+
self
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
directory = File.expand_path(File.dirname(__FILE__))
|
149
|
+
|
150
|
+
require File.join(directory, "teambox-client", "httpauth")
|
151
|
+
require File.join(directory, "teambox-client", "request")
|
152
|
+
require File.join(directory, "teambox-client", "base")
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{teambox-client}
|
8
|
+
s.version = "0.1.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Pablo Villalba", "John Nunemaker"]
|
12
|
+
s.date = %q{2010-06-19}
|
13
|
+
s.description = %q{Provides methods to read and write to Teambox for ruby apps}
|
14
|
+
s.email = %q{pablo@teambox.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
"History",
|
20
|
+
"License",
|
21
|
+
"Notes.md",
|
22
|
+
"README.md",
|
23
|
+
"Rakefile",
|
24
|
+
"TODO.example.rb",
|
25
|
+
"VERSION",
|
26
|
+
"examples/activities.rb",
|
27
|
+
"examples/helpers/config_store.rb",
|
28
|
+
"examples/users.rb",
|
29
|
+
"lib/teambox-client.rb",
|
30
|
+
"lib/teambox-client/base.rb",
|
31
|
+
"lib/teambox-client/httpauth.rb",
|
32
|
+
"lib/teambox-client/request.rb",
|
33
|
+
"teambox-client.gemspec"
|
34
|
+
]
|
35
|
+
s.homepage = %q{http://github.com/micho/teambox-ruby}
|
36
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
37
|
+
s.require_paths = ["lib"]
|
38
|
+
s.rubygems_version = %q{1.3.7}
|
39
|
+
s.summary = %q{A ruby gem wrapper for Teambox API}
|
40
|
+
s.test_files = [
|
41
|
+
"examples/activities.rb",
|
42
|
+
"examples/helpers/config_store.rb",
|
43
|
+
"examples/users.rb"
|
44
|
+
]
|
45
|
+
|
46
|
+
if s.respond_to? :specification_version then
|
47
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
48
|
+
s.specification_version = 3
|
49
|
+
|
50
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
51
|
+
s.add_runtime_dependency(%q<hashie>, ["~> 0.2.0"])
|
52
|
+
s.add_runtime_dependency(%q<httparty>, ["~> 0.5.0"])
|
53
|
+
s.add_runtime_dependency(%q<yajl-ruby>, ["~> 0.7.0"])
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<hashie>, ["~> 0.2.0"])
|
56
|
+
s.add_dependency(%q<httparty>, ["~> 0.5.0"])
|
57
|
+
s.add_dependency(%q<yajl-ruby>, ["~> 0.7.0"])
|
58
|
+
end
|
59
|
+
else
|
60
|
+
s.add_dependency(%q<hashie>, ["~> 0.2.0"])
|
61
|
+
s.add_dependency(%q<httparty>, ["~> 0.5.0"])
|
62
|
+
s.add_dependency(%q<yajl-ruby>, ["~> 0.7.0"])
|
63
|
+
end
|
64
|
+
end
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: teambox-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Pablo Villalba
|
14
|
+
- John Nunemaker
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2010-06-19 00:00:00 +02:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: hashie
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 23
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
- 2
|
34
|
+
- 0
|
35
|
+
version: 0.2.0
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id001
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: httparty
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 11
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
- 5
|
50
|
+
- 0
|
51
|
+
version: 0.5.0
|
52
|
+
type: :runtime
|
53
|
+
version_requirements: *id002
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: yajl-ruby
|
56
|
+
prerelease: false
|
57
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
- 7
|
66
|
+
- 0
|
67
|
+
version: 0.7.0
|
68
|
+
type: :runtime
|
69
|
+
version_requirements: *id003
|
70
|
+
description: Provides methods to read and write to Teambox for ruby apps
|
71
|
+
email: pablo@teambox.com
|
72
|
+
executables: []
|
73
|
+
|
74
|
+
extensions: []
|
75
|
+
|
76
|
+
extra_rdoc_files:
|
77
|
+
- README.md
|
78
|
+
files:
|
79
|
+
- History
|
80
|
+
- License
|
81
|
+
- Notes.md
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- TODO.example.rb
|
85
|
+
- VERSION
|
86
|
+
- examples/activities.rb
|
87
|
+
- examples/helpers/config_store.rb
|
88
|
+
- examples/users.rb
|
89
|
+
- lib/teambox-client.rb
|
90
|
+
- lib/teambox-client/base.rb
|
91
|
+
- lib/teambox-client/httpauth.rb
|
92
|
+
- lib/teambox-client/request.rb
|
93
|
+
- teambox-client.gemspec
|
94
|
+
has_rdoc: true
|
95
|
+
homepage: http://github.com/micho/teambox-ruby
|
96
|
+
licenses: []
|
97
|
+
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options:
|
100
|
+
- --charset=UTF-8
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
hash: 3
|
109
|
+
segments:
|
110
|
+
- 0
|
111
|
+
version: "0"
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
hash: 3
|
118
|
+
segments:
|
119
|
+
- 0
|
120
|
+
version: "0"
|
121
|
+
requirements: []
|
122
|
+
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 1.3.7
|
125
|
+
signing_key:
|
126
|
+
specification_version: 3
|
127
|
+
summary: A ruby gem wrapper for Teambox API
|
128
|
+
test_files:
|
129
|
+
- examples/activities.rb
|
130
|
+
- examples/helpers/config_store.rb
|
131
|
+
- examples/users.rb
|