bot

a bot object handles the dispatching of commands and check for callbacks that need to be fired.

class fbf.drivers.irc.bot.IRCBot(cfg={}, users=None, plugs=None, *args, **kwargs)

Bases: fbf.drivers.irc.irc.Irc

class that dispatches commands and checks for callbacks to fire.

broadcast(txt)

broadcast txt to all joined channels.

getchannelmode(channel)

send MODE request for channel.

gettopic(channel, event=None)

get topic data.

handle_311(ievent)

handle 311 response .. sync with userhosts cache.

handle_324(ievent)

handle mode request responses.

handle_352(ievent)

handle 352 response .. sync with userhosts cache.

handle_353(ievent)

handle 353 .. check if we are op.

handle_ievent(ievent)

check for callbacks, call Irc method.

handle_invite(ievent)

join channel if invited by OPER.

handle_join(ievent)

handle joins.

handle_kick(ievent)

handle kick event.

handle_mode(ievent)

check if mode is about channel if so request channel mode.

handle_nick(ievent)

update userhost cache on nick change.

handle_part(ievent)

handle parts.

handle_privmsg(ievent)

check if PRIVMSG is command, if so dispatch.

handle_quit(ievent)

check if quit is because of a split.

join(channel, password=None)

join a channel .. use optional password.

settopic(channel, txt)

set topic of channel to txt.

CODE

# fbf/socklib/irc/bot.py
#
#
#

"""
    a bot object handles the dispatching of commands and check for callbacks
    that need to be fired.

"""

fbf imports

from fbf.utils.exception import handle_exception
from fbf.utils.generic import waitforqueue, uniqlist, strippedtxt
from fbf.lib.commands import cmnds
from fbf.lib.callbacks import callbacks
from fbf.lib.plugins import plugs as plugins
from fbf.lib.threads import start_new_thread, threaded
from fbf.utils.dol import Dol
from fbf.utils.pdod import Pdod
from fbf.lib.persiststate import PersistState
from fbf.lib.errors import NoSuchCommand
from fbf.lib.channelbase import ChannelBase
from fbf.lib.exit import globalshutdown
from fbf.lib.botbase import BotBase
from fbf.lib.eventbase import EventBase
from fbf.lib.partyline import partyline
from fbf.lib.wait import waiter

from .channels import Channels
from .irc import Irc
from .ircevent import IrcEvent

basic imports

from collections import deque
import re
import socket
import struct
import queue
import time
import os
import types
import logging

defines

dccchatre = re.compile('\001DCC CHAT CHAT (\S+) (\d+)\001', re.I)

classes

