├── .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 | [](http://travis-ci.org/collectiveidea/tinder) [](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 |
--------------------------------------------------------------------------------