tinder 1.3.1 → 1.10.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/lib/tinder/room.rb CHANGED
@@ -1,37 +1,36 @@
1
+ # encoding: UTF-8
2
+ require 'time'
3
+
1
4
  module Tinder
2
5
  # A campfire room
3
6
  class Room
4
7
  attr_reader :id, :name
5
8
 
6
- def initialize(campfire, attributes = {})
7
- @campfire = campfire
9
+ def initialize(connection, attributes = {})
10
+ @connection = connection
8
11
  @id = attributes['id']
9
12
  @name = attributes['name']
10
13
  @loaded = false
11
14
  end
12
15
 
13
- # Join the room. Pass +true+ to join even if you've already joined.
14
- def join(force = false)
15
- post 'join'
16
+ # Join the room
17
+ # POST /room/#{id}/join.xml
18
+ # For whatever reason, #join() and #leave() are still xml endpoints
19
+ # whereas elsewhere in this API we're assuming json :\
20
+ def join
21
+ post 'join', 'xml'
16
22
  end
17
23
 
18
24
  # Leave a room
25
+ # POST /room/#{id}/leave.xml
19
26
  def leave
20
- post 'leave'
21
- end
22
-
23
- # Toggle guest access on or off
24
- def toggle_guest_access
25
- raise NotImplementedError
27
+ post 'leave', 'xml'
28
+ stop_listening
26
29
  end
27
30
 
28
31
  # Get the url for guest access
29
32
  def guest_url
30
- if guest_access_enabled?
31
- "http://#{@campfire.subdomain}.campfirenow.com/#{guest_invite_code}"
32
- else
33
- nil
34
- end
33
+ "#{@connection.uri}/#{guest_invite_code}" if guest_access_enabled?
35
34
  end
36
35
 
37
36
  def guest_access_enabled?
@@ -47,37 +46,33 @@ module Tinder
47
46
 
48
47
  # Change the name of the room
49
48
  def name=(name)
50
- connection.post("/room/#{@id}.json", :body => { :room => { :name => name } })
49
+ update :name => name
51
50
  end
52
51
  alias_method :rename, :name=
53
52
 
54
53
  # Change the topic
55
54
  def topic=(topic)
56
- connection.post("/room/#{@id}.json", :body => { :room => { :topic => name } })
55
+ update :topic => topic
56
+ end
57
+
58
+ def update(attrs)
59
+ connection.put("/room/#{@id}.json", {:room => attrs})
57
60
  end
58
61
 
59
62
  # Get the current topic
60
63
  def topic
61
- load
64
+ reload!
62
65
  @topic
63
66
  end
64
67
 
65
68
  # Lock the room to prevent new users from entering and to disable logging
66
69
  def lock
67
- post :lock
70
+ post 'lock'
68
71
  end
69
72
 
70
73
  # Unlock the room
71
74
  def unlock
72
- post :unlock
73
- end
74
-
75
- def ping(force = false)
76
- raise NotImplementedError
77
- end
78
-
79
- def destroy
80
- raise NotImplementedError
75
+ post 'unlock'
81
76
  end
82
77
 
83
78
  # Post a new message to the chat room
@@ -89,50 +84,129 @@ module Tinder
89
84
  send_message(message, 'PasteMessage')
90
85
  end
91
86
 
87
+ def play(sound)
88
+ send_message(sound, 'SoundMessage')
89
+ end
90
+
91
+ def tweet(url)
92
+ send_message(url, 'TweetMessage')
93
+ end
94
+
92
95
  # Get the list of users currently chatting for this room
93
96
  def users
97
+ @users ||= current_users
98
+ end
99
+
100
+ def current_users
94
101
  reload!
95
- @users
102
+ @current_users
96
103
  end
97
104
 
98
- # Get and array of the messages that have been posted to the room. Each
99
- # messages is a hash with:
100
- # * +:person+: the display name of the person that posted the message
101
- # * +:message+: the body of the message
102
- # * +:user_id+: Campfire user id
103
- # * +:id+: Campfire message id
104
- #
105
- # room.listen
106
- # #=> [{:person=>"Brandon", :message=>"I'm getting very sleepy", :user_id=>"148583", :id=>"16434003"}]
105
+ # return the user with the given id; if it isn't in our room cache,
106
+ # do a request to get it
107
+ def user(id)
108
+ if id
109
+ cached_user = users.detect {|u| u[:id] == id }
110
+ user = cached_user || fetch_user(id)
111
+ self.users << user
112
+ user
113
+ end
114
+ end
115
+
116
+ # Perform a request for the user with the given ID
117
+ def fetch_user(id)
118
+ user_data = connection.get("/users/#{id}.json")
119
+ user = user_data && user_data[:user]
120
+ user[:created_at] = Time.parse(user[:created_at])
121
+ user
122
+ end
123
+
124
+ # Modifies a hash representation of a Campfire message. Expands +:user_id+
125
+ # to a full hash at +:user+, generates Timestamp from +:created_at+.
107
126
  #
