collectiveidea/tinder

View on GitHub
lib/tinder/room.rb

Summary

Maintainability
B
6 hrs
Test Coverage
# encoding: UTF-8
require 'time'

module Tinder
  # A campfire room
  class Room
    attr_reader :id, :name

    def initialize(connection, attributes = {})
      @connection = connection
      @id = attributes['id']
      @name = attributes['name']
      @loaded = false
    end

    # Join the room
    # POST /room/#{id}/join.xml
    # For whatever reason, #join() and #leave() are still xml endpoints
    # whereas elsewhere in this API we're assuming json :\
    def join
      post 'join', 'xml'
    end

    # Leave a room
    # POST /room/#{id}/leave.xml
    def leave
      post 'leave', 'xml'
      stop_listening
    end

    # Get the url for guest access
    def guest_url
      "#{@connection.uri}/#{guest_invite_code}" if guest_access_enabled?
    end

    def guest_access_enabled?
      load
      @open_to_guests ? true : false
    end

    # The invite code use for guest
    def guest_invite_code
      load
      @active_token_value
    end

    # Change the name of the room
    def name=(name)
      update :name => name
    end
    alias_method :rename, :name=

    # Change the topic
    def topic=(topic)
      update :topic => topic
    end

    def update(attrs)
      connection.put("/room/#{@id}.json", {:room => attrs})
    end

    # Get the current topic
    def topic
      reload!
      @topic
    end

    # Lock the room to prevent new users from entering and to disable logging
    def lock
      post 'lock'
    end

    # Unlock the room
    def unlock
      post 'unlock'
    end

    # Post a new message to the chat room
    def speak(message, options = {})
      send_message(message)
    end

    def paste(message)
      send_message(message, 'PasteMessage')
    end

    def play(sound)
      send_message(sound, 'SoundMessage')
    end

    def tweet(url)
      send_message(url, 'TweetMessage')
    end

    # Get the list of users currently chatting for this room
    def users
      @users ||= current_users
    end

    def current_users
      reload!
      @current_users
    end

    # return the user with the given id; if it isn't in our room cache,
    # do a request to get it
    def user(id)
      if id
        cached_user = users.detect {|u| u[:id] == id }
        user = cached_user || fetch_user(id)
        self.users << user
        user
      end
    end

    # Perform a request for the user with the given ID
    def fetch_user(id)
      user_data = connection.get("/users/#{id}.json")
      user = user_data && user_data[:user]
      user[:created_at] = Time.parse(user[:created_at])
      user
    end

    # Modifies a hash representation of a Campfire message.  Expands +:user_id+
    # to a full hash at +:user+, generates Timestamp from +:created_at+.
    #
    # Full returned hash:
    # * +:body+: the body of the message
    # * +:user+: Campfire user, which is itself a hash, of:
    #   * +:id+: User id
    #   * +:name+: User name
    #   * +:email_address+: Email address
    #   * +:admin+: Boolean admin flag
    #   * +:created_at+: User creation timestamp
    #   * +:type+: User type (e.g. Member)
    # * +:id+: Campfire message id
    # * +:type+: Campfire message type
    # * +:room_id+: Campfire room id
    # * +:created_at+: Message creation timestamp
    def parse_message(message)
      message[:user] = user(message.delete(:user_id))
      message[:created_at] = Time.parse(message[:created_at])
      message
    end

    # Listen for new messages in the room, parsing them with #parse_message
    # and then yielding them to the provided block as they arrive.
    #
    #   room.listen do |m|
    #     room.speak "Go away!" if m[:body] =~ /Java/i
    #   end
    def listen(options = {})
      raise ArgumentError, "no block provided" unless block_given?

      Tinder.logger.info "Joining #{@name}…"
      join # you have to be in the room to listen

      require 'json'
      require 'hashie'
      require 'multi_json'
      require 'twitter/json_stream'

      auth = connection.basic_auth_settings
      options = {
        :host => "streaming.#{Connection::HOST}",
        :path => room_url_for('live'),
        :auth => "#{auth[:username]}:#{auth[:password]}",
        :timeout => 6,
        :ssl => connection.options[:ssl]
      }.merge(options)

      Tinder.logger.info "Starting EventMachine server…"
      EventMachine::run do
        @stream = Twitter::JSONStream.connect(options)
        Tinder.logger.info "Listening to #{@name}…"
        @stream.each_item do |message|
          message = Hashie::Mash.new(MultiJson.decode(message))
          message = parse_message(message)
          yield(message)
        end

        @stream.on_error do |message|
          raise ListenFailed.new("got an error! #{message.inspect}!")
        end

        @stream.on_max_reconnects do |timeout, retries|
          raise ListenFailed.new("Tried #{retries} times to connect. Got disconnected from #{@name}!")
        end

        # if we really get disconnected
        raise ListenFailed.new("got disconnected from #{@name}!") if !EventMachine.reactor_running?
      end
    end

    def listening?
      @stream != nil
    end

    def stop_listening
      return unless listening?

      Tinder.logger.info "Stopped listening to #{@name}…"
      @stream.stop
      @stream = nil
    end

    # Get the transcript for the given date (Returns a hash in the same format as #listen)
    #
    #   room.transcript(Time.now)
    #   #=> [{:message=>"foobar!",
    #         :user_id=>"99999",
    #         :person=>"Brandon",
    #         :id=>"18659245",
    #         :timestamp=>=>Tue May 05 07:15:00 -0700 2009}]
    #
    # The timestamp slot will typically have a granularity of five minutes.
    #
    def transcript(transcript_date = Date.today)
      url = "/room/#{@id}/transcript/#{transcript_date.strftime('%Y/%m/%d')}.json"
      connection.get(url)['messages'].map do |message|
        parse_message(message)
      end
    end

    # Search transcripts for the given term (returns an array of messages parsed
    # via #parse_message, see #parse_message for format of returned message)
    #
    def search(term)
      encoded_term = URI.encode(term)

      room_messages = connection.get("/search/#{encoded_term}.json")["messages"].select do |message|
        message[:room_id] == id
      end

      room_messages.map do |message|
        parse_message(message)
      end
    end

    def upload(file, content_type = nil, filename = nil)
      require 'mime/types'
      content_type ||= MIME::Types.type_for(filename || file)
      raw_post(:uploads, { :upload => Faraday::UploadIO.new(file, content_type, filename) })
    end

    # Get the list of latest files for this room
    def files(count = 5)
      get(:uploads)['uploads'].map { |u| u['full_url'] }
    end

    # Get a list of recent messages
    # Accepts a hash for options:
    # * +:limit+: Restrict the number of messages returned
    # * +:since_message_id+: Get messages created after the specified message id
    def recent(options = {})
      options = { :limit => 10, :since_message_id => nil }.merge(options)
      # Build url manually, faraday has to be 8.0 to do this
      url = "#{room_url_for(:recent)}?limit=#{options[:limit]}&since_message_id=#{options[:since_message_id]}"

      connection.get(url)['messages'].map do |msg|
        parse_message(msg)
      end
    end

  protected

    def load
      reload! unless @loaded
    end

    def reload!
      attributes = connection.get("/room/#{@id}.json")['room']

      @id = attributes['id']
      @name = attributes['name']
      @topic = attributes['topic']
      @full = attributes['full']
      @open_to_guests = attributes['open_to_guests']
      @active_token_value = attributes['active_token_value']
      @current_users = attributes['users'].map do |user|
        user[:created_at] = Time.parse(user[:created_at])
        user
      end

      @loaded = true
    end

    def send_message(message, type = 'TextMessage')
      post 'speak', {:message => {:body => message, :type => type}}
    end

    def get(action)
      connection.get(room_url_for(action))
    end

    def post(action, body = nil)
      connection.post(room_url_for(action), body)
    end

    def raw_post(action, body = nil)
      connection.raw_post(room_url_for(action), body)
    end

    def room_url_for(action, format="json")
      "/room/#{@id}/#{action}.#{format}"
    end

    def connection
      @connection
    end

  end
end