# core/bots.py
#
#
""" basic package for the program. """
__copyright__ = "Copyright 2014 B.H.J Thate"
## IMPORTS
from core.utils import get_name, short_date, split_txt, error, txt_parse, stripped
from core.dispatch import Dispatcher
from core import Object, kernel
from core.defines import BLA, GREEN, ENDC, pfc
import threading
import logging
import _thread
import getpass
import queue
import socket
import time
import cgi
import sys
import re
## BOTS
[docs]class Bot(Dispatcher):
""" Base Bot class. """
def __init__(zelf, *args, **kwargs):
Dispatcher.__init__(zelf, *args, **kwargs)
zelf.btype = get_name(zelf)
zelf.channels = []
zelf.waiting = True
if "channel" in zelf: zelf.channels.append(zelf.channel)
def _raw(zelf, *args, **kwargs): pass
[docs] def join_channels(zelf, *args, **kwargs):
""" join channels. """
for channel in zelf.channels: zelf.join(channel)
[docs] def exit(zelf, *args, **kwargs):
""" shutdown a bot. """
pass
[docs] def start(zelf, *args, **kwargs):
""" start a bot. """
zelf._status.status = "running"
zelf.connect()
while zelf._status.status:
time.sleep(0.01)
try: event = zelf.get_event()
except EOFError: return
except socket.timeout: zelf.connect() ; time.sleep(5.0) ; continue
except: raise
zelf._status.cmnd = event.get_cmnd()
event = kernel.cmnds.dispatch(event)
zelf._state.input = time.time()
event.wait()
if zelf._status.status == "once": break
zelf.ready()
_thread.interrupt_main()
begin = start
[docs] def get_prompt(zelf, *args, **kwargs):
""" return a prompt. """
return ""
[docs] def get_event(zelf, *args, **kwargs):
""" return an event. """
return Object()
[docs] def connect(zelf, *args, **kwargs):
""" connect to server. """
pass
[docs] def announce(zelf, *args, **kwargs):
""" announce on channels. """
logging.info("announce %s" % zelf.channels)
for channel in zelf.channels:
if not zelf.denied(channel) or zelf.allowed(channel):
zelf.say(channel, args[0])
continue
logging.warn("! ignore %s" % channel)
[docs] def say(zelf, *args, **kwargs):
""" output channel, txt. """
zelf._state.output = time.time()
zelf._target.say(*args, **kwargs)
[docs] def cmnd(zelf, *args, **kwargs):
""" run a command with provided string. """
o = Object()
o._target = zelf
o.txt = args[0]
kernel.cmnds.dispatch(o)
[docs] def echo(zelf, *args, **kwargs):
if kernel.cfg.shell: print("%s %s>%s %s" % (short_date(time.ctime(time.time())).split()[1], BLA, ENDC, str(args[0])))
else: print(str(args[0]))
## TESTBOT
[docs]class TestBot(Bot):
""" Bot used in unittests. """
def __init__(zelf, *args, **kwargs):
Bot.__init__(zelf, *args, **kwargs)
zelf.results = []
def _raw(zelf, *args, **kwargs):
zelf.results.append(args[0])
[docs] def check(zelf, *args, **kwargs):
if args[0] in zelf.results: return True
## CONSOLE
[docs]class ConsoleBot(Bot):
""" bot running on the console. """
def __init__(zelf, *args, **kwargs):
Bot.__init__(zelf, *args, **kwargs)
def _raw(zelf, *args, **kwargs): zelf.echo(*args, **kwargs)
[docs] def get_prompt(zelf, *args, **kwargs):
return "%s %s<%s " % (short_date(time.ctime(time.time())).split()[1], GREEN, ENDC)
[docs] def get_event(zelf, *args, **kwargs):
o = Object()
if not kernel.cfg.shell: in_txt = " ".join(kernel.cfg.runargs) ; zelf._status.status = "once"
else: in_txt = input(zelf.get_prompt())
sys.stdout.flush()
zelf._state.input = time.time()
o.txt = in_txt
o._target = zelf
return o
[docs] def announce(zelf, *args, **kwargs): zelf._raw(args[0])
## IRC
[docs]class IRCBot(Bot):
""" IRC bot. """
marker = "\r\n"
cc = "."
def __init__(zelf, *args, **kwargs):
Bot.__init__(zelf, *args, **kwargs)
zelf.connected = threading.Event()
zelf.register("004", zelf._onconnect)
zelf.register("513", zelf.handle_513)
zelf.register("433", zelf.handle_433)
zelf.register("366", zelf.handle_366)
zelf.register("PING", zelf.handle_ping)
zelf.register("INVITE", zelf.handle_invite)
zelf.register("PRIVMSG", zelf.handle_privmsg)
zelf.register("NOTICE", zelf.handle_notice)
zelf.register("JOIN", zelf.handle_join)
zelf._lock = _thread.allocate_lock()
zelf._buffer = []
zelf._lastline = ""
zelf.encoding = "utf-8"
if "realname" not in zelf: zelf.realname = "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)
def _raw(zelf, txt):
if not txt.endswith(zelf.marker): txt += zelf.marker
txt = txt[:512]
txt = bytes(txt, "utf-8")
logging.info("> irc %s" % txt)
try:
if 'ssl' in zelf and zelf.ssl: zelf.sock.write(txt)
else: zelf.sock.send(txt)
except: time.sleep(2) ; zelf.connect()
zelf._state.output = time.time()
def _connect(zelf):
zelf.stopped = False
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
zelf.connecttime = time.time()
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 CORELIB ' + event.nick + " !!\n", zelf.encoding))
_thread.start_new_thread(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("< dcc %s" % res.strip())
o = Object(_target=zelf, txt=res)
o.outer = sockfile
kernel.cmnds.dispatch(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:
rest = event.get_rest()
parsed = txt_parse(rest)
addr = parsed.args[2] ; port = 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 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.etype = obj.prefix
else: obj.etype = 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.etype in pfc:
obj.arguments = splitted[2:pfc[obj.etype]+2]
txtsplit = re.split('\s+', rawstr, pfc[obj.etype]+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 get_event(zelf, *args, **kwargs):
if not zelf._buffer: zelf.read_some()
try: line = zelf._buffer.pop(0)
except IndexError: return Object()
event = zelf.parse(line.rstrip())
event.cc = zelf.cc
zelf.run_func(event.etype, event)
return event
[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.info("< irc %s" % 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 "core")
zelf._raw("USER %s localhost %s :%s" % (zelf.username or "core", 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)
zelf.privmsg(channel, txt)
[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)
return res
[docs] def close(zelf):
if 'ssl' in zelf and zelf['ssl']: zelf.oldsock.shutdown(2) ; zelf.oldsock.close()
else: zelf.sock.shutdown(2) ; zelf.sock.close()
zelf.fsock.close()
[docs] def handle_join(zelf, event):
if event.channel not in zelf.channels: zelf.channels.append(event.channel)
[docs] def handle_notice(zelf, event): pass
[docs] def handle_ping(zelf, event): zelf.pongcheck = True ; zelf.pong()
[docs] def handle_433(zelf, event): zelf.donick(event.arguments[1] + "_")
[docs] def handle_366(zelf, event):
logging.warn("connected %s" % zelf.server)
[docs] def handle_invite(zelf, event): zelf.join(event.channel)
[docs] def handle_privmsg(zelf, event):
if event.txt.startswith("\001DCC"): zelf._dccconnect(event) ; return
[docs] def handle_513(zelf, event): 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/corelib'): 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 stop(zelf, *args, **kwargs): zelf.quit() ; zelf.exit()
## XMPP
[docs]class XMPPBot(Bot):
""" XMPP bot. """
def __init__(zelf, *args, **kwargs):
import sleekxmpp
from sleekxmpp import clientxmpp
Bot.__init__(zelf, *args, **kwargs)
if "port" not in zelf: zelf.port = 5222
zelf.queue = queue.Queue()
zelf.xmpp = clientxmpp.ClientXMPP(zelf.user, getpass.getpass())
zelf.xmpp.add_event_handler("session_start", zelf.session_start)
zelf.xmpp.add_event_handler("message", zelf.handle_message)
zelf.xmpp.add_event_handler('disconnected', zelf.handle_disconnected)
zelf.xmpp.add_event_handler('connected', zelf.handle_connected)
zelf.xmpp.add_event_handler('presence_available', zelf.handle_presence)
zelf.xmpp.add_event_handler('presence_dnd', zelf.handle_presence)
zelf.xmpp.add_event_handler('presence_xa', zelf.handle_presence)
zelf.xmpp.add_event_handler('presence_chat', zelf.handle_presence)
zelf.xmpp.add_event_handler('presence_away', zelf.handle_presence)
zelf.xmpp.add_event_handler('presence_unavailable', zelf.handle_presence)
zelf.xmpp.add_event_handler('presence_subscribe', zelf.handle_presence)
zelf.xmpp.add_event_handler('presence_subscribed', zelf.handle_presence)
zelf.xmpp.add_event_handler('presence_unsubscribe', zelf.handle_presence)
zelf.xmpp.add_event_handler('presence_unsubscribed', zelf.handle_presence)
zelf.xmpp.add_event_handler('groupchat_message', zelf.handle_message)
zelf.xmpp.add_event_handler('groupchat_presence', zelf.handle_presence)
zelf.xmpp.add_event_handler('groupchat_subject', zelf.handle_presence)
zelf.xmpp.add_event_handler('failed_auth', zelf.handle_failedauth)
zelf.xmpp.exception = zelf.exception
zelf.xmpp.use_signals()
zelf.openfire = kernel.cfg.openfire
if zelf.openfire:
logging.info("openfire")
import ssl
zelf.xmpp.ssl_version = ssl.PROTOCOL_SSLv3
zelf._connected = threading.Event()
zelf.channels = []
zelf._state.input = time.time()
def _raw(zelf, txt):
logging.info("> xmpp/raw %s" % txt)
zelf.xmpp.send_raw(args[0], args[1])
#zelf.xmpp.send_raw(cgi.escape(txt))
zelf._state.output = time.time()
[docs] def connect(zelf):
try: zelf.xmpp.connect((zelf.server, zelf.port), use_ssl=zelf.openfire)
except: zelf.xmpp.connect((zelf.server, zelf.port))
[docs] def session_start(zelf, event): zelf.xmpp.send_presence() ; logging.info("started")
[docs] def exception(zelf, ex):
zelf._status.error = ex
logging.error(str(ex))
zelf.exit()
[docs] def announce(zelf, *args, **kwargs): pass
[docs] def handle_failedauth(zelf, error, *args): logging.error(error)
[docs] def handle_failure(zelf, ex, *args, **kwargs):
zelf._status.error = ex
logging.error(ex)
[docs] def handle_disconnected(zelf, *args, **kwargs):
zelf._connected.clear()
zelf._status.status = "disconnected"
logging.error("disconnect")
[docs] def handle_connected(zelf, *args, **kwargs): zelf._status.status = "running" ; zelf._connected.set() ; logging.warn("connected %s" % zelf.user)
[docs] def loop(zelf, *args, **kwargs): zelf.xmpp.process()
[docs] def say(zelf, *args, **kwargs):
zelf.xmpp.send_message(args[0], args[1])
zelf._state.output = time.time()
logging.info("> xmpp/say %s %s" % (args[0], args[1]))
[docs] def get_event(zelf, *args, **kwargs):
zelf._state.input = time.time()
return zelf.queue.get()
[docs] def handle_message(zelf, data, *args, **kwargs):
if '<delay xmlns="urn:xmpp:delay"' in str(data): logging.debug("ignore") ; return
logging.info("< xmpp %s" % data)
m = Object(**data)
if m.type == "error": logging.error(m.to_json()) ; return
m["from"] = str(m["from"])
m.origin = stripped(m["from"])
m.channel = m.origin
m.to = m.origin
m.element = "message"
m.txt = m["body"]
m._target = zelf
kernel.cmnds.dispatch(m)
[docs] def handle_presence(zelf, data, *args, **kwargs):
logging.info("< xmpp %s" % data)
o = Object(data)
if "from" in o: o.origin = o["from"] = stripped(str(o["from"]))
o.element = "presence"
if o.type == 'subscribe':
pres = Object({'to': zelf["from"], 'type': 'subscribed'})
o.xmpp.send_presence(pres)
pres = Object({'to': zelf["from"], 'type': 'subscribe'})
o.xmpp.send_presence(pres)
else:
if o.origin != zelf.user and o.origin not in zelf.channels: zelf.channels.append(o.origin)
o.no_dispatch = True
o._target = zelf
kernel.cmnds.dispatch(o)
[docs] def stop(zelf, *args, **kwargs): zelf.xmpp.disconnect() ; zelf.exit()