Source code for zbot.drivers.irc

# zbot/drivers/irc.py
#
#

""" IRC bot. """

## IMPORTS

from zbot import Bot, Object, RemoteDisconnect, TargetMissing, kernel, handle_loop
from zbot.utils import error, split_txt, dispatch, resolve

import collections
import threading
import logging
import _thread
import socket
import time
import sys
import os
import re

## irc_parse function

[docs]def irc_parse(obj, *args, **kwargs): rawstr = str(args[0]) self = obj self.servermsg = False splitted = re.split('\s+', rawstr) if not rawstr[0] == ':': self.servermsg = True self.prefix = splitted[0] if self.servermsg: self.etype = self.prefix else: self.etype = splitted[1] try: nickuser = self.prefix.split('!') self.origin = nickuser[1] self.nick = nickuser[0][1:] except IndexError: self.origin = self.prefix ; self.servermsg = True if self.etype in pfc: self.arguments = splitted[2:pfc[self.etype]+2] txtsplit = re.split('\s+', rawstr, pfc[self.etype]+2) self.txt = txtsplit[-1] else: self.arguments = splitted[2:] if self.arguments: self.target = self.arguments[0] self.postfix = ' '.join(self.arguments) if not "txt" in self: self.txt = rawstr.rsplit(":")[-1] if self.txt.startswith(":"): self.txt = self.txt[1:] if not "channel" in self: for c in self.arguments + [self.txt, ]: if c.startswith("#"): self.channel = c if self.servermsg: self.origin = self.origin[1:-1] self.channel = self.origin if not "origin" in self: self.origin = self.channel if not "target" in self: self.target = self.channel or self.origin return obj ## IRCBot
[docs]class IRCBot(Bot): marker = "\r\n" cc = "." def __init__(self, *args, **kwargs): Bot.__init__(self, *args, **kwargs) self.connected = threading.Event() self.register("250", self._onconnect) self.register("001", self._onconnect) self.register("513", self.handle_513) self.register("433", self.handle_433) self.register("PING", self.handle_ping) self.register("INVITE", self.handle_invite) self.register("PRIVMSG", self.handle_privmsg) self._lock = _thread.allocate_lock() self._buffer = [] self._lastline = "" self.encoding = "utf-8" self.server = self.server or "localhost" self.port = self.port or 6667 self.nick = self.nick or "zbot" self.name = self.name or self.etype if not self.channels: self.channels = [] if self.channel and self.channel not in self.channels: self.channels.append(self.channel) def _raw(self, txt): logging.warn("> %s" % txt) if not txt.endswith(self.marker): txt += self.marker itxt = bytes(txt, self.encoding) if self.outer: o = self.outer ; o.write(itxt) ; o.write("\n") ; o.flush() ; return if 'ssl' in self and self.ssl: self.sock.write(itxt) else: self.sock.send(itxt[:512]) def _connect(self): self.stopped = False if self.ipv6: self.oldsock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) else: self.oldsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server = self.bind() logging.warn('connect %s - %s (%s)' % (self.server, self.port, self.name)) self.oldsock.settimeout(60) self.oldsock.connect((self.server, int(str(self.port or 6667)))) self.blocking = 1 self.oldsock.setblocking(self.blocking) logging.warn('connection made to %s (%s)' % (self.server, self.name)) self.fsock = self.oldsock.makefile("r") if self.blocking: socktimeout = 301.0 self.oldsock.settimeout(socktimeout) if 'ssl' in self and self['ssl']: self.sock = socket.ssl(self.oldsock) else: self.sock = self.oldsock self.connecttime = time.time() return True def _onconnect(self, *args, **kwargs): logging.warn("logged on !!") if "onconnect" in self: time.sleep(2) ; self._raw(self.onconnect) if "servermodes" in self: self._raw("MODE %s %s" % (self.nick, self.servermodes)) self.join_channels(*args, **kwargs) self.ready() def _dodcc(self, event, s): s.send(bytes('Welcome to ZBOT ' + event.nick + " !!\n", self.encoding)) _thread.start_new_thread(self._dccloop, (event, s)) def _dccloop(self, event, s): sockfile = s.makefile('rw') s.setblocking(True) while 1: try: res = sockfile.readline() if not res: break res = res.rstrip() logging.warn("< DCC %s" % res) o = Object(_target=self, txt=res) o.outer = sockfile self.put(o) except socket.timeout: time.sleep(0.01) except socket.error as ex: if ex.errno in [EAGAIN, ]: continue else: raise except Exception as ex: error() sockfile.close() def _dccconnect(self, event): try: addr = event.args[3] ; port = event.args[4][:-1] port = int(port) if re.search(':', addr): s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) else: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((addr, port)) except Exception as ex: error() ; return self._dodcc(event, s)
[docs] def get_one(self, *args, **kwargs): if not self._buffer: self.read_some() line = self._buffer.pop(0) e = Object() e._target = self o = irc_parse(e, line.rstrip()) return o
[docs] def read_some(self, *args, **kwargs): try: if "ssl" in self and self.ssl: inbytes = self.sock.read() else: inbytes = self.sock.recv(512) except Exception as ex: error() txt = str(inbytes, self.encoding) if txt == "": raise RemoteDisconnect() self._lastline += txt splitted = self._lastline.split(self.marker) for s in splitted[:-1]: logging.warn("< %s" % s) self._buffer.append(s) self._lastline = splitted[-1]
[docs] def send(self, txt): self._raw(txt)
[docs] def bind(self): server = self.server try: self.oldsock.bind((server, 0)) except socket.error: if not server: try: socket.inet_pton(socket.AF_INET6, self.server) except socket.error: pass else: server = self.server if not server: try: socket.inet_pton(socket.AF_INET, self.server) except socket.error: pass else: server = self.server if not server: ips = [] try: for item in socket.getaddrinfo(self.server, None): if item[0] in [socket.AF_INET, socket.AF_INET6] and item[1] == socket.SOCK_STREAM: ip = item[4][0] if ip not in ips: ips.append(ip) except socket.error: pass else: server = random.choice(ips) return server
[docs] def logon(self): if "password" in self: self._raw("PASS %s" % self.password) logging.warn('logging in on %s - this may take a while' % self.server) self._raw("NICK %s" % self.nick or "zbot") self._raw("USER %s localhost %s :%s" % (self.username or "zbot", self.server or "localhost", self.realname or "zbot"))
[docs] def say(self, channel, txt): for text in txt.split("\n"): for line in split_txt(text): if line: self.privmsg(channel, line)
[docs] def join_channels(self, *args, **kwargs): for channel in self.channels: self.join(channel)
def broadcast(self, txt): for i in self.channels: self.say(i, txt)
[docs] def connect(self, reconnect=True): res = self._connect() if res: time.sleep(1) ; self.logon() return res
[docs] def close(self): if 'ssl' in self and self['ssl']: self.oldsock.shutdown(2) else: self.sock.shutdown(2) if 'ssl' in self and self['ssl']: self.oldsock.close() else: self.sock.close() self.fsock.close()
[docs] def handle_ping(self, event): self.pongcheck = True self.pong()
[docs] def handle_433(self, event): self.donick(event.arguments[1] + "_")
[docs] def handle_invite(self, event): self.join(event.channel)
[docs] def handle_privmsg(self, event): if event.txt.startswith("\001DCC"): self._dccconnect(event) ; return if event.txt and event.txt[0] == self.cc: event.is_cmnd = True ; resolve(event)
[docs] def handle_513(self, event): self._raw("PONG %s" % event.arguments[6])
[docs] def handle_ctcp(self, event): if event.txt and event.txt[0] == self.cc: resolve(event)
[docs] def donick(self, nick, setorig=False, save=False, whois=False): nick = nick[:16] self.send('NICK %s\n' % nick)
[docs] def join(self, channel, password=None): if password: self._raw('JOIN %s %s' % (channel, password)) else: self._raw('JOIN %s' % channel)
[docs] def part(self, channel): self.send('PART %s' % channel) if channel in self.channels: self.channels.remove(channel) ; self.save()
[docs] def who(self, who): self.send('WHO %s' % who.strip())
[docs] def names(self, channel): self.send('NAMES %s' % channel)
[docs] def whois(self, who): self.send('WHOIS %s' % who)
[docs] def privmsg(self, printto, what): self.send('PRIVMSG %s :%s' % (printto, what))
[docs] def voice(self, channel, who): self.send('MODE %s +v %s' % (channel, who))
[docs] def doop(self, channel, who): self.send('MODE %s +o %s' % (channel, who))
[docs] def delop(self, channel, who): self.send('MODE %s -o %s' % (channel, who))
[docs] def quit(self, reason='https://pikacode.com/milla/zbot'): self.send('QUIT :%s' % reason)
[docs] def notice(self, printto, what): self.send('NOTICE %s :%s' % (printto, what))
[docs] def ctcp(self, printto, what): self.send("PRIVMSG %s :\001%s\001" % (printto, what))
[docs] def ctcpreply(self, printto, what): self.send("NOTICE %s :\001%s\001" % (printto, what))
[docs] def action(self, printto, what, event=None, *args, **kwargs): self.send("PRIVMSG %s :\001ACTION %s\001" % (printto, what))
[docs] def getchannelmode(self, channel): self.send('MODE %s' % channel)
[docs] def settopic(self, channel, txt): self.send('TOPIC %s :%s' % (channel, txt))
[docs] def ping(self, *args, **kwargs): self.send('PING :%s' % self.server)
[docs] def pong(self, *args, **kwargs): self.send('PONG :%s' % self.server)
[docs] def broadcast(self, txt): for i in self.channels: self.say(i, txt) ## POSTFIXCOUNT - how many arguments used in a IRC message
pfc = {} pfc['NICK'] = 0 pfc['QUIT'] = 0 pfc['SQUIT'] = 1 pfc['JOIN'] = 0 pfc['PART'] = 1 pfc['TOPIC'] = 1 pfc['KICK'] = 2 pfc['PRIVMSG'] = 1 pfc['NOTICE'] = 1 pfc['SQUERY'] = 1 pfc['PING'] = 0 pfc['ERROR'] = 0 pfc['AWAY'] = 0 pfc['WALLOPS'] = 0 pfc['INVITE'] = 1 pfc['001'] = 1 pfc['002'] = 1 pfc['003'] = 1 pfc['004'] = 4 pfc['005'] = 15 pfc['302'] = 1 pfc['303'] = 1 pfc['301'] = 2 pfc['305'] = 1 pfc['306'] = 1 pfc['311'] = 5 pfc['312'] = 3 pfc['313'] = 2 pfc['317'] = 3 pfc['318'] = 2 pfc['319'] = 2 pfc['314'] = 5 pfc['369'] = 2 pfc['322'] = 3 pfc['323'] = 1 pfc['325'] = 3 pfc['324'] = 4 pfc['331'] = 2 pfc['332'] = 2 pfc['341'] = 3 pfc['342'] = 2 pfc['346'] = 3 pfc['347'] = 2 pfc['348'] = 3 pfc['349'] = 2 pfc['351'] = 3 pfc['352'] = 7 pfc['315'] = 2 pfc['353'] = 3 pfc['366'] = 2 pfc['364'] = 3 pfc['365'] = 2 pfc['367'] = 2 pfc['368'] = 2 pfc['371'] = 1 pfc['374'] = 1 pfc['375'] = 1 pfc['372'] = 1 pfc['376'] = 1 pfc['378'] = 2 pfc['381'] = 1 pfc['382'] = 2 pfc['383'] = 5 pfc['391'] = 2 pfc['392'] = 1 pfc['393'] = 1 pfc['394'] = 1 pfc['395'] = 1 pfc['262'] = 3 pfc['242'] = 1 pfc['235'] = 3 pfc['250'] = 1 pfc['251'] = 1 pfc['252'] = 2 pfc['253'] = 2 pfc['254'] = 2 pfc['255'] = 1 pfc['256'] = 2 pfc['257'] = 1 pfc['258'] = 1 pfc['259'] = 1 pfc['263'] = 2 pfc['265'] = 1 pfc['266'] = 1 pfc['401'] = 2 pfc['402'] = 2 pfc['403'] = 2 pfc['404'] = 2 pfc['405'] = 2 pfc['406'] = 2 pfc['407'] = 2 pfc['408'] = 2 pfc['409'] = 1 pfc['411'] = 1 pfc['412'] = 1 pfc['413'] = 2 pfc['414'] = 2 pfc['415'] = 2 pfc['421'] = 2 pfc['422'] = 1 pfc['423'] = 2 pfc['424'] = 1 pfc['431'] = 1 pfc['432'] = 2 pfc['433'] = 2 pfc['436'] = 2 pfc['437'] = 2 pfc['441'] = 3 pfc['442'] = 2 pfc['443'] = 3 pfc['444'] = 2 pfc['445'] = 1 pfc['446'] = 1 pfc['451'] = 1 pfc['461'] = 2 pfc['462'] = 1 pfc['463'] = 1 pfc['464'] = 1 pfc['465'] = 1 pfc['467'] = 2 pfc['471'] = 2 pfc['472'] = 2 pfc['473'] = 2 pfc['474'] = 2 pfc['475'] = 2 pfc['476'] = 2 pfc['477'] = 2 pfc['478'] = 3 pfc['481'] = 1 pfc['482'] = 2 pfc['483'] = 1 pfc['484'] = 1 pfc['485'] = 1 pfc['491'] = 1 pfc['501'] = 1 pfc['502'] = 1 pfc['700'] = 2