andypiper
11/12/2011 - 9:16 PM

A (very) simple chat service using MQTT

A (very) simple chat service using MQTT

Install

  1. Install and run an MQTT broker (mosquitto is a good choice)

  2. Install mqtt and ncurses-ruby gems (the plain ncurses gem is harder to natively compile)

    sudo gem install mqtt ncurses-ruby

Run

  1. Run the client

    ruby ./mqtt-chat.rb

  2. If you want to configure the app to run against an alternative MQTT broker

require "rubygems"
require "ncurses"
require 'mqtt/client'

class ChatGui
   def read_line(y, x,
                 window     = Ncurses.stdscr,
                 max_len    = (window.getmaxx - x - 1), 
                 string     = "", 
                 cursor_pos = 0)
      loop do
         window.mvaddstr(y,x,string)
         window.move(y,x+cursor_pos)
         ch = window.getch
         case ch
         when Ncurses::KEY_ENTER, ?\n.ord, ?\r.ord
            return string
         when Ncurses::KEY_BACKSPACE, 127
            string = string[0...([0, cursor_pos-1].max)] + string[cursor_pos..-1]
            cursor_pos = [0, cursor_pos-1].max
            window.mvaddstr(y, x+string.length, " ")
         when (" "[0].ord..255)
            if (cursor_pos < max_len)
               string[cursor_pos,0] = ch.chr
               cursor_pos += 1
            else
               Ncurses.beep
            end 
         else
            Ncurses.beep
         end 
      end 
   end

   def add_message(message)
      @messages += message.split("\n")
      if @messages.size > @max_messages
         @messages.shift
      end

      refresh_messages_window
   end

   def refresh_messages_window
      @messages_window.clear
      y = 0
      @messages.each do |message|
         @messages_window.mvaddstr(y, 0, message)
         y = y + 1
      end
      @messages_window.refresh
   end

   def initialize(nick)
      @messages = []
      Ncurses.initscr
      Ncurses.cbreak
      Ncurses.noecho
      Ncurses.keypad(Ncurses.stdscr, true)

      @window = Ncurses.stdscr
      @maxy = @window.getmaxy - 1
      @maxx = @window.getmaxx - 1

      @prompt_window = Ncurses.newwin(2, @maxx, @maxy - 2, 0)
      @prompt = "#{nick} >"

      @messages_window = Ncurses.newwin(@maxy - 2, @maxx, 0, 0)
      @max_messages = @messages_window.getmaxy
   end

   def run(&b)
      loop do
         # refresh_messages_window

         @prompt_window.mvaddstr(0, 0, "-"*@maxx)
         @prompt_window.mvaddstr(1, 0, @prompt)
         message = read_line(1, @prompt.length + 1, @prompt_window)
         yield message
         @prompt_window.clear
      end
   end

   def quit
      Ncurses.endwin
   end
end

# -- main --

def help(gui)
   gui.add_message <<EOH
   /help : display this help
   /me <message> : send an action message
   /privmsg <nickname> <message> : send a private message
   /quit : Quit AMQP chat
   /who : ask who is here
EOH
end

nickname = ARGV[0]
raise "Usage : #{$0} <nickname>" if nickname.nil?

gui = ChatGui.new(nickname)
mqtt = MQTT::Client.new('localhost')

mqtt.connect do |client|
   client.subscribe('chat/public')
   client.subscribe('chat/system')
   client.subscribe("chat/private/#{nickname}")

   client.publish 'chat/public', "** #{nickname} enter"

   Thread.new do 
      gui.run do |message|
         case message
         when /^\/quit\s*(.*)/
            client.publish 'chat/public', "** #{nickname} has quit (#{$1})"
            client.disconnect
            gui.quit
            exit 1
         when /^\/privmsg\s*([^\s]*)\s*(.*)/
            client.publish "chat/private/#{$1}", ">> [#{Time.now.strftime('%H:%M:%S')}] #{nickname} : #{$2}"
            client.publish "chat/private/#{nickname}", ">> [#{Time.now.strftime('%H:%M:%S')}] #{nickname} : #{$2}"
         when /^\/who/
            client.publish "chat/system", nickname
         when /^\/help/
            help gui
         else
            client.publish 'chat/public', "[#{Time.now.strftime('%H:%M:%S')}] #{nickname} : #{message}"
         end
      end
   end

   loop do
      topic,message = client.get
      if topic =~ /chat\/system/
         client.publish "chat/private/#{message}", "-- #{nickname} is here"
      else
         gui.add_message message
      end
   end
end