# rext/__init__.py
#
#
"""
Basic package for the Rext program.
Defines all the basic Objects and functionality.
"""
__version__ = 2
## IMPORTS
from rext.log import datefmt
from rext.utils import *
## BASIC IMPORTS
import collections
import threading
import readline
import logging
import getpass
import _thread
import socket
import queue
import types
import errno
import select
import fcntl
import uuid
import json
import time
import imp
import sys
import os
import re
## ERRORS
[docs]class Error(BaseException):
""" Basic Exception used in the Rext program. """
pass
[docs]class NoTarget(Error):
""" Exception thrown incase of missing argument attribute. """
pass
[docs]class NoJSON(Error):
""" Exception thrown incase no JSON could be decoded/encoded. """
pass
[docs]class RemoteDisconnect(Error):
""" Exception thrown when the connection is disconnected. """
pass
[docs]class NotSet(Error):
""" Exception thrown when a attribute is not set. """
pass
[docs]class FileNotFoundError(Error):
""" Exception thrown when file is not found. """
pass
## BOOT
homedir = os.path.expanduser("~")
histfile = j(homedir, ".rext_history")
workdir = j(homedir, "rext.data", "")
try: hostname = socket.getfqdn()
except: hostname = "localhost"
port = 10102
[docs]def boot(*args, **kwargs):
""" The boot() function is needed to get the Rext program properly initialized. Use it at the beginning of your program. """
global kernel
global workdir
if args: cfg = args[0]
else: cfg = Object()
cfg.workdir = workdir
kernel.cfg.update(cfg)
if cfg.shell: hello("Rext")
if kernel.cfg.workdir: workdir = kernel.cfg.workdir
if not kernel.cfg.loglevel: cfg.loglevel = "error"
from rext.log import cfg_log
cfg_log(cfg.loglevel)
make_dir(workdir)
if kernel.cfg.loglevel:
logging.warn("E G G S")
logging.warn("")
show_eggs()
if kernel.cfg.loglevel:
logging.warn("")
logging.warn("C O N F I G")
logging.warn("")
for line in kernel.cfg.show(): logging.warn(line)
if kernel.cfg.loglevel:
logging.warn("")
logging.warn("B O O T")
logging.warn(" ")
kernel.plugs.load_package("rext.plugs")
kernel.plugs.do_init(kernel.cfg.init)
kernel.ready()
return kernel
## SHUTDOWN
[docs]def shutdown():
""" The shutdown() function is used to close the Rext program in a apropiate manner. """
global kernel
if kernel.cfg.shell: print("")
logging.info("shutdown is here !!")
readline.write_history_file(histfile)
for obj in kernel.run:
if obj == None: continue
try: obj.exit()
except AttributeError: pass
except: error()
kernel.cmnds.exit()
kernel.plugs.exit()
try: sys.stdout.flush() ; sys.stdout.close()
except: pass
os._exit(0)
## BASE
[docs]class Object(dict):
""" THE Basic Object on which the rest of the Rext program is based. """
def __getattribute__(zelf, *args, **kwargs):
name = args[0]
if name == "url_file": return zelf.get_url_file()
if name == "url_show": return zelf.get_url_show()
if name == "what": return get_name(zelf)
if name == "plugname": return get_plugname(3)
if name == "kind": return mj(zelf.plugname, zelf.what)
if name == "stdin": return sys.stdin
if name == "stdout": return sys.stdout
return dict.__getattribute__(zelf, *args, **kwargs)
def __getattr__(zelf, name):
if name in zelf: return zelf[name]
if name == "_ready": zelf["_ready"] = threading.Event()
if name == "_state": zelf["_state"] = Object()
if name == "_no_save": zelf["_no_save"] = Object()
if name not in zelf: raise AttributeError(name)
return zelf[name]
def __setattr__(zelf, name, value): return dict.__setitem__(zelf, name, value)
## FLEET
[docs] def announce(zelf, *args, **kwargs):
""" announce to all the running bots. """
for bot in kernel.fleet: bot.announce(args[0])
## EXEC
[docs] def exec_str(zelf, *args, **kwargs):
""" execute a string as a command. """
o = Object()
o.txt = args[0]
o._target = args[1]
kernel.cmnds.dispatch(o)
return o
## STATE
[docs] def define(zelf, *args, **kwargs):
""" set a attribute on this object. """
name = args[0]
value = args[1]
zelf[name] = value
[docs] def register(zelf, *args, **kwargs):
""" register a value in a list on this object. """
name = args[0]
func = args[1]
try: how = args[2]
except IndexError: how = "normal"
o = Object()
o.func = func
o.how = how
if name not in zelf: zelf[name] = []
zelf[name].append(o)
## GETTERS
[docs] def get_root(zelf, *args, **kwargs):
""" return the root dir of the Rext program. """
return j(os.path.expanduser("~"), *args)
[docs] def get_cmnd(zelf, *args, **kwargs):
""" determine the command in the zelf.txt attribute, if present. """
if "txt" in zelf and zelf.txt:
val = zelf.txt.split()[0]
if "cc" in zelf:
if val[0] != zelf.cc: return
else: return val[1:]
return val
[docs] def get_rest(zelf, *args, **kwargs):
""" get the rest of the txt arguments (words) e.g. minus the first word (the command). """
try: cmnd, rest = zelf.txt.split(" ", 1) ; return rest
except ValueError: return ""
[docs] def get_args(zelf, *args, **kwargs):
""" get the arguments of the txt attribute. """
if "txt" in zelf: return zelf.txt.split()
[docs] def get_days(zelf, *args, **kwargs):
""" get the number of days relative to the objects creation time. """
t1 = time.time()
t2 = a_time(zelf.get_timed())
if t2:
time_diff = float(t1 - t2)
return str_day(time_diff)
[docs] def get_timed(zelf, *args, **kwargs):
""" retrieve the creation time of an object. """
val = None
if "added" in zelf: val = short_date(zelf.added)
elif "Date" in zelf: val = short_date(zelf.Date)
elif "date" in zelf: val = short_date(zelf.date)
elif "published" in zelf: val = short_date(zelf.published)
return val
[docs] def get_parsed(zelf, *args, **kwargs):
""" parse the txt attribute, so command can be determined. """
rest = zelf.get_rest()
if rest: return txt_parse(rest)
return Object()
[docs] def get_keys(zelf, *args, **kwargs):
""" return the keys of this object, skipping the unwanted keys e.g those that start with a "_". """
for key in zelf:
k = str(key)
if not k.startswith("_"): yield key
[docs] def get_clean(zelf, *args, **kwargs):
""" return a cloned object, with only the proper keys used. """
res = Object()
for key in zelf.get_keys(): res[key] = zelf[key]
return res
[docs] def get_slice(zelf, *args, **kwargs):
""" args: list of keywords to slice the dict. """
o = Object()
arguments = args[0]
for arg in arguments:
try: o[arg] = zelf[arg]
except KeyError: continue
return o
[docs] def get_filetime(zelf, *args, **kwargs):
""" get timestamp related filename. """
if "path" in zelf._no_save: return zelf._no_save.path.split(os.sep)[-1]
[docs] def get_url_file(zelf, *args, **kwargs):
""" get the url of the object's file so that it can be retrieved when API server is running. """
import rext.plugs.api as api
fn = zelf.get_filetime()
if fn: return "http://%s:%s/get/%s" % (hostname, port, fn)
[docs] def get_url_show(zelf, *args, **kwargs):
""" return url that gives a readable representation of the object's JSON file. """
import rext.plugs.api as api
fn = zelf.get_filetime()
if fn: return "http://%s:%s/show/%s" % (hostname, port, fn)
## CHECKERS
[docs] def check_wanted(zelf, *args, **kwargs):
""" function to check whether an objects is desired, first argument is a dict that has the attributes to be matched. """
want = args[0]
for key, value in want.items():
if key == "format": continue
if key not in zelf: continue
if value.startswith("-"): continue
if value not in str(zelf[key]): return False
return True
[docs] def check_notwanted(zelf, *args, **kwargs):
""" function to check whether an objects is not desired, first argument is a dict that has the attributes to be matched. """
not_want = args[0]
for key, value in not_want.items():
if key == "format": continue
if key not in zelf: continue
if value in zelf[key]: return True
return False
## INPUT
[docs] def load(zelf, *args, **kwargs):
""" load a JSON file into this object. """
if args: path = args[0]
else: path = zelf._no_save["path"]
logging.debug("load %s" % path)
ondisk = zelf.read(path)
fromdisk = json.loads(ondisk)
zelf._no_save.path = path
if "data" in fromdisk: zelf.update(fromdisk["data"])
return zelf
[docs] def read(zelf, *args, **kwargs):
""" read the JSON file from disk. """
path = args[0]
try: f = open(path, "r")
except IOError as ex:
if ex.errno == errno.ENOENT: return "{}"
raise
if "do_test" in zelf: f.line_buffering = False
res = ""
for line in f:
if not line.strip().startswith("#"): res += line
if not res.strip(): return "{}"
f.close()
return res
## PERSISTENCE
[docs] def dump(zelf, *args, **kwargs):
""" create a JSON string ready to be saved to disk. """
path = args[0]
todisk = Object()
todisk.create_type = zelf.what
todisk.version = __version__
todisk.data = zelf.get_clean()
if "added" not in todisk.data: todisk.data.added = time.ctime(time.time())
todisk.signature = todisk.make_signature()
try: result = todisk.to_json(indent=2, ensure_ascii=False)
except TypeError: raise NoJSON()
return result
[docs] def save(zelf, *args, **kwargs):
""" save this object's JSON onto disk. """
if not args: t = rtime()
else: t = args[0]
root = j(workdir, t)
make_dir(workdir)
zelf.sync(root)
return root
[docs] def sync(zelf, *args, **kwargs):
""" sync this object's JSON to disk. """
try: path = args[0] ; zelf._no_save.path = path
except IndexError: path = zelf._no_save.path
logging.warn("sync %s" % path)
todisk = zelf.dump(path, **kwargs)
datafile = open(path + ".tmp", 'w')
fcntl.flock(datafile, fcntl.LOCK_EX | fcntl.LOCK_NB)
datafile.write(headertxt % (__version__, "%s characters" % len(todisk)))
datafile.write(todisk)
datafile.write("\n")
fcntl.flock(datafile, fcntl.LOCK_UN)
datafile.close()
os.rename(path + ".tmp", path)
return zelf
## OUTPUT
[docs] def reply(zelf, *args, **kwargs):
""" send reply to object's origin. """
if "outer" in zelf: zelf.outer.write(str(args[0]) + "\n") ; zelf.outer.flush() ; return
txt = args[0]
if "channel" in zelf: zelf.say(zelf.channel, txt)
else: zelf.say("", txt)
[docs] def say(zelf, *args, **kwargs):
""" output text through the _target attribute (the bot that received the object). """
if "_target" in zelf: zelf._target.say(args[0], args[1])
else: raise NoTarget
[docs] def show(zelf):
""" return a list of key,value pairs of this object's attributes. """
return ["%s=%s" % (a, b) for a, b in zelf.items() if b]
## ITERATORS
[docs] def objects(zelf, *args, **kwargs):
""" return a list of all object's in the workdir. """
objs = []
path = zelf.get_root(workdir)
for fnn in list_files(path, **kwargs):
obj = Object().load(fnn)
objs.append(obj)
logging.debug("objects called from %s" % get_strace(2))
return objs
[docs] def selected(zelf, *args, **kwargs):
""" return a list of desired objects, first argument is a dict giving the desired attributes. """
parsed = args[0]
objs = []
for obj in zelf.objects(*args, **kwargs):
if "deleted" in obj: continue
if not obj.check_wanted(parsed.wanted): continue
if not obj.selector(parsed.args or []): continue
if obj.check_notwanted(parsed.not_wanted): continue
objs.append(obj)
return objs
[docs] def obj_iter(zelf, *args, **kwargs):
""" iterate over the pure keys (not starting with "_"). """
for key in zelf.get_keys(): yield zelf[key]
## SELECTOR
[docs] def selector(zelf, *args, **kwargs):
""" boolean function to see if this objects has the desired attributes. """
wanted = args[0]
go = False
for w in wanted:
if zelf.get(w): go = True ; break
return go
## WAITERS
[docs] def ready(zelf):
""" signal the object into the ready state. """
zelf._ready.set()
[docs] def clear(zelf):
""" clear the object's ready state. """
zelf._ready.clear()
[docs] def wait(zelf, sec=180.0):
""" wait for the object to be put in a ready state. """
try: zelf._ready.wait(sec)
except: pass
## TIME RELATED
[docs] def first(zelf, *args, **kwargs):
""" return the first object where the key and/or value matches. """
key = args[0]
try: value = args[1]
except IndexError: value = None
result = None
for o in zelf.objects():
if key not in o: continue
if value and o[key] != value: continue
if not result: result = o
if o.get("timed", "") < result.get("timed", ""): result = o
return result
[docs] def last(zelf, *args, **kwargs):
""" return the last object where the key and/or value matches. """
key = args[0]
try: value = args[1]
except IndexError: value = None
result = None
for o in zelf.objects():
if key not in o: continue
if value and o[key] != value: continue
if not result: result = o
if o.get("timed", "") > result.get("timed", ""): result = o
return result
## HELPERS
[docs] def to_json(zelf, *args, **kwargs):
""" return the JSON string representation of this object. """
return json.dumps(zelf.get_clean(*args, **kwargs), default=smooth, *args, **kwargs)
[docs] def to_full(zelf, *args, **kwargs):
""" return full JSON dump of this object. """
return json.dumps(zelf, default=smooth, *args, **kwargs)
[docs] def make_signature(zelf, sig=None):
""" create a signature of the data contained in this object. """
if "data" in zelf: return str(hashlib.sha1(bytes(str(zelf.data), "utf-8")).hexdigest())
raise NotSet
[docs] def make_path(zelf, *args, **kwargs):
""" return workdir path and create the workdir if necessary. """
path = zelf.get_path(*args, **kwargs) ; make_dir(path) ; return path
[docs] def pretty(zelf):
""" return a nice formatted JSON string of this object. """
return json.dumps(zelf.get_clean(), indent=2, ensure_ascii=False)
## TASKS
[docs]class Runner(threading.Thread):
""" The working unit of the Rext program, arguments are function, objects pairs pushed to the Runner. """
def __init__(zelf, *args, **kwargs):
threading.Thread.__init__(zelf, None, zelf._loop, "thread.%s" % str(time.time()), args, kwargs)
zelf.setDaemon(True)
zelf._queue = queue.Queue()
zelf._outqueue = queue.Queue()
zelf._state = Object()
zelf._state.status = "waiting"
zelf._state.last_run = "none"
zelf._state.time_in = time.time()
zelf._state.time_start = time.time()
def _loop(zelf):
""" Loop that executes the function, object pairs.
Arguments: function, object, args
"""
while zelf._state.status:
zelf._state.status = "ready"
args, kwargs = zelf._queue.get()
if not zelf._state.status: break
func = args[0]
try: obj = args[1]
except: obj = Object()
logging.info("run %s" % get_name(func))
zelf._state.last_run = get_name(func)
zelf._state.time_in = time.time()
try: func(*args[1:], **kwargs)
except: error()
zelf._state.status = "done"
try: obj.ready()
except AttributeError: pass
time.sleep(0.01)
kernel.ready()
_thread.interrupt_main()
[docs] def exit(zelf, *args, **kwargs):
""" stop the Runner by setting the status to an empty string. """
zelf._state.status = ""
[docs] def put(zelf, *args, **kwargs):
""" put arguments/kwargs to the Runner. """
zelf._queue.put_nowait((args, kwargs))
## DISPATCH
[docs]class Dispatcher(Object):
""" The Dispatcher is the object to delegate the workload to the Runners. Runners get instantiated when needed. """
def __init__(zelf, *args, **kwargs):
Object.__init__(zelf, *args, **kwargs)
zelf.runners = collections.deque()
zelf._state = Object()
zelf._state.time_in = time.time()
zelf._state.time_out = time.time()
zelf._state.time_start = time.time()
zelf._state.last_run = "none"
zelf.max = 50
[docs] def is_alive(zelf, *args, **kwargs):
""" Check whether there are any Runners running. """
for runner in zelf.runners:
if runner.is_alive(): return True
return False
[docs] def register(zelf, *args, **kwargs):
""" register a callback type with corresponding callback function. """
cbtype = args[0]
cb = args[1]
try: how = args[2]
except IndexError: how = "normal"
o = Object()
o.func = cb
o.how = how
if cbtype not in zelf: zelf[cbtype] = []
zelf[cbtype].append(o)
[docs] def dispatch(zelf, *args, **kwargs):
""" dispatch an event (object) onto a runner if matching command is found. """
event = args[0]
zelf.execute(event.get_cmnd(), event)
[docs] def execute(zelf, *args, **kwargs):
""" Execute a command, event pair. If the "threaded" attribute is set, dispatch to a Runner. """
cmnd = args[0]
event = args[1]
if cmnd not in zelf: event.ready() ; return
for functor in zelf[cmnd]:
logging.info(str(functor.func))
try:
if functor.how == "threaded": kernel.workers.put(functor.func, event)
else: functor.func(event)
except: error()
[docs] def do_func(zelf, *args, **kwargs):
""" Execute command/event pair if command is registered. """
cmnd = args[0]
event = args[1]
if cmnd not in zelf: return
for functor in zelf[cmnd]: functor.func(event)
[docs] def exit(zelf, name=None):
""" Stop the Runners running in this Dispatcher. """
for runner in zelf.runners:
if name and name not in runner.name: continue
runner.join(0.1)
zelf.ready()
[docs] def put(zelf, *args, **kwargs):
""" Put load to the Runner, create a new Runner if necessary. """
target = zelf.make_new()
target.put(*args, **kwargs)
return target
[docs] def make_new(zelf, *args, **kwargs):
""" Create a Runner, try for idle Runners already available first. """
for runner in zelf.runners:
if runner._state.status != "running": return runner
if len(zelf.runners) < zelf.max:
runner = Runner(*args, **kwargs)
zelf.runners.append(runner)
runner.start()
else: runner = random.choice(zelf.runners)
return runner
[docs] def cleanup(zelf, dojoin=False):
""" Remove idle Runners. """
todo = []
for runner in zelf.runners:
if runner.stopped or not len(runner.queue): todo.append(runner)
for runner in todo: runner.stop() ; zelf.runners.remove(runner)
## PLUGINS
[docs]class Plugins(Object):
""" Object to register Plugins with. """
def __init__(zelf, *args, **kwargs):
Object.__init__(zelf, *args, **kwargs)
zelf.plugins = []
[docs] def get_names(zelf, plugsdir):
""" Return plugnames from the plugin directory. """
return [x[:-3] for x in os.listdir(plugsdir) if x.endswith(".py")]
[docs] def load_plugs(zelf, path):
""" Load plugins from the plugin directory. """
logging.warn("plugs %s" % path)
for plugname in zelf.get_names(path):
if "__" in plugname: continue
try:
zelf[plugname] = zelf.load_mod(plugname, path, force=True)
if plugname not in zelf.plugins: zelf.plugins.append(plugname)
except: error() ; continue
zelf.ready()
[docs] def load_package(zelf, modname):
""" Load the plugins package. """
mod = __import__(modname)
path, fn = os.path.split(mod.__file__)
path += os.sep + "plugs"
zelf.load_plugs(path)
[docs] def load_mod(zelf, plugname, pdir="", force=False):
""" load a plugin;'s module. """
logging.info("mod %s" % plugname)
if plugname in zelf:
if not force: return zelf[plugname]
zelf[plugname] = imp.reload(zelf[plugname])
else:
if not pdir: pdir = j(zelf.root, "plugs")
search = imp.find_module(plugname, [pdir,])
zelf[plugname] = imp.load_module(plugname, *search)
return zelf[plugname]
[docs] def plug_init(zelf, *args, **kwargs):
""" Initialize a plugin, use the init() function provided in the plugin, if available. """
if args: plugname = args[0]
else: plugname = None
if plugname not in zelf: return
if "init" in dir(zelf[plugname]):
logging.warn("init %s" % plugname)
zelf[plugname].init(*args, **kwargs)
[docs] def plug_shutdown(zelf, plugname):
""" Shutdown a plugin by calling the shutdown() function, if provided. """
if plugname not in zelf: return
if "shutdown" in dir(zelf[plugname]): zelf[plugname].shutdown()
[docs] def exit(zelf, *args, **kwargs):
""" close all plugins by calling there shutdown() function. """
for plugname in zelf: zelf.plug_shutdown(plugname)
zelf.ready()
[docs] def reload(zelf, plugname, force=False):
""" Reload (unload, load) a plugin. """
zelf.unload(plugname)
mod = zelf.load_mod(plugname, force)
return mod
[docs] def do_init(zelf, *args, **kwargs):
""" Call the init function of all plugin or a specific one if argument is given. """
if args: target = args[0]
else: target = None
for plugname in zelf.plugins:
if target and target != "all" and plugname != target: continue
zelf.plug_init(plugname, *args, **kwargs)
## LOOPS
[docs]class Looper(Object):
""" A Looper calls a function every x seconds. """
def __init__(zelf, *args, **kwargs):
zelf._state.time_sleep = args[0]
zelf._state.time_in = time.time()
zelf.func = args[1]
zelf.timer = threading.Timer(zelf._state.time_sleep, zelf.func_do)
zelf.timer.start()
[docs] def start(zelf, *args, **kwargs): zelf.func_do()
[docs] def func_do(zelf, *args, **kwargs):
zelf._state.time_in = time.time()
try: zelf.func()
except: error()
logging.info("loop %s" % get_funcname(zelf.func))
zelf.timer = threading.Timer(zelf._state.time_sleep, zelf.func_do)
zelf.timer.start()
## BOTS
[docs]class Bot(Dispatcher):
""" Base Bot class. """
def __init__(zelf, *args, **kwargs):
Dispatcher.__init__(zelf, *args, **kwargs)
zelf.type = get_name(zelf)
[docs] def set_cfg(zelf, *args, **kwargs): zelf.cfg = cfg
[docs] def exit(zelf, *args, **kwargs): zelf._state.status = "" ; zelf.ready()
[docs] def join_channels(zelf, *args, **kwargs): pass
[docs] def begin(zelf, *args, **kwargs):
zelf.connect()
if kernel.cfg.shell: print("")
zelf._state.status = "running"
while zelf._state.status:
time.sleep(0.01)
try: event = zelf.do_one()
except RemoteDisconnect: time.sleep(2) ; zelf.connect()
zelf._state.time_in = time.time()
if zelf._state.status == "once": break
kernel.ready()
_thread.interrupt_main()
[docs] def do_one(zelf, *args, **kwargs): pass
[docs] def get_prompt(zelf, *args, **kwargs): return ""
[docs] def connect(zelf, *args, **kwargs): pass
[docs] def announce(zelf, *args, **kwargs):
if "channels" in zelf:
for channel in zelf.channels: zelf.say(channel, args[0], **kwargs)
else: zelf.say(*args, **kwargs)
## CONSOLE
[docs]class ConsoleBot(Bot):
[docs] def boot(zelf, *args, **kwargs): logging.warn("")
def _raw(zelf, *args, **kwargs):
txt = args[0]
logging.debug("> %s" % txt)
try: sys.stdout.write(txt) ; sys.stdout.write("\n") ; sys.stdout.flush()
except Exception as ex: logging.warn(str(ex))
[docs] def say(zelf, *args, **kwargs):
try: txt = args[1]
except IndexError: txt = args[0]
zelf._state.time_out = time.time()
zelf._raw(txt)
[docs] def get_prompt(zelf, *args, **kwargs): return "%s -=- %s%s<%s " % (short_date(time.ctime(time.time())).split()[1], BOLD, YELLOW, ENDC)
[docs] def do_one(zelf, *args, **kwargs):
o = Object()
if not kernel.cfg.shell: in_txt = " ".join(kernel.cfg.runargs) ; zelf._state.status = "once"
else: in_txt = input(zelf.get_prompt())
zelf._state.time_in = time.time()
o.txt = in_txt
o._target = zelf
kernel.cmnds.dispatch(o)
return o
## IRC
[docs]class IRCBot(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._lock = _thread.allocate_lock()
zelf._buffer = []
zelf._lastline = ""
zelf.encoding = "utf-8"
if "realname" not in zelf: zelf.realname = "z_"
if "server" not in zelf: zelf.server = "localhost"
if "port" not in zelf: zelf.port = 6667
if "nick" not in zelf: zelf.nick = "z_"
if "channels" not in zelf: zelf.channels = []
if "channel" in zelf and zelf["channel"] not in zelf.channels: zelf.channels.append(zelf.channel)
def _raw(zelf, txt):
logging.debug("> %s" % txt)
if not txt.endswith(zelf.marker): txt += zelf.marker
txt = bytes(txt, zelf.encoding)
if 'ssl' in zelf and zelf.ssl: zelf.sock.write(txt)
else: zelf.sock.send(txt[:512])
zelf._state.time_out = 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()
logging.warn('connect %s - %s' % (zelf.server, zelf.port))
zelf.oldsock.settimeout(60)
zelf.oldsock.connect((zelf.server, int(str(zelf.port or 6667))))
zelf.blocking = 1
zelf.oldsock.setblocking(zelf.blocking)
logging.warn('connection made to %s' % zelf.server)
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):
logging.warn("logged on !!")
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.ready()
zelf.join_channels()
def _dodcc(zelf, event, s):
s.send(bytes('Welcome to Rext ' + 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.warn("< %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[1] ; port = parsed.args[2][:-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 do_one(zelf, *args, **kwargs):
if not zelf._buffer: zelf.read_some()
try: line = zelf._buffer.pop(0)
except IndexError: return Object()
e = Object()
e._target = zelf
o = irc_parse(e, line.rstrip())
o.cc = zelf.cc
try: zelf.do_func(o.etype, o)
except: error()
return o
[docs] def read_some(zelf, *args, **kwargs):
try:
if "ssl" in zelf and zelf.ssl: inbytes = zelf.sock.read()
else: inbytes = zelf.sock.recv(512)
except socket.timeout: return
except Exception as ex: error() ; return
try: txt = str(inbytes, zelf.encoding)
except UnicodeDecodeError: txt = " "
if txt == "": raise RemoteDisconnect()
zelf._lastline += txt
splitted = zelf._lastline.split(zelf.marker)
for s in splitted[:-1]: logging.warn("< %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)
logging.warn('logging in on %s - this may take a while' % zelf.server)
zelf._raw("NICK %s" % zelf.nick or "z_")
zelf._raw("USER %s localhost %s :%s" % (zelf.username or "z_", zelf.server or "localhost", zelf.realname))
[docs] def say(zelf, *args, **kwargs):
try: channel, txt = args
except ValueError: channel = zelf.channel ; txt = args[0]
zelf.privmsg(channel, txt)
[docs] def join_channels(zelf, *args, **kwargs):
for channel in zelf.channels: zelf.join(channel)
[docs] def broadcast(zelf, txt):
for i in zelf.channels: zelf.say(i, txt)
[docs] def connect(zelf, reconnect=True):
try: res = zelf._connect()
except: error() ; return
if res: time.sleep(1) ; zelf.logon()
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_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_invite(zelf, event): zelf.join(event.channel)
[docs] def handle_privmsg(zelf, event):
if event.txt.startswith("\001DCC"): zelf._dccconnect(event) ; return
kernel.cmnds.dispatch(event)
[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 handle_366(zelf, *args, **kwargs): zelf.ready()
[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, what): zelf.send('PRIVMSG %s :%s' % (printto, what))
[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/z2'): 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)
## irc_parse function
[docs]def irc_parse(obj, *args, **kwargs):
rawstr = str(args[0])
zelf = obj
zelf.servermsg = False
splitted = re.split('\s+', rawstr)
if not rawstr[0] == ':': zelf.servermsg = True
zelf.prefix = splitted[0]
if zelf.servermsg: zelf.etype = zelf.prefix
else: zelf.etype = splitted[1]
try:
nickuser = zelf.prefix.split('!')
zelf.origin = nickuser[1]
zelf.nick = nickuser[0][1:]
except IndexError: zelf.origin = zelf.prefix ; zelf.servermsg = True
if zelf.etype in pfc:
zelf.arguments = splitted[2:pfc[zelf.etype]+2]
txtsplit = re.split('\s+', rawstr, pfc[zelf.etype]+2)
zelf.txt = txtsplit[-1]
else: zelf.arguments = splitted[2:]
if zelf.arguments: zelf.target = zelf.arguments[0]
zelf.postfix = ' '.join(zelf.arguments)
if not "txt" in zelf: zelf.txt = rawstr.rsplit(":")[-1]
if zelf.txt.startswith(":"): zelf.txt = zelf.txt[1:]
if not "channel" in zelf:
for c in zelf.arguments + [zelf.txt, ]:
if c.startswith("#"): zelf.channel = c
if zelf.servermsg:
zelf.origin = zelf.origin[1:-1]
zelf.channel = zelf.origin
if not "origin" in zelf: zelf.origin = zelf.channel
return zelf
## XMPP
[docs]class XMPPBot(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(stream=sys.stdout))
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.warn("openfire used")
import ssl
zelf.xmpp.ssl_version = ssl.PROTOCOL_SSLv3
zelf._connected = threading.Event()
zelf.channels = []
zelf._state.time_in = time.time()
def _raw(zelf, txt):
logging.debug("> %s" % txt)
zelf.xmpp.send_raw(txt)
zelf._state.time_out = time.time()
[docs] def announce(zelf, *args, **kwargs):
for channel in zelf.channels: zelf.say(channel, args[0])
[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.warn("session started")
[docs] def exception(zelf, ex):
zelf._state.error = ex
logging.error(str(ex))
zelf.exit()
[docs] def handle_failedauth(zelf, error, *args): logging.error(error)
[docs] def handle_failure(zelf, ex, *args, **kwargs):
zelf._state.error = ex
logging.warn(ex)
[docs] def handle_disconnected(zelf, *args, **kwargs):
zelf._connected.clear()
zelf._state.status = "disconnected"
logging.error("disconnected")
[docs] def handle_connected(zelf, *args, **kwargs): zelf._state.status = "connected" ; zelf._connected.set() ; logging.warn("connected!")
[docs] def loop(zelf, *args, **kwargs): zelf.xmpp.process()
[docs] def say(zelf, *args, **kwargs):
try: channel, txt = args
except ValueError: logging.warn("channel not set") ; return
logging.debug("> %s" % txt)
zelf.xmpp.send_message(channel, txt)
zelf._state.time_out = time.time()
[docs] def do_one(zelf, *args, **kwargs):
zelf.time_in = 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("ignoring delayed message") ; return
logging.warn("< %s" % data)
m = Object(**data)
if m.type == "error": logging.error(m.to_json()) ; return
m.origin = stripped(str(m["from"]))
if not m.origin in zelf.channels: zelf.channels.append(m.origin)
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("< %s" % data)
o = Object(**data)
if "from" in zelf and zelf["from"]:
o.origin = stripped(str(zelf["from"]))
o.to = zelf.origin
else: o.origin = "" ; zelf.to = ""
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 "origin" in zelf: zelf.channels.remove(zelf.origin)
o.no_dispatch = True
o._target = zelf
kernel.cmnds.dispatch(o)
## VARS
kernel = Object()
kernel.cmnds = Dispatcher()
kernel.workers = Dispatcher()
kernel.plugs = Plugins()
kernel.run = Object()
kernel.cfg = Object()
kernel.fleet = []