108
- # Called without a block, listen will return an array of messages that have been
109
- # posted since you joined. listen also takes an optional block, which then polls
110
- # for new messages every 5 seconds and calls the block for each message.
127
+ # Full returned hash:
128
+ # * +:body+: the body of the message
129
+ # * +:user+: Campfire user, which is itself a hash, of:
130
+ # * +:id+: User id
131
+ # * +:name+: User name
132
+ # * +:email_address+: Email address
133
+ # * +:admin+: Boolean admin flag
134
+ # * +:created_at+: User creation timestamp
135
+ # * +:type+: User type (e.g. Member)
136
+ # * +:id+: Campfire message id
137
+ # * +:type+: Campfire message type
138
+ # * +:room_id+: Campfire room id
139
+ # * +:created_at+: Message creation timestamp
140
+ def parse_message(message)
141
+ message[:user] = user(message.delete(:user_id))
142
+ message[:created_at] = Time.parse(message[:created_at])
143
+ message
144
+ end
145
+
146
+ # Listen for new messages in the room, parsing them with #parse_message
147
+ # and then yielding them to the provided block as they arrive.
111
148
  #
112
149
  # room.listen do |m|
113
- # room.speak "#{m[:person]}, Go away!" if m[:message] =~ /Java/i
150
+ # room.speak "Go away!" if m[:body] =~ /Java/i
114
151
  # end
115
- #
116
- def listen(interval = 5)
117
- require 'yajl/http_stream'
118
-
119
- auth = connection.default_options[:basic_auth]
120
- url = URI.parse("http://#{auth[:username]}:#{auth[:password]}@streaming.#{Campfire::HOST}/room/#{@id}/live.json")
121
- Yajl::HttpStream.get(url) do |message|
122
- { :id => message['id'],
123
- :user_id => message['user_id'],
124
- :message => message['body'] }
152
+ def listen(options = {})
153
+ raise ArgumentError, "no block provided" unless block_given?
154
+
155
+ Tinder.logger.info "Joining #{@name}…"
156
+ join # you have to be in the room to listen
157
+
158
+ require 'json'
159
+ require 'hashie'
160
+ require 'multi_json'
161
+ require 'twitter/json_stream'
162
+
163
+ auth = connection.basic_auth_settings
164
+ options = {
165
+ :host => "streaming.#{Connection::HOST}",
166
+ :path => room_url_for('live'),
167
+ :auth => "#{auth[:username]}:#{auth[:password]}",
168
+ :timeout => 6,
169
+ :ssl => connection.options[:ssl]
170
+ }.merge(options)
171
+
172
+ Tinder.logger.info "Starting EventMachine server…"
173
+ EventMachine::run do
174
+ @stream = Twitter::JSONStream.connect(options)
175
+ Tinder.logger.info "Listening to #{@name}…"
176
+ @stream.each_item do |message|
177
+ message = Hashie::Mash.new(MultiJson.decode(message))
178
+ message = parse_message(message)
179
+ yield(message)
180
+ end
181
+
182
+ @stream.on_error do |message|
183
+ raise ListenFailed.new("got an error! #{message.inspect}!")
184
+ end
185
+
186
+ @stream.on_max_reconnects do |timeout, retries|
187
+ raise ListenFailed.new("Tried #{retries} times to connect. Got disconnected from #{@name}!")
188
+ end
189
+
190
+ # if we really get disconnected
191
+ raise ListenFailed.new("got disconnected from #{@name}!") if !EventMachine.reactor_running?
125
192
  end
126
193
  end
127
194
 
128
- # Get the dates for the available transcripts for this room
129
- def available_transcripts
130
- raise NotImplementedError
195
+ def listening?
196
+ @stream != nil
197
+ end
198
+
199
+ def stop_listening
200
+ return unless listening?
201
+
202
+ Tinder.logger.info "Stopped listening to #{@name}…"
203
+ @stream.stop
204
+ @stream = nil
131
205
  end
132
206
 
133
207
  # Get the transcript for the given date (Returns a hash in the same format as #listen)
134
208
  #
135
- # room.transcript(room.available_transcripts.first)
209
+ # room.transcript(Time.now)
136
210
  # #=> [{:message=>"foobar!",
137
211
  # :user_id=>"99999",
138
212
  # :person=>"Brandon",
@@ -141,65 +215,99 @@ module Tinder
141
215
  #
142
216
  # The timestamp slot will typically have a granularity of five minutes.
143
217
  #
144
- def transcript(transcript_date)
145
- url = "/room/#{@id}/transcript/#{transcript_date.to_date.strftime('%Y/%m/%d')}.json"
146
- connection.get(url)['messages'].map do |room|
147
- { :id => room['id'],
148
- :user_id => room['user_id'],
149
- :message => room['body'],
150
- :timestamp => Time.parse(room['created_at']) }
218
+ def transcript(transcript_date = Date.today)
219
+ url = "/room/#{@id}/transcript/#{transcript_date.strftime('%Y/%m/%d')}.json"
220
+ connection.get(url)['messages'].map do |message|
221
+ parse_message(message)
151
222
  end
152
223
  end
153
224
 
154
- def upload(filename)
155
- File.open(filename, "rb") do |file|
156
- params = Multipart::MultipartPost.new('upload' => file)
157
- connection.post("/room/#{@id}/uploads.json", :body => params.query)
225
+ # Search transcripts for the given term (returns an array of messages parsed
226
+ # via #parse_message, see #parse_message for format of returned message)
227
+ #
228
+ def search(term)
229
+ encoded_term = URI.encode(term)
230
+
231
+ room_messages = connection.get("/search/#{encoded_term}.json")["messages"].select do |message|
232
+ message[:room_id] == id
233
+ end
234
+
235
+ room_messages.map do |message|
236
+ parse_message(message)
158
237
  end
159
238
  end
160
239
 
240
+ def upload(file, content_type = nil, filename = nil)
241
+ require 'mime/types'
242
+ content_type ||= MIME::Types.type_for(filename || file)
243
+ raw_post(:uploads, { :upload => Faraday::UploadIO.new(file, content_type, filename) })
244
+ end
245
+
161
246
  # Get the list of latest files for this room
162
247
  def files(count = 5)
163
- connection.get(room_url_for(:uploads))['uploads'].map { |u| u['full_url'] }
248
+ get(:uploads)['uploads'].map { |u| u['full_url'] }
164
249
  end
165
250
 
166
- protected
167
- def load
168
- reload! unless @loaded
251
+ # Get a list of recent messages
252
+ # Accepts a hash for options:
253
+ # * +:limit+: Restrict the number of messages returned
254
+ # * +:since_message_id+: Get messages created after the specified message id
255
+ def recent(options = {})
256
+ options = { :limit => 10, :since_message_id => nil }.merge(options)
257
+ # Build url manually, faraday has to be 8.0 to do this
258
+ url = "#{room_url_for(:recent)}?limit=#{options[:limit]}&since_message_id=#{options[:since_message_id]}"
259
+
260
+ connection.get(url)['messages'].map do |msg|
261
+ parse_message(msg)
169
262
  end
263
+ end
170
264
 
171
- def reload!
172
- attributes = connection.get("/room/#{@id}.json")['room']
265
+ protected
173
266
 
174
- @id = attributes['id']
175
- @name = attributes['name']
176
- @topic = attributes['topic']
177
- @full = attributes['full']
178
- @open_to_guests = attributes['open-to-guests']
179
- @active_token_value = attributes['active-token-value']
180
- @users = attributes['users'].map { |u| u['name'] }
267
+ def load
268
+ reload! unless @loaded
269
+ end
181
270
 
182
- @loaded = true
183
- end
271
+ def reload!
272
+ attributes = connection.get("/room/#{@id}.json")['room']
184
273
 
185
- def send_message(message, type = 'Textmessage')
186
- post 'speak', :body => {:message => {:body => message, :type => type}}.to_json
274
+ @id = attributes['id']
275
+ @name = attributes['name']
276
+ @topic = attributes['topic']
277
+ @full = attributes['full']
278
+ @open_to_guests = attributes['open_to_guests']
279
+ @active_token_value = attributes['active_token_value']
280
+ @current_users = attributes['users'].map do |user|
281
+ user[:created_at] = Time.parse(user[:created_at])
282
+ user
187
283
  end
188
284
 
189
- def get(action, options = {})
190
- connection.get(room_url_for(action), options)
191
- end
285
+ @loaded = true
286
+ end
192
287
 
193
- def post(action, options = {})
194
- connection.post(room_url_for(action), options)
195
- end
288
+ def send_message(message, type = 'TextMessage')
289
+ post 'speak', {:message => {:body => message, :type => type}}
290
+ end
196
291
 
