fnichol/campy

View on GitHub
lib/campy/room.rb

Summary

Maintainability
A
0 mins
Test Coverage
# -*- encoding: utf-8 -*-
require 'net/https'
require 'json'

module Campy
  class Room
    # Public: Error class raised when a room ID cannot be found with a
    # given name.
    class NotFound < RuntimeError ; end

    # Public: Error class raised when an HTTP error has occured.
    class ConnectionError < RuntimeError ; end

    # Public: Returns the String account name.
    attr_reader :account

    # Public: Returns the String room name.
    attr_reader :room

    # Public: Returns the API token String for a user.
    attr_reader :ssl

    # Public: Initializes a Room from a Hash of configuration options.
    #
    # options - A Hash of options to set up the Room (default: {}):
    #           :account  - The String account/subdomain name.
    #           :room     - The String room name, not the room ID.
    #           :token    - The API token String for a user.
    #           :ssl      - A truthy object which is true when SSL is
    #                       required (default: true).
    def initialize(options = {})
      options = { :ssl => true }.merge(options)

      [:account, :room_id, :room, :token, :ssl].each do |option|
        instance_variable_set "@#{option}", options[option]
      end
    end

    # Public: Returns the Integer room ID of the Campfire room.
    #
    # Returns the Integer room ID.
    # Raises NotFound if a room cannot be found for the given name.
    # Raises ConnectionError if an HTTP error occurs.
    def room_id
      @room_id ||= fetch_room_id
    end

    # Public: Posts a message into the campfire room.
    #
    # msg - A String message.
    #
    # Returns true if message is delivered.
    # Raises ConnectionError if an HTTP error occurs.
    def speak(msg)
      send_message(msg)
    end

    # Public: Pastes a message into the campfire room.
    #
    # msg - A String message.
    #
    # Returns true if message is delivered.
    # Raises ConnectionError if an HTTP error occurs.
    def paste(msg)
      send_message(msg, 'PasteMessage')
    end

    # Public: Plays a sound into the campfire room.
    #
    # sound - A String representing the sound.
    #
    # Returns true if message is delivered.
    # Raises ConnectionError if an HTTP error occurs.
    def play(msg)
      send_message(msg, 'SoundMessage')
    end

    private

    # Internal: Array of errors that will be wrapped when using Net::HTTP.
    HTTP_ERRORS = [Timeout::Error, Errno::EINVAL, Errno::ECONNRESET,
      EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
      Net::ProtocolError, SocketError, OpenSSL::SSL::SSLError,
      Errno::ECONNREFUSED]

    # Internal: Returns the API token String for a user.
    attr_reader :token

    # Internal: Returns the campfire hostname with subdomain.
    def host
      "#{account}.campfirenow.com"
    end

    # Internal: Returns the Integer HTTP port number to connect with.
    def port
      ssl ? 443 : 80
    end

    # Internal: Returns the Integer number of the room.
    #
    # Returns the Integer room number.
    # Raises NotFound if a room cannot be found for the given name.
    # Raises ConnectionError if an HTTP error occurs.
    def fetch_room_id
      connect do |http|
        response = http.request(http_request(:get, "/rooms.json"))

        case response
        when Net::HTTPOK
          find_room_in_json(JSON.parse(response.body))
        else
          raise ConnectionError
        end
      end
    end

    # Internal: Posts a message to the campfire room.
    #
    # msg   - The String message to send.
    # type  - The String type of campfire message (default: TextMessage).
    #
    # Returns true if message is delivered.
    # Raises ConnectionError if an HTTP error occurs.
    def send_message(msg, type = 'TextMessage')
      connect do |http|
        request = http_request(:post, "/room/#{room_id}/speak.json")
        request.body = { :message => { :body => msg, :type => type } }.to_json
        response = http.request(request)

        case response
        when Net::HTTPCreated
          true
        else
          raise ConnectionError,
            "Error sending message '#{msg}' (#{response.class})"
        end
      end
    end

    # Internal: Parses through the rooms JSON response and returns the
    # Integer room ID.
    #
    # json  - the rooms Hash of JSON data.
    #
    # Returns the Integer room number.
    # Raises NotFound if a room cannot be found for the given name.
    def find_room_in_json(json)
      room_hash = json["rooms"].find { |r| r["name"] == room }

      if room_hash
        room_hash["id"]
      else
        raise NotFound, "Room name '#{room}' could not be found."
      end
    end

    # Internal: Creates a Net::HTTP connection and yields to a block with
    # the connection.
    #
    # Yields the Net::HTTP connection.
    #
    # Returns the return value (if any) of the block.
    # Raises ConnectionError if any common HTTP errors are raised.
    def connect
      http = Net::HTTP.new(host, port)
      http.use_ssl = ssl

      begin
        yield http
      rescue *HTTP_ERRORS => exception
        raise ConnectionError, "#{exception.class.name}: #{exception.message}"
      end
    end

    # Internal: Returns a Net::HTTPRequest object initialized with
    # authentication and content headers set.
    #
    # verb  - A Symbol representing an HTTP verb.
    # path  - The String path of the request.
    #
    # Examples
    #
    #   http_request(:get, "/rooms.json")
    #   http_request(:post, "/room/1/speak.json")
    #
    # Returns a Net::HTTPRequest object.
    def http_request(verb, path)
      klass = klass = Net::HTTP.const_get(verb.to_s.capitalize)
      request = klass.new(path)
      request.basic_auth(token, "X")
      request["Content-Type"] = "application/json"
      request
    end
  end
end