# 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