timriley-tinder 1.1.9
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/CHANGELOG.txt +30 -0
- data/Manifest.txt +9 -0
- data/README.txt +45 -0
- data/Rakefile +24 -0
- data/init.rb +1 -0
- data/lib/tinder.rb +15 -0
- data/lib/tinder/campfire.rb +211 -0
- data/lib/tinder/mechanize_ext.rb +17 -0
- data/lib/tinder/room.rb +206 -0
- data/lib/tinder/version.rb +9 -0
- data/spec/campfire_spec.rb +140 -0
- data/spec/spec_helper.rb +3 -0
- data/test/remote/remote_campfire_test.rb +58 -0
- data/test/test_helper.rb +7 -0
- metadata +112 -0
data/CHANGELOG.txt
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
0.1.7 - 2008-07-24
|
|
2
|
+
* Don't join the room when only speaking [Brian Donovan]
|
|
3
|
+
* Added support for HTTP proxies
|
|
4
|
+
* Fix listening for messages that contain URLs [Jared Kuolt]
|
|
5
|
+
|
|
6
|
+
0.1.6 - 2008-03-07
|
|
7
|
+
* Added Room#topic for getting the current topic [Even Weaver]
|
|
8
|
+
* Trap INT in #listen(&block) [borrowed from Chris Shea's Pyre]
|
|
9
|
+
|
|
10
|
+
0.1.5 - 2008-01-25
|
|
11
|
+
* Fixed Room#listen, which was broken by latest Campfire deploy
|
|
12
|
+
* Fixed timeout when listening but not speaking that will eventually log you out [Clinton R. Nixon]
|
|
13
|
+
|
|
14
|
+
0.1.4 - 2007-07-23
|
|
15
|
+
* Support for transcripts
|
|
16
|
+
* Fixed Room#leave, which was broken by a Campfire deployment [Andy Smith]
|
|
17
|
+
|
|
18
|
+
0.1.3 - 2007-02-12
|
|
19
|
+
* added ssl support [Tero Parviainen]
|
|
20
|
+
|
|
21
|
+
0.1.2 - 2007-01-27
|
|
22
|
+
* fixed bug preventing #listen from working without a block
|
|
23
|
+
|
|
24
|
+
0.1.1 - 2007-01-27
|
|
25
|
+
* fix bug preventing speak from working
|
|
26
|
+
* incorporated "watching" from http://soylentfoo.jnewland.com/articles/2006/12/07/updates-to-marshmallow-the-campfire-bot
|
|
27
|
+
|
|
28
|
+
0.1.0 - 2007-01-23
|
|
29
|
+
* Initial release as gem
|
|
30
|
+
* Get the users in a room [Tero Parviainen]
|
data/Manifest.txt
ADDED
data/README.txt
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
= Tinder - get the Campfire started
|
|
2
|
+
|
|
3
|
+
This is a fork of tinder that provides file upload support, currently using the mechanize gem.
|
|
4
|
+
|
|
5
|
+
Tinder is a library for interfacing with Campfire, the chat application from 37Signals. Unlike Marshmallow, it is designed to be a full-featured API (since 37Signals doesn't provide a real one), allowing you to programatically manage and speak/listen in chat rooms.
|
|
6
|
+
|
|
7
|
+
== Usage
|
|
8
|
+
|
|
9
|
+
campfire = Campfire.new 'mysubdomain'
|
|
10
|
+
campfire.login 'myemail@example.com', 'mypassword'
|
|
11
|
+
|
|
12
|
+
room = campfire.create_room 'New Room', 'My new campfire room to test tinder'
|
|
13
|
+
room.rename 'New Room Name'
|
|
14
|
+
room.speak 'Hello world!'
|
|
15
|
+
room.paste "my pasted\ncode"
|
|
16
|
+
room.destroy
|
|
17
|
+
|
|
18
|
+
room = campfire.find_room_by_guest_hash 'abc123', 'John Doe'
|
|
19
|
+
room.speak 'Hello world!'
|
|
20
|
+
|
|
21
|
+
See the RDoc for more details.
|
|
22
|
+
|
|
23
|
+
== Requirements
|
|
24
|
+
|
|
25
|
+
* Active Support
|
|
26
|
+
gem install activesupport
|
|
27
|
+
* Hpricot
|
|
28
|
+
gem install hpricot
|
|
29
|
+
|
|
30
|
+
== Installation
|
|
31
|
+
|
|
32
|
+
Tinder can be installed as a gem or a Rails plugin:
|
|
33
|
+
|
|
34
|
+
gem install tinder
|
|
35
|
+
|
|
36
|
+
script/plugin install http://source.collectiveidea.com/public/tinder/trunk
|
|
37
|
+
|
|
38
|
+
== Development
|
|
39
|
+
|
|
40
|
+
The source for Tinder is available at http://source.collectiveidea.com/public/tinder/trunk. Development can be followed at http://opensoul.org/tags/tinder. Contributions are welcome!
|
|
41
|
+
|
|
42
|
+
== ToDo
|
|
43
|
+
|
|
44
|
+
* Tests! (unit and remote)
|
|
45
|
+
* Marshmallow-style integration scripts for exception notification and continuous integration
|
data/Rakefile
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'hoe'
|
|
3
|
+
require File.join(File.dirname(__FILE__), 'lib', 'tinder', 'version')
|
|
4
|
+
|
|
5
|
+
# RDOC_OPTS = ['--quiet', '--title', "Tinder",
|
|
6
|
+
# "--opname", "index.html",
|
|
7
|
+
# "--line-numbers",
|
|
8
|
+
# "--main", "README",
|
|
9
|
+
# "--inline-source"]
|
|
10
|
+
#
|
|
11
|
+
# Generate all the Rake tasks
|
|
12
|
+
|
|
13
|
+
hoe = Hoe.new('tinder', ENV['VERSION'] || Tinder::VERSION::STRING) do |p|
|
|
14
|
+
p.rubyforge_name = 'tinder'
|
|
15
|
+
p.summary = "An (unofficial) Campfire API"
|
|
16
|
+
p.description = "An API for interfacing with Campfire, the 37Signals chat application."
|
|
17
|
+
p.author = 'Brandon Keepers'
|
|
18
|
+
p.email = 'brandon@opensoul.org'
|
|
19
|
+
p.url = 'http://tinder.rubyforge.org'
|
|
20
|
+
p.test_globs = ["test/**/*_test.rb"]
|
|
21
|
+
p.changes = p.paragraphs_of('CHANGELOG.txt', 0..1).join("\n\n")
|
|
22
|
+
p.extra_deps << ['activesupport']
|
|
23
|
+
p.extra_deps << ['hpricot']
|
|
24
|
+
end
|
data/init.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'tinder'
|
data/lib/tinder.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'active_support'
|
|
3
|
+
require 'uri'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'net/https'
|
|
6
|
+
require 'open-uri'
|
|
7
|
+
require 'hpricot'
|
|
8
|
+
require 'mechanize'
|
|
9
|
+
|
|
10
|
+
Dir[File.join(File.dirname(__FILE__), 'tinder/**/*.rb')].sort.each { |lib| require lib }
|
|
11
|
+
|
|
12
|
+
module Tinder
|
|
13
|
+
class Error < StandardError; end
|
|
14
|
+
class SSLRequiredError < Error; end
|
|
15
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
module Tinder
|
|
2
|
+
|
|
3
|
+
# == Usage
|
|
4
|
+
#
|
|
5
|
+
# campfire = Tinder::Campfire.new 'mysubdomain'
|
|
6
|
+
# campfire.login 'myemail@example.com', 'mypassword'
|
|
7
|
+
#
|
|
8
|
+
# room = campfire.create_room 'New Room', 'My new campfire room to test tinder'
|
|
9
|
+
# room.speak 'Hello world!'
|
|
10
|
+
# room.destroy
|
|
11
|
+
#
|
|
12
|
+
# room = campfire.find_room_by_guest_hash 'abc123', 'John Doe'
|
|
13
|
+
# room.speak 'Hello world!'
|
|
14
|
+
class Campfire
|
|
15
|
+
attr_reader :subdomain, :uri
|
|
16
|
+
|
|
17
|
+
# Create a new connection to the campfire account with the given +subdomain+.
|
|
18
|
+
#
|
|
19
|
+
# == Options:
|
|
20
|
+
# * +:ssl+: use SSL for the connection, which is required if you have a Campfire SSL account.
|
|
21
|
+
# Defaults to false
|
|
22
|
+
# * +:proxy+: a proxy URI. (e.g. :proxy => 'http://user:pass@example.com:8000')
|
|
23
|
+
#
|
|
24
|
+
# c = Tinder::Campfire.new("mysubdomain", :ssl => true)
|
|
25
|
+
def initialize(subdomain, options = {})
|
|
26
|
+
options = { :ssl => false }.merge(options)
|
|
27
|
+
@cookie = nil
|
|
28
|
+
@subdomain = subdomain
|
|
29
|
+
@uri = URI.parse("#{options[:ssl] ? 'https' : 'http' }://#{subdomain}.campfirenow.com")
|
|
30
|
+
if options[:proxy]
|
|
31
|
+
uri = URI.parse(options[:proxy])
|
|
32
|
+
@http = Net::HTTP::Proxy(uri.host, uri.port, uri.user, uri.password)
|
|
33
|
+
else
|
|
34
|
+
@http = Net::HTTP
|
|
35
|
+
end
|
|
36
|
+
@logged_in = false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Log in to campfire using your +email+ and +password+
|
|
40
|
+
def login(email, password)
|
|
41
|
+
unless verify_response(post("login", :email_address => email, :password => password), :redirect_to => url_for(:only_path => false))
|
|
42
|
+
raise Error, "Campfire login failed"
|
|
43
|
+
end
|
|
44
|
+
# ensure that SSL is set if required on this account
|
|
45
|
+
raise SSLRequiredError, "Your account requires SSL" unless verify_response(get, :success)
|
|
46
|
+
@logged_in = true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns true when successfully logged in
|
|
50
|
+
def logged_in?
|
|
51
|
+
@logged_in == true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def logout
|
|
55
|
+
returning verify_response(get("logout"), :redirect) do |result|
|
|
56
|
+
@logged_in = !result
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Get an array of all the available rooms
|
|
61
|
+
# TODO: detect rooms that are full (no link)
|
|
62
|
+
def rooms
|
|
63
|
+
Hpricot(get.body).search("//h2/a").collect do |a|
|
|
64
|
+
Room.new(self, room_id_from_url(a.attributes['href']), a.inner_html)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Find a campfire room by name
|
|
69
|
+
def find_room_by_name(name)
|
|
70
|
+
rooms.detect {|room| room.name == name }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Find a campfire room by its guest hash
|
|
74
|
+
def find_room_by_guest_hash(hash, name)
|
|
75
|
+
res = post(hash, :name => name)
|
|
76
|
+
|
|
77
|
+
Room.new(self, room_id_from_url(res['location'])) if verify_response(res, :redirect)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Creates and returns a new Room with the given +name+ and optionally a +topic+
|
|
81
|
+
def create_room(name, topic = nil)
|
|
82
|
+
find_room_by_name(name) if verify_response(post("account/create/room?from=lobby", {:room => {:name => name, :topic => topic}}, :ajax => true), :success)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def find_or_create_room_by_name(name)
|
|
86
|
+
find_room_by_name(name) || create_room(name)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# List the users that are currently chatting in any room
|
|
90
|
+
def users(*room_names)
|
|
91
|
+
users = Hpricot(get.body).search("div.room").collect do |room|
|
|
92
|
+
if room_names.empty? || room_names.include?((room/"h2/a").inner_html)
|
|
93
|
+
room.search("//li.user").collect { |user| user.inner_html }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
users.flatten.compact.uniq.sort
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Get the dates of the available transcripts by room
|
|
100
|
+
#
|
|
101
|
+
# campfire.available_transcripts
|
|
102
|
+
# #=> {"15840" => [#<Date: 4908311/2,0,2299161>, #<Date: 4908285/2,0,2299161>]}
|
|
103
|
+
#
|
|
104
|
+
def available_transcripts(room = nil)
|
|
105
|
+
url = "files%2Btranscripts"
|
|
106
|
+
url += "?room_id#{room}" if room
|
|
107
|
+
transcripts = (Hpricot(get(url).body) / ".transcript").inject({}) do |result,transcript|
|
|
108
|
+
link = (transcript / "a").first.attributes['href']
|
|
109
|
+
(result[room_id_from_url(link)] ||= []) << Date.parse(link.scan(/\/transcript\/(\d{4}\/\d{2}\/\d{2})/).to_s)
|
|
110
|
+
result
|
|
111
|
+
end
|
|
112
|
+
room ? transcripts[room.to_s] : transcripts
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Is the connection to campfire using ssl?
|
|
116
|
+
def ssl?
|
|
117
|
+
uri.scheme == 'https'
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def room_id_from_url(url)
|
|
123
|
+
url.scan(/room\/(\d*)/).to_s
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def url_for(*args)
|
|
127
|
+
options = {:only_path => true}.merge(args.last.is_a?(Hash) ? args.pop : {})
|
|
128
|
+
path = args.shift
|
|
129
|
+
"#{options[:only_path] ? '' : uri}/#{path}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def post(path, data = {}, options = {})
|
|
133
|
+
perform_request(options) do
|
|
134
|
+
returning Net::HTTP::Post.new(url_for(path)) do |request|
|
|
135
|
+
request.add_field 'Content-Type', 'application/x-www-form-urlencoded'
|
|
136
|
+
request.set_form_data flatten(data)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def post_file(path, file_path, options = {})
|
|
142
|
+
agent = WWW::Mechanize.new
|
|
143
|
+
|
|
144
|
+
agent.additional_headers = { 'User-Agent' => "Tinder/#{Tinder::VERSION::STRING} (http://tinder.rubyforge.org)" }
|
|
145
|
+
agent.additional_headers.merge!({ 'Cookie' => @cookie }) if @cookie
|
|
146
|
+
|
|
147
|
+
node = Hpricot::Elem.new(Hpricot::STag.new('form'))
|
|
148
|
+
node['action'] = url_for(path, :only_path => false)
|
|
149
|
+
node['method'] = 'POST'
|
|
150
|
+
node['enctype'] = 'multipart/form-data'
|
|
151
|
+
|
|
152
|
+
form = WWW::Mechanize::Form.new(node)
|
|
153
|
+
form.file_uploads << WWW::Mechanize::Form::FileUpload.new('upload', file_path)
|
|
154
|
+
|
|
155
|
+
page = agent.submit(form)
|
|
156
|
+
return page.response
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def get(path = nil, options = {})
|
|
160
|
+
perform_request(options) { Net::HTTP::Get.new(url_for(path)) }
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def prepare_request(request, options = {})
|
|
164
|
+
returning request do
|
|
165
|
+
request.add_field 'User-Agent', "Tinder/#{Tinder::VERSION::STRING} (http://tinder.rubyforge.org)"
|
|
166
|
+
request.add_field 'Cookie', @cookie if @cookie
|
|
167
|
+
if options[:ajax]
|
|
168
|
+
request.add_field 'X-Requested-With', 'XMLHttpRequest'
|
|
169
|
+
request.add_field 'X-Prototype-Version', '1.5.1.1'
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def perform_request(options = {}, &block)
|
|
175
|
+
@request = prepare_request(yield, options)
|
|
176
|
+
http = @http.new(uri.host, uri.port)
|
|
177
|
+
http.use_ssl = ssl?
|
|
178
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if ssl?
|
|
179
|
+
@response = returning http.request(@request) do |response|
|
|
180
|
+
@cookie = response['set-cookie'] if response['set-cookie']
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# flatten a nested hash (:room => {:name => 'foobar'} to 'user[name]' => 'foobar')
|
|
185
|
+
def flatten(params)
|
|
186
|
+
params = params.dup
|
|
187
|
+
params.stringify_keys!.each do |k,v|
|
|
188
|
+
if v.is_a? Hash
|
|
189
|
+
params.delete(k)
|
|
190
|
+
v.each {|subk,v| params["#{k}[#{subk}]"] = v }
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def verify_response(response, options = {})
|
|
196
|
+
if options.is_a?(Symbol)
|
|
197
|
+
codes = case options
|
|
198
|
+
when :success; [200]
|
|
199
|
+
when :redirect; 300..399
|
|
200
|
+
else raise(ArgumentError, "Unknown response #{options}")
|
|
201
|
+
end
|
|
202
|
+
codes.include?(response.code.to_i)
|
|
203
|
+
elsif options[:redirect_to]
|
|
204
|
+
verify_response(response, :redirect) && response['location'] == options[:redirect_to]
|
|
205
|
+
else
|
|
206
|
+
false
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# from http://d.hatena.ne.jp/kitamomonga/20080720/ruby_mechanize_tadd_addtitional_header
|
|
2
|
+
|
|
3
|
+
module WWW
|
|
4
|
+
class Mechanize
|
|
5
|
+
attr_accessor :additional_headers
|
|
6
|
+
alias :_set_headers :set_headers
|
|
7
|
+
def set_headers(uri, request, cur_page)
|
|
8
|
+
request = _set_headers(uri, request, cur_page)
|
|
9
|
+
if @additional_headers then
|
|
10
|
+
@additional_headers.each do |k, v|
|
|
11
|
+
(v ? request[k] = v : request.delete(k))
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
request
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/tinder/room.rb
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
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\": \"([a-z0-9]+)\"/).to_s
|
|
17
|
+
@user_id = room.body.scan(/\"userID\": (\d+)/).to_s
|
|
18
|
+
@last_cache_id = room.body.scan(/\"lastCacheID\": (\d+)/).to_s
|
|
19
|
+
@timestamp = room.body.scan(/\"timestamp\": (\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
|
+
message if verify_response(post("room/#{id}/speak", {:message => message,
|
|
99
|
+
:t => Time.now.to_i}.merge(options), :ajax => true), :success)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def paste(message)
|
|
103
|
+
speak message, :paste => true
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def upload(file_path)
|
|
107
|
+
post_file("upload.cgi/room/#{id}/uploads/new", file_path)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Get the list of users currently chatting for this room
|
|
111
|
+
def users
|
|
112
|
+
@campfire.users name
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Get and array of the messages that have been posted to the room. Each
|
|
116
|
+
# messages is a hash with:
|
|
117
|
+
# * +:person+: the display name of the person that posted the message
|
|
118
|
+
# * +:message+: the body of the message
|
|
119
|
+
# * +:user_id+: Campfire user id
|
|
120
|
+
# * +:id+: Campfire message id
|
|
121
|
+
#
|
|
122
|
+
# room.listen
|
|
123
|
+
# #=> [{:person=>"Brandon", :message=>"I'm getting very sleepy", :user_id=>"148583", :id=>"16434003"}]
|
|
124
|
+
#
|
|
125
|
+
# Called without a block, listen will return an array of messages that have been
|
|
126
|
+
# posted since you joined. listen also takes an optional block, which then polls
|
|
127
|
+
# for new messages every 5 seconds and calls the block for each message.
|
|
128
|
+
#
|
|
129
|
+
# room.listen do |m|
|
|
130
|
+
# room.speak "#{m[:person]}, Go away!" if m[:message] =~ /Java/i
|
|
131
|
+
# end
|
|
132
|
+
#
|
|
133
|
+
def listen(interval = 5)
|
|
134
|
+
join
|
|
135
|
+
if block_given?
|
|
136
|
+
catch(:stop_listening) do
|
|
137
|
+
trap('INT') { throw :stop_listening }
|
|
138
|
+
loop do
|
|
139
|
+
ping
|
|
140
|
+
self.messages.each {|msg| yield msg }
|
|
141
|
+
sleep interval
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
else
|
|
145
|
+
self.messages
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Get the dates for the available transcripts for this room
|
|
150
|
+
def available_transcripts
|
|
151
|
+
@campfire.available_transcripts(id)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Get the transcript for the given date (Returns a hash in the same format as #listen)
|
|
155
|
+
#
|
|
156
|
+
# room.transcript(room.available_transcripts.first)
|
|
157
|
+
# #=> [{:message=>"foobar!", :user_id=>"99999", :person=>"Brandon", :id=>"18659245"}]
|
|
158
|
+
#
|
|
159
|
+
def transcript(date)
|
|
160
|
+
(Hpricot(get("room/#{id}/transcript/#{date.to_date.strftime('%Y/%m/%d')}").body) / ".message").collect do |message|
|
|
161
|
+
person = (message / '.person span').first
|
|
162
|
+
body = (message / '.body div').first
|
|
163
|
+
{:id => message.attributes['id'].scan(/message_(\d+)/).to_s,
|
|
164
|
+
:person => person ? person.inner_html : nil,
|
|
165
|
+
:user_id => message.attributes['class'].scan(/user_(\d+)/).to_s,
|
|
166
|
+
:message => body ? body.inner_html : nil
|
|
167
|
+
}
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
protected
|
|
172
|
+
|
|
173
|
+
def messages
|
|
174
|
+
returning [] do |messages|
|
|
175
|
+
response = post("poll.fcgi", {:l => @last_cache_id, :m => @membership_key,
|
|
176
|
+
:s => @timestamp, :t => "#{Time.now.to_i}000"}, :ajax => true)
|
|
177
|
+
if response.body.length > 1
|
|
178
|
+
lines = response.body.split("\r\n")
|
|
179
|
+
|
|
180
|
+
if lines.length > 0
|
|
181
|
+
@last_cache_id = lines.pop.scan(/chat.poller.lastCacheID = (\d+)/).to_s
|
|
182
|
+
lines.each do |msg|
|
|
183
|
+
unless msg.match(/timestamp_message/)
|
|
184
|
+
if msg.length > 0
|
|
185
|
+
messages << {
|
|
186
|
+
:id => msg.scan(/message_(\d+)/).to_s,
|
|
187
|
+
:user_id => msg.scan(/user_(\d+)/).to_s,
|
|
188
|
+
:person => msg.scan(/\\u003Ctd class=\\"person\\"\\u003E(?:\\u003Cspan\\u003E)?(.+?)(?:\\u003C\/span\\u003E)?\\u003C\/td\\u003E/).to_s,
|
|
189
|
+
:message => msg.scan(/\\u003Ctd class=\\"body\\"\\u003E\\u003Cdiv\\u003E(.+?)\\u003C\/div\\u003E\\u003C\/td\\u003E/).to_s
|
|
190
|
+
}
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
[:post, :post_file, :get, :verify_response].each do |method|
|
|
200
|
+
define_method method do |*args|
|
|
201
|
+
@campfire.send method, *args
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
|
2
|
+
|
|
3
|
+
context "Preparing a campfire request" do
|
|
4
|
+
setup 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
|
+
specify "should return the request" do
|
|
14
|
+
prepare_request.should equal(@request)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
specify "should set the cookie" do
|
|
18
|
+
@campfire.instance_variable_set("@cookie", "foobar")
|
|
19
|
+
prepare_request['Cookie'].should == 'foobar'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
specify "should set the user agent" do
|
|
23
|
+
prepare_request['User-Agent'].should =~ /^Tinder/
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# context "Performing a campfire request" do
|
|
28
|
+
#
|
|
29
|
+
# setup 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
|
+
# specify "should set cookie" do
|
|
38
|
+
# @campfire.instance_variable_get("@cookie").should == 'foobar'
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# end
|
|
42
|
+
|
|
43
|
+
context "Verifying a 200 response" do
|
|
44
|
+
|
|
45
|
+
setup do
|
|
46
|
+
@campfire = Tinder::Campfire.new("foobar")
|
|
47
|
+
@response.should_receive(:code).and_return(200)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
specify "should return true when expecting success" do
|
|
51
|
+
@campfire.send(:verify_response, @response, :success).should equal(true)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
specify "should return false when expecting a redirect" do
|
|
55
|
+
@campfire.send(:verify_response, @response, :redirect).should equal(false)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
specify "should return false when expecting a redirect to a specific path" do
|
|
59
|
+
@campfire.send(:verify_response, @response, :redirect_to => '/foobar').should equal(false)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
context "Verifying a 302 response" do
|
|
65
|
+
|
|
66
|
+
setup do
|
|
67
|
+
@campfire = Tinder::Campfire.new("foobar")
|
|
68
|
+
@response.should_receive(:code).and_return(302)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
specify "should return true when expecting redirect" do
|
|
72
|
+
@campfire.send(:verify_response, @response, :redirect).should equal(true)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
specify "should return false when expecting success" do
|
|
76
|
+
@campfire.send(:verify_response, @response, :success).should equal(false)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
specify "should return true when expecting a redirect to a specific path" do
|
|
80
|
+
@response.should_receive(:[]).with('location').and_return("/foobar")
|
|
81
|
+
@campfire.send(:verify_response, @response, :redirect_to => '/foobar').should equal(true)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
specify "should return false when redirecting to a different path than expected" do
|
|
85
|
+
@response.should_receive(:[]).with('location').and_return("/baz")
|
|
86
|
+
@campfire.send(:verify_response, @response, :redirect_to => '/foobar').should equal(false)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
context "A failed login" do
|
|
92
|
+
|
|
93
|
+
setup do
|
|
94
|
+
@campfire = Tinder::Campfire.new 'foobar'
|
|
95
|
+
@response = mock("response")
|
|
96
|
+
@campfire.should_receive(:post).and_return(@response)
|
|
97
|
+
@response.should_receive(:code).and_return("302")
|
|
98
|
+
@response.should_receive(:[]).with("location").and_return("/login")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
specify "should raise an error" do
|
|
102
|
+
lambda do
|
|
103
|
+
@campfire.login "doesn't", "matter"
|
|
104
|
+
end.should raise_error(Tinder::Error)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
specify "should not set logged in status" do
|
|
108
|
+
@campfire.login 'foo', 'bar' rescue
|
|
109
|
+
@campfire.logged_in?.should equal(false)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
context "Accessing a room with guest access" do
|
|
115
|
+
|
|
116
|
+
setup do
|
|
117
|
+
@room_id = 123
|
|
118
|
+
@campfire = Tinder::Campfire.new 'foobar'
|
|
119
|
+
@response = mock("response")
|
|
120
|
+
@campfire.stub!(:post).and_return(@response)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
specify "should return a room for the public room" do
|
|
124
|
+
@response.should_receive(:code).and_return(302)
|
|
125
|
+
@response.should_receive(:[]).with("location").and_return("/rooms/#{@room_id}")
|
|
126
|
+
|
|
127
|
+
room = @campfire.find_room_by_guest_hash "valid_hash", "John Doe"
|
|
128
|
+
room.should be_kind_of(Tinder::Room)
|
|
129
|
+
room.campfire.should == @campfire
|
|
130
|
+
room.id.to_i.should == @room_id
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
specify "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
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
|
2
|
+
|
|
3
|
+
class RemoteCampfireTest < Test::Unit::TestCase
|
|
4
|
+
|
|
5
|
+
def setup
|
|
6
|
+
# @subdomain = 'domain'
|
|
7
|
+
# @user, @pass = 'email@example.com', 'password'
|
|
8
|
+
@ssl = false
|
|
9
|
+
raise "Set your campfire credentials before running the remote tests" unless @user && @pass && @subdomain
|
|
10
|
+
@campfire = Tinder::Campfire.new @subdomain, :ssl => @ssl
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_ssl_required
|
|
14
|
+
if @ssl
|
|
15
|
+
campfire = Tinder::Campfire.new @subdomain
|
|
16
|
+
assert_raises(Tinder::SSLRequiredError) do
|
|
17
|
+
campfire.login(@user, @pass)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_create_and_delete_room
|
|
23
|
+
assert login
|
|
24
|
+
assert @campfire.logged_in?
|
|
25
|
+
|
|
26
|
+
room = @campfire.create_room("Testing#{Time.now.to_i}")
|
|
27
|
+
|
|
28
|
+
assert_instance_of Tinder::Room, room
|
|
29
|
+
assert_not_nil room.id
|
|
30
|
+
|
|
31
|
+
room.name = "new name"
|
|
32
|
+
assert_equal "new name", room.name
|
|
33
|
+
|
|
34
|
+
room.destroy
|
|
35
|
+
assert_nil @campfire.find_room_by_name(room.name)
|
|
36
|
+
|
|
37
|
+
assert @campfire.logout
|
|
38
|
+
ensure
|
|
39
|
+
room.destroy rescue nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_failed_login
|
|
43
|
+
assert_raises(Tinder::Error) { @campfire.login(@user, 'notmypassword') }
|
|
44
|
+
assert !@campfire.logged_in?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_find_nonexistent_room
|
|
48
|
+
login
|
|
49
|
+
assert_nil @campfire.find_room_by_name('No Room Should Have This Name')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def login(user = @user, pass = @pass)
|
|
55
|
+
@campfire.login(user, pass)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: timriley-tinder
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.1.9
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Brandon Keepers
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2008-10-16 00:00:00 -07:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: mechanize
|
|
17
|
+
version_requirement:
|
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
19
|
+
requirements:
|
|
20
|
+
- - ">"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: 0.0.0
|
|
23
|
+
version:
|
|
24
|
+
- !ruby/object:Gem::Dependency
|
|
25
|
+
name: activesupport
|
|
26
|
+
version_requirement:
|
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
28
|
+
requirements:
|
|
29
|
+
- - ">"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: 0.0.0
|
|
32
|
+
version:
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: hpricot
|
|
35
|
+
version_requirement:
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 0.0.0
|
|
41
|
+
version:
|
|
42
|
+
- !ruby/object:Gem::Dependency
|
|
43
|
+
name: mechanize
|
|
44
|
+
version_requirement:
|
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">"
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: 0.0.0
|
|
50
|
+
version:
|
|
51
|
+
- !ruby/object:Gem::Dependency
|
|
52
|
+
name: hoe
|
|
53
|
+
version_requirement:
|
|
54
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">"
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: 1.7.0
|
|
59
|
+
version:
|
|
60
|
+
description: This is a fork of Tinder with file upload support.
|
|
61
|
+
email: brandon@opensoul.org
|
|
62
|
+
executables: []
|
|
63
|
+
|
|
64
|
+
extensions: []
|
|
65
|
+
|
|
66
|
+
extra_rdoc_files:
|
|
67
|
+
- CHANGELOG.txt
|
|
68
|
+
- Manifest.txt
|
|
69
|
+
- README.txt
|
|
70
|
+
files:
|
|
71
|
+
- CHANGELOG.txt
|
|
72
|
+
- Manifest.txt
|
|
73
|
+
- README.txt
|
|
74
|
+
- Rakefile
|
|
75
|
+
- init.rb
|
|
76
|
+
- lib/tinder.rb
|
|
77
|
+
- lib/tinder/campfire.rb
|
|
78
|
+
- lib/tinder/room.rb
|
|
79
|
+
- lib/tinder/version.rb
|
|
80
|
+
- lib/tinder/mechanize_ext.rb
|
|
81
|
+
has_rdoc: true
|
|
82
|
+
homepage: http://github.com/timriley/tinder
|
|
83
|
+
post_install_message:
|
|
84
|
+
rdoc_options:
|
|
85
|
+
- --main
|
|
86
|
+
- README.txt
|
|
87
|
+
require_paths:
|
|
88
|
+
- lib
|
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: "0"
|
|
94
|
+
version:
|
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: "0"
|
|
100
|
+
version:
|
|
101
|
+
requirements: []
|
|
102
|
+
|
|
103
|
+
rubyforge_project:
|
|
104
|
+
rubygems_version: 1.2.0
|
|
105
|
+
signing_key:
|
|
106
|
+
specification_version: 2
|
|
107
|
+
summary: An API for interfacing with Campfire, the 37Signals chat application.
|
|
108
|
+
test_files:
|
|
109
|
+
- test/test_helper.rb
|
|
110
|
+
- test/remote/remote_campfire_test.rb
|
|
111
|
+
- spec/campfire_spec.rb
|
|
112
|
+
- spec/spec_helper.rb
|