Author: nornagon
Date: 2005-04-11 22:36:42 -0400 (Mon, 11 Apr 2005)
New Revision: 669
Added:
trunk/clients/ravish/
trunk/clients/ravish/ravish.rb
Log:
Ravish -- initial import
Added: trunk/clients/ravish/ravish.rb
===================================================================
--- trunk/clients/ravish/ravish.rb 2005-04-10 19:19:33 UTC (rev 668)
+++ trunk/clients/ravish/ravish.rb 2005-04-12 02:36:42 UTC (rev 669)
@@ -0,0 +1,485 @@
+# ravish -- Haver client written in ruby
+
+$:.unshift "/home/nornagon/dev/termvisual/lib"
+
+require 'term/visual'
+require 'socket'
+require 'io/nonblock'
+require 'observer'
+require 'thread'
+require 'delegate'
+
+class RavishVT < Term::Visual
+ def message msg
+ $windows[0][1].addline msg,
'%(bblue)-%(bwhite)!%(bblue)-%(default) '
+ end
+end
+
+$vt = RavishVT.new
+
+$vt.global_prefix = lambda { Time.now.strftime("%H:%M:%S ") }
+
+palette = {
+ 'bblue' => 'bold blue on default',
+ 'bwhite' => 'bold white on default',
+ 'nick' => 'default on default',
+ 'nickdecs' => 'bold black on default',
+ 'action' => 'bold white on default',
+ 'statdecs' => 'cyan on blue',
+}
+
+$vt.palette.setcolors(palette)
+
+$windows = []
+
+$windows[0] = ['(server)', $vt.create_window]
+$windows[0][1].title = "Ravish: Haver client"
+
+switch_window = lambda { |ch|
+ win = nil
+ if ch == ?1 then win = 1
+ elsif ch == ?2 then win = 2
+ elsif ch == ?3 then win = 3
+ elsif ch == ?4 then win = 4
+ elsif ch == ?5 then win = 5
+ elsif ch == ?6 then win = 6
+ elsif ch == ?7 then win = 7
+ elsif ch == ?8 then win = 8
+ elsif ch == ?9 then win = 9
+ elsif ch == ?0 then win = 10
+ elsif ch == ?q then win = 11
+ elsif ch == ?w then win = 12
+ elsif ch == ?e then win = 13
+ elsif ch == ?r then win = 14
+ elsif ch == ?t then win = 15
+ elsif ch == ?y then win = 16
+ elsif ch == ?u then win = 17
+ elsif ch == ?i then win = 18
+ elsif ch == ?o then win = 19
+ else
+ return
+ end
+ if $windows[win-1] and to = $windows[win-1][1]
+ $vt.switch_window(to)
+ end
+}
+$vt.bind('A-1', switch_window)
+$vt.bind('A-2', switch_window)
+$vt.bind('A-3', switch_window)
+$vt.bind('A-4', switch_window)
+$vt.bind('A-5', switch_window)
+$vt.bind('A-6', switch_window)
+$vt.bind('A-7', switch_window)
+$vt.bind('A-8', switch_window)
+$vt.bind('A-9', switch_window)
+$vt.bind('A-0', switch_window)
+$vt.bind('A-Q', switch_window)
+$vt.bind('A-W', switch_window)
+$vt.bind('A-E', switch_window)
+$vt.bind('A-R', switch_window)
+$vt.bind('A-T', switch_window)
+$vt.bind('A-Y', switch_window)
+$vt.bind('A-U', switch_window)
+$vt.bind('A-I', switch_window)
+$vt.bind('A-O', switch_window)
+
+module Haver
+ module Event
+ CONNECTING = 0
+ CONNECTED = 1
+ MESG_RECV = 2
+ DISCONNECTING = 3
+ DISCONNECTED = 4
+ end
+
+ class Connector
+ include Observable
+
+ BufSize = 128
+
+ attr_reader :server, :port
+ attr_accessor :nick
+
+ def initialize server, port=7070, hash={}
+ @server = server || hash['server'] || 'hardison.net'
+ @port = port || hash['port'] || 7070
+ @nick = hash['nick'] || 'nornagon'
+
+ @socket = nil
+ @alive = false
+ @sendqueue = Queue.new
+ @recvqueue = Queue.new
+
+ @recvthread = nil
+ @sendthread = nil
+ end
+
+ def open
+ if self.alive?
+ return false
+ end
+
+ notify Event::CONNECTING
+ begin
+ @socket = TCPSocket.new(@server, @port)
+ rescue StandardError, SocketError
+ notify Event::LINK_FAILED, $!
+ return false
+ end
+ @socket.nonblock = true
+
+ @recvthread = Thread.new do
+ self.readfromsocket
+ end
+
+ @sendthread = Thread.new do
+ self.writetosocket
+ end
+
+ notify Event::CONNECTED
+ return true
+ end
+
+ def close
+ notify Event::DISCONNECTING
+
+ if [EMAIL PROTECTED] and @recvthread.alive?
+ @recvthread.exit
+ end
+
+ if [EMAIL PROTECTED] and @sendthread.alive?
+ @sendthread.exit
+ end
+
+ @socket.close unless @socket.closed?
+
+ notify Event::DISCONNECTED
+ end
+
+ def alive?
+ if [EMAIL PROTECTED](Thread) || [EMAIL
PROTECTED](Thread)
+ return false
+ end
+ if [EMAIL PROTECTED] || [EMAIL PROTECTED] ||
@socket.closed?
+ return false
+ end
+
+ return true
+ end
+
+ def write msg
+ if msg.kind_of?(Array)
+ @sendqueue.push msg.join("\t") + "\r\n"
+ else
+ @sendqueue.push msg
+ end
+ end
+
+ def read
+ @recvqueue.pop
+ end
+
+ protected
+
+ def notify event
+ changed
+ notify_observers event, Time.now
+ end
+
+ def readfromsocket
+ buf = ''
+ loop do
+ if @socket.eof?
+ break
+ end
+ begin
+ res = select([EMAIL PROTECTED])
+ if res
+ until buf =~ /\r\n/ do
+ read_str =
@socket.read(1)
+ buf += read_str
+ Thread.pass
+ end
+
@recvqueue.push(buf.slice!(/^(.+?)\r\n/).chomp)
+ notify Event::MESG_RECV
+ end
+ rescue StandardError, SocketError, IOError
+ break
+ end
+ end
+
+ @recvthread = nil
+ self.close
+ Thread.current.exit
+ end
+
+ def writetosocket
+ loop do
+ msg = @sendqueue.pop
+ sent = 0
+ begin
+ begin
+ res = select(nil, [EMAIL
PROTECTED])
+ if res then sent +=
@socket.write msg end
+ Thread.pass
+ end until sent == msg.length
+ rescue StandardError, SocketError, IOError
+ break
+ end
+ end
+
+ @sendthread = nil
+ self.close
+ Thread.current.exit
+ end
+ end
+
+ class Client < SimpleDelegator
+ def initialize server, port=7070, hash={}
+ hash['nick'] ||= 'nornagon'
+ @conn = Connector.new(server, port, hash)
+ @conn.add_observer(self)
+ @conn.open
+ end
+
+ def nick
+ @conn.nick
+ end
+
+ def update event, time
+ case event
+ when Event::CONNECTING
+ if self.respond_to?(:on_connecting)
+ self.on_connecting
+ end
+ when Event::CONNECTED
+ if self.respond_to?(:on_connected)
+ self.on_connected
+ end
+ when Event::DISCONNECTING
+ if self.respond_to?(:on_disconnecting)
+ self.on_disconnecting
+ end
+ when Event::DISCONNECTED
+ if self.respond_to?(:on_disconnected)
+ self.on_disconnected
+ end
+ when Event::MESG_RECV
+ mesg = @conn.read
+ args = mesg.split("\t")
+ cmd = args.shift
+ fun = ("on_cmd_" + cmd.downcase).to_sym
+ if self.respond_to?(fun)
+ self.send fun, *args
+ elsif self.respond_to?(:on_unhandled_cmd)
+ self.send :on_unhandled_cmd, cmd, args
+ end
+ if self.respond_to?(:on_mesg_recv)
+ self.send :on_mesg_recv, cmd, args
+ end
+ end
+ end
+
+ def on_connected
+ @conn.write ['HAVER', 'ravish/0.0.1']
+ end
+
+ def on_cmd_want cmd
+ case cmd
+ when 'IDENT'
+ @conn.write ['IDENT', @conn.nick]
+ end
+ end
+
+ def on_cmd_fail cmd, reason, *args
+ case cmd
+ when 'IDENT'
+ if reason == 'used.id'
+ @conn.nick += '_'
+ end
+ end
+ end
+
+ def cmd_join cid
+ @conn.write ['JOIN', cid]
+ end
+
+ def cmd_part cid
+ @conn.write ['PART', cid]
+ end
+
+ def cmd_list_users cid
+ @conn.write ['LIST', cid, 'user']
+ end
+
+ def cmd_msg cid, msg
+ @conn.write ['MSG', cid, '"', msg]
+ end
+
+ def cmd_action cid, act
+ @conn.write ['MSG', cid, ':', act]
+ end
+ end
+end
+
+# client class
+
+class HaverConnection < Haver::Client
+ def initialize server, port=7070, hash={}
+ super
+ end
+
+ def on_connecting
+ $vt.message "Connecting to [EMAIL PROTECTED]:[EMAIL PROTECTED]"
+ end
+
+ def on_connected
+ $vt.message "Connected."
+ super
+ end
+
+ def on_mesg_recv cmd, args
+ $vt.message "S: #{cmd} #{args.join(' ')}"
+ end
+
+ def on_cmd_haver version
+ $vt.message "Server version: #{version}"
+ end
+
+ def on_cmd_want cmd
+ $vt.message "Server wants #{cmd}"
+ super
+ end
+
+ def on_cmd_accept uid
+ $vt.message "Accepted UID #{uid}"
+ end
+
+ def on_cmd_join cid, uid
+ if uid == @conn.nick
+ $windows.push [cid, $vt.create_window]
+ self.cmd_list_users(cid)
+ end
+ end
+
+ def on_cmd_part cid, uid
+ if uid == @conn.nick
+ win = $windows.find { |w| w[0] == cid }
+ $vt.delete_window win[1]
+ $windows.delete_at $windows.index(win)
+ $vt.message "Left channel #{cid}"
+ end
+ end
+
+ def on_cmd_msg cid, uid, type, msg
+ win = $windows.find { |w| w[0] == cid }
+ msgpre = "%(nickdecs)<%(nick)#{uid}%(nickdecs)>%(default) "
+ mepre = "%(action)* #{uid}%(default) "
+ chpre = "%(channel)#{cid}%(chsep):%(default)"
+ case type
+ when '"'
+ if win
+ win[1].addline msg, msgpre
+ else
+ $windows[0][1].addline msg, chpre + msgpre
+ end
+ when ':'
+ if win
+ win[1].addline msg, mepre
+ else
+ $windows[0][1].addline msg, chpre + msgpre
+ end
+ end
+ end
+
+ def on_cmd_list cid, type, *args
+ case type
+ when 'user'
+ win = $windows.find { |w| w[0] == cid }
+ win ||= $windows[0]
+ win[1].print "Users in #{cid}: #{args.join ', '}"
+ else
+ $vt.message "Unknown LIST received: #{type}"
+ end
+ end
+
+ def cmd_raw *args
+ @conn.write args
+ end
+end
+
+$haver = nil
+
+# user commands
+
+ucmds = {
+ 'blah' => lambda { |*args|
+ $vt.current_window.print "args: #{args.join ', '}"
+ },
+ 'join' => lambda { |*args|
+ if $haver
+ $haver.cmd_join(args[0])
+ else
+ $vt.message "You are not connected to a server!"
+ end
+ },
+ 'raw' => lambda { |*args|
+ if $haver
+ $haver.cmd_raw(args)
+ else
+ $vt.message "You are not connected to a server!"
+ end
+ },
+ 'me' => lambda { |args|
+ if $haver
+ win = $windows.find { |w| w[1] == $vt.current_window }
+ if win
+ $haver.cmd_action(win[0], args.join(' '))
+ end
+ else
+ $vt.message "You are not connected to a server!"
+ end
+ },
+ 'part' => lambda { |args|
+ if $haver
+ if args.length < 1
+ win = $windows.find { |w| w[1] ==
$vt.current_window }
+ else
+ win = $windows.find { |w| w[0] == args[0] }
+ end
+ if win
+ $haver.cmd_part(win[0])
+ end
+ else
+ $vt.message "You are not connected to a server!"
+ end
+ }
+}
+
+# event loop!
+
+$haver = HaverConnection.new('hardison.net')
+
+loop do
+ cwin = $windows.find { |w| w[1] == $vt.current_window }
+ if line = $vt.getline
+ if line =~ %r|^[/.]+(.+)|
+ # we have a command!
+ args = $1.split
+ cmd = args.shift
+ if ucmds[cmd]
+ ucmds[cmd].call args
+ else
+ $vt.message "Unknown command: #{cmd}"
+ end
+ else
+ $haver.cmd_msg(cwin[0], line)
+ end
+ end
+
+ cwin[1].status =
"%(statdecs)[%(status)#{Time.now.strftime("%H:%M:%S")}" +
+ "%(statdecs)] [%(status)#{$haver.nick}%(statdecs)] [%(status)" +
+ "#{$windows.index(cwin) + 1}:#{cwin[0]}%(statdecs)]%(status)"
+
+ sleep 0.0001
+end