197
- def room_url_for(action)
198
- "/room/#{@id}/#{action}.json"
199
- end
292
+ def get(action)
293
+ connection.get(room_url_for(action))
294
+ end
295
+
296
+ def post(action, body = nil)
297
+ connection.post(room_url_for(action), body)
298
+ end
299
+
300
+ def raw_post(action, body = nil)
301
+ connection.raw_post(room_url_for(action), body)
302
+ end
303
+
304
+ def room_url_for(action, format="json")
305
+ "/room/#{@id}/#{action}.#{format}"
306
+ end
307
+
308
+ def connection
309
+ @connection
310
+ end
200
311
 
201
- def connection
202
- @campfire.connection
203
- end
204
312
  end
205
313
  end
@@ -0,0 +1,4 @@
1
+ # encoding: UTF-8
2
+ module Tinder
3
+ VERSION = '1.10.1' unless defined?(::Tinder::VERSION)
4
+ end
data/lib/tinder.rb CHANGED
@@ -1,16 +1,20 @@
1
- require 'rubygems'
2
- require 'active_support'
3
- require 'uri'
4
- require 'net/http'
5
- require 'net/https'
6
- require 'open-uri'
7
-
1
+ # encoding: UTF-8
8
2
  require 'tinder/connection'
9
- require 'tinder/multipart'
10
3
  require 'tinder/campfire'
11
4
  require 'tinder/room'
5
+ require 'logger'
12
6
 
13
7
  module Tinder
14
8
  class Error < StandardError; end
15
9
  class SSLRequiredError < Error; end
10
+ class AuthenticationFailed < Error; end
11
+ class ListenFailed < Error; end
12
+
13
+ def self.logger
14
+ @logger ||= Logger.new(ENV['TINDER_LOGGING'] ? STDOUT : nil)
15
+ end
16
+
17
+ def self.logger=(logger)
18
+ @logger = logger
19
+ end
16
20
  end
data/site/index.html CHANGED
@@ -34,7 +34,7 @@
34
34
  <div id="content">
35
35
  <p>Tinder is an API for interfacing with <a href="http://campfirenow.com">Campfire</a>, the 37Signals chat application.</p>
36
36
  <h2>Example</h2>
37
-
37
+
38
38
  <pre><code class="ruby">campfire = Tinder::Campfire.new 'mysubdomain'
39
39
  campfire.login 'myemail@example.com', 'mypassword'</code></pre>
40
40
 
@@ -77,17 +77,17 @@ campfire.users # users in all rooms</code></pre>
77
77
  <p>See the <a href="tinder">API documentation</a> for more details.</p>
78
78
 
79
79
  <h2>Installation</h2>
80
-
80
+
81
81
  <p>Tinder can be installed as a gem or a Rails plugin. Install the gem by executing:</p>
82
-
82
+
83
83
  <pre>gem install tinder</pre>
84
-
84
+
85
85
  <p>Or, download it from <a href="http://rubyforge.org/frs/?group_id=2922">RubyForge</a>.</p>
86
-
86
+
87
87
  <h2>Source</h2>
88
-
88
+
89
89
  <p>Contributions are welcome and appreciated! The source is available from:</p>
90
-
90
+
91
91
  <pre>http://github.com/collectiveidea/tinder</pre>
92
92
  </div>
93
93
  </div>
