Skip to content

Instantly share code, notes, and snippets.

@halogenandtoast
Created January 16, 2015 19:13
Show Gist options
  • Select an option

  • Save halogenandtoast/93a9bff652d48d7ed22d to your computer and use it in GitHub Desktop.

Select an option

Save halogenandtoast/93a9bff652d48d7ed22d to your computer and use it in GitHub Desktop.
require "json"
require "socket"
require "ipaddr"
require "securerandom"
require "curses"
class Message
attr_reader :client_id, :handle, :content
def self.inflate(json)
attributes = JSON.parse(json)
new(attributes)
end
def initialize(attributes = {})
@client_id = attributes.fetch("client_id")
@handle = attributes.fetch("handle")
@content = attributes.fetch("content")
end
def to_json
{ client_id: client_id, handle: handle, content: content }.to_json
end
end
#
class Client
MULTICAST_ADDR = "224.6.8.11"
BIND_ADDR = "0.0.0.0"
PORT = 6811
def initialize(handle)
@handle = handle
@client_id = SecureRandom.hex(5)
@listeners = []
end
def add_message_listener(listener)
listen unless listening?
@listeners << listener
end
def transmit(content)
message = Message.new(
"client_id" => @client_id,
"handle" => @handle,
"content" => content
)
socket.send(message.to_json, 0, MULTICAST_ADDR, PORT)
message
end
private
def listen
socket.bind(BIND_ADDR, PORT)
Thread.new do
loop do
attributes, _ = socket.recvfrom(1024)
message = Message.inflate(attributes)
unless message.client_id == @client_id
@listeners.each { |listener| listener.new_message(message) }
end
end
end
@listening = true
end
def listening?
@listening
end
def socket
@socket ||= UDPSocket.open.tap do |socket|
socket.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, bind_address)
socket.setsockopt(:IPPROTO_IP, :IP_MULTICAST_TTL, 1)
socket.setsockopt(:SOL_SOCKET, :SO_REUSEPORT, 1)
end
end
def bind_address
IPAddr.new(MULTICAST_ADDR).hton + IPAddr.new(BIND_ADDR).hton
end
end
class Window
include Curses
def initialize(client)
@client = client
@messages = []
end
def start
init_screen
start_color
init_pair(COLOR_WHITE, COLOR_BLACK, COLOR_WHITE)
use_default_colors
redraw
@client.add_message_listener(self)
loop do
capture_input
end
end
def new_message(message)
@messages << message
redraw
end
private
def capture_input
content = getstr
if content.length > 0
message = @client.transmit(content)
new_message(message)
end
end
def redraw
draw_text_field
draw_messages
cursor_to_input_line
refresh
end
def draw_text_field
setpos(divider_line, 0)
attron(color_pair(COLOR_WHITE) | A_NORMAL) do
addstr(" [chat]" + " " * cols)
end
cursor_to_input_line
clrtoeol
end
def draw_messages
@messages.last(window_line_size).each_with_index do |message, line_number|
setpos(line_number, 0)
clrtoeol
addstr("<#{message.handle}> #{message.content}")
end
end
def input_line
lines - 1
end
def divider_line
lines - 2
end
def window_line_size
lines - 2
end
def cursor_to_input_line
setpos(input_line, 0)
end
end
print "Handle: "
handle = gets.chomp.strip
client = Client.new(handle)
window = Window.new(client)
window.start
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment