BRIMIL01-meetup_api 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/README +67 -0
- data/Rakefile +37 -0
- data/lib/meetup_api.rb +189 -0
- data/test/api_key.README +4 -0
- data/test/meetup_api_tester.rb +106 -0
- metadata +67 -0
data/README
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
= Meetup API
|
2
|
+
|
3
|
+
== Developers
|
4
|
+
* {Bosco So}[http://boscomonkey.com] <rubymeetup _AT_ boscoso _DOT_ com>
|
5
|
+
|
6
|
+
== Description
|
7
|
+
Meetup API is a port of the official Python client released by Meetup
|
8
|
+
for their API. This port provides an object based API to query or
|
9
|
+
update Meetup data via pure Ruby. It hides the ugly HTTP/REST calls
|
10
|
+
from your code.
|
11
|
+
|
12
|
+
The Meetup Python API client can be found at
|
13
|
+
http://www.meetup.com/meetup_api/clients/
|
14
|
+
|
15
|
+
== External Dependencies
|
16
|
+
* Ruby (tested with 1.8.6)
|
17
|
+
* net/http
|
18
|
+
* json (tested with versions: 1.1.3)
|
19
|
+
|
20
|
+
== Usage Examples
|
21
|
+
Start by creating an instance of the Meetup object with your API key
|
22
|
+
as the constructor argument. You can get your API key at
|
23
|
+
http://www.meetup.com/meetup_api/key/
|
24
|
+
|
25
|
+
mu = MeetupApi::Client.new 'your_api_key'
|
26
|
+
|
27
|
+
Then, you can query the API directly by using one of the following
|
28
|
+
methods:
|
29
|
+
|
30
|
+
mu.get_events HASH_PARAMS
|
31
|
+
mu.get_rsvps HASH_PARAMS
|
32
|
+
|
33
|
+
Call the method using keyword args containing the parameters of the
|
34
|
+
query (identical to parameters described on the main documentation
|
35
|
+
page - http://www.meetup.com/meetup_api/docs/). For example, find all
|
36
|
+
the events around zip 94108:
|
37
|
+
|
38
|
+
local_events = mu.get_events :zip => 94108
|
39
|
+
|
40
|
+
Another example, find the event with id 9348580 (the results method
|
41
|
+
still returns an array, but it only has 1 element):
|
42
|
+
|
43
|
+
ruby_events = mu.get_events :id => 9348580
|
44
|
+
|
45
|
+
The returned value will be an object of type
|
46
|
+
MeetupApi::ApiResponse. This object's property meta is a Hash
|
47
|
+
containing all the metadata from the query results. The property
|
48
|
+
results is an Array containing objects of type MeetupApi::Event or
|
49
|
+
MeetupApi::Rsvp (depending which MeetupApi::Client method was
|
50
|
+
used). These objects support additional methods that will make API
|
51
|
+
calls to get more information (taking the MeetupApi::Client object as
|
52
|
+
an argument to make the request). For example, take the ruby event
|
53
|
+
from above and retrieve its RSVP list:
|
54
|
+
|
55
|
+
first_event = ruby_events.results.first
|
56
|
+
rsvps = first_event.get_rsvps mu
|
57
|
+
|
58
|
+
Of course, if you just wanted to only get the RSVP's, you can simply
|
59
|
+
call MeetupApi::Client#get_rsvps directly:
|
60
|
+
|
61
|
+
rsvps = mu.get_rsvps :event_id => 9348580
|
62
|
+
|
63
|
+
== To Do
|
64
|
+
* Currently, only MeetupApi::Client#get_events and get_rsvps are
|
65
|
+
implemented; so, finish the other client methods.
|
66
|
+
* Implement OAuth
|
67
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
task :default => :test
|
6
|
+
|
7
|
+
spec = Gem::Specification.new do |s|
|
8
|
+
s.name = 'meetup_api'
|
9
|
+
s.version = '0.1.0'
|
10
|
+
s.has_rdoc = true
|
11
|
+
s.extra_rdoc_files = %w(README)
|
12
|
+
s.rdoc_options = %w(--main README)
|
13
|
+
s.summary = "Ruby port of Meetup's official Python API client"
|
14
|
+
s.author = 'Bosco So'
|
15
|
+
s.email = 'git@boscoso.com'
|
16
|
+
s.homepage = 'http://boscoso.com'
|
17
|
+
s.files = %w(README Rakefile) + Dir.glob("{lib,test}/**/*")
|
18
|
+
|
19
|
+
s.add_dependency('json', '= 1.1.3')
|
20
|
+
end
|
21
|
+
|
22
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
23
|
+
pkg.gem_spec = spec
|
24
|
+
end
|
25
|
+
|
26
|
+
Rake::TestTask.new do |t|
|
27
|
+
t.libs << 'test'
|
28
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
29
|
+
t.verbose = true
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Generate the gemspec to serve this Gem from Github'
|
33
|
+
task :github do
|
34
|
+
file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
|
35
|
+
File.open(file, 'w') {|f| f << spec.to_ruby }
|
36
|
+
puts "Created gemspec: #{file}"
|
37
|
+
end
|
data/lib/meetup_api.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module MeetupApi
|
6
|
+
DEV = ''
|
7
|
+
API_BASE_URL = "http://api#{DEV}.meetup.com/"
|
8
|
+
EVENTS_URI = 'events'
|
9
|
+
RSVPS_URI = 'rsvps'
|
10
|
+
MEMBERS_URI = 'members'
|
11
|
+
GROUPS_URI = 'groups'
|
12
|
+
PHOTOS_URI = 'photos'
|
13
|
+
TOPICS_URI = 'topics'
|
14
|
+
COMMENTS_URI = 'comments'
|
15
|
+
|
16
|
+
class Client
|
17
|
+
def initialize(apiKey)
|
18
|
+
@key = apiKey
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_events(args)
|
22
|
+
ApiResponse.new(fetch(EVENTS_URI, args), Event)
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_rsvps(args)
|
26
|
+
ApiResponse.new(fetch(RSVPS_URI, args), Rsvp)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_members(args)
|
30
|
+
ApiResponse.new(fetch(MEMBERS_URI, args), Member)
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_groups(args)
|
34
|
+
ApiResponse.new(fetch(GROUPS_URI, args), Group)
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_photos(args)
|
38
|
+
ApiResponse.new(fetch(PHOTOS_URI, args), Photo)
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_topics(args)
|
42
|
+
ApiResponse.new(fetch(TOPICS_URI, args), Photo)
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_comments(args)
|
46
|
+
ApiResponse.new(fetch(COMMENTS_URI, args), Comment)
|
47
|
+
end
|
48
|
+
|
49
|
+
def fetch(uri, url_args={})
|
50
|
+
url_args['format'] = 'json'
|
51
|
+
url_args['key'] = @key if @key
|
52
|
+
args = URI.escape(url_args.collect{|k,v| "#{k}=#{v}"}.join('&'))
|
53
|
+
url = "#{API_BASE_URL}#{uri}/?#{args}"
|
54
|
+
data = Net::HTTP.get_response(URI.parse(url)).body
|
55
|
+
|
56
|
+
# ugh - rate limit error throws badly formed JSON
|
57
|
+
begin
|
58
|
+
JSON.parse(data)
|
59
|
+
rescue Exception => e
|
60
|
+
raise BaseException(e)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class ApiResponse
|
66
|
+
attr_reader :meta, :results
|
67
|
+
|
68
|
+
def initialize(json, klass)
|
69
|
+
if (meta_data = json['meta'])
|
70
|
+
@meta = meta_data
|
71
|
+
@results = json['results'].collect {|hash| klass.new hash}
|
72
|
+
else
|
73
|
+
raise ClientException.new(json)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Turns a hash into an object - see http://tinyurl.com/97dtjj
|
79
|
+
class Hashit
|
80
|
+
def initialize(hash)
|
81
|
+
hash.each do |k,v|
|
82
|
+
# create and initialize an instance variable for this key/value pair
|
83
|
+
self.instance_variable_set("@#{k}", v)
|
84
|
+
# create the getter that returns the instance variable
|
85
|
+
self.class.send(:define_method, k, proc{self.instance_variable_get("@#{k}")})
|
86
|
+
# create the setter that sets the instance variable (disabled for readonly)
|
87
|
+
##self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("@#{k}", v)})
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Base class for an item in a result set returned by the API.
|
93
|
+
class ApiItem < Hashit
|
94
|
+
end
|
95
|
+
|
96
|
+
class Event < ApiItem
|
97
|
+
def get_rsvps(apiclient, extraparams={})
|
98
|
+
extraparams['event_id'] = self.id
|
99
|
+
apiclient.get_rsvps extraparams
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_s
|
103
|
+
"Event #{self.id} named #{self.name} at #{self.time} (url: #{self.event_url})"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class Rsvp < ApiItem
|
108
|
+
def to_s
|
109
|
+
"Rsvp by #{self.name} (#{self.link}) with comment: #{self.comment}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class Group < ApiItem
|
114
|
+
def get_members(apiclient, extraparams={})
|
115
|
+
extraparams['group_id'] = self.id
|
116
|
+
apiclient.get_members extraparams
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_photos(apiclient, extraparams={})
|
120
|
+
extraparams['group_id'] = self.id
|
121
|
+
apiclient.get_photos extraparams
|
122
|
+
end
|
123
|
+
|
124
|
+
def get_comments(apiclient, extraparams={})
|
125
|
+
extraparams['group_id'] = self.id
|
126
|
+
apiclient.get_comments extraparams
|
127
|
+
end
|
128
|
+
|
129
|
+
def to_s
|
130
|
+
"Group #{self.id} named #{self.name}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class Member < ApiItem
|
135
|
+
def to_s
|
136
|
+
"Member #{self.id} named #{self.name}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class Photo < ApiItem
|
141
|
+
def to_s
|
142
|
+
"Photo #{self.id} named #{self.name}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class Topic < ApiItem
|
147
|
+
def get_photos(apiclient, extraparams={})
|
148
|
+
extraparams['topic_id'] = self.id
|
149
|
+
apiclient.get_photos extraparams
|
150
|
+
end
|
151
|
+
|
152
|
+
def to_s
|
153
|
+
"Topic #{self.id} named #{self.name}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class Comment < ApiItem
|
158
|
+
def to_s
|
159
|
+
"Comment #{self.id} named #{self.name}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Base class for unexpected errors returned by the Client
|
164
|
+
class BaseException < Exception
|
165
|
+
attr_reader :problem
|
166
|
+
|
167
|
+
def initialize(e)
|
168
|
+
@problem = e
|
169
|
+
end
|
170
|
+
|
171
|
+
def to_s
|
172
|
+
"#{self.problem}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class ClientException < BaseException
|
177
|
+
attr_reader :description
|
178
|
+
|
179
|
+
def initialize(error_json)
|
180
|
+
@description = error_json['details']
|
181
|
+
@problem = error_json['problem']
|
182
|
+
end
|
183
|
+
|
184
|
+
def to_s
|
185
|
+
"#{self.problem}: #{self.description}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
data/test/api_key.README
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require File.join(File.expand_path(File.dirname(__FILE__)),
|
5
|
+
"..", "lib", "meetup_api")
|
6
|
+
|
7
|
+
class MeetupApiTester < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'api_key')
|
11
|
+
|
12
|
+
@key = API_KEY
|
13
|
+
@api = MeetupApi::Client.new(@key)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_instantiate_new_client
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_fetch
|
20
|
+
# 8337541 is ID for 'My Rake is Bigger than your Rake (Ooga Labs)'
|
21
|
+
json = @api.fetch(MeetupApi::RSVPS_URI, :event_id => 8337541)
|
22
|
+
assert_not_nil(json, 'json cannot be nil')
|
23
|
+
assert_instance_of Hash, json
|
24
|
+
assert_equal(2, json.keys.size, 'valid JSON should have only 2 keys')
|
25
|
+
json
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_fetch_meta
|
29
|
+
json = test_fetch
|
30
|
+
meta = json['meta']
|
31
|
+
assert_not_nil(meta, "result metadata can't be nil")
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_fetch_results
|
35
|
+
json = test_fetch
|
36
|
+
results = json['results']
|
37
|
+
assert_not_nil(results, "result data can't be nil")
|
38
|
+
assert_instance_of Array, results
|
39
|
+
assert_equal(62, results.size, "should have 62 RSVP's")
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_fetch_missing_2nd_argument
|
43
|
+
json = @api.fetch(MeetupApi::RSVPS_URI)
|
44
|
+
assert_not_nil(json)
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_fetch_missing_event_id
|
48
|
+
json = @api.fetch(MeetupApi::RSVPS_URI)
|
49
|
+
assert_instance_of Hash, json
|
50
|
+
assert_equal(2, json.keys.size, 'valid JSON should have only 2 keys')
|
51
|
+
assert_not_nil(json['details'])
|
52
|
+
assert_not_nil(json['problem'])
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_fetch_unauthorized
|
56
|
+
client = MeetupApi::Client.new('invalid_key')
|
57
|
+
json = client.fetch(MeetupApi::RSVPS_URI, :event_id => 8337541)
|
58
|
+
assert_instance_of Hash, json
|
59
|
+
assert_equal(2, json.keys.size, 'valid JSON should have only 2 keys')
|
60
|
+
assert_not_nil(json['details'])
|
61
|
+
assert_not_nil(json['problem'])
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def test_rsvp_valid
|
66
|
+
ret = @api.get_rsvps :event_id => 8337541
|
67
|
+
verify_my_rake_is_bigger_than_your_rake ret
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_rsvp_missing_id
|
71
|
+
begin
|
72
|
+
ret = @api.get_rsvps({})
|
73
|
+
flunk 'previous call should raise'
|
74
|
+
rescue MeetupApi::ClientException => e
|
75
|
+
assert_not_nil e.description
|
76
|
+
assert_not_nil e.problem
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_events
|
81
|
+
ret = @api.get_events :id => 8337541, :after => '01011970'
|
82
|
+
assert_not_nil ret.meta
|
83
|
+
assert_not_nil ret.results
|
84
|
+
assert_instance_of Array, ret.results
|
85
|
+
assert_equal 1, ret.results.size
|
86
|
+
ret
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_events_rsvps
|
90
|
+
events = test_events
|
91
|
+
rsvps = events.results.first.get_rsvps @api
|
92
|
+
verify_my_rake_is_bigger_than_your_rake rsvps
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def verify_my_rake_is_bigger_than_your_rake(ret)
|
98
|
+
assert_not_nil ret, "RSVP's for 'My Rake...' event should not be nil"
|
99
|
+
assert_not_nil ret.meta
|
100
|
+
assert_not_nil ret.results
|
101
|
+
assert_instance_of Array, ret.results
|
102
|
+
assert_equal 62, ret.results.size
|
103
|
+
ret.results.each {|r| assert_instance_of MeetupApi::Rsvp, r}
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: BRIMIL01-meetup_api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bosco So
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-04 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: json
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - "="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.1.3
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: user@example.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README
|
33
|
+
files:
|
34
|
+
- README
|
35
|
+
- Rakefile
|
36
|
+
- lib/meetup_api.rb
|
37
|
+
- test/api_key.README
|
38
|
+
- test/meetup_api_tester.rb
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://example.com
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options:
|
43
|
+
- --main
|
44
|
+
- README
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.2.0
|
63
|
+
signing_key:
|
64
|
+
specification_version: 2
|
65
|
+
summary: Ruby port of Meetup's official Python API client
|
66
|
+
test_files: []
|
67
|
+
|