collectiveidea-tinder 1.2.2

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.
@@ -0,0 +1,246 @@
1
+ module Tinder
2
+ # A campfire room
3
+ class Room
4
+ attr_reader :id, :name
5
+
6
+ def initialize(campfire, id, name = nil)
7
+ @campfire = campfire
8
+ @id = id
9
+ @name = name
10
+ end
11
+
12
+ # Join the room. Pass +true+ to join even if you've already joined.
13
+ def join(force = false)
14
+ @room = returning(get("room/#{id}")) do |room|
15
+ raise Error, "Could not join room" unless verify_response(room, :success)
16
+ @membership_key = room.body.scan(/\"membershipKey\":\s?\"([a-z0-9]+)\"/).to_s
17
+ @user_id = room.body.scan(/\"userID\":\s?(\d+)/).to_s
18
+ @last_cache_id = room.body.scan(/\"lastCacheID\":\s?(\d+)/).to_s
19
+ @timestamp = room.body.scan(/\"timestamp\":\s?(\d+)/).to_s
20
+ @idle_since = Time.now
21
+ end if @room.nil? || force
22
+ ping
23
+ true
24
+ end
25
+
26
+ # Leave a room
27
+ def leave
28
+ returning verify_response(post("room/#{id}/leave"), :redirect) do
29
+ @room, @membership_key, @user_id, @last_cache_id, @timestamp, @idle_since = nil
30
+ end
31
+ end
32
+
33
+ # Toggle guest access on or off
34
+ def toggle_guest_access
35
+ # re-join the room to get the guest url
36
+ verify_response(post("room/#{id}/toggle_guest_access"), :success) && join(true)
37
+ end
38
+
39
+ # Get the url for guest access
40
+ def guest_url
41
+ join
42
+ link = (Hpricot(@room.body)/"#guest_access h4").first
43
+ link.inner_html if link
44
+ end
45
+
46
+ def guest_access_enabled?
47
+ !guest_url.nil?
48
+ end
49
+
50
+ # The invite code use for guest
51
+ def guest_invite_code
52
+ guest_url.scan(/\/(\w*)$/).to_s
53
+ end
54
+
55
+ # Change the name of the room
56
+ def name=(name)
57
+ @name = name if verify_response(post("account/edit/room/#{id}", { :room => { :name => name }}, :ajax => true), :success)
58
+ end
59
+ alias_method :rename, :name=
60
+
61
+ # Change the topic
62
+ def topic=(topic)
63
+ topic if verify_response(post("room/#{id}/change_topic", { 'room' => { 'topic' => topic }}, :ajax => true), :success)
64
+ end
65
+
66
+ # Get the current topic
67
+ def topic
68
+ join
69
+ h = (Hpricot(@room.body)/"#topic")
70
+ if h
71
+ (h/:span).remove
72
+ h.inner_text.strip
73
+ end
74
+ end
75
+
76
+ # Lock the room to prevent new users from entering and to disable logging
77
+ def lock
78
+ verify_response(post("room/#{id}/lock", {}, :ajax => true), :success)
79
+ end
80
+
81
+ # Unlock the room
82
+ def unlock
83
+ verify_response(post("room/#{id}/unlock", {}, :ajax => true), :success)
84
+ end
85
+
86
+ def ping(force = false)
87
+ returning verify_response(post("room/#{id}/tabs", { }, :ajax => true), :success) do
88
+ @idle_since = Time.now
89
+ end if @idle_since < 1.minute.ago || force
90
+ end
91
+
92
+ def destroy
93
+ verify_response(post("account/delete/room/#{id}"), :success)
94
+ end
95
+
96
+ # Post a new message to the chat room
97
+ def speak(message, options = {})
98
+ post_options = {
99
+ :message => message,
100
+ :t => Time.now.to_i
101
+ }.merge(options)
102
+
103
+ post_options.delete(:paste) unless post_options[:paste]
104
+ response = post("room/#{id}/speak", post_options, :ajax => true)
105
+
106
+ if verify_response(response, :success)
107
+ message
108
+ end
109
+ end
110
+
111
+ def paste(message)
112
+ speak message, :paste => true
113
+ end
114
+
115
+ # Get the list of users currently chatting for this room
116
+ def users
117
+ @campfire.users name
118
+ end
119
+
120
+ # Get and array of the messages that have been posted to the room. Each
121
+ # messages is a hash with:
122
+ # * +:person+: the display name of the person that posted the message
123
+ # * +:message+: the body of the message
124
+ # * +:user_id+: Campfire user id
125
+ # * +:id+: Campfire message id
126
+ #
127
+ # room.listen
128
+ # #=> [{:person=>"Brandon", :message=>"I'm getting very sleepy", :user_id=>"148583", :id=>"16434003"}]
129
+ #
130
+ # Called without a block, listen will return an array of messages that have been
131
+ # posted since you joined. listen also takes an optional block, which then polls
132
+ # for new messages every 5 seconds and calls the block for each message.
133
+ #
134
+ # room.listen do |m|
135
+ # room.speak "#{m[:person]}, Go away!" if m[:message] =~ /Java/i
136
+ # end
137
+ #
138
+ def listen(interval = 5)
139
+ join
140
+ if block_given?
141
+ catch(:stop_listening) do
142
+ trap('INT') { throw :stop_listening }
143
+ loop do
144
+ ping
145
+ self.messages.each {|msg| yield msg }
146
+ sleep interval
147
+ end
148
+ end
149
+ else
150
+ self.messages
151
+ end
152
+ end
153
+
154
+ # Get the dates for the available transcripts for this room
155
+ def available_transcripts
156
+ @campfire.available_transcripts(id)
157
+ end
158
+
159
+ # Get the transcript for the given date (Returns a hash in the same format as #listen)
160
+ #
161
+ # room.transcript(room.available_transcripts.first)
162
+ # #=> [{:message=>"foobar!",
163
+ # :user_id=>"99999",
164
+ # :person=>"Brandon",
165
+ # :id=>"18659245",
166
+ # :timestamp=>=>Tue May 05 07:15:00 -0700 2009}]
167
+ #
168
+ # The timestamp slot will typically have a granularity of five minutes.
169
+ #
170
+ def transcript(transcript_date)
171
+ url = "room/#{id}/transcript/#{transcript_date.to_date.strftime('%Y/%m/%d')}"
172
+ date, time = nil, nil
173
+ (Hpricot(get(url).body) / ".message").collect do |message|
174
+ person = (message / '.person span').first
175
+ if !person
176
+ # No span for enter/leave the room messages
177
+ person = (message / '.person').first
178
+ end
179
+ body = (message / '.body div').first
180
+ if d = (message / '.date span').first
181
+ date = d.inner_html
182
+ end
183
+ if t = (message / '.time div').first
184
+ time = t.inner_html
185
+ end
186
+ {:id => message.attributes['id'].scan(/message_(\d+)/).to_s,
187
+ :person => person ? person.inner_html : nil,
188
+ :user_id => message.attributes['class'].scan(/user_(\d+)/).to_s,
189
+ :message => body ? body.inner_html : nil,
190
+ # Use the transcript_date to fill in the correct year
191
+ :timestamp => Time.parse("#{date} #{time}", transcript_date)
192
+ }
193
+ end
194
+ end
195
+
196
+ def upload(filename)
197
+ File.open(filename, "rb") do |file|
198
+ params = Multipart::MultipartPost.new({'upload' => file, 'submit' => "Upload"})
199
+ verify_response post("upload.cgi/room/#{@id}/uploads/new", params.query, :multipart => true), :success
200
+ end
201
+ end
202
+
203
+ # Get the list of latest files for this room
204
+ def files(count = 5)
205
+ join
206
+ (Hpricot(@room.body)/"#file_list li a").to_a[0,count].map do |link|
207
+ @campfire.send :url_for, link.attributes['href'][1..-1], :only_path => false
208
+ end
209
+ end
210
+
211
+ protected
212
+
213
+ def messages
214
+ returning [] do |messages|
215
+ response = post("poll.fcgi", {:l => @last_cache_id, :m => @membership_key,
216
+ :s => @timestamp, :t => "#{Time.now.to_i}000"}, :ajax => true)
217
+ if response.body.length > 1
218
+ lines = response.body.split("\r\n")
219
+
220
+ if lines.length > 0
221
+ @last_cache_id = lines.pop.scan(/chat.poller.lastCacheID = (\d+)/).to_s
222
+ lines.each do |msg|
223
+ unless msg.match(/timestamp_message/)
224
+ if msg.length > 0
225
+ messages << {
226
+ :id => msg.scan(/message_(\d+)/).to_s,
227
+ :user_id => msg.scan(/user_(\d+)/).to_s,
228
+ :person => msg.scan(/\\u003Ctd class=\\"person\\"\\u003E(?:\\u003Cspan\\u003E)?(.+?)(?:\\u003C\/span\\u003E)?\\u003C\/td\\u003E/).to_s,
229
+ :message => msg.scan(/\\u003Ctd class=\\"body\\"\\u003E\\u003Cdiv\\u003E(.+?)\\u003C\/div\\u003E\\u003C\/td\\u003E/).to_s
230
+ }
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
238
+
239
+ [:post, :get, :verify_response].each do |method|
240
+ define_method method do |*args|
241
+ @campfire.send method, *args
242
+ end
243
+ end
244
+
245
+ end
246
+ end
data/site/index.html ADDED
@@ -0,0 +1,101 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
+
4
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5
+ <head>
6
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
7
+ <title>Tinder</title>
8
+ <link rel="stylesheet" type="text/css" href="http://opensoul.org/stylesheets/code.css" />
9
+ <link rel="stylesheet" type="text/css" href="stylesheets/style.css" />
10
+ <link href="http://opensoul.org/stylesheets/ci.css" rel="stylesheet" type="text/css" />
11
+ <script src="http://opensoul.org/javascripts/code_highlighter.js" type="text/javascript"></script>
12
+ <script src="http://opensoul.org/javascripts/ruby.js" type="text/javascript"></script>
13
+ </head>
14
+
15
+ <body>
16
+ <div id="collectiveidea">
17
+ <a href="http://collectiveidea.com"><img src="http://opensoul.org/images/header_logo.gif" alt="Collective Idea" class="logo" width="17" height="22" /></a>
18
+ <ul class="links">
19
+ <li><a href="http://daniel.collectiveidea.com/blog">Daniel</a></li>
20
+ <li><a href="http://opensoul.org">Brandon</a></li>
21
+ <li class="name"><a href="http://collectiveidea.com"><img src="http://opensoul.org/images/header_collectiveidea.gif" alt="Collective Idea" width="123" height="21" /></a></li>
22
+ </ul>
23
+ </div>
24
+ <div id="main">
25
+ <div id="header">
26
+ <h1><a href="/">Tinder</a></h1>
27
+ <p>Getting the campfire started</p>
28
+ <ul id="nav">
29
+ <li><a href="tinder">API Docs</a></li>
30
+ <li><a href="http://rubyforge.org/projects/tinder">RubyForge</a></li>
31
+ <li><a href="http://opensoul.org/tags/tinder">Blog</a></li>
32
+ </ul>
33
+ </div>
34
+ <div id="content">
35
+ <p>Tinder is an API for interfacing with <a href="http://campfirenow.com">Campfire</a>, the 37Signals chat application.</p>
36
+ <h2>Example</h2>
37
+
38
+ <pre><code class="ruby">campfire = Tinder::Campfire.new 'mysubdomain'
39
+ campfire.login 'myemail@example.com', 'mypassword'</code></pre>
40
+
41
+ <h3>Create, find and destroy rooms</h3>
42
+ <pre><code class="ruby">room = campfire.create_room 'New Room', 'My new campfire room to test tinder'
43
+ room = campfire.find_room_by_name 'Other Room'
44
+ room.destroy</code></pre>
45
+
46
+ <h3>Speak and Paste</h3>
47
+ <pre><code class="ruby">room.speak 'Hello world!'
48
+ room.paste File.read(&quot;path/to/your/file.txt&quot;)</code></pre>
49
+
50
+ <h3>Listening</h3>
51
+ <pre><code class="ruby">room.listen
52
+ #=&gt; [{:person=&gt;&quot;Brandon&quot;, :message=&gt;&quot;I'm getting very sleepy&quot;, :user_id=&gt;&quot;148583&quot;, :id=&gt;&quot;16434003&quot;}]
53
+
54
+ # or in block form
55
+ room.listen do |m|
56
+ room.speak 'Welcome!' if m[:message] == /hello/
57
+ end</code></pre>
58
+
59
+ <h3>Guest Access</h3>
60
+ <pre><code class="ruby">room.toggle_guest_access
61
+ room.guest_url #=> http://mysubdomain.campfirenow.com/11111
62
+ room.guest_invite_code #=> 11111</code></pre>
63
+
64
+ <h3>Change the name and topic</h3>
65
+ <pre><code class="ruby">room.name = 'Tinder Demo'
66
+ room.topic = 'Showing how to change the room name and topic with tinder…'</code></pre>
67
+
68
+ <h3>Users</h3>
69
+ <pre><code class="ruby">room.users
70
+ campfire.users # users in all rooms</code></pre>
71
+
72
+ <h3>Transcripts</h3>
73
+ <pre><code class="ruby">transcript = room.transcript(room.available_transcripts.first)
74
+ #=&gt; [{:message=&gt;&quot;foobar!&quot;, :user_id=&gt;&quot;99999&quot;, :person=&gt;&quot;Brandon&quot;, :id=&gt;&quot;18659245&quot;, :timestamp=&gt;Tue May 05 07:15:00 -0700 2009}]
75
+ </code></pre>
76
+
77
+ <p>See the <a href="tinder">API documentation</a> for more details.</p>
78
+
79
+ <h2>Installation</h2>
80
+
81
+ <p>Tinder can be installed as a gem or a Rails plugin. Install the gem by executing:</p>
82
+
83
+ <pre>gem install tinder</pre>
84
+
85
+ <p>Or, download it from <a href="http://rubyforge.org/frs/?group_id=2922">RubyForge</a>.</p>
86
+
87
+ <h2>Source</h2>
88
+
89
+ <p>Contributions are welcome and appreciated! The source is available from:</p>
90
+
91
+ <pre>http://github.com/collectiveidea/tinder</pre>
92
+ </div>
93
+ </div>
94
+ <script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
95
+ </script>
96
+ <script type="text/javascript">
97
+ _uacct = "UA-194397-8";
98
+ urchinTracker();
99
+ </script>
100
+ </body>
101
+ </html>
@@ -0,0 +1,77 @@
1
+ body {
2
+ font-family: "Lucida Grande", Helvetica, Arial, sans-serif;
3
+ font-size: 76%;
4
+ background: #2A2A2A;
5
+ margin: 0;
6
+ padding: 0;
7
+ }
8
+
9
+ #collectiveidea {
10
+ border-bottom: 1px solid #444;
11
+ }
12
+
13
+ a {
14
+ color: #2D5385;
15
+ }
16
+
17
+ #main {
18
+ background-color: #FFF;
19
+ width: 700px;
20
+ margin: 0 auto;
21
+ border: 5px #CCC;
22
+ border-left-style: solid;
23
+ border-right-style: solid;
24
+ padding: 0 1em;
25
+ }
26
+
27
+ #header {
28
+ position: relative;
29
+ border-bottom: 1px solid #999;
30
+ padding: 1em;
31
+ }
32
+
33
+ #header h1 {
34
+ margin: 0;
35
+ padding: 0;
36
+ color: #2D5385;
37
+ }
38
+
39
+ #header h1 a {
40
+ text-decoration: none;
41
+ }
42
+
43
+ #header p {
44
+ margin: 0;
45
+ padding: 0;
46
+ font-size: 0.8em;
47
+ color: #999;
48
+ }
49
+
50
+ #nav {
51
+ list-style: none;
52
+ position: absolute;
53
+ right: 0;
54
+ top: 0.6em;
55
+ }
56
+ #nav li {
57
+ display: inline;
58
+ padding: 0 0.5em;
59
+ }
60
+
61
+ #content {
62
+ padding: 1em 0;
63
+ }
64
+
65
+ dl {
66
+ background-color: #DDD;
67
+ padding: 1em;
68
+ border: 1px solid #CCC;
69
+ }
70
+ dl .pronunciation {
71
+ color: #C00;
72
+ }
73
+ dl .description {
74
+ text-transform: uppercase;
75
+ font-size: 0.8em;
76
+ font-family: fixed;
77
+ }
@@ -0,0 +1,225 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe "Preparing a campfire request" do
4
+ before do
5
+ @campfire = Tinder::Campfire.new("foobar")
6
+ @request = Net::HTTP::Get.new("does_not_matter")
7
+ end
8
+
9
+ def prepare_request
10
+ @campfire.send(:prepare_request, @request)
11
+ end
12
+
13
+ it "should return the request" do
14
+ prepare_request.should equal(@request)
15
+ end
16
+
17
+ it "should set the cookie" do
18
+ @campfire.instance_variable_set("@cookie", "foobar")
19
+ prepare_request['Cookie'].should == 'foobar'
20
+ end
21
+
22
+ it "should set the user agent" do
23
+ prepare_request['User-Agent'].should =~ /^Tinder/
24
+ end
25
+ end
26
+
27
+ # describe "Performing a campfire request" do
28
+ #
29
+ # before do
30
+ # @response = mock("response")
31
+ # Net::HTTP.any_instance.stubs(:request).returns(response)
32
+ # request = Net::HTTP::Get.new("does_not_matter")
33
+ # response.expects(:[]).with('set-cookie').and_return('foobar')
34
+ # @campfire.send(:perform_request) { request }
35
+ # end
36
+ #
37
+ # it "should set cookie" do
38
+ # @campfire.instance_variable_get("@cookie").should == 'foobar'
39
+ # end
40
+ #
41
+ # end
42
+
43
+ describe "Verifying a 200 response" do
44
+
45
+ before do
46
+ @campfire = Tinder::Campfire.new("foobar")
47
+ @response = mock("response")
48
+ @response.should_receive(:code).and_return(200)
49
+ end
50
+
51
+ it "should return true when expecting success" do
52
+ @campfire.send(:verify_response, @response, :success).should equal(true)
53
+ end
54
+
55
+ it "should return false when expecting a redirect" do
56
+ @campfire.send(:verify_response, @response, :redirect).should equal(false)
57
+ end
58
+
59
+ it "should return false when expecting a redirect to a specific path" do
60
+ @campfire.send(:verify_response, @response, :redirect_to => '/foobar').should equal(false)
61
+ end
62
+
63
+ end
64
+
65
+ describe "Verifying a 302 response" do
66
+
67
+ before do
68
+ @campfire = Tinder::Campfire.new("foobar")
69
+ @response = mock("response")
70
+ @response.should_receive(:code).and_return(302)
71
+ end
72
+
73
+ it "should return true when expecting redirect" do
74
+ @campfire.send(:verify_response, @response, :redirect).should equal(true)
75
+ end
76
+
77
+ it "should return false when expecting success" do
78
+ @campfire.send(:verify_response, @response, :success).should equal(false)
79
+ end
80
+
81
+ it "should return true when expecting a redirect to a specific path" do
82
+ @response.should_receive(:[]).with('location').and_return("/foobar")
83
+ @campfire.send(:verify_response, @response, :redirect_to => '/foobar').should equal(true)
84
+ end
85
+
86
+ it "should return false when redirecting to a different path than expected" do
87
+ @response.should_receive(:[]).with('location').and_return("/baz")
88
+ @campfire.send(:verify_response, @response, :redirect_to => '/foobar').should equal(false)
89
+ end
90
+
91
+ end
92
+
93
+ describe "A failed login" do
94
+
95
+ before do
96
+ @campfire = Tinder::Campfire.new 'foobar'
97
+ @response = mock("response")
98
+ @campfire.should_receive(:post).and_return(@response)
99
+ @response.should_receive(:code).and_return("302")
100
+ @response.should_receive(:[]).with("location").and_return("/login")
101
+ end
102
+
103
+ it "should raise an error" do
104
+ lambda do
105
+ @campfire.login "doesn't", "matter"
106
+ end.should raise_error(Tinder::Error)
107
+ end
108
+
109
+ it "should not set logged in status" do
110
+ @campfire.login 'foo', 'bar' rescue
111
+ @campfire.logged_in?.should equal(false)
112
+ end
113
+
114
+ end
115
+
116
+ describe "Accessing a room with guest access" do
117
+
118
+ before do
119
+ @room_id = 123
120
+ @campfire = Tinder::Campfire.new 'foobar'
121
+ @response = mock("response")
122
+ @campfire.stub!(:post).and_return(@response)
123
+ end
124
+
125
+ it "should return a room for the public room" do
126
+ @response.should_receive(:code).and_return(302)
127
+ @response.should_receive(:[]).with("location").and_return("/rooms/#{@room_id}")
128
+
129
+ room = @campfire.find_room_by_guest_hash "valid_hash", "John Doe"
130
+ room.should be_kind_of(Tinder::Room)
131
+ end
132
+
133
+ it "should raise an error if given an invalid room hash" do
134
+ @response.should_receive(:code).and_return(500)
135
+
136
+ room = @campfire.find_room_by_guest_hash "invalid_hash", "John Doe"
137
+ room.should be_nil
138
+ end
139
+
140
+ end
141
+
142
+ describe "Accessing a room" do
143
+
144
+ before do
145
+ @request = mock("request")
146
+ @response = mock("response")
147
+ Net::HTTP.stub!(:new).and_return(@request)
148
+ @request.stub!(:use_ssl=)
149
+ @request.stub!(:request).and_return(@response)
150
+ @response.stub!(:[]).and_return(true)
151
+ end
152
+
153
+ describe "when the room is full" do
154
+
155
+ before do
156
+ @html = File.read(File.dirname(__FILE__) + '/html/full_lobby.html')
157
+ @response.stub!(:body).and_return(@html)
158
+ @campfire = Tinder::Campfire.new 'foobar'
159
+ end
160
+
161
+ it "should return a room" do
162
+ @campfire.rooms.should_not be_empty
163
+ end
164
+
165
+ it "should find a room by name" do
166
+ @campfire.find_room_by_name("Just Fishin").class.should == Tinder::Room
167
+ end
168
+
169
+ end
170
+
171
+ describe "when the room is not full" do
172
+
173
+ before do
174
+ @html = File.read(File.dirname(__FILE__) + '/html/normal_lobby.html')
175
+ @response.stub!(:body).and_return(@html)
176
+ @campfire = Tinder::Campfire.new 'foobar'
177
+ end
178
+
179
+ it "should return a room" do
180
+ @campfire.rooms.should_not be_empty
181
+ end
182
+
183
+ it "should find a room by name" do
184
+ @campfire.find_room_by_name("Just Fishin").class.should == Tinder::Room
185
+ end
186
+ end
187
+
188
+ end
189
+
190
+ describe "Accessing a room's transcript" do
191
+
192
+ before do
193
+ @room = Tinder::Room.new nil, 42
194
+ @html = File.read(File.dirname(__FILE__) + '/html/transcript.html')
195
+ @response = mock("response")
196
+ @response.stub!(:body).and_return(@html)
197
+ @room.stub!(:get).with("room/42/transcript/2009/05/05").
198
+ and_return(@response)
199
+ require 'time'
200
+ @transcript = @room.transcript(Time.parse("2009-05-05"))
201
+ end
202
+
203
+ it "should return some messages" do
204
+ @transcript.should_not be_empty
205
+ end
206
+
207
+ describe "the first message" do
208
+ # This is a timestamp message
209
+ it "should include a timestamp" do
210
+ @transcript.first[:timestamp].should == Time.parse("2009-05-05 09:35")
211
+ end
212
+ end
213
+
214
+ describe "the second message" do
215
+ it "should include a timestamp" do
216
+ @transcript.second[:timestamp].should == Time.parse("2009-05-05 09:35")
217
+ end
218
+ end
219
+
220
+ describe "when entering the room" do
221
+ it "a transcript message should include the person who entered" do
222
+ @transcript.second[:person].should == "Marcel"
223
+ end
224
+ end
225
+ end