matsimitsu-tinder 1.3.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.
@@ -0,0 +1,3 @@
1
+ pkg
2
+ rdoc
3
+ test/remote/credentials.rb
@@ -0,0 +1,39 @@
1
+ 1.2.1 - 2009-08-27
2
+ * Fixes for listening after campfire updates [Jordan Byron]
3
+
4
+ 1.2.0 - 2009-01-28
5
+ * Get the list of available files [Christopher MacGown]
6
+ * Upload files [Joshua Wand]
7
+ * Find rooms even when full [Josh Owens]
8
+ * Join rooms as a guest [Ian Lesperance]
9
+
10
+ 1.1.7 - 2008-07-24
11
+ * Don't join the room when only speaking [Brian Donovan]
12
+ * Added support for HTTP proxies
13
+ * Fix listening for messages that contain URLs [Jared Kuolt]
14
+
15
+ 0.1.6 - 2008-03-07
16
+ * Added Room#topic for getting the current topic [Even Weaver]
17
+ * Trap INT in #listen(&block) [borrowed from Chris Shea's Pyre]
18
+
19
+ 0.1.5 - 2008-01-25
20
+ * Fixed Room#listen, which was broken by latest Campfire deploy
21
+ * Fixed timeout when listening but not speaking that will eventually log you out [Clinton R. Nixon]
22
+
23
+ 0.1.4 - 2007-07-23
24
+ * Support for transcripts
25
+ * Fixed Room#leave, which was broken by a Campfire deployment [Andy Smith]
26
+
27
+ 0.1.3 - 2007-02-12
28
+ * added ssl support [Tero Parviainen]
29
+
30
+ 0.1.2 - 2007-01-27
31
+ * fixed bug preventing #listen from working without a block
32
+
33
+ 0.1.1 - 2007-01-27
34
+ * fix bug preventing speak from working
35
+ * incorporated "watching" from http://soylentfoo.jnewland.com/articles/2006/12/07/updates-to-marshmallow-the-campfire-bot
36
+
37
+ 0.1.0 - 2007-01-23
38
+ * Initial release as gem
39
+ * Get the users in a room [Tero Parviainen]
@@ -0,0 +1,10 @@
1
+ CHANGELOG.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ init.rb
6
+ lib/tinder.rb
7
+ lib/tinder/campfire.rb
8
+ lib/tinder/multipart.rb
9
+ lib/tinder/room.rb
10
+ lib/tinder/version.rb
@@ -0,0 +1,54 @@
1
+ = Tinder - get the Campfire started
2
+
3
+
4
+ This branch is a rewrite of Tinder to use the official Campfire API. The API is intended to be backwards compatible so consumers can easily migrate off the HTML API.
5
+
6
+ -- Joshua Peek (Programmer, 37signals)
7
+
8
+
9
+ 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.
10
+
11
+ == Usage
12
+
13
+ campfire = Tinder::Campfire.new 'mysubdomain'
14
+ campfire.login 'my_token'
15
+
16
+ room = campfire.create_room 'New Room', 'My new campfire room to test tinder'
17
+ room.rename 'New Room Name'
18
+ room.speak 'Hello world!'
19
+ room.paste "my pasted\ncode"
20
+ room.destroy
21
+
22
+ room = campfire.find_room_by_guest_hash 'abc123', 'John Doe'
23
+ room.speak 'Hello world!'
24
+
25
+ See the RDoc for more details.
26
+
27
+ == Installation
28
+
29
+ Tinder can be installed as a gem or a Rails plugin:
30
+
31
+ gem install tinder
32
+
33
+ script/plugin install git://github.com/collectiveidea/tinder.git
34
+
35
+ == How to contribute
36
+
37
+ If you find what looks like a bug:
38
+
39
+ 1. Check the GitHub issue tracker to see if anyone else has had the same issue.
40
+ http://github.com/collectiveidea/tinder/issues/
41
+ 2. If you don't see anything, create an issue with information on how to reproduce it.
42
+
43
+ If you want to contribute an enhancement or a fix:
44
+
45
+ 1. Fork the project on github.
46
+ http://github.com/collectiveidea/tinder
47
+ 2. Make your changes with tests.
48
+ 3. Commit the changes without making changes to the Rakefile, VERSION, or any other files that aren't related to your enhancement or fix
49
+ 4. Send a pull request.
50
+
51
+ == ToDo
52
+
53
+ * Tests! (unit and remote)
54
+ * Marshmallow-style integration scripts for exception notification and continuous integration
@@ -0,0 +1,65 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gem|
4
+ gem.name = "matsimitsu-tinder"
5
+ gem.summary = "An (unofficial) Campfire API"
6
+ gem.description = "An API for interfacing with Campfire, the 37Signals chat application."
7
+ gem.authors = ['Brandon Keepers']
8
+ gem.email = 'brandon@opensoul.org'
9
+ gem.homepage = 'http://github.com/collectiveidea/tinder'
10
+ gem.rubyforge_project = "tinder"
11
+ gem.add_dependency "activesupport"
12
+ gem.add_dependency "httparty"
13
+ gem.add_dependency "mime-types"
14
+ gem.add_development_dependency "rspec"
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
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
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ if File.exist?('VERSION')
46
+ version = File.read('VERSION')
47
+ else
48
+ version = ""
49
+ end
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "tinder #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
56
+
57
+ require 'spec/rake/spectask'
58
+ desc "Run the specs under spec"
59
+ Spec::Rake::SpecTask.new do |t|
60
+ t.spec_opts = ['--options', "spec/spec.opts"]
61
+ t.spec_files = FileList['spec/**/*_spec.rb']
62
+ end
63
+
64
+ desc "Run tests"
65
+ task :default => [:spec, :test]
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.3.1
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'lib/tinder'
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'net/https'
6
+ require 'open-uri'
7
+
8
+ require 'tinder/connection'
9
+ require 'tinder/multipart'
10
+ require 'tinder/campfire'
11
+ require 'tinder/room'
12
+
13
+ module Tinder
14
+ class Error < StandardError; end
15
+ class SSLRequiredError < Error; end
16
+ end
@@ -0,0 +1,106 @@
1
+ module Tinder
2
+
3
+ # == Usage
4
+ #
5
+ # campfire = Tinder::Campfire.new 'mysubdomain'
6
+ # campfire.login 'my_token'
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
+ HOST = "campfirenow.com"
16
+
17
+ attr_reader :connection, :subdomain, :uri
18
+
19
+ # Create a new connection to the campfire account with the given +subdomain+.
20
+ #
21
+ # == Options:
22
+ # * +:ssl+: use SSL for the connection, which is required if you have a Campfire SSL account.
23
+ # Defaults to false
24
+ # * +:proxy+: a proxy URI. (e.g. :proxy => 'http://user:pass@example.com:8000')
25
+ #
26
+ # c = Tinder::Campfire.new("mysubdomain", :ssl => true)
27
+ 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 token
44
+ def login(token)
45
+ connection.basic_auth(token, 'x')
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
57
+ end
58
+
59
+ # Get an array of all the available rooms
60
+ # TODO: detect rooms that are full (no link)
61
+ def rooms
62
+ connection.get('/rooms.json')['rooms'].map do |room|
63
+ Room.new(self, room)
64
+ end
65
+ end
66
+
67
+ # Find a campfire room by name
68
+ def find_room_by_name(name)
69
+ rooms.detect { |room| room.name == name }
70
+ end
71
+
72
+ # Find a campfire room by its guest hash
73
+ def find_room_by_guest_hash(hash, name)
74
+ rooms.detect { |room| room.guest_invite_code == hash }
75
+ end
76
+
77
+ # Creates and returns a new Room with the given +name+ and optionally a +topic+
78
+ def create_room(name, topic = nil)
79
+ connection.post('/rooms.json', :body => { :room => { :name => name, :topic => topic } }.to_json)
80
+ find_room_by_name(name)
81
+ end
82
+
83
+ def find_or_create_room_by_name(name)
84
+ find_room_by_name(name) || create_room(name)
85
+ end
86
+
87
+ # 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
99
+ end
100
+
101
+ # Is the connection to campfire using ssl?
102
+ def ssl?
103
+ uri.scheme == 'https'
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,21 @@
1
+ require 'httparty'
2
+
3
+ module Tinder
4
+ class Connection
5
+ def initialize
6
+ class << self
7
+ include HTTParty
8
+
9
+ headers 'Content-Type' => 'application/json'
10
+ end
11
+ end
12
+
13
+ def metaclass
14
+ class << self; self; end
15
+ end
16
+
17
+ def method_missing(*args, &block)
18
+ metaclass.send(*args, &block)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,63 @@
1
+ require 'mime/types'
2
+ require 'net/http'
3
+ require 'cgi'
4
+
5
+ module Multipart #:nodoc:
6
+ # From: http://deftcode.com/code/flickr_upload/multipartpost.rb
7
+ ## Helper class to prepare an HTTP POST request with a file upload
8
+ ## Mostly taken from
9
+ #http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/113774
10
+ ### WAS:
11
+ ## Anything that's broken and wrong probably the fault of Bill Stilwell
12
+ ##(bill@marginalia.org)
13
+ ### NOW:
14
+ ## Everything wrong is due to keith@oreilly.com
15
+
16
+ class Param #:nodoc:
17
+ attr_accessor :k, :v
18
+ def initialize(k, v)
19
+ @k = k
20
+ @v = v
21
+ end
22
+
23
+ def to_multipart
24
+ "Content-Disposition: form-data; name=\"#{k}\"\r\n\r\n#{v}\r\n"
25
+ end
26
+ end
27
+
28
+ class FileParam #:nodoc:
29
+ attr_accessor :k, :filename, :content
30
+ def initialize(k, filename, content)
31
+ @k = k
32
+ @filename = filename
33
+ @content = content
34
+ end
35
+
36
+ def to_multipart
37
+ "Content-Disposition: form-data; name=\"#{k}\"; filename=\"#{filename}\"\r\n" +
38
+ "Content-Transfer-Encoding: binary\r\n" +
39
+ "Content-Type: #{MIME::Types.type_for(@filename)}\r\n\r\n" +
40
+ @content + "\r\n"
41
+ end
42
+ end
43
+
44
+ class MultipartPost #:nodoc:
45
+ BOUNDARY = 'campfire-is-awesome'
46
+ HEADER = {"Content-type" => "multipart/form-data, boundary=" + BOUNDARY + " "}
47
+ TIMEOUT_SECONDS = 30
48
+
49
+ attr_accessor :params, :query, :headers
50
+ def initialize(params)
51
+ @params = params
52
+ @query = {}
53
+ self.prepare_query
54
+ end
55
+
56
+ def prepare_query()
57
+ @query = @params.map do |k,v|
58
+ param = v.respond_to?(:read) ? FileParam.new(k, v.path, v.read) : Param.new(k, v)
59
+ "--#{BOUNDARY}\r\n#{param.to_multipart}"
60
+ end.join("") + "--#{BOUNDARY}--"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,205 @@
1
+ module Tinder
2
+ # A campfire room
3
+ class Room
4
+ attr_reader :id, :name
5
+
6
+ def initialize(campfire, attributes = {})
7
+ @campfire = campfire
8
+ @id = attributes['id']
9
+ @name = attributes['name']
10
+ @loaded = false
11
+ end
12
+
13
+ # Join the room. Pass +true+ to join even if you've already joined.
14
+ def join(force = false)
15
+ post 'join'
16
+ end
17
+
18
+ # Leave a room
19
+ def leave
20
+ post 'leave'
21
+ end
22
+
23
+ # Toggle guest access on or off
24
+ def toggle_guest_access
25
+ raise NotImplementedError
26
+ end
27
+
28
+ # Get the url for guest access
29
+ def guest_url
30
+ if guest_access_enabled?
31
+ "http://#{@campfire.subdomain}.campfirenow.com/#{guest_invite_code}"
32
+ else
33
+ nil
34
+ end
35
+ end
36
+
37
+ def guest_access_enabled?
38
+ load
39
+ @open_to_guests ? true : false
40
+ end
41
+
42
+ # The invite code use for guest
43
+ def guest_invite_code
44
+ load
45
+ @active_token_value
46
+ end
47
+
48
+ # Change the name of the room
49
+ def name=(name)
50
+ connection.post("/room/#{@id}.json", :body => { :room => { :name => name } })
51
+ end
52
+ alias_method :rename, :name=
53
+
54
+ # Change the topic
55
+ def topic=(topic)
56
+ connection.post("/room/#{@id}.json", :body => { :room => { :topic => name } })
57
+ end
58
+
59
+ # Get the current topic
60
+ def topic
61
+ load
62
+ @topic
63
+ end
64
+
65
+ # Lock the room to prevent new users from entering and to disable logging
66
+ def lock
67
+ post :lock
68
+ end
69
+
70
+ # Unlock the room
71
+ 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
81
+ end
82
+
83
+ # Post a new message to the chat room
84
+ def speak(message, options = {})
85
+ send_message(message)
86
+ end
87
+
88
+ def paste(message)
89
+ send_message(message, 'PasteMessage')
90
+ end
91
+
92
+ # Get the list of users currently chatting for this room
93
+ def users
94
+ reload!
95
+ @users
96
+ end
97
+
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"}]
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.
111
+ #
112
+ # room.listen do |m|
113
+ # room.speak "#{m[:person]}, Go away!" if m[:message] =~ /Java/i
114
+ # 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'] }
125
+ end
126
+ end
127
+
128
+ # Get the dates for the available transcripts for this room
129
+ def available_transcripts
130
+ raise NotImplementedError
131
+ end
132
+
133
+ # Get the transcript for the given date (Returns a hash in the same format as #listen)
134
+ #
135
+ # room.transcript(room.available_transcripts.first)
136
+ # #=> [{:message=>"foobar!",
137
+ # :user_id=>"99999",
138
+ # :person=>"Brandon",
139
+ # :id=>"18659245",
140
+ # :timestamp=>=>Tue May 05 07:15:00 -0700 2009}]
141
+ #
142
+ # The timestamp slot will typically have a granularity of five minutes.
143
+ #
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']) }
151
+ end
152
+ end
153
+
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)
158
+ end
159
+ end
160
+
161
+ # Get the list of latest files for this room
162
+ def files(count = 5)
163
+ connection.get(room_url_for(:uploads))['uploads'].map { |u| u['full_url'] }
164
+ end
165
+
166
+ protected
167
+ def load
168
+ reload! unless @loaded
169
+ end
170
+
171
+ def reload!
172
+ attributes = connection.get("/room/#{@id}.json")['room']
173
+
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'] }
181
+
182
+ @loaded = true
183
+ end
184
+
185
+ def send_message(message, type = 'Textmessage')
186
+ post 'speak', :body => {:message => {:body => message, :type => type}}.to_json
187
+ end
188
+
189
+ def get(action, options = {})
190
+ connection.get(room_url_for(action), options)
191
+ end
192
+
193
+ def post(action, options = {})
194
+ connection.post(room_url_for(action), options)
195
+ end
196
+
197
+ def room_url_for(action)
198
+ "/room/#{@id}/#{action}.json"
199
+ end
200
+
201
+ def connection
202
+ @campfire.connection
203
+ end
204
+ end
205
+ end