Source code for bard.bots.irc

# bard/bots/irc.py
#
#

""" basic package for the program. """

__copyright__ = "Copyright 2015, B.H.J Thate"

## IMPORTS

from bard.dispatcher import DISPATCHER
from bard.runtime import kernel, fleet
from bard.object import Object
from bard.bot import BOT

from bard.utils import error, split_txt, txt_parse
from bard.defines import BLA, GREEN, ENDC
from bard.errors import RemoteDisconnect
from bard.defaults import irc

import threading
import logging
import _thread
import socket
import time
import re

## IRC

[docs]class IRC(BOT): """ IRC bot. """ marker = "\r\n" cc = "." def __init__(zelf, *args, **kwargs): BOT.__init__(zelf, *args, **kwargs) zelf.connected = threading.Event() zelf._handlers = DISPATCHER() zelf._handlers.register("004", zelf._onconnect) zelf._handlers.register("513", zelf.handle_513) zelf._handlers.register("433", zelf.handle_433) zelf._handlers.register("366", zelf.handle_366) zelf._handlers.register("PING", zelf.handle_ping) zelf._handlers.register("PONG", zelf.handle_pong) zelf._handlers.register("QUIT", zelf.handle_quit) zelf._handlers.register("INVITE", zelf.handle_invite) zelf._handlers.register("PRIVMSG", zelf.handle_privmsg) zelf._handlers.register("NOTICE", zelf.handle_notice) zelf._handlers.register("JOIN", zelf.handle_join) zelf._lock = _thread.allocate_lock() zelf._buffer = [] zelf._lastline = "" zelf.channels = [] zelf.encoding = "utf-8" if "realname" not in zelf: zelf.realname = "bard" if "username" not in zelf: zelf.username = "bard" if "server" not in zelf: zelf.server = "localhost" if "port" not in zelf: zelf.port = 6667 if "nick" not in zelf: zelf.nick = "bard" if "channel" in zelf and zelf.channel not in zelf.channels: zelf.channels.append(zelf.channel) def _raw(zelf, txt): if not txt.endswith(zelf.marker): txt += zelf.marker txt = txt[:512] txt = bytes(txt, "utf-8") logging.debug("> %s/raw %s" % (zelf.type, txt)) try: if 'ssl' in zelf and zelf.ssl: zelf._sock.write(txt) else: zelf._sock.send(txt) except BrokenPipeError: zelf._connect() def _connect(zelf): zelf.stopped = False logging.warn("! %s/connect to %s" % (zelf.type, zelf.server)) if "ipv6" in zelf: zelf._oldsock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) else: zelf._oldsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) zelf.server = zelf.bind() zelf._oldsock.settimeout(60) zelf._oldsock.connect((zelf.server, int(str(zelf.port or 6667)))) zelf.blocking = 1 zelf._oldsock.setblocking(zelf.blocking) zelf.fsock = zelf._oldsock.makefile("r") if "blocking" in zelf: zelf._oldsock.settimeout(301.0) if 'ssl' in zelf and zelf['ssl']: zelf._sock = socket.ssl(zelf._oldsock) else: zelf._sock = zelf._oldsock return True def _onconnect(zelf, *args, **kwargs): if "onconnect" in zelf: time.sleep(0.5) ; zelf._raw(zelf.onconnect) if "servermodes" in zelf: zelf._raw("MODE %s %s" % (zelf.nick, zelf.servermodes)) zelf.join_channels() def _dodcc(zelf, event, s): s.send(bytes('Welcome to BARD ' + event.nick + " !!\n", zelf.encoding)) kernel.workers.put(zelf._dccloop, event, s) def _dccloop(zelf, event, s): sockfile = s.makefile('rw') s.setblocking(True) while 1: try: res = sockfile.readline() if not res: break res = res.rstrip() logging.info("< %s/DCC %s" % (zelf.type, res.strip())) o = Object(_target=zelf, txt=res) o.outer = sockfile o.origin = event.origin BOT.handle_event(zelf, 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(zelf, event): try: addr = event.parsed.args[2] ; port = event.parsed.args[3][:-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 zelf._dodcc(event, s)
[docs] def start(zelf, *args, **kwargs): zelf.connect() BOT.start(zelf, *args, **kwargs)
[docs] def parse(zelf, *args, **kwargs): """ parse a string into an IRC event. """ rawstr = str(args[0]) obj = Object() obj._target = zelf obj.servermsg = False splitted = re.split('\s+', rawstr) if not rawstr[0] == ':': obj.servermsg = True obj.prefix = splitted[0] if obj.servermsg: obj.type = obj.prefix else: obj.type = splitted[1] try: nickuser = obj.prefix.split('!') obj.origin = nickuser[1] obj.nick = nickuser[0][1:] except IndexError: obj.origin = obj.prefix ; obj.servermsg = True if obj.type in pfc: obj.arguments = splitted[2:pfc[obj.type]+2] txtsplit = re.split('\s+', rawstr, pfc[obj.type]+2) obj.txt = txtsplit[-1] else: obj.arguments = splitted[2:] if obj.arguments: obj.target = obj.arguments[0] obj.postfix = ' '.join(obj.arguments) if not "txt" in obj: obj.txt = rawstr.rsplit(":")[-1] if obj.txt.startswith(":"): obj.txt = obj.txt[1:] if not "channel" in obj: for c in obj.arguments + [obj.txt, ]: if c.startswith("#"): obj.channel = c if obj.servermsg: obj.origin = obj.origin[1:-1] obj.channel = obj.origin if not "origin" in obj: obj.origin = obj.channel return obj
[docs] def event(zelf, *args, **kwargs): if not zelf._buffer: zelf.read_some() line = zelf._buffer.pop(0) event = zelf.parse(line.rstrip()) event.cc = zelf.cc return event
[docs] def handle_event(zelf, *args, **kwargs): event = args[0] if event.type not in zelf._handlers: return for handler in zelf._handlers[event.type]: zelf._handlers.put(handler.func, *args, **kwargs)
[docs] def read_some(zelf, *args, **kwargs): if "ssl" in zelf and zelf.ssl: inbytes = zelf._sock.read() else: inbytes = zelf._sock.recv(512) txt = str(inbytes, zelf.encoding) if txt == "": raise RemoteDisconnect() zelf._lastline += txt splitted = zelf._lastline.split(zelf.marker) for s in splitted[:-1]: logging.debug("< %s/read %s" % (zelf.type, s.strip())) ; zelf._buffer.append(s) zelf._lastline = splitted[-1]
[docs] def send(zelf, txt): zelf._raw(txt) ; time.sleep(3.0)
[docs] def bind(zelf): server = zelf.server try: zelf._oldsock.bind((server, 0)) except socket.error: if not server: try: socket.inet_pton(socket.AF_INET6, zelf.server) except socket.error: pass else: server = zelf.server if not server: try: socket.inet_pton(socket.AF_INET, zelf.server) except socket.error: pass else: server = zelf.server if not server: ips = [] try: for item in socket.getaddrinfo(zelf.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(zelf): if "password" in zelf: zelf._raw("PASS %s" % zelf.password) zelf._raw("NICK %s" % zelf.nick or "bard") zelf._raw("USER %s localhost %s :%s" % (zelf.username or "bard", zelf.server or "localhost", zelf.realname))
[docs] def say(zelf, *args, **kwargs): channel = args[0] txt = args[1] txt_list = split_txt(txt) for txt in txt_list: time.sleep(0.1) for t in txt.split("\n"): zelf.privmsg(channel, t)
[docs] def connect(zelf, reconnect=True): res = None while 1: try: res = zelf._connect() ; time.sleep(1) ; zelf.logon() ; break except Exception as ex: error() time.sleep(10) zelf.ready() return res
[docs] def exit(zelf): zelf.quit()
[docs] def close(zelf): if 'ssl' in zelf and zelf['ssl']: zelf.oldsock.shutdown(1) ; zelf.oldsock.close() else: zelf._sock.shutdown(1) ; zelf._sock.close() zelf.fsock.close()
[docs] def handle_join(zelf, *args, **kwargs): event = args[0] if event.channel not in zelf.channels: zelf.channels.append(event.channel)
[docs] def handle_notice(zelf, *args, **kwargs): pass
[docs] def handle_ping(zelf, *args, **kwargs): zelf.pongcheck = True ; zelf.pong()
[docs] def handle_pong(zelf, *args, **kwargs): pass
[docs] def handle_433(zelf, *args, **kwargs): event = args[0] zelf.donick(event.parsed.args[1] + "_")
[docs] def handle_quit(zelf, *args, **kwargs): event = args[0] if "Ping timeout" in event.txt and event.nick == zelf.nick: zelf.connect()
[docs] def handle_366(zelf, *args, **kwargs): logging.warn("! %s/logged on %s" % (zelf.type, zelf.server))
[docs] def handle_invite(zelf, *args, **kwargs): event = args[0] zelf.join(event.channel)
[docs] def handle_privmsg(zelf, *args, **kwargs): event = args[0] if event.txt.startswith("\001DCC"): zelf._dccconnect(event) ; return if event.txt: BOT.handle_event(zelf, event)
[docs] def handle_513(zelf, *args, **kwargs): event = args[0] zelf._raw("PONG %s" % event.arguments[6])
[docs] def handle_ctcp(zelf, event): if event.txt and event.txt[0] == zelf.cc: resolve(event)
[docs] def donick(zelf, nick, setorig=False, save=False, whois=False): zelf.send('NICK %s\n' % nick[:16])
[docs] def join(zelf, channel, password=None): if password: zelf._raw('JOIN %s %s' % (channel, password)) else: zelf._raw('JOIN %s' % channel)
[docs] def part(zelf, channel): zelf.send('PART %s' % channel) if channel in zelf.channels: zelf.channels.remove(channel) ; zelf.save()
[docs] def who(zelf, who): zelf.send('WHO %s' % who.strip())
[docs] def names(zelf, channel): zelf.send('NAMES %s' % channel)
[docs] def whois(zelf, who): zelf.send('WHOIS %s' % who)
[docs] def privmsg(zelf, printto, txt): zelf.send('PRIVMSG %s :%s' % (printto, txt))
[docs] def voice(zelf, channel, who): zelf.send('MODE %s +v %s' % (channel, who))
[docs] def doop(zelf, channel, who): zelf.send('MODE %s +o %s' % (channel, who))
[docs] def delop(zelf, channel, who): zelf.send('MODE %s -o %s' % (channel, who))
[docs] def quit(zelf, reason='https://pikacode.com/bthate/bard'): zelf.send('QUIT :%s' % reason)
[docs] def notice(zelf, printto, what): zelf.send('NOTICE %s :%s' % (printto, what))
[docs] def ctcp(zelf, printto, what): zelf.send("PRIVMSG %s :\001%s\001" % (printto, what))
[docs] def ctcpreply(zelf, printto, what): zelf.send("NOTICE %s :\001%s\001" % (printto, what))
[docs] def action(zelf, printto, what, event=None, *args, **kwargs): zelf.send("PRIVMSG %s :\001ACTION %s\001" % (printto, what))
[docs] def getchannelmode(zelf, channel): zelf.send('MODE %s' % channel)
[docs] def settopic(zelf, channel, txt): zelf.send('TOPIC %s :%s' % (channel, txt))
[docs] def ping(zelf, *args, **kwargs): zelf.send('PING :%s' % zelf.server)
[docs] def pong(zelf, *args, **kwargs): zelf.send('PONG :%s' % zelf.server)
[docs]def init(*args, **kwargs): ncfg = kernel.last("cfg", "irc") if not ncfg: ncfg = irc ; ncfg.save() bot = IRC(**ncfg) fleet.append(bot) kernel.put(bot.start) ## 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