Source code for core.bots

# 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()