tinder 1.3.1 → 1.4.0

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.
@@ -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