##
## server.py -- IRC server interface.
##
## Copyleft 1997,1998 Teemu Kalvas <chery@s2.org>
##

## +class Server             (p,       persistent storage object
##                            server)  configuration data
##  -method read             (p)       persistent storage object
##  -method write            (p)       persistent storage object
##  -method handle_XXX       (p,       persistent storage object
##                            nick,    nickname or server name of sender
##                            addr,    address of sender
##                            args)    command tail
##  -method handle_ctcp_XXX  (p,       persistent storage object
##                            nick,    nickname or server name of sender
##                            addr,    address of sender
##                            chan,    target channel or nickname
##                            args)    ctcp command arguments
##  -method sock
##  -method out
##  -method connected
##  -method quitting
##  -method nick

import config
import regex
import socket
import string
import time
from types import *

import crypt

def check_and_op(p, (lnick, chan, serv)):
    if serv.channels.has_key(chan):
	dict = serv.channels[chan][1]
	if dict.has_key(lnick) and '@' not in dict[lnick]:
	    serv.out.append('MODE ' + chan + ' +o ' + lnick)

class Server:
    def __init__(self, p, server):
	self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	self.id = server[:2]
	self.sock.connect(self.id)
	self.nicks = server[2]
	if config.realname == '':
	    config.realname = 'Happy Clirc User'
	self.out = ['USER ' + config.login + ' * * :' + config.realname,
		    'NICK ' + self.nicks[0]]
	self.autojoin = server[3]
	self.ind = ''
	self.window = p.screen.new_window('(none)', self)
	self.channels = {}
	self.rcline = regex.compile(':\([^ !]*\)\(!\([^ ]*\)\)? \([a-zA-Z0-9]*\) \(.*\)')
	self.rcctcp = regex.compile('\001\([^ ]+\)\( \(.*\)\)?\001')
	self.connected = 1
	self.quitting = 0
	self.nick = '(no one)'
        self.msg_nicklist = []

    def reconnect(self, p):
	self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	self.sock.connect(self.id)
	self.out = ['USER ' + config.login + ' * * :' + config.realname,
		    'NICK ' + self.nicks[0]]
	self.ind = ''
	self.autojoin = []
	for chan in self.channels.keys():
	    if chan[0] in '#&':
		self.autojoin.append(chan)
		del self.channels[chan]
	self.connected = 1
	self.nick = '(no one)'

    def clean(self, p):
	p.servers.remove(self)
	win = p.screen.current.next
	while win <> p.screen.current:
	    next = win.next
	    if win.server == self:
		win.next.prev = win.prev
		win.prev.next = win.next
		del win
	    win = next
	if win.next <> win:
	    p.screen.current = win.next
	    p.screen.touched = p.screen.touched | 63
	    win.next.prev = win.prev
	    win.prev.next = win.next
	else:
	    p.quit = 1

    def read(self, p):
	try:
	    ind = self.sock.recv(768)
	except socket.error, param:
	    self.connected = 0
            self.window.log('*** disconnected by exception: ' + repr(param))
	    return
	if ind == '':
	    self.connected = 0
            self.window.log('*** disconnected by remote host')
            self.sock.close()
	    return
	self.ind = self.ind + ind
	f = string.find(self.ind, '\r\n')
	while f <> -1:
	    line, self.ind = self.ind[:f], self.ind[f + 2:]
	    p.rand = p.rand ^ hash(line)
	    if regex.match('PING', line) > 0:
		self.out.append('PONG' + line[4:])
            elif regex.match('ERROR', line) > 0:
                self.connected = 0
                self.window.log('*** disconnected: ' + line[6:])
                self.sock.close()
	    elif self.rcline.match(line) > 0:
		nick, addr, command, tail = self.rcline.group(1, 3, 4, 5)
		command = string.lower(command)
		args = []
		while 1:
		    if len(tail) == 0:
			break
		    if tail[0] == ':':
			args.append(tail[1:])
			break
		    f = string.find(tail, ' ')
		    if f == -1:
			args.append(tail)
			break
		    args.append(tail[:f])
		    tail = tail[f + 1:]
		if not addr or config.ignore.match(addr) <= 0:
                    handler = 'handle_' + command
                    if hasattr(self, handler):
                        getattr(self, handler)(p, nick, addr, args)
		    elif config.verbose:
			self.window.log('*** unknown: '
					+ repr((nick, addr, command, args)))
	    f = string.find(self.ind, '\r\n')

    def write(self, p):
	self.sock.send(self.out[0] + '\r\n')
	del self.out[0]

    ## :irc-2.cs.hut.fi 001 ilciin :Welcome to the Internet Relay Network \
    ## ilciin!chery@a31b.mtalo.ton.tut.fi
    def handle_001(self, p, nick, addr, args):
	self.name = nick
	self.window.set_name(nick)
	self.nick = args[0]
	for chan in self.autojoin:
            if type(chan) == TupleType:
                self.out.append('JOIN ' + chan[0] + ' :' + chan[1])
            else:
                self.out.append('JOIN ' + chan)

    def handle_002(self, p, nick, addr, args):
	pass

    def handle_003(self, p, nick, addr, args):
	pass

    def handle_004(self, p, nick, addr, args):
	pass

    def handle_215(self, p, nick, addr, args):
        self.window.log('*** ' + nick + ' stats: '
                        + reduce(lambda x, y: x + ' ' + y, args[1:]))

    def handle_218(self, p, nick, addr, args):
        self.window.log('*** ' + nick + ' stats: '
                        + reduce(lambda x, y: x + ' ' + y, args[1:]))

    def handle_219(self, p, nick, addr, args):
        pass

    def handle_251(self, p, nick, addr, args):
	self.window.log('*** ' + args[1])

    def handle_252(self, p, nick, addr, args):
	self.window.log('*** ' + args[1] + ' ' + args[2])

    def handle_253(self, p, nick, addr, args):
	self.window.log('*** ' + args[1] + ' ' + args[2])

    def handle_254(self, p, nick, addr, args):
	self.window.log('*** ' + args[1] + ' ' + args[2])

    def handle_255(self, p, nick, addr, args):
	self.window.log('*** ' + args[1])

    def handle_301(self, p, nick, addr, args):
        self.window.log('*** ' + args[1] + ' is away: ' + args[2])

    ## :irc.atm.tut.fi 311 ilciin jma jma one.sci.fi * :Joonas Mäkinen
    def handle_311(self, p, nick, addr, args):
	self.window.log('*** ' + args[1] + ' (' + args[2] + '@' + args[3] +
			') is ' + args[5])

    ## :irc.atm.tut.fi 312 ilciin jma irc.atm.tut.fi :Tampere Univ of Tech \
    ## <IRC Net>
    def handle_312(self, p, nick, addr, args):
	self.window.log('*** ' + args[1] + ' is on irc via ' + args[2] + ' (' +
			args[3] + ')')

    ## :irc.jyu.fi 313 ilciin ville :is as IRC Operator
    def handle_313(self, p, nick, addr, args):
	self.window.log('*** ' + args[1] + ' ' + args[2])

    ## :irc.atm.tut.fi 317 ilciin jma 298 :seconds idle
    def handle_317(self, p, nick, addr, args):
	self.window.log('*** ' + args[1] + ' has been ' + args[2] + ' ' +
			args[3])

    ## :irc.atm.tut.fi 318 ilciin jma :End if /WHOIS list.
    def handle_318(self, p, nick, addr, args):
	pass

    ## :irc.atm.tut.fi 319 ilciin jma :#yakki @#pallomeri
    def handle_319(self, p, nick, addr, args):
	self.window.log('*** ' + args[1] + ' is on channels: ' + args[2])

    ## :irc.pspt.fi 332 ilciin #tampere :Ristipolytysta 17,90 mk/min
    def handle_332(self, p, nick, addr, args):
	self.channels[config.low(args[1])][0].log('*** topic on ' + args[1] +
						  ': ' + args[2])

    ## :irc-2.cs.hut.fi 353 ilciin = #qwer :@ilciin 
    def handle_353(self, p, nick, addr, args):
	chan = config.low(args[2])
        if self.channels.has_key(chan):
            win = self.channels[chan][0]
            for nick in string.split(args[3]):
                if nick[0] == '@' or nick[0] == '+':
                    lnick = config.low(nick[1:])
                else:
                    lnick = config.low(nick)
                self.channels[chan][1][lnick] = filter(
                    lambda x: x in '@+', nick)
        else:
            win = self.window
        win.log('*** users on ' + args[2] + ': ' + args[3])

    def handle_366(self, p, nick, addr, args):
	pass

    ## :irc.warwick.ac.uk 367 cliini #Tampere :*!*@*dyn.sci.fi
    def handle_367(self, p, nick, addr, args):
	chan = config.low(args[1])
	self.channels[chan][0].log('*** ban: ' + args[2])

    def handle_368(self, p, nick, addr, args):
	pass

    def handle_372(self, p, nick, addr, args):
	if config.motd:
	    self.window.log('*M* ' + args[1])

    def handle_375(self, p, nick, addr, args):
	if config.motd:
	    self.window.log('*M* ' + args[1])

    def handle_376(self, p, nick, addr, args):
	pass

    ## :irc-2.cs.hut.fi 433 ilciin cliini :Nickname is already in use.
    def handle_433(self, p, nick, addr, args):
	self.nicks = self.nicks[1:] + self.nicks[:1]
	self.out.append('NICK ' + self.nicks[0])

    ## :irc.pspt.fi 437 * cliini :Nick/channel is temporarily unavailable
    def handle_437(self, p, nick, addr, args):
	self.nicks = self.nicks[1:] + self.nicks[:1]
	self.out.append('NICK ' + self.nicks[0])

    ## :ilciin!chery@a31b.mtalo.ton.tut.fi JOIN :#qwer
    def handle_join(self, p, nick, addr, args):
	f = string.find(args[0], '\007')
	mode = ''
	if f == -1:
	    chan = config.low(args[0])
	else:
	    chan = config.low(args[0][:f])
	    if 'o' in args[0][f + 1:]:
		mode = mode + '@'
	    if 'v' in args[0][f + 1:]:
		mode = mode + '+'
	lnick = config.low(nick)
	if lnick == config.low(self.nick):
	    win = p.screen.current.next
	    while win <> p.screen.current:
		if config.low(win.name) == chan and win.server == self:
		    break
		win = win.next
	    else:
		if config.low(win.name) <> chan or win.server <> self:
		    win = p.screen.new_window(chan, self)
	    self.channels[chan] = win, {}
	if f == -1:
            self.display_join(self.channels[chan][0], nick, '', addr)
	else:
            self.display_join(self.channels[chan][0], nick, args[0][f + 1:],
                              addr)
	self.channels[chan][1][lnick] = mode
	if config.autoop(addr, chan):
	    p.wait_add(time.time() + (p.rand % 17000) / 1000.0 + 5.0,
		       check_and_op, (lnick, chan, self))

    def display_join(self, win, nick, modes, addr):
        if modes:
            win.log('*** ' + nick + ' +' + modes + ' (' + addr
                    + ') has joined')
        else:
            win.log('*** ' + nick + ' (' + addr + ') has joined')

    ## :ilciin!chery@a31b.mtalo.ton.tut.fi KICK #qwer ilciin :bleh
    def handle_kick(self, p, nick, addr, args):
	chan = config.low(args[0])
	lnick = config.low(args[1])
        self.display_kick(self.channels[chan][0], nick, args[1], args[2])
	del self.channels[chan][1][lnick]
	if lnick == config.low(self.nick):
	    self.channels[chan] = self.channels[chan][0], {}

    def display_kick(self, win, actor, victim, message):
        win.log('*** ' + actor + ' has kicked ' + victim + ' (' + message
                + ')')

    ## :ilciin!chery@a31b.mtalo.ton.tut.fi MODE #qwer +snt 
    def handle_mode(self, p, nick, addr, args):
	chan = config.low(args[0])
	if self.channels.has_key(chan):
            self.display_mode(self.channels[chan][0], nick,
                              reduce(lambda x, y: x + ' ' + y, args[1:]))
	    chs, args = args[1], args[2:]
	    for ch in chs:
		if ch == '+':
		    sign = 1
		elif ch == '-':
		    sign = 0
		## someone ought to explain to me the +o with no argument modes
		## from servers after splits
		elif ch in 'ov' and len(args) > 0:
		    dict = self.channels[chan][1]
		    lnick = config.low(args[0])
		    ## ignore breaks the whole thing :\
		    if dict.has_key(lnick):
			if sign:
			    if ch == 'o':
				if '@' not in dict[lnick]:
				    dict[lnick] = dict[lnick] + '@'
			    elif ch == 'v':
				if '+' not in dict[lnick]:
				    dict[lnick] = dict[lnick] + '+'
			elif ch == 'o':
			    dict[lnick] = filter(lambda x: x <> '@',
						 dict[lnick])
			else:
			    dict[lnick] = filter(lambda x: x <> '+',
						 dict[lnick])
		    del args[0]
		elif ch in 'bk' or (ch == 'l' and sign):
		    del args[0]
	else:
            self.display_mode(self.window, nick,
                              reduce(lambda x, y: x + ' ' + y, args))

    def display_mode(self, win, nick, mode):
        win.log('*** ' + nick + ' has set mode ' + mode)

    ## :ilciin!chery@a31b.mtalo.ton.tut.fi NICK :niicli
    def handle_nick(self, p, nick, addr, args):
	lnick = config.low(nick)
	for chan in self.channels.values():
	    if chan[1].has_key(lnick):
                self.display_nick(chan[0], nick, args[0])
		if lnick <> config.low(args[0]):
		    chan[1][config.low(args[0])] = chan[1][lnick]
		    del chan[1][lnick]
	if lnick == config.low(self.nick):
	    self.nick = args[0]
	    p.screen.touched = p.screen.touched | 16

    def display_nick(self, win, old_nick, new_nick):
        win.log('*** ' + old_nick + ' is now known as ' + new_nick)

    ## :ilciin!chery@a31b.mtalo.ton.tut.fi PART #qwer :Nymmä lähren enkä \
    ## takasin tuu!
    def handle_part(self, p, nick, addr, args):
	chan = config.low(args[0])
	lnick = config.low(nick)
	if len(args) > 1:
            self.display_part(self.channels[chan][0], nick, args[1])
	else:
            self.display_part(self.channels[chan][0], nick)
	del self.channels[chan][1][lnick]
	if lnick == config.low(self.nick):
	    win = p.screen.current
	    while win <> self.channels[chan][0]:
		win = win.next
	    if win == p.screen.current:
		p.screen.current = win.next
		p.screen.touched = p.screen.touched | 63
	    win.next.prev = win.prev
	    win.prev.next = win.next
	    del self.channels[chan]

    def display_part(self, win, nick, reason = None):
        if reason == None:
            win.log('*** ' + nick + ' has left')
        else:
            win.log('*** ' + nick + ' has left (' + reason + ')')

    def handle_pong(self, p, nick, addr, args):
	self.window.log('*** server pong from ' + nick + ': ' +
			repr(time.time() - self.ping_time) + 's')

    ## :cliini!chery@tjtk2.trin.cam.ac.uk PRIVMSG ilciin :bleh
    def handle_privmsg(self, p, nick, addr, args):
	chan = config.low(args[0])
	lnick = config.low(nick)
	if self.rcctcp.match(args[1]) > 0:
	    command, tail = self.rcctcp.group(1, 3)
	    command = 'handle_ctcp_' + string.lower(command)
            if hasattr(self, command):
                getattr(self, command)(p, nick, addr, chan, tail)
	else:
	    dm = crypt.decrypt_message(args[1])
	    n = 1 + (dm[0] > 0)
	    if dm[0]:
		if self.channels.has_key(chan):
                    self.display_privmsg(self.channels[chan][0], nick, addr,
                                         dm[1], dm[0] > 0, 0)
		elif self.channels.has_key(lnick):
                    self.display_privmsg(self.channels[lnick][0], nick, addr,
                                         dm[1], dm[0] > 0, 0)
		else:
                    self.display_privmsg(self.window, nick, addr, dm[1],
                                         dm[0] > 0, 1)
	    else:
		self.window.log('*** [crypt] ' + dm[1])

    def display_privmsg(self, win, nick, addr, message, crypt, private):
        n = crypt + 1
        if private:
            win.log(n*'*' + nick + n*'*' + ' ' + message)
        else:
            win.log(n*'<' + nick + n*'>' + ' ' + message)

    ## ERROR :Closing Link: ilciin[chery@a31b.mtalo.ton.tut.fi] (ör.)
    def handle_quit(self, p, nick, addr, args):
	lnick = config.low(nick)
	for chan in self.channels.values():
	    if chan[1].has_key(lnick):
		chan[0].log('*** ' + nick + ' has quit irc (' + args[0] + ')')
		del chan[1][lnick]

    ## :ilciin!chery@tjtk2.trin.cam.ac.uk TOPIC #Tampere :Voi olla vain yksi \
    ## Kaipila.
    def handle_topic(self, p, nick, addr, args):
	self.channels[config.low(args[0])][0].log('*** ' + nick +
						  ' has set the topic on ' +
						  args[0] + ' to: ' + args[1])

    def handle_ctcp_action(self, p, nick, addr, chan, args):
	if self.channels.has_key(chan):
	    self.channels[chan][0].log('* ' + nick + ' ' + args)
	else:
	    self.window.log('* ' + nick + ' ' + args)

    def handle_ctcp_dcc(self, p, nick, addr, chan, args):
	self.window.log('*** DCC request ' + repr(args) + ' from ' + nick)
	#args = string.split(args, ' ')
	#if len(args) == 5 and string.lower(args[0]) == 'send':
	#    popen('dcc receive ' + args[2] + ' ' + args[3] + ' ' + args[4] +
	#	  ' ' + args[1], 'r')

    def handle_ctcp_ping(self, p, nick, addr, chan, args):
	if args == None:
	    args = ''
	self.out.append('NOTICE ' + nick + ' :\001PING ' + args + '\001')

    def handle_ctcp_version(self, p, nick, addr, chan, args):
	self.out.append('NOTICE ' + nick + ' :\001VERSION ' + config.version +
			'\001')

## End. ##