@@ -0,0 +1,26 @@
1
+ {"rooms": [
2
+ {
3
+ "name": "Room 1",
4
+ "created_at": "2007/03/16 18:03:21 +0000",
5
+ "updated_at": "2007/03/16 18:03:21 +0000",
6
+ "topic": "Testing",
7
+ "id": 80749,
8
+ "membership_limit": 4
9
+ },
10
+ {
11
+ "name": "Room 2",
12
+ "created_at": "2007/03/16 18:04:54 +0000",
13
+ "updated_at": "2007/03/16 18:04:54 +0000",
14
+ "topic": "test",
15
+ "id": 80751,
16
+ "membership_limit": 4
17
+ },
18
+ {
19
+ "name": "Room 3",
20
+ "created_at": "2012/03/16 18:04:54 +0000",
21
+ "updated_at": "2013/03/16 18:04:54 +0000",
22
+ "topic": "presence",
23
+ "id": 80754,
24
+ "membership_limit": 4
25
+ }]
26
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "messages": [{
3
+ "starred": false,
4
+ "type": "TextMessage",
5
+ "room_id": 490096,
6
+ "created_at": "2012/04/05 10:53:14 +0000",
7
+ "id": 537713173,
8
+ "user_id": 1158839,
9
+ "body": "https://github.com/technomancy/dotfiles/commit/e19989d33777bb392c0ad1205444762dfecbaa5f "
10
+ }, {
11
+ "starred": false,
12
+ "type": "TextMessage",
13
+ "room_id": 490096,
14
+ "created_at": "2012/04/05 10:54:20 +0000",
15
+ "id": 537713420,
16
+ "user_id": 1158837,
17
+ "body": "Lol"
18
+ }]
19
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "room": {
3
+ "full": false,
4
+ "name": "Room 1",
5
+ "created_at": "2007/03/16 18:03:21 +0000",
6
+ "updated_at": "2007/03/16 18:03:21 +0000",
7
+ "users": [{
8
+ "type": "Member",
9
+ "created_at": "2006/12/07 21:20:39 +0000",
10
+ "email_address": "jane@example.com",
11
+ "admin": true,
12
+ "id": 1,
13
+ "name": "Jane Doe"
14
+ }],
15
+ "topic": "Testing",
16
+ "active_token_value": "90cf7",
17
+ "id": 80749,
18
+ "open_to_guests": true,
19
+ "membership_limit": 4
20
+ }
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "room": {
3
+ "full": false,
4
+ "name": "Room 2",
5
+ "created_at": "2007/03/16 18:03:21 +0000",
6
+ "updated_at": "2007/03/16 18:03:21 +0000",
7
+ "users": [{
8
+ "type": "Member",
9
+ "created_at": "2006/12/07 21:20:39 +0000",
10
+ "email_address": "john@example.com",
11
+ "admin": true,
12
+ "id": 2,
13
+ "name": "John Doe"
14
+ }],
15
+ "topic": "Testing 2",
16
+ "active_token_value": "90cf7",
17
+ "id": 80751,
18
+ "open_to_guests": true,
19
+ "membership_limit": 4
20
+ }
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "room": {
3
+ "full": false,
4
+ "name": "Room 1",
5
+ "created_at": "2007/03/16 18:03:21 +0000",
6
+ "updated_at": "2007/03/16 18:03:21 +0000",
7
+ "users": [{
8
+ "type": "Member",
9
+ "created_at": "2006/12/07 21:20:39 +0000",
10
+ "email_address": "brandon@opensoul.org",
11
+ "admin": true,
12
+ "id": 129553,
13
+ "name": "Brandon Keepers"
14
+ }],
15
+ "topic": "Testing",
16
+ "active_token_value": "90cf7",
17
+ "id": 80749,
18
+ "open_to_guests": true,
19
+ "membership_limit": 4
20
+ }
21
+ }
@@ -0,0 +1,18 @@
1
+ {"rooms": [
2
+ {
3
+ "name": "Room 1",
4
+ "created_at": "2007/03/16 18:03:21 +0000",
5
+ "updated_at": "2007/03/16 18:03:21 +0000",
6
+ "topic": "Testing",
7
+ "id": 80749,
8
+ "membership_limit": 4
9
+ },
10
+ {
11
+ "name": "Room 2",
12
+ "created_at": "2007/03/16 18:04:54 +0000",
13
+ "updated_at": "2007/03/16 18:04:54 +0000",
14
+ "topic": "test",
15
+ "id": 80751,
16
+ "membership_limit": 4
17
+ }]
18
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "user": {
3
+ "email_address": "user@example.com",
4
+ "type": "Member",
5
+ "created_at": "2006/04/06 00:38:28 +0000",
6
+ "admin": true,
7
+ "id": 12345,
8
+ "name": "John Doe",
9
+ "api_auth_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
10
+ }
11
+ }
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,27 @@
1
- require 'rubygems'
2
- require 'spec'
1
+ # encoding: UTF-8
2
+ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rspec'
3
5
  require 'tinder'
6
+ require 'fakeweb'
7
+
8
+ FakeWeb.allow_net_connect = false
9
+
10
+ def fixture(name)
11
+ File.read(File.dirname(__FILE__) + "/fixtures/#{name}")
12
+ end
13
+
14
+ def stub_connection(object, &block)
15
+ @stubs ||= Faraday::Adapter::Test::Stubs.new
16
+
17
+ object.connection.build do |builder|
18
+ builder.use FaradayMiddleware::EncodeJson
19
+ builder.use FaradayMiddleware::Mashify
20
+ builder.use FaradayMiddleware::ParseJson
21
+ builder.use Faraday::Response::RemoveWhitespace
22
+ builder.use Faraday::Response::RaiseOnAuthenticationFailure
23
+ builder.adapter :test, @stubs
24
+ end
25
+
26
+ block.call(@stubs)
27
+ end