class IRCBot(Irc):

    """ class that dispatches commands and checks for callbacks to fire. """

    def __init__(self, cfg={}, users=None, plugs=None, *args, **kwargs):
        Irc.__init__(self, cfg, users, plugs, *args, **kwargs)
        if self.state:
            if 'opchan' not in self.state: self.state['opchan'] = []
        if 'joinedchannels' not in self.state: self.state['joinedchannels'] = []

    def _resume(self, data, botname, reto=None):
        """ resume the bot. """
        if not Irc._resume(self, data, botname, reto): return 0
        for channel in self.state['joinedchannels']: self.who(channel)
        return 1

    def _dccresume(self, sock, nick, userhost, channel=None):
        """ resume dcc loop. """
        if not nick or not userhost: return
        start_new_thread(self._dccloop, (sock, nick, userhost, channel))

    def _dcclisten(self, nick, userhost, channel):
        """ accept dcc chat requests. """
        try:
            listenip = socket.gethostbyname(socket.gethostname())
            (port, listensock) = getlistensocket(listenip)
            ipip2 = socket.inet_aton(listenip)
            ipip = struct.unpack('>L', ipip2)[0]
            chatmsg = 'DCC CHAT CHAT %s %s' % (ipip, port)
            self.ctcp(nick, chatmsg)
            self.sock = sock = listensock.accept()[0]
        except Exception as ex:
            handle_exception()
            logging.error('%s - dcc error: %s' % (self.cfg.name, str(ex)))
            return
        self._dodcc(sock, nick, userhost, channel)

    def _dodcc(self, sock, nick, userhost, channel=None):
        """ send welcome message and loop for dcc commands. """
        if not nick or not userhost: return
        try:
            sock.send(bytes('Welcome to the FBFBOT partyline ' + nick + " ;]\n", self.encoding or "utf-8"))
            partylist = partyline.list_nicks()
            if partylist: sock.send(bytes("people on the partyline: %s\n" % ' .. '.join(partylist, self.encoding or "utf-8")))
            sock.send(bytes("control character is ! .. bot broadcast is @\n", self.encoding or "utf-8"))
        except Exception as ex:
            handle_exception()
            logging.error('%s - dcc error: %s' % (self.cfg.name, str(ex)))
            return
        start_new_thread(self._dccloop, (sock, nick, userhost, channel))

    def _dccloop(self, sock, nick, userhost, channel=None):
        """ loop for dcc commands. """
        sockfile = sock.makefile('r')
        sock.setblocking(True)
        res = ""
        partyline.add_party(self, sock, nick, userhost, channel)
        while 1:
            time.sleep(0.01)
            try:
                res = sockfile.readline()
                logging.warn("%s got %s (%s)" % ( userhost, res, self.cfg.name))
                if self.stopped or not res:
                    logging.warn('closing dcc with %s (%s)' % (nick, self.cfg.name))
                    partyline.del_party(nick)
                    return
            except socket.timeout:
                continue
            except socket.error as ex:
                try:
                    (errno, errstr) = ex
                except:
                    errno = 0
                    errstr = str(ex)
                if errno == 35 or errno == 11:
                    continue
                else:
                    raise
            except Exception as ex:
                handle_exception()
                logging.warn('closing dcc with %s' % (nick, self.cfg.name))
                partyline.del_party(nick)
                return
            try:
                res = self.normalize(res)
                ievent = IrcEvent()
                ievent.printto = sock
                ievent.bottype = "irc"
                ievent.nick = nick
                ievent.userhost = userhost
                ievent.auth = userhost
                ievent.channel = channel or ievent.userhost
                ievent.origtxt = res
                ievent.txt = res
                ievent.cmnd = 'DCC'
                ievent.cbtype = 'DCC'
                ievent.bot = self
                ievent.sock = sock
                ievent.speed = 1
                ievent.isdcc = True
                ievent.msg = True
                logging.debug("%s - dcc - constructed event" % self.cfg.name)
                ievent.bind()
                if ievent.hascc():
                    ievent.iscommand = True
                    ievent.showall = True
                    ievent.nodispatch = False
                    ievent.bind()
                    self.put(ievent)
                    continue
                elif ievent.txt[0] == "@":
                    partyline.say_broadcast_notself(ievent.nick, "[%s] %s" % (ievent.nick, ievent.txt))
                    q = queue.Queue()
                    ievent.queues = [q]
                    ievent.txt = ievent.txt[1:]
                    self.doevent(ievent)
                    result = waitforqueue(q, 3000)
                    if result:
                        for i in result:
                            partyline.say_broadcast("[bot] %s" % i)
                    continue
                else:
                    partyline.say_broadcast_notself(ievent.nick, "[%s] %s" % (ievent.nick, ievent.txt))
            except socket.error as ex:
                try:
                    (errno, errstr) = ex
                except:
                    errno = 0
                    errstr = str(ex)
                if errno == 35 or errno == 11:
                    continue
            except Exception as ex:
                handle_exception()
        sockfile.close()
        logging.warn('closing dcc with %s (%s)' %  (nick, self.cfg.name))

    def _dccconnect(self, nick, userhost, addr, port):
        """ connect to dcc request from nick. """
        try:
            port = int(port)
            logging.warn("connecting to %s:%s -=- %s (%s)" % (addr, port, userhost, self.cfg.name))
            if re.search(':', addr):
                sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
                sock.connect((addr, port))
            else:
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.connect((addr, port))
        except Exception as ex:
            logging.error('%s - dcc error: %s' % (self.cfg.name, str(ex)))
            return
        self._dodcc(sock, nick, userhost, userhost)

    def broadcast(self, txt):
        """ broadcast txt to all joined channels. """
        for i in self.state['joinedchannels']: self.say(i, txt)

    def getchannelmode(self, channel):
        """ send MODE request for channel. """
        if not channel:
            return
        self.putonqueue(9, None, 'MODE %s' % channel)

    def join(self, channel, password=None):
        """ join a channel .. use optional password. """
        chan = ChannelBase(channel, self.cfg.name)
        if password:
            chan.data.key = password.strip()
            chan.save()
        result = Irc.join(self, channel, chan.data.key)
        if result != 1:
            return result
        got = False
        if not chan.data.cc:
            chan.data.cc = self.cfg.defaultcc or '!;'
            got = True
        if not chan.data.perms:
            chan.data.perms = []
            got = True
        if not chan.data.mode:
            chan.data.mode = ""
            got = True
        if got:
            chan.save()
        self.getchannelmode(channel)
        return 1

    def handle_privmsg(self, ievent):
        """ check if PRIVMSG is command, if so dispatch. """
        if ievent.nick in self.nicks401:
            logging.debug("%s - %s is available again" % (self.cfg,name, ievent.nick))
            self.nicks401.remove(ievent.nick)
        if not ievent.txt: return
        ievent.nodispatch = False
        chat = re.search(dccchatre, ievent.txt)
        if chat:
            if self.users.allowed(ievent.userhost, 'USER'):
                start_new_thread(self._dccconnect, (ievent.nick, ievent.userhost, chat.group(1), chat.group(2)))
                return
        if '\001' in ievent.txt:
            Irc.handle_privmsg(self, ievent)
            return
        ievent.bot = self
        ievent.sock = self.sock
        chan = ievent.channel
        if chan == self.cfg.nick:
            ievent.msg = True
            ievent.speed =  4
            ievent.printto = ievent.nick
            ccs = ['!', '@', self.cfg['defaultcc']]
            if ievent.isresponse:
                return
            if self.cfg['noccinmsg'] and self.msg:
                self.put(ievent)
            elif ievent.txt[0] in ccs:
                self.put(ievent)
            return
        self.put(ievent)

    def handle_join(self, ievent):
        """ handle joins. """
        if ievent.nick in self.nicks401:
             logging.debug("%s - %s is available again" % (self.cfg.name, ievent.nick))
             self.nicks401.remove(ievent.nick)
        chan = ievent.channel
        nick = ievent.nick
        if nick == self.cfg.nick:
            logging.warn("joined %s (%s)" % (ievent.channel, self.cfg.name))
            time.sleep(0.5)
            self.who(chan)
            return
        logging.info("%s - %s joined %s" % (self.cfg.name, ievent.nick, ievent.channel))
        self.userhosts[nick] = ievent.userhost

    def handle_kick(self, ievent):
        """ handle kick event. """
        try:
            who = ievent.arguments[1]
        except IndexError:
            return
        chan = ievent.channel
        if who == self.cfg.nick:
            if chan in self.state['joinedchannels']:
                self.state['joinedchannels'].remove(chan)
                self.state.save()

    def handle_nick(self, ievent):
        """ update userhost cache on nick change. """
        nick = ievent.txt
        self.userhosts[nick] = ievent.userhost
        if ievent.nick == self.cfg.nick or ievent.nick == self.cfg.orignick:
            self.cfg['nick'] = nick
            self.cfg.save()

    def handle_part(self, ievent):
        """ handle parts. """
        chan = ievent.channel
        if ievent.nick == self.cfg.nick:
            logging.warn('parted channel %s (%s)' % (chan, self.cfg.name))
            if chan in self.state['joinedchannels']:
                self.state['joinedchannels'].remove(chan)
                self.state.save()

    def handle_ievent(self, ievent):
        """ check for callbacks, call Irc method. """
        try:
            Irc.handle_ievent(self, ievent)
            if ievent.cmnd == 'JOIN' or ievent.msg:
                if ievent.nick in self.nicks401: self.nicks401.remove(ievent.nick)
            if ievent.cmnd != "PRIVMSG":
                i = IrcEvent()
                i.copyin(ievent)
                i.bot = self
                i.sock = self.sock
                ievent.nocb = True
                self.doevent(i)
        except:
            handle_exception()

    def handle_quit(self, ievent):
        """ check if quit is because of a split. """
        if '*.' in ievent.txt or self.cfg.server in ievent.txt: self.splitted.append(ievent.nick)

    def handle_mode(self, ievent):
        """ check if mode is about channel if so request channel mode. """
        logging.warn("mode change %s (%s)" % (str(ievent.arguments), self.cfg.name))
        try:
            dummy = ievent.arguments[2]
        except IndexError:
            chan = ievent.channel
            self.getchannelmode(chan)
            if not ievent.chan: ievent.bind(self)
            if ievent.chan:
                ievent.chan.data.mode = ievent.arguments[1]
                ievent.chan.save()

    def handle_311(self, ievent):
        """ handle 311 response .. sync with userhosts cache. """
        target, nick, user, host, dummy = ievent.arguments
        nick = nick
        userhost = "%s@%s" % (user, host)
        logging.debug('%s - adding %s to userhosts: %s' % (self.cfg.name, nick, userhost))
        self.userhosts[nick] = userhost

    def handle_352(self, ievent):
        """ handle 352 response .. sync with userhosts cache. """
        args = ievent.arguments
        channel = args[1]
        nick = args[5]
        user = args[2]
        host = args[3]
        userhost = "%s@%s" % (user, host)
        logging.debug('%s - adding %s to userhosts: %s' % (self.cfg.name, nick, userhost))
        self.userhosts[nick.lower()] = userhost.lower()
        self.nicks[userhost.lower()] = nick.lower()

    def handle_353(self, ievent):
        """ handle 353 .. check if we are op. """
        userlist = ievent.txt.split()
        chan = ievent.channel
        for i in userlist:
            if i[0] == '@' and i[1:] == self.cfg.nick:
                if chan not in self.state['opchan']:
                    self.state['opchan'].append(chan)

    def handle_324(self, ievent):
        """ handle mode request responses. """
        if not ievent.chan: ievent.bind(self, force=True)
        ievent.chan.data.mode = ievent.arguments[2]
        ievent.chan.save()

    def handle_invite(self, ievent):
        """ join channel if invited by OPER. """
        if self.users and self.users.allowed(ievent.userhost, ['OPER', ]): self.join(ievent.txt)

    def settopic(self, channel, txt):
        """ set topic of channel to txt. """
        self.putonqueue(7, None, 'TOPIC %s :%s' % (channel, txt))

    def gettopic(self, channel, event=None):
        """ get topic data. """
        q = queue.Queue()
        i332 = waiter.register("332", queue=q)
        i333 = waiter.register("333", queue=q)
        self.putonqueue(7, None, 'TOPIC %s' % channel)
        res = waitforqueue(q, 5000)
        who = what = when = None
        for r in res:
            if not r.postfix: continue
            try:
                if r.cmnd == "332": what = r.txt ; waiter.ready(i332) ; continue
                waiter.ready(i333)
                splitted = r.postfix.split()
                who = splitted[2]
                when = float(splitted[3])
            except (IndexError, ValueError): continue
            return (what, who, when)

Table Of Contents

Previous topic

irc

Next topic

channels

This Page