Source code for core.bots.irc

# core/bots/irc.py
#
#

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

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

## IMPORTS

from core.kernel import kernel, fleet
from core.dispatcher import Dispatcher
from core.bots import Bot
from core.thing import Thing

from core.utils.other import split_txt
from core.utils.parse import txt_parse

from core.errors import RemoteDisconnect, error
from core.cfg import cfg_irc
from core import __version__

import logging
import _thread
import socket
import queue
import time
import re

## KLASSEN

[docs]class IRC(Bot): """ Internet Relay Chat. """ default = "" marker = "\r\n" cc = "!" def __init__(zelf, *args, **kwargs): Bot.__init__(zelf, *args, **kwargs) zelf._handlers = Dispatcher() zelf._handlers.register("004", zelf.connected) zelf._handlers.register("366", zelf.h366) zelf._handlers.register("433", zelf.h433) zelf._handlers.register("451", zelf.logon) zelf._handlers.register("513", zelf.h513) zelf._handlers.register("PING", zelf.pinged) zelf._handlers.register("PONG", zelf.ponged) zelf._handlers.register("QUIT", zelf.quited) zelf._handlers.register("INVITE", zelf.invited) zelf._handlers.register("PRIVMSG", zelf.privmsged) zelf._handlers.register("NOTICE", zelf.noticed) zelf._handlers.register("JOIN", zelf.joined) zelf._lock = _thread.allocate_lock() zelf._outqueue = queue.Queue() zelf._buffer = [] zelf._lastline = "" zelf._status = "started" zelf.channels = [] zelf.encoding = "utf-8" if "realname" not in zelf: zelf.realname = "core" if "username" not in zelf: zelf.username = "core" if "server" not in zelf: zelf.server = "localhost" if "port" not in zelf: zelf.port = 6667 if "nick" not in zelf: zelf.nick = "core" if "channel" in zelf and zelf.channel not in zelf.channels: zelf.channels.append(zelf.channel) ## HANDLERS
[docs] def joined(zelf, *args, **kwargs): event = args[0] if event.channel not in zelf.channels: zelf.channels.append(event.channel)
[docs] def noticed(zelf, *args, **kwargs): pass
[docs] def pinged(zelf, *args, **kwargs): event = args[0] zelf.pongcheck = True zelf.pong(event.txt)
[docs] def ponged(zelf, *args, **kwargs): pass
[docs] def h433(zelf, *args, **kwargs): event = args[0] zelf.donick(event.arguments[1] + "_")
[docs] def quited(zelf, *args, **kwargs): event = args[0] if "Ping timeout" in event.txt and event.nick == zelf.nick: zelf.connect()
[docs] def h366(zelf, *args, **kwargs): logging.warn("# %s/connected with %s" % (zelf.type, zelf.server))
[docs] def invited(zelf, *args, **kwargs): event = args[0] zelf.join(event.channel)
[docs] def privmsged(zelf, *args, **kwargs): event = args[0] if event.txt.startswith("\001DCC"): zelf.dccconnect(event) ; return elif event.txt.startswith("\001VERSION"): zelf.ctcpreply(event.nick, "VERSION CORE #%s - http://pypi.python.org/bthate/core" % __version__) ; return Bot.dispatch(zelf, event)
[docs] def h513(zelf, *args, **kwargs): event = args[0] zelf.raw("PONG %s" % event.arguments[6])
[docs] def ctcped(zelf, event): pass
[docs] def connected(zelf, *args, **kwargs): if "servermodes" in zelf: zelf.raw("MODE %s %s" % (zelf.nick, zelf.servermodes)) zelf.joining() ## OUTPUT
[docs] def raw(zelf, txt): if not txt.endswith(zelf.marker): txt += zelf.marker txt = txt[:512] txt = bytes(txt, "utf-8") logging.info("> %s/write %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() ## CONNECT
[docs] def connect(zelf): zelf.stopped = False logging.warn("# %s/connecting 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 = True zelf._oldsock.setblocking(zelf.blocking) zelf._oldsock.settimeout(700.0) zelf.fsock = zelf._oldsock.makefile("r") if 'ssl' in zelf and zelf['ssl']: zelf._sock = socket.ssl(zelf._oldsock) else: zelf._sock = zelf._oldsock return True ## DCC
[docs] def dcc(zelf, event, s): s.send(bytes('Welcome to CORE ' + event.nick + " !!\n", zelf.encoding)) kernel.put(zelf.dccloop, event, s)
[docs] 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, event.origin)) o = Thing() o._target = zelf o.txt = res o.outer = sockfile o.origin = event.origin o.parsed = txt_parse(zelf, res, "none") Bot.dispatch(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()
[docs] 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.dcc(event, s)
[docs] def start(zelf, *args, **kwargs): zelf.connecting() zelf._status = "running" Bot.start(zelf, *args, **kwargs)
[docs] def parse(zelf, *args, **kwargs): """ parse a string into an Anderen event. """ rawstr = str(args[0]) obj = Thing() obj._target = zelf obj.servermsg = False splitted = re.split('\s+', rawstr) if not rawstr[0] == ':': obj.servermsg = True obj.pre = splitted[0] if obj.servermsg: obj.type = obj.pre else: obj.type = splitted[1] try: nickuser = obj.pre.split('!') obj.origin = nickuser[1] obj.nick = nickuser[0][1:] except IndexError: obj.origin = obj.pre ; 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.some() line = zelf._buffer.pop(0) return zelf.parse(line.rstrip())
[docs] def dispatch(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 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.info("< %s/read %s" % (zelf.type, s.strip())) ; zelf._buffer.append(s) zelf._lastline = splitted[-1]
[docs] def announce(zelf, *args, **kwargs): zelf._outqueue.put(args[0])
[docs] def push(zelf, *args, **kwargs): zelf.pushing = True while zelf._status: txt = zelf._outqueue.get() if not txt or not zelf._status: break for channel in zelf.channels: zelf.say(channel, txt) logging.warn("end %s/push" % zelf.type)
[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, *args, **kwargs): if "password" in zelf: zelf.raw("PASS %s" % zelf.password) zelf.raw("NICK %s" % zelf.nick or "core") zelf.raw("USER %s localhost %s :%s" % (zelf.username or "core", zelf.server or "localhost", zelf.realname)) zelf.ready()
[docs] def say(zelf, *args, **kwargs): channel = args[0] txt = args[1] txt_list = split_txt(txt) for txt in txt_list: for t in txt.split("\n"): tt = t.strip() if not tt: continue zelf.privmsg(channel, tt) time.sleep(3.0)
[docs] def connecting(zelf, reconnect=True): zelf._status = "connecting" res = None while 1: try: res = zelf.connect() ; break except Exception as ex: error() time.sleep(5) zelf.logon() 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 donick(zelf, nick, setorig=False, save=False, whois=False): zelf.raw('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.raw('PART %s' % channel) if channel in zelf.channels: zelf.channels.remove(channel) ; zelf.save()
[docs] def who(zelf, who): zelf.raw('WHO %s' % who.strip())
[docs] def names(zelf, channel): zelf.raw('NAMES %s' % channel)
[docs] def whois(zelf, who): zelf.raw('WHOIS %s' % who)
[docs] def privmsg(zelf, printto, txt): zelf.raw('PRIVMSG %s :%s' % (printto, txt))
[docs] def voice(zelf, channel, who): zelf.raw('MODE %s +v %s' % (channel, who))
[docs] def doop(zelf, channel, who): zelf.raw('MODE %s +o %s' % (channel, who))
[docs] def delop(zelf, channel, who): zelf.raw('MODE %s -o %s' % (channel, who))
[docs] def quit(zelf, reason='https://pikacode.com/bthate/core2'): zelf.raw('QUIT :%s' % reason)
[docs] def notice(zelf, printto, what): zelf.raw('NOTICE %s :%s' % (printto, what))
[docs] def ctcp(zelf, printto, what): zelf.raw("PRIVMSG %s :\001%s\001" % (printto, what))
[docs] def ctcpreply(zelf, printto, what): zelf.raw("NOTICE %s :\001%s\001" % (printto, what))
[docs] def action(zelf, printto, what, event=None, *args, **kwargs): zelf.raw("PRIVMSG %s :\001ACTION %s\001" % (printto, what))
[docs] def getchannelmode(zelf, channel): zelf.raw('MODE %s' % channel)
[docs] def settopic(zelf, channel, txt): zelf.raw('TOPIC %s :%s' % (channel, txt))
[docs] def ping(zelf, *args, **kwargs): zelf.raw('PING :%s' % args[0])
[docs] def pong(zelf, *args, **kwargs): zelf.raw('PONG :%s' % args[0])
[docs]def init(*args, **kwargs): ncfg = kernel.last("cfg", "irc") if not ncfg: ncfg = cfg_irc ; ncfg.save() bot = IRC(**ncfg) fleet.append(bot) kernel.put(bot.start) kernel.put(bot.push)
[docs]def stop(*args, **kwargs): for bot in fleet: if bot.type == "irc": bot.exit() ## POSTFIXCOUNT - how many arguments used in a Anderen 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