├── .gemtest ├── init.rb ├── .rspec ├── .gitignore ├── lib ├── tinder │ ├── version.rb │ ├── campfire.rb │ ├── connection.rb │ └── room.rb ├── faraday │ └── response │ │ ├── remove_whitespace.rb │ │ └── raise_on_authentication_failure.rb └── tinder.rb ├── Gemfile ├── Rakefile ├── .travis.yml ├── spec ├── fixtures │ ├── users │ │ └── me.json │ ├── rooms.json │ └── rooms │ │ ├── recent.json │ │ ├── room80749.json │ │ ├── room80751.json │ │ └── show.json ├── spec_helper.rb └── tinder │ ├── campfire_spec.rb │ ├── connection_spec.rb │ └── room_spec.rb ├── MIT-LICENSE ├── tinder.gemspec ├── site ├── stylesheets │ └── style.css └── index.html ├── README.markdown └── CHANGELOG.txt /.gemtest: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | require 'tinder' 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format=nested 3 | --backtrace 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | .rvmrc 6 | -------------------------------------------------------------------------------- /lib/tinder/version.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | module Tinder 3 | VERSION = '1.9.2' unless defined?(::Tinder::VERSION) 4 | end 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | platforms :jruby do 4 | gem 'jruby-openssl', '~> 0.7' 5 | end 6 | 7 | gemspec 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | 4 | require 'rspec/core/rake_task' 5 | desc 'Run the specs' 6 | RSpec::Core::RakeTask.new 7 | 8 | task :default => :spec 9 | task :test => :spec 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - rbx-19mode 4 | - rbx-18mode 5 | - jruby-19mode 6 | - jruby-18mode 7 | - 1.8.7 8 | - 1.9.2 9 | - 1.9.3 10 | only: 11 | - master 12 | notifications: 13 | disabled: true 14 | -------------------------------------------------------------------------------- /lib/faraday/response/remove_whitespace.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'faraday' 3 | 4 | module Faraday 5 | class Response::RemoveWhitespace < Response::Middleware 6 | def parse(body) 7 | body =~ /^\s+$/ ? "" : body 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/faraday/response/raise_on_authentication_failure.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'faraday' 3 | 4 | module Faraday 5 | class Response::RaiseOnAuthenticationFailure < Response::Middleware 6 | def on_complete(response) 7 | raise Tinder::AuthenticationFailed if [401, 404].include?(response[:status]) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/fixtures/users/me.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "email_address": "user@example.com", 4 | "type": "Member", 5 | "created_at": "2006/04/06 00:38:28 +0000", 6 | "admin": true, 7 | "id": 12345, 8 | "name": "John Doe", 9 | "api_auth_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spec/fixtures/rooms.json: -------------------------------------------------------------------------------- 1 | {"rooms": [ 2 | { 3 | "name": "Room 1", 4 | "created_at": "2007/03/16 18:03:21 +0000", 5 | "updated_at": "2007/03/16 18:03:21 +0000", 6 | "topic": "Testing", 7 | "id": 80749, 8 | "membership_limit": 4 9 | }, 10 | { 11 | "name": "Room 2", 12 | "created_at": "2007/03/16 18:04:54 +0000", 13 | "updated_at": "2007/03/16 18:04:54 +0000", 14 | "topic": "test", 15 | "id": 80751, 16 | "membership_limit": 4 17 | }] 18 | } 19 | -------------------------------------------------------------------------------- /lib/tinder.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'tinder/connection' 3 | require 'tinder/campfire' 4 | require 'tinder/room' 5 | require 'logger' 6 | 7 | module Tinder 8 | class Error < StandardError; end 9 | class SSLRequiredError < Error; end 10 | class AuthenticationFailed < Error; end 11 | class ListenFailed < Error; end 12 | 13 | def self.logger 14 | @logger ||= Logger.new(ENV['TINDER_LOGGING'] ? STDOUT : nil) 15 | end 16 | 17 | def self.logger=(logger) 18 | @logger = logger 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/fixtures/rooms/recent.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages": [{ 3 | "starred": false, 4 | "type": "TextMessage", 5 | "room_id": 490096, 6 | "created_at": "2012/04/05 10:53:14 +0000", 7 | "id": 537713173, 8 | "user_id": 1158839, 9 | "body": "https://github.com/technomancy/dotfiles/commit/e19989d33777bb392c0ad1205444762dfecbaa5f " 10 | }, { 11 | "starred": false, 12 | "type": "TextMessage", 13 | "room_id": 490096, 14 | "created_at": "2012/04/05 10:54:20 +0000", 15 | "id": 537713420, 16 | "user_id": 1158837, 17 | "body": "Lol" 18 | }] 19 | } 20 | -------------------------------------------------------------------------------- /spec/fixtures/rooms/room80749.json: -------------------------------------------------------------------------------- 1 | { 2 | "room": { 3 | "full": false, 4 | "name": "Room 1", 5 | "created_at": "2007/03/16 18:03:21 +0000", 6 | "updated_at": "2007/03/16 18:03:21 +0000", 7 | "users": [{ 8 | "type": "Member", 9 | "created_at": "2006/12/07 21:20:39 +0000", 10 | "email_address": "jane@example.com", 11 | "admin": true, 12 | "id": 1, 13 | "name": "Jane Doe" 14 | }], 15 | "topic": "Testing", 16 | "active_token_value": "90cf7", 17 | "id": 80749, 18 | "open_to_guests": true, 19 | "membership_limit": 4 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spec/fixtures/rooms/room80751.json: -------------------------------------------------------------------------------- 1 | { 2 | "room": { 3 | "full": false, 4 | "name": "Room 2", 5 | "created_at": "2007/03/16 18:03:21 +0000", 6 | "updated_at": "2007/03/16 18:03:21 +0000", 7 | "users": [{ 8 | "type": "Member", 9 | "created_at": "2006/12/07 21:20:39 +0000", 10 | "email_address": "john@example.com", 11 | "admin": true, 12 | "id": 2, 13 | "name": "John Doe" 14 | }], 15 | "topic": "Testing 2", 16 | "active_token_value": "90cf7", 17 | "id": 80751, 18 | "open_to_guests": true, 19 | "membership_limit": 4 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spec/fixtures/rooms/show.json: -------------------------------------------------------------------------------- 1 | { 2 | "room": { 3 | "full": false, 4 | "name": "Room 1", 5 | "created_at": "2007/03/16 18:03:21 +0000", 6 | "updated_at": "2007/03/16 18:03:21 +0000", 7 | "users": [{ 8 | "type": "Member", 9 | "created_at": "2006/12/07 21:20:39 +0000", 10 | "email_address": "brandon@opensoul.org", 11 | "admin": true, 12 | "id": 129553, 13 | "name": "Brandon Keepers" 14 | }], 15 | "topic": "Testing", 16 | "active_token_value": "90cf7", 17 | "id": 80749, 18 | "open_to_guests": true, 19 | "membership_limit": 4 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') 3 | 4 | require 'rspec' 5 | require 'tinder' 6 | require 'fakeweb' 7 | 8 | FakeWeb.allow_net_connect = false 9 | 10 | def fixture(name) 11 | File.read(File.dirname(__FILE__) + "/fixtures/#{name}") 12 | end 13 | 14 | def stub_connection(object, &block) 15 | @stubs ||= Faraday::Adapter::Test::Stubs.new 16 | 17 | object.connection.build do |builder| 18 | builder.use FaradayMiddleware::EncodeJson 19 | builder.use FaradayMiddleware::Mashify 20 | builder.use FaradayMiddleware::ParseJson 21 | builder.use Faraday::Response::RemoveWhitespace 22 | builder.use Faraday::Response::RaiseOnAuthenticationFailure 23 | builder.adapter :test, @stubs 24 | end 25 | 26 | block.call(@stubs) 27 | end 28 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2010 Brandon Keepers, Collective Idea 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND 17 | NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /tinder.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path('../lib', __FILE__) 3 | require 'tinder/version' 4 | 5 | Gem::Specification.new do |gem| 6 | gem.add_dependency 'eventmachine', '~> 1.0' 7 | gem.add_dependency 'faraday', '~> 0.8' 8 | gem.add_dependency 'faraday_middleware', '~> 0.9' 9 | gem.add_dependency 'hashie', ['>= 1.0', '< 3'] 10 | gem.add_dependency 'json', '~> 1.7.5' 11 | gem.add_dependency 'mime-types', '~> 1.19' 12 | gem.add_dependency 'multi_json', '~> 1.5' 13 | gem.add_dependency 'twitter-stream', '~> 0.1' 14 | 15 | gem.add_development_dependency 'fakeweb' 16 | gem.add_development_dependency 'rake' 17 | gem.add_development_dependency 'rspec' 18 | 19 | gem.authors = ["Brandon Keepers", "Brian Ryckbost"] 20 | gem.description = %q{A Ruby API for interfacing with Campfire, the 37Signals chat application.} 21 | gem.email = ['brandon@opensoul.org', 'bryckbost@gmail.com'] 22 | gem.extra_rdoc_files = ['README.markdown'] 23 | gem.files = `git ls-files`.split("\n") 24 | gem.homepage = 'http://github.com/collectiveidea/tinder' 25 | gem.name = 'tinder' 26 | gem.require_paths = ['lib'] 27 | gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') 28 | gem.summary = %q{Ruby wrapper for the Campfire API} 29 | gem.test_files = `git ls-files -- spec/*`.split("\n") 30 | gem.version = Tinder::VERSION 31 | end 32 | -------------------------------------------------------------------------------- /site/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Lucida Grande", Helvetica, Arial, sans-serif; 3 | font-size: 76%; 4 | background: #2A2A2A; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | #collectiveidea { 10 | border-bottom: 1px solid #444; 11 | } 12 | 13 | a { 14 | color: #2D5385; 15 | } 16 | 17 | #main { 18 | background-color: #FFF; 19 | width: 700px; 20 | margin: 0 auto; 21 | border: 5px #CCC; 22 | border-left-style: solid; 23 | border-right-style: solid; 24 | padding: 0 1em; 25 | } 26 | 27 | #header { 28 | position: relative; 29 | border-bottom: 1px solid #999; 30 | padding: 1em; 31 | } 32 | 33 | #header h1 { 34 | margin: 0; 35 | padding: 0; 36 | color: #2D5385; 37 | } 38 | 39 | #header h1 a { 40 | text-decoration: none; 41 | } 42 | 43 | #header p { 44 | margin: 0; 45 | padding: 0; 46 | font-size: 0.8em; 47 | color: #999; 48 | } 49 | 50 | #nav { 51 | list-style: none; 52 | position: absolute; 53 | right: 0; 54 | top: 0.6em; 55 | } 56 | #nav li { 57 | display: inline; 58 | padding: 0 0.5em; 59 | } 60 | 61 | #content { 62 | padding: 1em 0; 63 | } 64 | 65 | dl { 66 | background-color: #DDD; 67 | padding: 1em; 68 | border: 1px solid #CCC; 69 | } 70 | dl .pronunciation { 71 | color: #C00; 72 | } 73 | dl .description { 74 | text-transform: uppercase; 75 | font-size: 0.8em; 76 | font-family: fixed; 77 | } 78 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 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 | # or if you have an OAuth token then you can use that to connect 11 | # campfire = Tinder::Campfire.new 'mysubdomain', :oauth_token => '546884b3d8fee4d80665g561caf7h9f3ea7b999e' 12 | 13 | room = campfire.rooms.first 14 | room.rename 'New Room Names' 15 | room.speak 'Hello world!' 16 | room.paste "my pasted\ncode" 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 | ## Installation 24 | 25 | gem install tinder 26 | 27 | ## Continuous Integration 28 | 29 | [![Build Status](https://secure.travis-ci.org/collectiveidea/tinder.png)](http://travis-ci.org/collectiveidea/tinder) [![Dependency Status](https://gemnasium.com/collectiveidea/tinder.png)](https://gemnasium.com/collectiveidea/tinder) 30 | 31 | ## How to contribute 32 | 33 | If you find what looks like a bug: 34 | 35 | 1. Check the GitHub issue tracker to see if anyone else has had the same issue. 36 | http://github.com/collectiveidea/tinder/issues/ 37 | 2. If you don't see anything, create an issue with information on how to reproduce it. 38 | 39 | If you want to contribute an enhancement or a fix: 40 | 41 | 1. Fork the project on github. 42 | http://github.com/collectiveidea/tinder 43 | 2. Make your changes with tests. 44 | 3. Commit the changes without making changes to the Rakefile, VERSION, or any other files that aren't related to your enhancement or fix 45 | 4. Send a pull request. 46 | -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | 1.9.2 - 2013-01-04 2 | * Update dependencies to latest versions 3 | 4 | 1.9.0 - 2012-07-16 5 | * Add Room#recent to get a list of recent messages 6 | * Add Room#search to search a room's transcripts 7 | 8 | 1.4.3 - 2010-12-07 9 | * explicitly require 'uri' 10 | * added Room#tweet(url) 11 | 12 | 1.4.2 - 2010-11-13 13 | * Use Faraday instead of HTTParty [eric] 14 | * Fix file uploads [eric] 15 | 16 | 1.4.1 - 2010-10-09 17 | * Make SSL the default since it is available for all Campfire accounts. 18 | * Added MIT License 19 | 20 | 1.4 - 2010-05-11 21 | * Remove methods no longer supported by API 22 | Campfire#available_transcripts, Room#ping, Room#destroy, Room#toggle_guest_access 23 | * Added Room#play 24 | * ActiveSupport 3.0 support 25 | * Fix streaming API support 26 | * Allow SSL for listening 27 | * Add support for HTTP proxies [c13bcc0b] 28 | 29 | 1.3.1 - 2009-12-17 30 | * Declare HTTParty dependency 31 | * Fix Room#paste 32 | 33 | 1.3.0 - 2009-12-15 34 | * Rewrite to use Official Campfire API 35 | 36 | 1.2.2 - 2009-09-12 37 | * Work around CSRF protection bug 38 | * Fixes for changes to Campfire markup 39 | * Include timestamps in the transcript 40 | 41 | 1.2.1 - 2009-08-27 42 | * Fixes for listening after campfire updates [Jordan Byron] 43 | 44 | 1.2.0 - 2009-01-28 45 | * Get the list of available files [Christopher MacGown] 46 | * Upload files [Joshua Wand] 47 | * Find rooms even when full [Josh Owens] 48 | * Join rooms as a guest [Ian Lesperance] 49 | 50 | 1.1.7 - 2008-07-24 51 | * Don't join the room when only speaking [Brian Donovan] 52 | * Added support for HTTP proxies 53 | * Fix listening for messages that contain URLs [Jared Kuolt] 54 | 55 | 0.1.6 - 2008-03-07 56 | * Added Room#topic for getting the current topic [Even Weaver] 57 | * Trap INT in #listen(&block) [borrowed from Chris Shea's Pyre] 58 | 59 | 0.1.5 - 2008-01-25 60 | * Fixed Room#listen, which was broken by latest Campfire deploy 61 | * Fixed timeout when listening but not speaking that will eventually log you out [Clinton R. Nixon] 62 | 63 | 0.1.4 - 2007-07-23 64 | * Support for transcripts 65 | * Fixed Room#leave, which was broken by a Campfire deployment [Andy Smith] 66 | 67 | 0.1.3 - 2007-02-12 68 | * added ssl support [Tero Parviainen] 69 | 70 | 0.1.2 - 2007-01-27 71 | * fixed bug preventing #listen from working without a block 72 | 73 | 0.1.1 - 2007-01-27 74 | * fix bug preventing speak from working 75 | * incorporated "watching" from http://soylentfoo.jnewland.com/articles/2006/12/07/updates-to-marshmallow-the-campfire-bot 76 | 77 | 0.1.0 - 2007-01-23 78 | * Initial release as gem 79 | * Get the users in a room [Tero Parviainen] 80 | -------------------------------------------------------------------------------- /lib/tinder/campfire.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | module Tinder 3 | 4 | # == Usage 5 | # 6 | # campfire = Tinder::Campfire.new 'mysubdomain', :token => 'xyz' 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 :connection 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 true 22 | # * +:ssl_options+: SSL options passed to the underlaying Faraday connection. Allows to specify if the SSL certificate should be verified (:verify => true|false) and to specify the path to the ssl certs directory (:ca_path => "path/certs") 23 | # Defaults to {:verify => true} 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 | @connection = Connection.new(subdomain, options) 29 | end 30 | 31 | # Get an array of all the available rooms 32 | # TODO: detect rooms that are full (no link) 33 | def rooms 34 | connection.get('/rooms.json')['rooms'].map do |room| 35 | Room.new(connection, room) 36 | end 37 | end 38 | 39 | # Find a campfire room by id 40 | # NOTE: id should be of type Integer 41 | def find_room_by_id(id) 42 | id = id.to_i 43 | rooms.detect { |room| room.id == id } 44 | end 45 | 46 | # Find a campfire room by name 47 | def find_room_by_name(name) 48 | rooms.detect { |room| room.name == name } 49 | end 50 | 51 | # Find a campfire room by its guest hash 52 | def find_room_by_guest_hash(hash, name) 53 | rooms.detect { |room| room.guest_invite_code == hash } 54 | end 55 | 56 | # Creates and returns a new Room with the given +name+ and optionally a +topic+ 57 | def create_room(name, topic = nil) 58 | connection.post('/rooms.json', { :room => { :name => name, :topic => topic } }) 59 | find_room_by_name(name) 60 | end 61 | 62 | def find_or_create_room_by_name(name) 63 | find_room_by_name(name) || create_room(name) 64 | end 65 | 66 | # List the users that are currently chatting in any room 67 | def users 68 | rooms.map(&:users).flatten.compact.uniq.sort_by {|u| u[:name]} 69 | end 70 | 71 | # get the user info of the current user 72 | def me 73 | connection.get("/users/me.json")["user"] 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/tinder/campfire_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'spec_helper' 3 | 4 | describe Tinder::Campfire do 5 | before do 6 | @campfire = Tinder::Campfire.new('test', :token => 'mytoken') 7 | end 8 | 9 | describe "rooms" do 10 | before do 11 | stub_connection(@campfire.connection) do |stub| 12 | stub.get('/rooms.json') {[200, {}, fixture('rooms.json')]} 13 | end 14 | end 15 | 16 | it "should return rooms" do 17 | @campfire.rooms.size.should be == 2 18 | @campfire.rooms.first.should be_kind_of(Tinder::Room) 19 | end 20 | 21 | it "should set the room name and id" do 22 | room = @campfire.rooms.first 23 | room.name.should be == 'Room 1' 24 | room.id.should be == 80749 25 | end 26 | end 27 | 28 | describe "find_room_by_id" do 29 | before do 30 | stub_connection(@campfire.connection) do |stub| 31 | stub.get('/rooms.json') {[200, {}, fixture('rooms.json')]} 32 | end 33 | end 34 | 35 | it "should return a Tinder::Room object when a match is found" do 36 | room = @campfire.find_room_by_id 80749 37 | room.should be_kind_of(Tinder::Room) 38 | end 39 | 40 | it "should return nil when no match is found" do 41 | room = @campfire.find_room_by_id 123 42 | room.should be nil 43 | end 44 | end 45 | 46 | describe "find_room_by_name" do 47 | before do 48 | stub_connection(@campfire.connection) do |stub| 49 | stub.get('/rooms.json') {[200, {}, fixture('rooms.json')]} 50 | end 51 | end 52 | 53 | it "should return a Tinder::Room object when a match is found" do 54 | room = @campfire.find_room_by_name 'Room 1' 55 | room.should be_kind_of(Tinder::Room) 56 | end 57 | 58 | it "should return nil when no match is found" do 59 | room = @campfire.find_room_by_name 'asdf' 60 | room.should be nil 61 | end 62 | end 63 | 64 | describe "users" do 65 | before do 66 | stub_connection(@campfire.connection) do |stub| 67 | stub.get('/rooms.json') {[200, {}, fixture('rooms.json')]} 68 | 69 | [80749, 80751].each do |id| 70 | stub.get("/room/#{id}.json") {[200, {}, fixture("rooms/room#{id}.json")]} 71 | end 72 | end 73 | end 74 | 75 | it "should return a sorted list of users in all rooms" do 76 | @campfire.users.length.should be == 2 77 | @campfire.users.first[:name].should be == "Jane Doe" 78 | @campfire.users.last[:name].should be == "John Doe" 79 | end 80 | end 81 | 82 | describe "me" do 83 | before do 84 | stub_connection(@campfire.connection) do |stub| 85 | stub.get("/users/me.json") {[200, {}, fixture('users/me.json')]} 86 | end 87 | end 88 | 89 | it "should return the current user's information" do 90 | @campfire.me["name"].should be == "John Doe" 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/tinder/connection.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'faraday' 3 | require 'faraday/response/raise_on_authentication_failure' 4 | require 'faraday/response/remove_whitespace' 5 | require 'faraday_middleware' 6 | require 'json' 7 | require 'uri' 8 | 9 | module Tinder 10 | class Connection 11 | HOST = 'campfirenow.com' 12 | 13 | attr_reader :subdomain, :uri, :options 14 | 15 | def self.connection 16 | @connection ||= Faraday.new do |builder| 17 | builder.use FaradayMiddleware::EncodeJson 18 | builder.use FaradayMiddleware::Mashify 19 | builder.use FaradayMiddleware::ParseJson 20 | builder.use Faraday::Response::RemoveWhitespace 21 | builder.use Faraday::Response::RaiseOnAuthenticationFailure 22 | builder.adapter Faraday.default_adapter 23 | end 24 | end 25 | 26 | def self.raw_connection 27 | @raw_connection ||= Faraday.new do |builder| 28 | builder.use Faraday::Request::Multipart 29 | builder.use FaradayMiddleware::Mashify 30 | builder.use FaradayMiddleware::ParseJson 31 | builder.use Faraday::Response::RemoveWhitespace 32 | builder.use Faraday::Response::RaiseOnAuthenticationFailure 33 | builder.adapter Faraday.default_adapter 34 | end 35 | end 36 | 37 | def initialize(subdomain, options = {}) 38 | @subdomain = subdomain 39 | @options = {:ssl => true, :ssl_options => {:verify => true}, :proxy => ENV['HTTP_PROXY']} 40 | @options[:ssl_options][:verify] = options.delete(:ssl_verify) unless options[:ssl_verify].nil? 41 | @options.merge!(options) 42 | @uri = URI.parse("#{@options[:ssl] ? 'https' : 'http' }://#{subdomain}.#{HOST}") 43 | @token = options[:token] 44 | @oauth_token = options[:oauth_token] 45 | 46 | if @oauth_token 47 | connection.headers["Authorization"] = "Bearer #{@oauth_token}" 48 | raw_connection.headers["Authorization"] = "Bearer #{@oauth_token}" 49 | else 50 | connection.basic_auth token, 'X' 51 | raw_connection.basic_auth token, 'X' 52 | end 53 | end 54 | 55 | def basic_auth_settings 56 | {:username => token, :password => 'X'} 57 | end 58 | 59 | def connection 60 | @connection ||= begin 61 | conn = self.class.connection.dup 62 | set_connection_options(conn) 63 | conn 64 | end 65 | end 66 | 67 | def raw_connection 68 | @raw_connection ||= begin 69 | conn = self.class.raw_connection.dup 70 | set_connection_options(conn) 71 | conn 72 | end 73 | end 74 | 75 | def token 76 | @token ||= begin 77 | connection.basic_auth(options[:username], options[:password]) 78 | get('/users/me.json')['user']['api_auth_token'] 79 | end 80 | end 81 | 82 | def get(url, *args) 83 | response = connection.get(url, *args) 84 | response.body 85 | end 86 | 87 | def post(url, body = nil, *args) 88 | response = connection.post(url, body, *args) 89 | response.body 90 | end 91 | 92 | def raw_post(url, body = nil, *args) 93 | response = raw_connection.post(url, body, *args) 94 | end 95 | 96 | def put(url, body = nil, *args) 97 | response = connection.put(url, body, *args) 98 | response.body 99 | end 100 | 101 | # Is the connection to campfire using ssl? 102 | def ssl? 103 | uri.scheme == 'https' 104 | end 105 | 106 | private 107 | def set_connection_options(conn) 108 | conn.url_prefix = @uri.to_s 109 | conn.proxy options[:proxy] 110 | if options[:ssl_options] 111 | conn.ssl.merge!(options[:ssl_options]) 112 | end 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /spec/tinder/connection_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'spec_helper' 3 | 4 | describe Tinder::Connection do 5 | describe "authentication" do 6 | it "should raise an exception with bad credentials" do 7 | stub_connection(Tinder::Connection) do |stub| 8 | stub.get("/rooms.json") {[401, {}, "Unauthorized"]} 9 | end 10 | 11 | connection = Tinder::Connection.new('test', :token => 'foo') 12 | lambda { connection.get('/rooms.json') }.should raise_error(Tinder::AuthenticationFailed) 13 | end 14 | 15 | it "should raise an exception when an invalid subdomain is specified" do 16 | stub_connection(Tinder::Connection) do |stub| 17 | stub.get("/rooms.json") {[404, {}, "Not found"]} 18 | end 19 | 20 | connection = Tinder::Connection.new('test', :token => 'foo') 21 | lambda { connection.get('/rooms.json') }.should raise_error(Tinder::AuthenticationFailed) 22 | end 23 | 24 | it "should lookup token when username/password provided" do 25 | stub_connection(Tinder::Connection) do |stub| 26 | stub.get("/users/me.json") {[200, {}, fixture('users/me.json')]} 27 | end 28 | 29 | connection = Tinder::Connection.new('test', :username => 'user', :password => 'pass') 30 | connection.token.should == "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 31 | end 32 | 33 | it "should use basic auth for credentials" do 34 | stub_connection(Tinder::Connection) do |stub| 35 | stub.get("/rooms.json") {[200, {}, fixture('rooms.json')]} 36 | end 37 | connection = Tinder::Connection.new('test', :token => 'mytoken') 38 | lambda { connection.get('/rooms.json') }.should_not raise_error 39 | end 40 | 41 | end 42 | 43 | describe "oauth" do 44 | let (:oauth_token) { "myoauthtoken" } 45 | let (:connection) { Tinder::Connection.new('test', :oauth_token => oauth_token) } 46 | 47 | before do 48 | stub_connection(Tinder::Connection) do |stub| 49 | stub.get("/rooms.json") {[200, {}, fixture('rooms.json')]} 50 | end 51 | end 52 | 53 | it "should authenticate" do 54 | lambda { connection.get('/rooms.json') }.should_not raise_error 55 | end 56 | 57 | it "should set the oauth_token" do 58 | connection.get('/rooms.json') 59 | connection.options[:oauth_token].should == oauth_token 60 | end 61 | 62 | it "should set an Authorization header" do 63 | connection.get('/rooms.json') 64 | connection.connection.headers["Authorization"].should == "Bearer #{oauth_token}" 65 | end 66 | 67 | end 68 | 69 | describe "ssl" do 70 | it "should turn on ssl by default" do 71 | stub_connection(Tinder::Connection) do |stub| 72 | stub.get("/users/me.json") {[200, {}, fixture('users/me.json')]} 73 | end 74 | 75 | connection = Tinder::Connection.new('test', :username => 'user', :password => 'pass') 76 | connection.ssl?.should be_true 77 | end 78 | 79 | it "should should allow peer verification to be turned off" do 80 | stub_connection(Tinder::Connection) do |stub| 81 | stub.get("/users/me.json") {[200, {}, fixture('users/me.json')]} 82 | end 83 | 84 | connection = Tinder::Connection.new('test', :username => 'user', :password => 'pass', :ssl_verify => false) 85 | connection.connection.ssl[:verify].should be == false 86 | end 87 | 88 | it "should allow passing any ssl_options to Faraday" do 89 | stub_connection(Tinder::Connection) do |stub| 90 | stub.get("/users/me.json") {[200, {}, fixture('users/me.json')]} 91 | end 92 | connection = Tinder::Connection.new('test', 93 | :username => 'user', 94 | :password => 'pass', 95 | :ssl_options => { 96 | :verify => false, 97 | :ca_path => "/usr/lib/ssl/certs", 98 | :ca_file => "/etc/ssl/custom" 99 | } 100 | ) 101 | connection.connection.ssl.should eql(:verify => false, :ca_path => "/usr/lib/ssl/certs", :ca_file => "/etc/ssl/custom") 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /site/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | Tinder 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 23 |
24 |
25 | 34 |
35 |

Tinder is an API for interfacing with Campfire, the 37Signals chat application.

36 |

Example

37 | 38 |
campfire = Tinder::Campfire.new 'mysubdomain'
 39 | campfire.login 'myemail@example.com', 'mypassword'
40 | 41 |

Create, find and destroy rooms

42 |
room = campfire.create_room 'New Room', 'My new campfire room to test tinder'
 43 | room = campfire.find_room_by_name 'Other Room'
 44 | room.destroy
45 | 46 |

Speak and Paste

47 |
room.speak 'Hello world!'
 48 | room.paste File.read("path/to/your/file.txt")
49 | 50 |

Listening

51 |
room.listen
 52 | #=> [{:person=>"Brandon", :message=>"I'm getting very sleepy", :user_id=>"148583", :id=>"16434003"}]
 53 | 
 54 | # or in block form
 55 | room.listen do |m|
 56 |   room.speak 'Welcome!' if m[:message] == /hello/
 57 | end
58 | 59 |

Guest Access

60 |
room.toggle_guest_access
 61 | room.guest_url         #=> http://mysubdomain.campfirenow.com/11111
 62 | room.guest_invite_code #=> 11111
63 | 64 |

Change the name and topic

65 |
room.name = 'Tinder Demo'
 66 | room.topic = 'Showing how to change the room name and topic with tinder…'
67 | 68 |

Users

69 |
room.users
 70 | campfire.users # users in all rooms
71 | 72 |

Transcripts

73 |
transcript = room.transcript(room.available_transcripts.first)
 74 | #=> [{:message=>"foobar!", :user_id=>"99999", :person=>"Brandon", :id=>"18659245", :timestamp=>Tue May 05 07:15:00 -0700 2009}]
 75 | 
76 | 77 |

See the API documentation for more details.

78 | 79 |

Installation

80 | 81 |

Tinder can be installed as a gem or a Rails plugin. Install the gem by executing:

82 | 83 |
gem install tinder
84 | 85 |

Or, download it from RubyForge.

86 | 87 |

Source

88 | 89 |

Contributions are welcome and appreciated! The source is available from:

90 | 91 |
http://github.com/collectiveidea/tinder
92 |
93 |
94 | 96 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /lib/tinder/room.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'time' 3 | 4 | module Tinder 5 | # A campfire room 6 | class Room 7 | attr_reader :id, :name 8 | 9 | def initialize(connection, attributes = {}) 10 | @connection = connection 11 | @id = attributes['id'] 12 | @name = attributes['name'] 13 | @loaded = false 14 | end 15 | 16 | # Join the room 17 | # POST /room/#{id}/join.xml 18 | # For whatever reason, #join() and #leave() are still xml endpoints 19 | # whereas elsewhere in this API we're assuming json :\ 20 | def join 21 | post 'join', 'xml' 22 | end 23 | 24 | # Leave a room 25 | # POST /room/#{id}/leave.xml 26 | def leave 27 | post 'leave', 'xml' 28 | stop_listening 29 | end 30 | 31 | # Get the url for guest access 32 | def guest_url 33 | "#{@connection.uri}/#{guest_invite_code}" if guest_access_enabled? 34 | end 35 | 36 | def guest_access_enabled? 37 | load 38 | @open_to_guests ? true : false 39 | end 40 | 41 | # The invite code use for guest 42 | def guest_invite_code 43 | load 44 | @active_token_value 45 | end 46 | 47 | # Change the name of the room 48 | def name=(name) 49 | update :name => name 50 | end 51 | alias_method :rename, :name= 52 | 53 | # Change the topic 54 | def topic=(topic) 55 | update :topic => topic 56 | end 57 | 58 | def update(attrs) 59 | connection.put("/room/#{@id}.json", {:room => attrs}) 60 | end 61 | 62 | # Get the current topic 63 | def topic 64 | reload! 65 | @topic 66 | end 67 | 68 | # Lock the room to prevent new users from entering and to disable logging 69 | def lock 70 | post 'lock' 71 | end 72 | 73 | # Unlock the room 74 | def unlock 75 | post 'unlock' 76 | end 77 | 78 | # Post a new message to the chat room 79 | def speak(message, options = {}) 80 | send_message(message) 81 | end 82 | 83 | def paste(message) 84 | send_message(message, 'PasteMessage') 85 | end 86 | 87 | def play(sound) 88 | send_message(sound, 'SoundMessage') 89 | end 90 | 91 | def tweet(url) 92 | send_message(url, 'TweetMessage') 93 | end 94 | 95 | # Get the list of users currently chatting for this room 96 | def users 97 | @users ||= current_users 98 | end 99 | 100 | def current_users 101 | reload! 102 | @current_users 103 | end 104 | 105 | # return the user with the given id; if it isn't in our room cache, 106 | # do a request to get it 107 | def user(id) 108 | if id 109 | cached_user = users.detect {|u| u[:id] == id } 110 | user = cached_user || fetch_user(id) 111 | self.users << user 112 | user 113 | end 114 | end 115 | 116 | # Perform a request for the user with the given ID 117 | def fetch_user(id) 118 | user_data = connection.get("/users/#{id}.json") 119 | user = user_data && user_data[:user] 120 | user[:created_at] = Time.parse(user[:created_at]) 121 | user 122 | end 123 | 124 | # Modifies a hash representation of a Campfire message. Expands +:user_id+ 125 | # to a full hash at +:user+, generates Timestamp from +:created_at+. 126 | # 127 | # Full returned hash: 128 | # * +:body+: the body of the message 129 | # * +:user+: Campfire user, which is itself a hash, of: 130 | # * +:id+: User id 131 | # * +:name+: User name 132 | # * +:email_address+: Email address 133 | # * +:admin+: Boolean admin flag 134 | # * +:created_at+: User creation timestamp 135 | # * +:type+: User type (e.g. Member) 136 | # * +:id+: Campfire message id 137 | # * +:type+: Campfire message type 138 | # * +:room_id+: Campfire room id 139 | # * +:created_at+: Message creation timestamp 140 | def parse_message(message) 141 | message[:user] = user(message.delete(:user_id)) 142 | message[:created_at] = Time.parse(message[:created_at]) 143 | message 144 | end 145 | 146 | # Listen for new messages in the room, parsing them with #parse_message 147 | # and then yielding them to the provided block as they arrive. 148 | # 149 | # room.listen do |m| 150 | # room.speak "Go away!" if m[:body] =~ /Java/i 151 | # end 152 | def listen(options = {}) 153 | raise ArgumentError, "no block provided" unless block_given? 154 | 155 | Tinder.logger.info "Joining #{@name}…" 156 | join # you have to be in the room to listen 157 | 158 | require 'json' 159 | require 'hashie' 160 | require 'multi_json' 161 | require 'twitter/json_stream' 162 | 163 | auth = connection.basic_auth_settings 164 | options = { 165 | :host => "streaming.#{Connection::HOST}", 166 | :path => room_url_for('live'), 167 | :auth => "#{auth[:username]}:#{auth[:password]}", 168 | :timeout => 6, 169 | :ssl => connection.options[:ssl] 170 | }.merge(options) 171 | 172 | Tinder.logger.info "Starting EventMachine server…" 173 | EventMachine::run do 174 | @stream = Twitter::JSONStream.connect(options) 175 | Tinder.logger.info "Listening to #{@name}…" 176 | @stream.each_item do |message| 177 | message = Hashie::Mash.new(MultiJson.decode(message)) 178 | message = parse_message(message) 179 | yield(message) 180 | end 181 | 182 | @stream.on_error do |message| 183 | raise ListenFailed.new("got an error! #{message.inspect}!") 184 | end 185 | 186 | @stream.on_max_reconnects do |timeout, retries| 187 | raise ListenFailed.new("Tried #{retries} times to connect. Got disconnected from #{@name}!") 188 | end 189 | 190 | # if we really get disconnected 191 | raise ListenFailed.new("got disconnected from #{@name}!") if !EventMachine.reactor_running? 192 | end 193 | end 194 | 195 | def listening? 196 | @stream != nil 197 | end 198 | 199 | def stop_listening 200 | return unless listening? 201 | 202 | Tinder.logger.info "Stopped listening to #{@name}…" 203 | @stream.stop 204 | @stream = nil 205 | end 206 | 207 | # Get the transcript for the given date (returns an array of messages parsed 208 | # via #parse_message, see #parse_message for format of returned message) 209 | # 210 | def transcript(transcript_date = Date.today) 211 | unless transcript_date.is_a?(Date) 212 | transcript_date = transcript_date.to_date 213 | end 214 | url = "/room/#{@id}/transcript/#{transcript_date.strftime('%Y/%m/%d')}.json" 215 | connection.get(url)['messages'].map do |message| 216 | parse_message(message) 217 | end 218 | end 219 | 220 | # Search transcripts for the given term (returns an array of messages parsed 221 | # via #parse_message, see #parse_message for format of returned message) 222 | # 223 | def search(term) 224 | encoded_term = URI.encode(term) 225 | 226 | room_messages = connection.get("/search/#{encoded_term}.json")["messages"].select do |message| 227 | message[:room_id] == id 228 | end 229 | 230 | room_messages.map do |message| 231 | parse_message(message) 232 | end 233 | end 234 | 235 | def upload(file, content_type = nil, filename = nil) 236 | require 'mime/types' 237 | content_type ||= MIME::Types.type_for(filename || file) 238 | raw_post(:uploads, { :upload => Faraday::UploadIO.new(file, content_type, filename) }) 239 | end 240 | 241 | # Get the list of latest files for this room 242 | def files(count = 5) 243 | get(:uploads)['uploads'].map { |u| u['full_url'] } 244 | end 245 | 246 | # Get a list of recent messages 247 | # Accepts a hash for options: 248 | # * +:limit+: Restrict the number of messages returned 249 | # * +:since_message_id+: Get messages created after the specified message id 250 | def recent(options = {}) 251 | options = { :limit => 10, :since_message_id => nil }.merge(options) 252 | # Build url manually, faraday has to be 8.0 to do this 253 | url = "#{room_url_for(:recent)}?limit=#{options[:limit]}&since_message_id=#{options[:since_message_id]}" 254 | 255 | connection.get(url)['messages'].map do |msg| 256 | parse_message(msg) 257 | end 258 | end 259 | 260 | protected 261 | 262 | def load 263 | reload! unless @loaded 264 | end 265 | 266 | def reload! 267 | attributes = connection.get("/room/#{@id}.json")['room'] 268 | 269 | @id = attributes['id'] 270 | @name = attributes['name'] 271 | @topic = attributes['topic'] 272 | @full = attributes['full'] 273 | @open_to_guests = attributes['open_to_guests'] 274 | @active_token_value = attributes['active_token_value'] 275 | @current_users = attributes['users'].map do |user| 276 | user[:created_at] = Time.parse(user[:created_at]) 277 | user 278 | end 279 | 280 | @loaded = true 281 | end 282 | 283 | def send_message(message, type = 'TextMessage') 284 | post 'speak', {:message => {:body => message, :type => type}} 285 | end 286 | 287 | def get(action) 288 | connection.get(room_url_for(action)) 289 | end 290 | 291 | def post(action, body = nil) 292 | connection.post(room_url_for(action), body) 293 | end 294 | 295 | def raw_post(action, body = nil) 296 | connection.raw_post(room_url_for(action), body) 297 | end 298 | 299 | def room_url_for(action, format="json") 300 | "/room/#{@id}/#{action}.#{format}" 301 | end 302 | 303 | def connection 304 | @connection 305 | end 306 | end 307 | end 308 | -------------------------------------------------------------------------------- /spec/tinder/room_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'spec_helper' 3 | require 'date' 4 | 5 | describe Tinder::Room do 6 | before do 7 | @connection = Tinder::Connection.new('test', :token => 'mytoken') 8 | 9 | stub_connection(@connection) do |stub| 10 | stub.get('/room/80749.json') {[200, {}, fixture('rooms/show.json')]} 11 | end 12 | 13 | @room = Tinder::Room.new(@connection, 'id' => 80749, 'name' => 'Room 1') 14 | 15 | # Get EventMachine out of the way. We could be using em-spec, but seems like overkill 16 | require 'twitter/json_stream' 17 | module EventMachine; def self.run; yield end end 18 | EventMachine.stub!(:reactor_running?).and_return(true) 19 | @stream = mock(Twitter::JSONStream) 20 | @stream.stub!(:each_item) 21 | @stream.stub!(:on_error) 22 | @stream.stub!(:on_max_reconnects) 23 | end 24 | 25 | describe "join" do 26 | before do 27 | stub_connection(@connection) do |stub| 28 | stub.post('/room/80749/join.json') {[200, {}, ""]} 29 | end 30 | end 31 | 32 | it "should post to join url" do 33 | @room.join 34 | end 35 | end 36 | 37 | describe "leave" do 38 | before do 39 | stub_connection(@connection) do |stub| 40 | stub.post('/room/80749/leave.json') {[200, {}, ""]} 41 | end 42 | end 43 | 44 | it "should post to leave url" do 45 | @room.leave 46 | end 47 | 48 | it "stops listening" do 49 | @room.should_receive(:stop_listening) 50 | @room.leave 51 | end 52 | end 53 | 54 | describe "lock" do 55 | before do 56 | stub_connection(@connection) do |stub| 57 | stub.post('/room/80749/lock.json') {[200, {}, ""]} 58 | end 59 | end 60 | 61 | it "should post to lock url" do 62 | @room.lock 63 | end 64 | end 65 | 66 | describe "search" do 67 | before do 68 | stub_connection(@connection) do |stub| 69 | stub.get('/search/foo.json') {[200, {}, fixture("rooms/recent.json")]} 70 | end 71 | end 72 | 73 | it "should GET the search endpoint with the search term and filter by room" do 74 | @room.stub(:id).and_return(490096) 75 | @room.should_receive(:parse_message).exactly(2).times 76 | @room.search("foo") 77 | end 78 | 79 | it "should return empty array if no messages in room" do 80 | @room.should_receive(:parse_message).never 81 | @room.search("foo").should be_empty 82 | end 83 | end 84 | 85 | describe "transcript" do 86 | it "should GET the transcript endpoint with the provided date" do 87 | stub_connection(@connection) do |stub| 88 | stub.get('/room/80749/transcript/2012/10/15.json') {[200, {}, fixture("rooms/recent.json")]} 89 | end 90 | @room.should_receive(:parse_message).exactly(2).times 91 | @room.transcript(Date.parse('2012-10-15')) 92 | end 93 | 94 | it "should default to today's date" do 95 | stub_connection(@connection) do |stub| 96 | stub.get('/room/80749/transcript/1981/03/21.json') {[200, {}, fixture("rooms/recent.json")]} 97 | end 98 | Date.stub(:today).and_return(Date.parse('1981-03-21')) 99 | @room.should_receive(:parse_message).exactly(2).times 100 | @room.transcript 101 | end 102 | end 103 | 104 | describe "unlock" do 105 | before do 106 | stub_connection(@connection) do |stub| 107 | stub.post('/room/80749/unlock.json') {[200, {}, ""]} 108 | end 109 | end 110 | 111 | it "should post to unlock url" do 112 | @room.unlock 113 | end 114 | end 115 | 116 | describe "guest_url" do 117 | it "should use guest_invite_code if active" do 118 | @room.stub!(:guest_access_enabled? => true, :guest_invite_code => '123') 119 | @room.guest_url.should == "https://test.campfirenow.com/123" 120 | end 121 | 122 | it "should return nil when guest access is not enabled" do 123 | @room.stub!(:guest_access_enabled?).and_return(false) 124 | @room.guest_url.should be_nil 125 | end 126 | end 127 | 128 | it "should set guest_invite_code" do 129 | @room.guest_invite_code.should == "90cf7" 130 | end 131 | 132 | it "should set guest_access_enabled?" do 133 | @room.guest_access_enabled?.should be_true 134 | end 135 | 136 | describe "topic" do 137 | it "should get the current topic" do 138 | @room.topic.should == "Testing" 139 | end 140 | 141 | it "should get the current topic even if it's changed" do 142 | @room.topic.should == "Testing" 143 | 144 | # reinitialize a new connection since we can't modify the 145 | # faraday stack after a request has already been submitted 146 | @connection = Tinder::Connection.new('test', :token => 'mytoken') 147 | 148 | # returning a different room's json to get a diff topic 149 | stub_connection(@connection) do |stub| 150 | stub.get('/room/80749.json') {[200, {}, fixture('rooms/room80751.json')]} 151 | end 152 | 153 | @room.topic.should == "Testing 2" 154 | 155 | end 156 | end 157 | 158 | 159 | describe "name=" do 160 | it "should put to update the room" do 161 | stub_connection(@connection) do |stub| 162 | stub.put('/room/80749.json') {[200, {}, ""]} 163 | end 164 | 165 | @room.name = "Foo" 166 | end 167 | end 168 | 169 | describe "listen" do 170 | before do 171 | stub_connection(@connection) do |stub| 172 | stub.post('/room/80749/join.json') {[200, {}, ""]} 173 | end 174 | end 175 | 176 | it "should get from the streaming url" do 177 | Twitter::JSONStream.should_receive(:connect).with( 178 | { 179 | :host=>"streaming.campfirenow.com", 180 | :path=>"/room/80749/live.json", 181 | :auth=>"mytoken:X", 182 | :timeout=>6, 183 | :ssl=>true 184 | } 185 | ).and_return(@stream) 186 | 187 | @room.listen { } 188 | end 189 | 190 | it "should raise an exception if no block is given" do 191 | lambda { 192 | @room.listen 193 | }.should raise_error(ArgumentError, "no block provided") 194 | end 195 | 196 | it "marks the room as listening" do 197 | Twitter::JSONStream.stub!(:connect).and_return(@stream) 198 | lambda { 199 | @room.listen { } 200 | }.should change(@room, :listening?).from(false).to(true) 201 | end 202 | end 203 | 204 | describe "stop_listening" do 205 | before do 206 | stub_connection(@connection) do |stub| 207 | stub.post('/room/80749/join.json') {[200, {}, ""]} 208 | end 209 | 210 | Twitter::JSONStream.stub!(:connect).and_return(@stream) 211 | @stream.stub!(:stop) 212 | end 213 | 214 | it "changes a listening room to a non-listening room" do 215 | @room.listen { } 216 | lambda { 217 | @room.stop_listening 218 | }.should change(@room, :listening?).from(true).to(false) 219 | end 220 | 221 | it "tells the json stream to stop" do 222 | @room.listen { } 223 | @stream.should_receive(:stop) 224 | @room.stop_listening 225 | end 226 | 227 | it "does nothing if the room is not listening" do 228 | @room.listen { } 229 | @room.stop_listening 230 | @room.stop_listening 231 | end 232 | end 233 | 234 | describe "recent" do 235 | before do 236 | stub_connection(@connection) do |stub| 237 | stub.get('/room/80749/recent.json') {[ 238 | 200, {}, fixture('rooms/recent.json') 239 | ]} 240 | end 241 | end 242 | 243 | it "should get a list of parsed recent messages" do 244 | @room.should_receive(:parse_message).exactly(2).times 245 | messages = @room.recent 246 | end 247 | end 248 | 249 | describe "parse_message" do 250 | it "expands user and parses created_at" do 251 | unparsed_message = { 252 | :user_id => 123, 253 | :body => 'An aunt is worth two nieces', 254 | :created_at => '2012/02/14 16:21:00 +0000' 255 | } 256 | expected = { 257 | :user => { 258 | :name => 'Dr. Noodles' 259 | }, 260 | :body => 'An aunt is worth two nieces', 261 | :created_at => Time.parse('2012/02/14 16:21:00 +0000') 262 | } 263 | @room.stub(:user).with(123).and_return({ :name => 'Dr. Noodles' }) 264 | 265 | actual = @room.parse_message(unparsed_message) 266 | actual.should == expected 267 | end 268 | end 269 | 270 | describe "user" do 271 | before do 272 | @room.stub(:current_users).and_return([ 273 | { :id => 1, :name => 'The Amazing Crayon Executive'}, 274 | { :id => 2, :name => 'Lord Pants'}, 275 | ]) 276 | @not_current_user = { :id => 3, :name => 'Patriot Sally'} 277 | end 278 | 279 | it "looks up user if not already in room's cache" do 280 | @room.should_receive(:fetch_user).with(3). 281 | and_return(@not_current_user) 282 | @room.user(3).should == @not_current_user 283 | end 284 | 285 | it "pulls user from room's cache if user in participant list" do 286 | @room.should_receive(:fetch_user).never 287 | user = @room.user(1) 288 | end 289 | 290 | it "adds user to cache after first lookup" do 291 | @room.should_receive(:fetch_user).with(3).at_most(:once). 292 | and_return(@not_current_user) 293 | @room.user(3).should == @not_current_user 294 | @room.user(3).should == @not_current_user 295 | end 296 | end 297 | 298 | describe "fetch_user" do 299 | before do 300 | stub_connection(@connection) do |stub| 301 | stub.get("/users/5.json") {[200, {}, fixture('users/me.json')]} 302 | end 303 | end 304 | 305 | it "requests via GET and returns the requested user's information" do 306 | @room.fetch_user(5)['name'].should == 'John Doe' 307 | end 308 | end 309 | 310 | describe "current_users" do 311 | it "returns list of currently participating users" do 312 | current_users = @room.current_users 313 | current_users.size.should == 1 314 | current_users.first[:name].should == 'Brandon Keepers' 315 | end 316 | end 317 | 318 | describe "users" do 319 | it "returns current users if cache has not been initialized yet" do 320 | @room.should_receive(:current_users).and_return(:the_whole_spittoon) 321 | @room.users.should == :the_whole_spittoon 322 | end 323 | 324 | it "returns current users plus any added cached users" do 325 | @room.should_receive(:current_users).and_return([:mia_cuttlefish]) 326 | @room.users << :guy_wearing_new_mexico_as_a_hat 327 | @room.users.should == [:mia_cuttlefish, :guy_wearing_new_mexico_as_a_hat] 328 | end 329 | end 330 | end 331 | --------------------------------------------------------------------------------