sjauld/lita-coffee

View on GitHub
lib/lita/handlers/coffee.rb

Summary

Maintainability
A
3 hrs
Test Coverage
module Lita
  module Handlers
    class Coffee < Handler
      # TODO: money - probably in a separate handler and maybe it already exists??

      # Dependencies
      require 'json'

      # Configuration
      # default_group - the name of the default group in which users will order a coffee
      # default_coffee - the coffee we will order if users don't specify what they would like
      config :default_group, type: String, default: 'coffee-lovers'
      config :default_coffee, type: String, default: 'Single origin espresso'
      config :default_timeout, type: Integer, default: 28800
      on :loaded, :set_constants

      def set_constants(payload)
        @@DEFAULT_GROUP   = config.default_group
        @@DEFAULT_COFFEE  = config.default_coffee
        @@DEFAULT_GROUP_TIMEOUT = config.default_timeout
      end

      # ---------------
      # Nice new routes
      # ---------------

      # Welcome new users
      route(
        /coffee/i,
        :init_user,
      )

      # Order a coffee
      route(
        /^\s*\(?coffee\)?\s+\+\s*(\S.*)?$/i,
        :get_me_a_coffee,
        help: {
          'coffee +'                    => "Order a coffee",
        }
      )

      # Cancel your order
      route(
        /^\s*\(?coffee\)?\s+\-c\s*$/i,
        :cancel_order,
        help: {
          'coffee -c'                   => "Cancel your order",
        }
      )

      # List orders
      route(
        /^\s*\(?coffee\)?\s*$/i,
        :list_orders,
        help: {
          'coffee'                      => "List the orders for your group",
        }
      )

      # Display profile informatino
      route(
        /^\s*\(?coffee\)?\s+\-i\s*$/i,
        :display_profile,
        help: {
          'coffee -i'                   => "Display your profile",
        }
      )

      # Set preferences
      route(
        /^\s*\(?coffee\)?\s+\-([sg])\s+(.*)$/i,
        :set_prefs,
        help: {
          'coffee -s Colombian Filter'  => "Set your coffee preference",
          'coffee -g Cool Kids'  => "Change your group",
        }
      )

      # Buy coffees
      route(
        /^\s*\(?coffee\)?\s+\-b\s*(.*)$/i,
        :buy_coffees,
        help: {
          'coffee -b You owe me one!'   => "Buy coffee for your group, clear the orders and send a message to each coffee drinker",
        }
      )

      # Display system settings
      route(
        /^\s*\(?coffee\)?\s+\-t\s*$/i,
        :system_settings,
        help: {
          'coffee -t'                   => "Display system settings",
        }
      )

      # Delete me
      route(
        /^\s*\(?coffee\)?\s+\-d\s*$/i,
        :delete_me,
        help: {
          'coffee -d'                   => "Delete you from the coffee system",
        }
      )

      # List all groups
      route(
        /^\s*\(?coffee\)?\s+\-l\s*$/i,
        :list_groups,
        help: {
          'coffee -l'                   => "List the available coffee groups",
        }
      )

      # Coffee stats a.k.a. who owes whom?
      route(
        /^\s*\(?coffee\)?\s+\-w\s*(\S.*)?$/i,
        :show_stats,
        help: {
          'coffee -w'                   => "Show stats for a group",
        }
      )

      # Setup new users
      def init_user(response)
        response.reply("Welcome to coffee! You have been added to the #{@@DEFAULT_GROUP} group with an order of #{@@DEFAULT_COFFEE}. Type help coffee for help.") if initialize_user_redis(response.user.name) == :new_user
      end

      # Order coffee
      def get_me_a_coffee(response)
        group = response.matches[0][0].strip rescue get_group(response.user.name)
        orders = (get_orders(group) + [response.user.name]).uniq
        result = redis.set("orders:#{group}",orders.to_json)
        set_timeout(group)
        if result == "OK"
          response.reply("Ordered you a coffee from #{group}")
        else
          response.reply("(sadpanda) Failed to order your coffee for some reason: #{result.inspect}")
        end
      end

      # Cancel coffee order
      def cancel_order(response)
        group = get_group(response.user.name)
        orders = get_orders(group)
        orders.delete(response.user.name)
        result = redis.set("orders:#{group}",orders.to_json)
        set_timeout(group)
        if result == "OK"
          response.reply("Cancelled your coffee")
        else
          response.reply("(sadpanda) Failed to cancel your coffee for some reason: #{result.inspect}")
        end
      end

      # List the coffee orders for your group
      def list_orders(response)
        group = get_group(response.user.name)
        response.reply("Current orders for #{group}:-\n--")
        get_orders(group).each do |order|
          response.reply("#{order}: #{get_coffee(order)}")
        end
      end

      # Display profile
      def display_profile(response)
        settings = get_settings(response.user.name)
        response.reply("Your current coffee is #{settings['coffee']}. You are in the #{settings['group']} group.")
      end

      # Set coffee preference
      # TODO: a single method to update user info
      def set_prefs(response)
        mapping = {'g' => :group, 's' => :coffee}
        setting = mapping.detect{|k,v| k == response.matches[0][0]}[1]
        preference = response.matches[0][1].strip rescue nil
        update_preference(response,setting,preference)
      end

      # Buy all the coffee for your group
      def buy_coffees(response)
        group = get_group(response.user.name)
        message = response.matches[0][0].strip rescue nil
        response.reply("Thanks for ordering the coffee for #{group}!\n--")
        stats = get_coffee_stats(group)
        get_orders(group).each do |order|
          response.reply("#{order}: #{get_coffee(order)}")
          send_coffee_message(order,response.user.name,message) unless order == response.user.name
          stats[order] -= 1 rescue stats[order] = -1
          stats[response.user.name] += 1 rescue stats[response.user.name] = 1
        end
        set_coffee_stats(group,stats)
        result = clear_orders(group)
        if result == 1
          response.reply("Cleared all orders for #{group}")
        else
          response.reply("(sadpanda) Failed to clear the orders for some reason: #{result.inspect}")
        end
      end

      # Display the system settings
      def system_settings(response)
        response.reply("Default coffee: #{@@DEFAULT_COFFEE}, Default group: #{@@DEFAULT_GROUP}")
      end

      # Delete a user
      def delete_me(response)
        result = redis.del("settings:#{response.user.name}")
        if result == 1
          response.reply("You have been deleted from coffee")
        else
          response.reply("(sadpanda) Failed to delete you from coffee for some reason: #{result.inspect}")
        end
      end

      # List groups
      def list_groups(response)
        groups = redis.keys('stats:*')
        response.reply("The following groups are active:-\n--\n#{groups.map{|g| g.split(':')[1]}.join("\n")}")
      end

      # Display the stats
      def show_stats(response)
        group = response.matches[0][0].strip rescue get_group(response.user.name)
        stats = get_coffee_stats(group)
        owing = []
        owed = []
        stats.each do |stat|
          stat[1] > 0 ? owed << stat : owing << stat
        end
        owing.sort!{|a,b| a[1] <=> b[1]} # Negative for owing, ergo sort in ascending order
        owed.sort!{|a,b| b[1] <=> a[1]}  # Positive for owed, ergo sort in descending order to show who is owed most
        # Provide a response
        response.reply("Coffees owed to others\n--\n#{owing.map{|s| "#{s[0]}: #{s[1].abs}"}.join("\n")}")
        response.reply("Coffees to be repaid\n--\n#{owed.map{|s| "#{s[0]}: #{s[1]}"}.join("\n")}")
      end

      #######
      private
      #######

      def get_coffee_stats(group)
        JSON.parse(redis.get("stats:#{group}")) rescue {}
      end

      def set_coffee_stats(group,stats)
        redis.set("stats:#{group}",stats.to_json)
      end

      def initialize_user_redis(user)
        if redis.get("settings:#{user}").nil?
          redis.set("settings:#{user}",{group: @@DEFAULT_GROUP, coffee: @@DEFAULT_COFFEE}.to_json)
          return :new_user
        else
          return :existing_user
        end
      end

      def get_settings(user)
        JSON.parse(redis.get("settings:#{user}")) rescue {group: @@DEFAULT_GROUP, coffee: @@DEFAULT_COFFEE}
      end

      def get_orders(group)
        set_timeout(group)
        JSON.parse(redis.get("orders:#{group}")) rescue []
      end

      def get_coffee(user)
        JSON.parse(redis.get("settings:#{user}"))['coffee'] rescue @@DEFAULT_COFFEE
      end

      def get_group(user)
        JSON.parse(redis.get("settings:#{user}"))['group'] rescue @@DEFAULT_GROUP
      end

      def update_preference(response,setting,preference)
        my_settings = get_settings(response.user.name)
        my_settings[setting] = preference
        result = redis.set("settings:#{response.user.name}",my_settings.to_json)
        if result == "OK"
          response.reply("Updated your #{setting} to #{preference}")
        else
          response.reply("(sadpanda) Failed to update your #{setting} for some reason: #{result.inspect}")
        end
      end

      def clear_orders(group)
        set_timeout(group)
        redis.del("orders:#{group}")
      end

      def send_coffee_message(user,purchaser,message)
        myuser = Lita::User.find_by_name(user)
        msg = Lita::Message.new(robot,'',Lita::Source.new(user: myuser))
        msg.reply("#{purchaser} has bought you a coffee!")
        msg.reply(message) # what happens if message is nil?
      rescue => e
        Lita.logger.error("Coffee#send_coffee_message raised #{e.class}: #{e.message}\n#{e.backtrace}")
      end

      def set_timeout(group)
        redis.expire("orders:#{group}",@@DEFAULT_GROUP_TIMEOUT)
      end


    end

    Lita.register_handler(Coffee)
  end
end