matsimitsu-tinder 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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