tinder 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,24 @@
1
+ 1.4 - 2010-05-11
2
+ * Remove methods no longer supported by API
3
+ Campfire#available_transcripts, Room#ping, Room#destroy, Room#toggle_guest_access
4
+ * Added Room#play
5
+ * ActiveSupport 3.0 support
6
+ * Fix streaming API support
7
+ * Allow SSL for listening
8
+ * Add support for HTTP proxies [c13bcc0b]
9
+
10
+ 1.3.1 - 2009-12-17
11
+ * Declare HTTParty dependency
12
+ * Fix Room#paste
13
+
14
+ 1.3.0 - 2009-12-15
15
+ * Rewrite to use Official Campfire API
16
+
17
+ 1.2.2 - 2009-09-12
18
+ * Work around CSRF protection bug
19
+ * Fixes for changes to Campfire markup
20
+ * Include timestamps in the transcript
21
+
1
22
  1.2.1 - 2009-08-27
2
23
  * Fixes for listening after campfire updates [Jordan Byron]
3
24
 
@@ -0,0 +1,39 @@
1
+ # Tinder - get the Campfire started
2
+
3
+ Tinder is a library for interfacing with Campfire, the chat application from 37Signals, allowing you to programmatically manage and speak/listen in chat rooms. As of December 2009, thanks to initial work from Joshua Peek at 37signals, it now makes use of the official Campfire API (described at: http://developer.37signals.com/campfire/).
4
+
5
+ ## Usage
6
+
7
+ campfire = Tinder::Campfire.new 'mysubdomain', :token => '546884b3d8fee4d80665g561caf7h9f3ea7b999e'
8
+ # or you can still use username/password and Tinder will look up your token
9
+ # campfire = Tinder::Campfire.new 'mysubdomain', :username => 'user', :password => 'pass'
10
+
11
+ room = campfire.rooms.first
12
+ room.rename 'New Room Names'
13
+ room.speak 'Hello world!'
14
+ room.paste "my pasted\ncode"
15
+
16
+ room = campfire.find_room_by_guest_hash 'abc123', 'John Doe'
17
+ room.speak 'Hello world!'
18
+
19
+ See the RDoc for more details.
20
+
21
+ ## Installation
22
+
23
+ gem install tinder
24
+
25
+ ## How to contribute
26
+
27
+ If you find what looks like a bug:
28
+
29
+ 1. Check the GitHub issue tracker to see if anyone else has had the same issue.
30
+ http://github.com/collectiveidea/tinder/issues/
31
+ 2. If you don't see anything, create an issue with information on how to reproduce it.
32
+
33
+ If you want to contribute an enhancement or a fix:
34
+
35
+ 1. Fork the project on github.
36
+ http://github.com/collectiveidea/tinder
37
+ 2. Make your changes with tests.
38
+ 3. Commit the changes without making changes to the Rakefile, VERSION, or any other files that aren't related to your enhancement or fix
39
+ 4. Send a pull request.
data/Rakefile CHANGED
@@ -2,8 +2,8 @@ begin
2
2
  require 'jeweler'
3
3
  Jeweler::Tasks.new do |gem|
4
4
  gem.name = "tinder"
5
- gem.summary = "An (unofficial) Campfire API"
6
- gem.description = "An API for interfacing with Campfire, the 37Signals chat application."
5
+ gem.summary = "Ruby wrapper for the Campfire API"
6
+ gem.description = "A Ruby API for interfacing with Campfire, the 37Signals chat application."
7
7
  gem.authors = ['Brandon Keepers']
8
8
  gem.email = 'brandon@opensoul.org'
9
9
  gem.homepage = 'http://github.com/collectiveidea/tinder'
@@ -11,35 +11,16 @@ begin
11
11
  gem.add_dependency "activesupport"
12
12
  gem.add_dependency "httparty"
13
13
  gem.add_dependency "mime-types"
14
+ gem.add_dependency "twitter-stream"
15
+ gem.add_dependency "eventmachine"
14
16
  gem.add_development_dependency "rspec"
17
+ gem.add_development_dependency "fakeweb"
15
18
  end
16
19
  Jeweler::GemcutterTasks.new
17
20
  rescue LoadError
18
21
  puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
22
  end
20
23
 
21
- require 'rake/testtask'
22
- Rake::TestTask.new(:test) do |test|
23
- test.libs << 'lib' << 'test'
24
- test.pattern = 'test/**/*_test.rb'
25
- test.verbose = true
26
- end
27
-
28
- begin
29
- require 'rcov/rcovtask'
30
- Rcov::RcovTask.new do |test|
31
- test.libs << 'test'
32
- test.pattern = 'test/**/*_test.rb'
33
- test.verbose = true
34
- end
35
- rescue LoadError
36
- task :rcov do
37
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
- end
39
- end
40
-
41
- task :test => :check_dependencies
42
-
43
24
  require 'rake/rdoctask'
44
25
  Rake::RDocTask.new do |rdoc|
45
26
  if File.exist?('VERSION')
@@ -60,6 +41,11 @@ Spec::Rake::SpecTask.new do |t|
60
41
  t.spec_opts = ['--options', "spec/spec.opts"]
61
42
  t.spec_files = FileList['spec/**/*_spec.rb']
62
43
  end
44
+ task :spec => :check_dependencies
63
45
 
64
- desc "Run tests"
65
- task :default => [:spec, :test]
46
+ task :default do
47
+ %w(2.3.5 3.0.0.beta3).each do |version|
48
+ puts "Running specs with Rails #{version}"
49
+ system("RAILS_VERSION=#{version} rake -s spec;")
50
+ end
51
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.1
1
+ 1.4.0
@@ -1,9 +1,5 @@
1
- require 'rubygems'
2
1
  require 'active_support'
3
- require 'uri'
4
- require 'net/http'
5
- require 'net/https'
6
- require 'open-uri'
2
+ require 'active_support/json'
7
3
 
8
4
  require 'tinder/connection'
9
5
  require 'tinder/multipart'
@@ -13,4 +9,6 @@ require 'tinder/room'
13
9
  module Tinder
14
10
  class Error < StandardError; end
15
11
  class SSLRequiredError < Error; end
12
+ class AuthenticationFailed < Error; end
13
+ class ListenFailed < Error; end
16
14
  end
@@ -2,8 +2,7 @@ module Tinder
2
2
 
3
3
  # == Usage
4
4
  #
5
- # campfire = Tinder::Campfire.new 'mysubdomain'
6
- # campfire.login 'myemail@example.com', 'mypassword'
5
+ # campfire = Tinder::Campfire.new 'mysubdomain', :token => 'xyz'
7
6
  #
8
7
  # room = campfire.create_room 'New Room', 'My new campfire room to test tinder'
9
8
  # room.speak 'Hello world!'
@@ -12,9 +11,7 @@ module Tinder
12
11
  # room = campfire.find_room_by_guest_hash 'abc123', 'John Doe'
13
12
  # room.speak 'Hello world!'
14
13
  class Campfire
15
- HOST = "campfirenow.com"
16
-
17
- attr_reader :connection, :subdomain, :uri
14
+ attr_reader :connection
18
15
 
19
16
  # Create a new connection to the campfire account with the given +subdomain+.
20
17
  #
@@ -25,42 +22,14 @@ module Tinder
25
22
  #
26
23
  # c = Tinder::Campfire.new("mysubdomain", :ssl => true)
27
24
  def initialize(subdomain, options = {})
28
- options = { :ssl => false }.merge(options)
29
- @connection = Connection.new
30
- @cookie = nil
31
- @subdomain = subdomain
32
- @uri = URI.parse("#{options[:ssl] ? 'https' : 'http' }://#{subdomain}.#{HOST}")
33
- connection.base_uri @uri.to_s
34
- if options[:proxy]
35
- uri = URI.parse(options[:proxy])
36
- @http = Net::HTTP::Proxy(uri.host, uri.port, uri.user, uri.password)
37
- else
38
- @http = Net::HTTP
39
- end
40
- @logged_in = false
41
- end
42
-
43
- # Log in to campfire using your +email+ and +password+
44
- def login(username, password)
45
- connection.basic_auth(username, password)
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
- connection.default_options.delete(:basic_auth)
56
- @logged_in = false
25
+ @connection = Connection.new(subdomain, options)
57
26
  end
58
27
 
59
28
  # Get an array of all the available rooms
60
29
  # TODO: detect rooms that are full (no link)
61
30
  def rooms
62
31
  connection.get('/rooms.json')['rooms'].map do |room|
63
- Room.new(self, room)
32
+ Room.new(connection, room)
64
33
  end
65
34
  end
66
35
 
@@ -85,22 +54,13 @@ module Tinder
85
54
  end
86
55
 
87
56
  # List the users that are currently chatting in any room
88
- def users(*room_names)
89
- rooms.map(&:users).flatten.compact.uniq.sort
90
- end
91
-
92
- # Get the dates of the available transcripts by room
93
- #
94
- # campfire.available_transcripts
95
- # #=> {"15840" => [#<Date: 4908311/2,0,2299161>, #<Date: 4908285/2,0,2299161>]}
96
- #
97
- def available_transcripts(room = nil)
98
- raise NotImplementedError
57
+ def users
58
+ rooms.map(&:users).flatten.compact.uniq.sort_by {|u| u[:name]}
99
59
  end
100
60
 
101
- # Is the connection to campfire using ssl?
102
- def ssl?
103
- uri.scheme == 'https'
61
+ # get the user info of the current user
62
+ def me
63
+ connection.get("/users/me.json")["user"]
104
64
  end
105
65
  end
106
66
  end
@@ -1,13 +1,61 @@
1
1
  require 'httparty'
2
+ require 'active_support/core_ext/hash/indifferent_access'
3
+
4
+ # override HTTParty's json parser to return a HashWithIndifferentAccess
5
+ module HTTParty
6
+ class Parser
7
+ protected
8
+ def json
9
+ result = Crack::JSON.parse(body)
10
+ if result.is_a?(Hash)
11
+ result = HashWithIndifferentAccess.new(result)
12
+ end
13
+ result
14
+ end
15
+ end
16
+ end
2
17
 
3
18
  module Tinder
4
19
  class Connection
5
- def initialize
20
+ HOST = "campfirenow.com"
21
+
22
+ attr_reader :subdomain, :uri, :options
23
+
24
+ def initialize(subdomain, options = {})
25
+ @subdomain = subdomain
26
+ @options = { :ssl => false, :proxy => ENV['HTTP_PROXY'] }.merge(options)
27
+ @uri = URI.parse("#{@options[:ssl] ? 'https' : 'http' }://#{subdomain}.#{HOST}")
28
+ @token = options[:token]
29
+
30
+
6
31
  class << self
7
32
  include HTTParty
8
-
33
+ extend HTTPartyExtensions
34
+
9
35
  headers 'Content-Type' => 'application/json'
10
36
  end
37
+
38
+ if @options[:proxy]
39
+ proxy_uri = URI.parse(@options[:proxy])
40
+ http_proxy proxy_uri.host, proxy_uri.port
41
+ end
42
+ base_uri @uri.to_s
43
+ basic_auth token, 'X'
44
+ end
45
+
46
+ module HTTPartyExtensions
47
+ def perform_request(http_method, path, options) #:nodoc:
48
+ response = super
49
+ raise AuthenticationFailed if response.code == 401
50
+ response
51
+ end
52
+ end
53
+
54
+ def token
55
+ @token ||= begin
56
+ self.basic_auth(options[:username], options[:password])
57
+ self.get('/users/me.json')['user']['api_auth_token']
58
+ end
11
59
  end
12
60
 
13
61
  def metaclass
@@ -17,5 +65,11 @@ module Tinder
17
65
  def method_missing(*args, &block)
18
66
  metaclass.send(*args, &block)
19
67
  end
68
+
69
+ # Is the connection to campfire using ssl?
70
+ def ssl?
71
+ uri.scheme == 'https'
72
+ end
73
+
20
74
  end
21
75
  end
@@ -3,8 +3,8 @@ module Tinder
3
3
  class Room
4
4
  attr_reader :id, :name
5
5
 
6
- def initialize(campfire, attributes = {})
7
- @campfire = campfire
6
+ def initialize(connection, attributes = {})
7
+ @connection = connection
8
8
  @id = attributes['id']
9
9
  @name = attributes['name']
10
10
  @loaded = false
@@ -20,18 +20,9 @@ module Tinder
20
20
  post 'leave'
21
21
  end
22
22
 
23
- # Toggle guest access on or off
24
- def toggle_guest_access
25
- raise NotImplementedError
26
- end
27
-
28
23
  # Get the url for guest access
29
24
  def guest_url
30
- if guest_access_enabled?
31
- "http://#{@campfire.subdomain}.campfirenow.com/#{guest_invite_code}"
32
- else
33
- nil
34
- end
25
+ "#{@connection.uri}/#{guest_invite_code}" if guest_access_enabled?
35
26
  end
36
27
 
37
28
  def guest_access_enabled?
@@ -47,13 +38,17 @@ module Tinder
47
38
 
48
39
  # Change the name of the room
49
40
  def name=(name)
50
- connection.post("/room/#{@id}.json", :body => { :room => { :name => name } })
41
+ update :name => name
51
42
  end
52
43
  alias_method :rename, :name=
53
44
 
54
45
  # Change the topic
55
46
  def topic=(topic)
56
- connection.post("/room/#{@id}.json", :body => { :room => { :topic => name } })
47
+ update :topic => topic
48
+ end
49
+
50
+ def update(attrs)
51
+ connection.put("/room/#{@id}.json", :body => {:room => attrs}.to_json)
57
52
  end
58
53
 
59
54
  # Get the current topic
@@ -72,14 +67,6 @@ module Tinder
72
67
  post :unlock
73
68
  end
74
69
 
75
- def ping(force = false)
76
- raise NotImplementedError
77
- end
78
-
79
- def destroy
80
- raise NotImplementedError
81
- end
82
-
83
70
  # Post a new message to the chat room
84
71
  def speak(message, options = {})
85
72
  send_message(message)
@@ -89,47 +76,75 @@ module Tinder
89
76
  send_message(message, 'PasteMessage')
90
77
  end
91
78
 
79
+ def play(sound)
80
+ send_message(sound, 'SoundMessage')
81
+ end
82
+
92
83
  # Get the list of users currently chatting for this room
93
84
  def users
94
85
  reload!
95
86
  @users
96
87
  end
97
88
 
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
89
+ # return the user with the given id; if it isn't in our room cache, do a request to get it
90
+ def user(id)
91
+ if id
92
+ user = users.detect {|u| u[:id] == id }
93
+ unless user
94
+ user_data = connection.get("/users/#{id}.json")
95
+ user = user_data && user_data[:user]
96
+ end
97
+ user[:created_at] = Time.parse(user[:created_at])
98
+ user
99
+ end
100
+ end
101
+
102
+ # Listen for new messages in the room, yielding them to the provided block as they arrive.
103
+ # Each message is a hash with:
104
+ # * +:body+: the body of the message
105
+ # * +:user+: Campfire user, which is itself a hash, of:
106
+ # * +:id+: User id
107
+ # * +:name+: User name
108
+ # * +:email_address+: Email address
109
+ # * +:admin+: Boolean admin flag
110
+ # * +:created_at+: User creation timestamp
111
+ # * +:type+: User type (e.g. Member)
103
112
  # * +:id+: Campfire message id
104
- #
105
- # room.listen
106
- # #=> [{:person=>"Brandon", :message=>"I'm getting very sleepy", :user_id=>"148583", :id=>"16434003"}]
107
- #
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.
113
+ # * +:type+: Campfire message type
114
+ # * +:room_id+: Campfire room id
115
+ # * +:created_at+: Message creation timestamp
111
116
  #
112
117
  # room.listen do |m|
113
- # room.speak "#{m[:person]}, Go away!" if m[:message] =~ /Java/i
118
+ # room.speak "Go away!" if m[:body] =~ /Java/i
114
119
  # end
115
- #
116
- def listen(interval = 5)
117
- require 'yajl/http_stream'
120
+ def listen(options = {})
121
+ raise "no block provided" unless block_given?
122
+
123
+ join # you have to be in the room to listen
118
124
 
125
+ require 'twitter/json_stream'
126
+
119
127
  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'] }
128
+ options = {
129
+ :host => "streaming.#{Connection::HOST}",
130
+ :path => room_url_for(:live),
131
+ :auth => "#{auth[:username]}:#{auth[:password]}",
132
+ :timeout => 6,
133
+ :ssl => connection.options[:ssl]
134
+ }.merge(options)
135
+ EventMachine::run do
136
+ stream = Twitter::JSONStream.connect(options)
137
+ stream.each_item do |message|
138
+ message = HashWithIndifferentAccess.new(JSON.parse(message))
139
+ message[:user] = user(message.delete(:user_id))
140
+ message[:created_at] = Time.parse(message[:created_at])
141
+ yield(message)
142
+ end
143
+ # if we really get disconnected
144
+ raise ListenFailed.new("got disconnected from #{@name}!") if !EventMachine.reactor_running?
125
145
  end
126
146
  end
127
147
 
128
- # Get the dates for the available transcripts for this room
129
- def available_transcripts
130
- raise NotImplementedError
131
- end
132
-
133
148
  # Get the transcript for the given date (Returns a hash in the same format as #listen)
134
149
  #
135
150
  # room.transcript(room.available_transcripts.first)
@@ -154,13 +169,13 @@ module Tinder
154
169
  def upload(filename)
155
170
  File.open(filename, "rb") do |file|
156
171
  params = Multipart::MultipartPost.new('upload' => file)
157
- connection.post("/room/#{@id}/uploads.json", :body => params.query)
172
+ post(:uploads, :body => params.query)
158
173
  end
159
174
  end
160
175
 
161
176
  # Get the list of latest files for this room
162
177
  def files(count = 5)
163
- connection.get(room_url_for(:uploads))['uploads'].map { |u| u['full_url'] }
178
+ get(:uploads)['uploads'].map { |u| u['full_url'] }
164
179
  end
165
180
 
166
181
  protected
@@ -175,14 +190,14 @@ module Tinder
175
190
  @name = attributes['name']
176
191
  @topic = attributes['topic']
177
192
  @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'] }
193
+ @open_to_guests = attributes['open_to_guests']
194
+ @active_token_value = attributes['active_token_value']
195
+ @users = attributes['users']
181
196
 
182
197
  @loaded = true
183
198
  end
184
199
 
185
- def send_message(message, type = 'Textmessage')
200
+ def send_message(message, type = 'TextMessage')
186
201
  post 'speak', :body => {:message => {:body => message, :type => type}}.to_json
187
202
  end
188
203
 
@@ -199,7 +214,7 @@ module Tinder
199
214
  end
200
215
 
201
216
  def connection
202
- @campfire.connection
217
+ @connection
203
218
  end
204
219
  end
205
220
  end