rssbot
Advanced tools
| # This file is placed in the Public Domain. | ||
| __doc__ = __name__.upper() | ||
| from .object import * # noqa: F403 | ||
| from .object import __dir__ | ||
| __all__ = __dir__() |
| # This file is placed in the Public Domain. | ||
| "client" | ||
| import threading | ||
| from .fleet import Fleet | ||
| from .engine import Engine | ||
| class Client(Engine): | ||
| def __init__(self): | ||
| Engine.__init__(self) | ||
| self.olock = threading.RLock() | ||
| Fleet.add(self) | ||
| def announce(self, txt): | ||
| pass | ||
| def display(self, event): | ||
| with self.olock: | ||
| for tme in sorted(event.result): | ||
| self.dosay(event.channel, event.result[tme]) | ||
| def dosay(self, channel, txt): | ||
| self.say(channel, txt) | ||
| def raw(self, txt): | ||
| raise NotImplementedError("raw") | ||
| def say(self, channel, txt): | ||
| self.raw(txt) | ||
| def __dir__(): | ||
| return ( | ||
| "Client", | ||
| ) |
+137
| # This file is placed in the Public Domain. | ||
| "command" | ||
| import inspect | ||
| from .fleet import Fleet | ||
| from .object import Default | ||
| from .thread import launch | ||
| from .utils import spl | ||
| class Commands: | ||
| cmds = {} | ||
| names = {} | ||
| @staticmethod | ||
| def add(func, mod=None): | ||
| Commands.cmds[func.__name__] = func | ||
| if mod: | ||
| Commands.names[func.__name__] = mod.__name__.split(".")[-1] | ||
| @staticmethod | ||
| def get(cmd): | ||
| return Commands.cmds.get(cmd, None) | ||
| @staticmethod | ||
| def scan(mod): | ||
| for key, cmdz in inspect.getmembers(mod, inspect.isfunction): | ||
| if key.startswith("cb"): | ||
| continue | ||
| if "event" in cmdz.__code__.co_varnames: | ||
| Commands.add(cmdz, mod) | ||
| def command(evt): | ||
| parse(evt) | ||
| func = Commands.get(evt.cmd) | ||
| if not func: | ||
| evt.ready() | ||
| return | ||
| func(evt) | ||
| Fleet.display(evt) | ||
| evt.ready() | ||
| def inits(pkg, names): | ||
| modz = [] | ||
| for name in sorted(spl(names)): | ||
| mod = getattr(pkg, name, None) | ||
| if not mod: | ||
| continue | ||
| if "init" in dir(mod): | ||
| thr = launch(mod.init) | ||
| modz.append((mod, thr)) | ||
| return modz | ||
| def scan(pkg): | ||
| mods = [] | ||
| for modname in dir(pkg): | ||
| mod = getattr(pkg, modname) | ||
| Commands.scan(mod) | ||
| mods.append(mod) | ||
| return mods | ||
| def parse(obj, txt=""): | ||
| if txt == "": | ||
| if "txt" in dir(obj): | ||
| txt = obj.txt | ||
| else: | ||
| txt = "" | ||
| args = [] | ||
| obj.args = [] | ||
| obj.cmd = "" | ||
| obj.gets = Default() | ||
| obj.index = None | ||
| obj.mod = "" | ||
| obj.opts = "" | ||
| obj.result = {} | ||
| obj.sets = Default() | ||
| obj.silent = Default() | ||
| obj.txt = txt | ||
| obj.otxt = obj.txt | ||
| _nr = -1 | ||
| for spli in obj.otxt.split(): | ||
| if spli.startswith("-"): | ||
| try: | ||
| obj.index = int(spli[1:]) | ||
| except ValueError: | ||
| obj.opts += spli[1:] | ||
| continue | ||
| if "-=" in spli: | ||
| key, value = spli.split("-=", maxsplit=1) | ||
| setattr(obj.silent, key, value) | ||
| setattr(obj.gets, key, value) | ||
| continue | ||
| if "==" in spli: | ||
| key, value = spli.split("==", maxsplit=1) | ||
| setattr(obj.gets, key, value) | ||
| continue | ||
| if "=" in spli: | ||
| key, value = spli.split("=", maxsplit=1) | ||
| if key == "mod": | ||
| if obj.mod: | ||
| obj.mod += f",{value}" | ||
| else: | ||
| obj.mod = value | ||
| continue | ||
| setattr(obj.sets, key, value) | ||
| continue | ||
| _nr += 1 | ||
| if _nr == 0: | ||
| obj.cmd = spli | ||
| continue | ||
| args.append(spli) | ||
| if args: | ||
| obj.args = args | ||
| obj.txt = obj.cmd or "" | ||
| obj.rest = " ".join(obj.args) | ||
| obj.txt = obj.cmd + " " + obj.rest | ||
| else: | ||
| obj.txt = obj.cmd or "" | ||
| def __dir__(): | ||
| return ( | ||
| 'Commands', | ||
| 'command', | ||
| 'parse', | ||
| 'scan' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "disk" | ||
| import json | ||
| import json.decoder | ||
| import pathlib | ||
| import threading | ||
| from .object import dump, load, update | ||
| lock = threading.RLock() | ||
| class Cache: | ||
| objs = {} | ||
| @staticmethod | ||
| def add(path, obj): | ||
| Cache.objs[path] = obj | ||
| @staticmethod | ||
| def get(path): | ||
| return Cache.objs.get(path, None) | ||
| @staticmethod | ||
| def update(path, obj): | ||
| if not obj: | ||
| return | ||
| if path in Cache.objs: | ||
| update(Cache.objs[path], obj) | ||
| else: | ||
| Cache.add(path, obj) | ||
| def cdir(path): | ||
| pth = pathlib.Path(path) | ||
| pth.parent.mkdir(parents=True, exist_ok=True) | ||
| def read(obj, path): | ||
| with lock: | ||
| with open(path, "r", encoding="utf-8") as fpt: | ||
| try: | ||
| update(obj, load(fpt)) | ||
| except json.decoder.JSONDecodeError as ex: | ||
| ex.add_note(path) | ||
| raise ex | ||
| def write(obj, path): | ||
| with lock: | ||
| cdir(path) | ||
| with open(path, "w", encoding="utf-8") as fpt: | ||
| dump(obj, fpt, indent=4) | ||
| return path | ||
| def __dir__(): | ||
| return ( | ||
| "Cache", | ||
| "cdir", | ||
| "read", | ||
| "write" | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "callback engine" | ||
| import queue | ||
| import threading | ||
| import _thread | ||
| from .thread import launch | ||
| class Engine: | ||
| def __init__(self): | ||
| self.lock = _thread.allocate_lock() | ||
| self.cbs = {} | ||
| self.queue = queue.Queue() | ||
| self.ready = threading.Event() | ||
| self.stopped = threading.Event() | ||
| def available(self, event): | ||
| return event.type in self.cbs | ||
| def callback(self, event): | ||
| func = self.cbs.get(event.type, None) | ||
| if func: | ||
| event._thr = launch(func, event) | ||
| def loop(self): | ||
| while not self.stopped.is_set(): | ||
| event = self.poll() | ||
| if event is None: | ||
| break | ||
| event.orig = repr(self) | ||
| self.callback(event) | ||
| def poll(self): | ||
| return self.queue.get() | ||
| def put(self, event): | ||
| self.queue.put(event) | ||
| def register(self, typ, cbs): | ||
| self.cbs[typ] = cbs | ||
| def start(self, daemon=True): | ||
| self.stopped.clear() | ||
| launch(self.loop, daemon=daemon) | ||
| def stop(self): | ||
| self.stopped.set() | ||
| self.queue.put(None) | ||
| def wait(self): | ||
| pass | ||
| def __dir__(): | ||
| return ( | ||
| "Engine", | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "event" | ||
| import threading | ||
| import time | ||
| from .object import Default | ||
| class Event(Default): | ||
| def __init__(self): | ||
| Default.__init__(self) | ||
| self._ready = threading.Event() | ||
| self._thr = None | ||
| self.ctime = time.time() | ||
| self.result = {} | ||
| self.type = "event" | ||
| def done(self): | ||
| self.reply("ok") | ||
| def ready(self): | ||
| self._ready.set() | ||
| def reply(self, txt): | ||
| self.result[time.time()] = txt | ||
| def wait(self, timeout=None): | ||
| self._ready.wait() | ||
| if self._thr: | ||
| self._thr.join() | ||
| def __dir__(): | ||
| return ( | ||
| "Event", | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "find" | ||
| import os | ||
| import time | ||
| from .disk import Cache, read | ||
| from .object import Object, items, update | ||
| from .paths import fqn, long, store | ||
| def find(clz, selector=None, deleted=False, matching=False): | ||
| clz = long(clz) | ||
| if selector is None: | ||
| selector = {} | ||
| for pth in fns(clz): | ||
| obj = Cache.get(pth) | ||
| if not obj: | ||
| obj = Object() | ||
| read(obj, pth) | ||
| Cache.add(pth, obj) | ||
| if not deleted and isdeleted(obj): | ||
| continue | ||
| if selector and not search(obj, selector, matching): | ||
| continue | ||
| yield pth, obj | ||
| def fns(clz): | ||
| pth = store(clz) | ||
| for rootdir, dirs, _files in os.walk(pth, topdown=False): | ||
| for dname in dirs: | ||
| ddd = os.path.join(rootdir, dname) | ||
| for fll in os.listdir(ddd): | ||
| yield os.path.join(ddd, fll) | ||
| def fntime(daystr): | ||
| datestr = " ".join(daystr.split(os.sep)[-2:]) | ||
| datestr = datestr.replace("_", " ") | ||
| if "." in datestr: | ||
| datestr, rest = datestr.rsplit(".", 1) | ||
| else: | ||
| rest = "" | ||
| timed = time.mktime(time.strptime(datestr, "%Y-%m-%d %H:%M:%S")) | ||
| if rest: | ||
| timed += float("." + rest) | ||
| return float(timed) | ||
| def isdeleted(obj): | ||
| return "__deleted__" in dir(obj) and obj.__deleted__ | ||
| def last(obj, selector=None): | ||
| if selector is None: | ||
| selector = {} | ||
| result = sorted(find(fqn(obj), selector), key=lambda x: fntime(x[0])) | ||
| res = "" | ||
| if result: | ||
| inp = result[-1] | ||
| update(obj, inp[-1]) | ||
| res = inp[0] | ||
| return res | ||
| def search(obj, selector, matching=False): | ||
| res = False | ||
| if not selector: | ||
| return res | ||
| for key, value in items(selector): | ||
| val = getattr(obj, key, None) | ||
| if not val: | ||
| continue | ||
| if matching and value == val: | ||
| res = True | ||
| elif str(value).lower() in str(val).lower() or value == "match": | ||
| res = True | ||
| else: | ||
| res = False | ||
| break | ||
| return res | ||
| def __dir__(): | ||
| return ( | ||
| "find", | ||
| "fns", | ||
| "fntime", | ||
| "last", | ||
| "search" | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "fleet" | ||
| import time | ||
| class Fleet: | ||
| clients = {} | ||
| @staticmethod | ||
| def add(client): | ||
| Fleet.clients[repr(client)] = client | ||
| @staticmethod | ||
| def all(): | ||
| return Fleet.clients.values() | ||
| @staticmethod | ||
| def announce(txt): | ||
| for client in Fleet.all(): | ||
| client.announce(txt) | ||
| @staticmethod | ||
| def dispatch(evt): | ||
| client = Fleet.get(evt.orig) | ||
| client.put(evt) | ||
| @staticmethod | ||
| def display(evt): | ||
| client = Fleet.get(evt.orig) | ||
| client.display(evt) | ||
| @staticmethod | ||
| def first(): | ||
| clt = list(Fleet.all()) | ||
| res = None | ||
| if clt: | ||
| res = clt[0] | ||
| return res | ||
| @staticmethod | ||
| def get(orig): | ||
| return Fleet.clients.get(orig, None) | ||
| @staticmethod | ||
| def say(orig, channel, txt): | ||
| client = Fleet.get(orig) | ||
| if client: | ||
| client.say(channel, txt) | ||
| @staticmethod | ||
| def shutdown(): | ||
| for client in Fleet.all(): | ||
| client.stop() | ||
| @staticmethod | ||
| def wait(): | ||
| time.sleep(0.1) | ||
| for client in Fleet.all(): | ||
| client.wait() | ||
| def __dir__(): | ||
| return ( | ||
| "Fleet", | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "logging" | ||
| import logging | ||
| LEVELS = { | ||
| "debug": logging.DEBUG, | ||
| "info": logging.INFO, | ||
| "warning": logging.WARNING, | ||
| "warn": logging.WARNING, | ||
| "error": logging.ERROR, | ||
| "critical": logging.CRITICAL, | ||
| } | ||
| def level(loglevel="debug"): | ||
| if loglevel != "none": | ||
| format_short = "%(message)-80s" | ||
| datefmt = "%H:%M:%S" | ||
| logging.basicConfig(datefmt=datefmt, format=format_short, force=True) | ||
| logging.getLogger().setLevel(LEVELS.get(loglevel)) | ||
| def rlog(loglevel, txt, ignore=None): | ||
| if ignore is None: | ||
| ignore = [] | ||
| for ign in ignore: | ||
| if ign in str(txt): | ||
| return | ||
| logging.log(LEVELS.get(loglevel), txt) | ||
| def __dir__(): | ||
| return ( | ||
| "level", | ||
| "rlog" | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "methods" | ||
| from .object import items, keys | ||
| def edit(obj, setter, skip=True): | ||
| for key, val in items(setter): | ||
| if skip and val == "": | ||
| continue | ||
| try: | ||
| setattr(obj, key, int(val)) | ||
| continue | ||
| except ValueError: | ||
| pass | ||
| try: | ||
| setattr(obj, key, float(val)) | ||
| continue | ||
| except ValueError: | ||
| pass | ||
| if val in ["True", "true"]: | ||
| setattr(obj, key, True) | ||
| elif val in ["False", "false"]: | ||
| setattr(obj, key, False) | ||
| else: | ||
| setattr(obj, key, val) | ||
| def fmt(obj, args=None, skip=None, plain=False, empty=False): | ||
| if args is None: | ||
| args = keys(obj) | ||
| if skip is None: | ||
| skip = [] | ||
| txt = "" | ||
| for key in args: | ||
| if key.startswith("__"): | ||
| continue | ||
| if key in skip: | ||
| continue | ||
| value = getattr(obj, key, None) | ||
| if value is None: | ||
| continue | ||
| if not empty and not value: | ||
| continue | ||
| if plain: | ||
| txt += f"{value} " | ||
| elif isinstance(value, str): | ||
| txt += f'{key}="{value}" ' | ||
| else: | ||
| txt += f"{key}={value} " | ||
| return txt.strip() | ||
| def __dir__(): | ||
| return ( | ||
| "edit", | ||
| "fmt" | ||
| ) |
| # This file is been placed in the Public Domain. | ||
| "available commands" | ||
| from ..cmnd import Commands | ||
| def cmd(event): | ||
| event.reply(",".join(sorted(Commands.cmds))) |
| # This file is placed in the Public Domain. | ||
| "log text" | ||
| import time | ||
| from ..disk import write | ||
| from ..find import find, fntime | ||
| from ..object import Object | ||
| from ..paths import ident, store | ||
| from ..utils import elapsed | ||
| class Log(Object): | ||
| def __init__(self): | ||
| super().__init__() | ||
| self.txt = '' | ||
| def log(event): | ||
| if not event.rest: | ||
| nmr = 0 | ||
| for fnm, obj in find('log'): | ||
| lap = elapsed(time.time() - fntime(fnm)) | ||
| event.reply(f'{nmr} {obj.txt} {lap}') | ||
| nmr += 1 | ||
| if not nmr: | ||
| event.reply('no log') | ||
| return | ||
| obj = Log() | ||
| obj.txt = event.rest | ||
| write(obj, store(ident(obj))) | ||
| event.done() |
| # This file is been placed in the Public Domain. | ||
| "available types" | ||
| from ..paths import types | ||
| def ls(event): | ||
| event.reply(",".join([x.split(".")[-1].lower() for x in types()])) |
| # This file is placed in the Public Domain | ||
| "create service file" | ||
| from ..object import Default | ||
| class Main: | ||
| name = Default.__module__.split(".")[-2] | ||
| TXT = """[Unit] | ||
| Description=%s | ||
| After=network-online.target | ||
| [Service] | ||
| Type=simple | ||
| User=%s | ||
| Group=%s | ||
| ExecStart=/home/%s/.local/bin/%s -s | ||
| [Install] | ||
| WantedBy=multi-user.target""" | ||
| def srv(event): | ||
| import getpass | ||
| name = getpass.getuser() | ||
| event.reply(TXT % (Main.name.upper(), name, name, name, Main.name)) |
| # This file is placed in the Public Domain. | ||
| "todo list" | ||
| import time | ||
| from ..disk import write | ||
| from ..find import find, fntime | ||
| from ..object import Object | ||
| from ..paths import ident, store | ||
| from ..utils import elapsed | ||
| class Todo(Object): | ||
| def __init__(self): | ||
| Object.__init__(self) | ||
| self.txt = '' | ||
| def dne(event): | ||
| if not event.args: | ||
| event.reply("dne <txt>") | ||
| return | ||
| selector = {'txt': event.args[0]} | ||
| nmr = 0 | ||
| for fnm, obj in find('todo', selector): | ||
| nmr += 1 | ||
| obj.__deleted__ = True | ||
| write(obj, fnm) | ||
| event.done() | ||
| break | ||
| if not nmr: | ||
| event.reply("nothing todo") | ||
| def tdo(event): | ||
| if not event.rest: | ||
| nmr = 0 | ||
| for fnm, obj in find('todo'): | ||
| lap = elapsed(time.time()-fntime(fnm)) | ||
| event.reply(f'{nmr} {obj.txt} {lap}') | ||
| nmr += 1 | ||
| if not nmr: | ||
| event.reply("no todo") | ||
| return | ||
| obj = Todo() | ||
| obj.txt = event.rest | ||
| write(obj, store(ident(obj))) | ||
| event.done() |
+127
| # This file is placed in the Public Domain. | ||
| "a clean namespace" | ||
| import json | ||
| class Object: | ||
| def __contains__(self, key): | ||
| return key in dir(self) | ||
| def __iter__(self): | ||
| return iter(self.__dict__) | ||
| def __len__(self): | ||
| return len(self.__dict__) | ||
| def __str__(self): | ||
| return str(self.__dict__) | ||
| def construct(obj, *args, **kwargs): | ||
| if args: | ||
| val = args[0] | ||
| if isinstance(val, zip): | ||
| update(obj, dict(val)) | ||
| elif isinstance(val, dict): | ||
| update(obj, val) | ||
| elif isinstance(val, Object): | ||
| update(obj, vars(val)) | ||
| if kwargs: | ||
| update(obj, kwargs) | ||
| def items(obj): | ||
| if isinstance(obj, dict): | ||
| return obj.items() | ||
| return obj.__dict__.items() | ||
| def keys(obj): | ||
| if isinstance(obj, dict): | ||
| return obj.keys() | ||
| return obj.__dict__.keys() | ||
| def update(obj, data): | ||
| if isinstance(data, dict): | ||
| return obj.__dict__.update(data) | ||
| obj.__dict__.update(vars(data)) | ||
| def values(obj): | ||
| if isinstance(obj, dict): | ||
| return obj.values() | ||
| return obj.__dict__.values() | ||
| class Encoder(json.JSONEncoder): | ||
| def default(self, o): | ||
| if isinstance(o, dict): | ||
| return o.items() | ||
| if issubclass(type(o), Object): | ||
| return vars(o) | ||
| if isinstance(o, list): | ||
| return iter(o) | ||
| try: | ||
| return json.JSONEncoder.default(self, o) | ||
| except TypeError: | ||
| try: | ||
| return vars(o) | ||
| except TypeError: | ||
| return repr(o) | ||
| def dump(obj, fp, *args, **kw): | ||
| kw["cls"] = Encoder | ||
| json.dump(obj, fp, *args, **kw) | ||
| def dumps(obj, *args, **kw): | ||
| kw["cls"] = Encoder | ||
| return json.dumps(obj, *args, **kw) | ||
| def hook(objdict): | ||
| obj = Object() | ||
| construct(obj, objdict) | ||
| return obj | ||
| def load(fp, *args, **kw): | ||
| kw["object_hook"] = hook | ||
| return json.load(fp, *args, **kw) | ||
| def loads(s, *args, **kw): | ||
| kw["object_hook"] = hook | ||
| return json.loads(s, *args, **kw) | ||
| class Default(Object): | ||
| def __getattr__(self, key): | ||
| if key not in self: | ||
| setattr(self, key, "") | ||
| return self.__dict__.get(key, "") | ||
| def __dir__(): | ||
| return ( | ||
| 'Default', | ||
| 'Object', | ||
| 'construct', | ||
| 'dump', | ||
| 'dumps', | ||
| 'items', | ||
| 'keys', | ||
| 'load', | ||
| 'loads', | ||
| 'update', | ||
| 'values' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "output" | ||
| import queue | ||
| import threading | ||
| from .thread import launch | ||
| class Output: | ||
| def __init__(self): | ||
| self.olock = threading.RLock() | ||
| self.oqueue = queue.Queue() | ||
| self.ostop = threading.Event() | ||
| def display(self, event): | ||
| with self.olock: | ||
| for tme in sorted(event.result): | ||
| self.dosay(event.channel, event.result[tme]) | ||
| def dosay(self, channel, txt): | ||
| raise NotImplementedError("dosay") | ||
| def oput(self, event): | ||
| self.oqueue.put(event) | ||
| def output(self): | ||
| while not self.ostop.is_set(): | ||
| event = self.oqueue.get() | ||
| if event is None: | ||
| self.oqueue.task_done() | ||
| break | ||
| self.display(event) | ||
| self.oqueue.task_done() | ||
| def start(self): | ||
| self.ostop.clear() | ||
| launch(self.output) | ||
| def stop(self): | ||
| self.ostop.set() | ||
| self.oqueue.put(None) | ||
| def wait(self): | ||
| self.oqueue.join() | ||
| def __dir__(): | ||
| return ( | ||
| "Output", | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "paths" | ||
| import datetime | ||
| import os | ||
| import pathlib | ||
| class Workdir: | ||
| name = __file__.rsplit(os.sep, maxsplit=2)[-2] | ||
| wdr = "" | ||
| def fqn(obj): | ||
| kin = str(type(obj)).split()[-1][1:-2] | ||
| if kin == "type": | ||
| kin = f"{obj.__module__}.{obj.__name__}" | ||
| return kin | ||
| def getpath(obj): | ||
| return store(ident(obj)) | ||
| def ident(obj): | ||
| return os.path.join(fqn(obj), *str(datetime.datetime.now()).split()) | ||
| def long(name): | ||
| split = name.split(".")[-1].lower() | ||
| res = name | ||
| for names in types(): | ||
| if split == names.split(".")[-1].lower(): | ||
| res = names | ||
| break | ||
| return res | ||
| def pidname(name): | ||
| return os.path.join(Workdir.wdr, f"{name}.pid") | ||
| def setwd(name, path=""): | ||
| path = path or os.path.expanduser(f"~/.{name}") | ||
| Workdir.wdr = path | ||
| skel() | ||
| def skel(): | ||
| pth = pathlib.Path(store()) | ||
| pth.mkdir(parents=True, exist_ok=True) | ||
| return str(pth) | ||
| def store(pth=""): | ||
| return os.path.join(Workdir.wdr, "store", pth) | ||
| def strip(pth, nmr=2): | ||
| return os.path.join(pth.split(os.sep)[-nmr:]) | ||
| def types(): | ||
| return os.listdir(store()) | ||
| def wdr(pth): | ||
| return os.path.join(Workdir.wdr, pth) | ||
| def __dir__(): | ||
| return ( | ||
| "Workdir", | ||
| "fqn", | ||
| "getpath", | ||
| "long", | ||
| "ident", | ||
| "pidname", | ||
| "setwd", | ||
| "skel", | ||
| "store", | ||
| "wdr" | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "threads" | ||
| import logging | ||
| import queue | ||
| import time | ||
| import threading | ||
| import _thread | ||
| lock = threading.RLock() | ||
| class Thread(threading.Thread): | ||
| def __init__(self, func, thrname, *args, daemon=True, **kwargs): | ||
| super().__init__(None, self.run, thrname, (), daemon=daemon) | ||
| self.name = thrname or kwargs.get("name", name(func)) | ||
| self.queue = queue.Queue() | ||
| self.result = None | ||
| self.starttime = time.time() | ||
| self.stopped = threading.Event() | ||
| self.queue.put((func, args)) | ||
| def __iter__(self): | ||
| return self | ||
| def __next__(self): | ||
| yield from dir(self) | ||
| def run(self): | ||
| func, args = self.queue.get() | ||
| try: | ||
| self.result = func(*args) | ||
| except (KeyboardInterrupt, EOFError): | ||
| _thread.interrupt_main() | ||
| except Exception as ex: | ||
| logging.exception(ex) | ||
| _thread.interrupt_main() | ||
| def join(self, timeout=None): | ||
| super().join(timeout) | ||
| return self.result | ||
| def launch(func, *args, **kwargs): | ||
| with lock: | ||
| nme = kwargs.get("name", None) | ||
| if not nme: | ||
| nme = name(func) | ||
| thread = Thread(func, nme, *args, **kwargs) | ||
| thread.start() | ||
| return thread | ||
| def name(obj): | ||
| typ = type(obj) | ||
| if "__builtins__" in dir(typ): | ||
| return obj.__name__ | ||
| if "__self__" in dir(obj): | ||
| return f"{obj.__self__.__class__.__name__}.{obj.__name__}" | ||
| if "__class__" in dir(obj) and "__name__" in dir(obj): | ||
| return f"{obj.__class__.__name__}.{obj.__name__}" | ||
| if "__class__" in dir(obj): | ||
| return f"{obj.__class__.__module__}.{obj.__class__.__name__}" | ||
| if "__name__" in dir(obj): | ||
| return f"{obj.__class__.__name__}.{obj.__name__}" | ||
| return "" | ||
| def __dir__(): | ||
| return ( | ||
| "Thread", | ||
| "launch", | ||
| "name" | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "timer/repeater" | ||
| import threading | ||
| import time | ||
| from .thread import launch, name | ||
| class Timy(threading.Timer): | ||
| def __init__(self, sleep, func, *args, **kwargs): | ||
| super().__init__(sleep, func) | ||
| self.name = kwargs.get("name", name(func)) | ||
| self.sleep = sleep | ||
| self.state = {} | ||
| self.state["latest"] = time.time() | ||
| self.state["starttime"] = time.time() | ||
| self.starttime = time.time() | ||
| class Timed: | ||
| def __init__(self, sleep, func, *args, thrname="", **kwargs): | ||
| self.args = args | ||
| self.func = func | ||
| self.kwargs = kwargs | ||
| self.sleep = sleep | ||
| self.name = thrname or kwargs.get("name", name(func)) | ||
| self.target = time.time() + self.sleep | ||
| self.timer = None | ||
| def run(self): | ||
| self.timer.latest = time.time() | ||
| self.func(*self.args) | ||
| def start(self): | ||
| self.kwargs["name"] = self.name | ||
| timer = Timy(self.sleep, self.run, *self.args, **self.kwargs) | ||
| timer.start() | ||
| self.timer = timer | ||
| def stop(self): | ||
| if self.timer: | ||
| self.timer.cancel() | ||
| class Repeater(Timed): | ||
| def run(self): | ||
| launch(self.start) | ||
| super().run() | ||
| def __dir__(): | ||
| return ( | ||
| "Repeater", | ||
| "Timed" | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "utilities" | ||
| def elapsed(seconds, short=True): | ||
| txt = "" | ||
| nsec = float(seconds) | ||
| if nsec < 1: | ||
| return f"{nsec:.2f}s" | ||
| yea = 365 * 24 * 60 * 60 | ||
| week = 7 * 24 * 60 * 60 | ||
| nday = 24 * 60 * 60 | ||
| hour = 60 * 60 | ||
| minute = 60 | ||
| yeas = int(nsec / yea) | ||
| nsec -= yeas * yea | ||
| weeks = int(nsec / week) | ||
| nsec -= weeks * week | ||
| nrdays = int(nsec / nday) | ||
| nsec -= nrdays * nday | ||
| hours = int(nsec / hour) | ||
| nsec -= hours * hour | ||
| minutes = int(nsec / minute) | ||
| nsec -= int(minute * minutes) | ||
| sec = int(nsec) | ||
| if yeas: | ||
| txt += f"{yeas}y" | ||
| if weeks: | ||
| nrdays += weeks * 7 | ||
| if nrdays: | ||
| txt += f"{nrdays}d" | ||
| if short and txt: | ||
| return txt.strip() | ||
| if hours: | ||
| txt += f"{hours}h" | ||
| if minutes: | ||
| txt += f"{minutes}m" | ||
| if sec: | ||
| txt += f"{sec}s" | ||
| txt = txt.strip() | ||
| return txt | ||
| def spl(txt): | ||
| try: | ||
| result = txt.split(",") | ||
| except (TypeError, ValueError): | ||
| result = [ | ||
| txt, | ||
| ] | ||
| return [x for x in result if x] | ||
| def __dir__(): | ||
| return ( | ||
| 'elapsed', | ||
| 'spl' | ||
| ) |
+1
-1
| Metadata-Version: 2.4 | ||
| Name: rssbot | ||
| Version: 647 | ||
| Version: 650 | ||
| Summary: 24/7 Feed Fetcher. | ||
@@ -5,0 +5,0 @@ Author-email: Bart Thate <rssbotd@gmail.com> |
+1
-1
@@ -12,3 +12,3 @@ [build-system] | ||
| description = "24/7 Feed Fetcher." | ||
| version = "647" | ||
| version = "650" | ||
| authors = [ | ||
@@ -15,0 +15,0 @@ {name = "Bart Thate",email = "rssbotd@gmail.com"}, |
| Metadata-Version: 2.4 | ||
| Name: rssbot | ||
| Version: 647 | ||
| Version: 650 | ||
| Summary: 24/7 Feed Fetcher. | ||
@@ -5,0 +5,0 @@ Author-email: Bart Thate <rssbotd@gmail.com> |
| README.rst | ||
| pyproject.toml | ||
| rssbot/__init__.py | ||
| rssbot/__main__.py | ||
| rssbot/clients.py | ||
| rssbot/command.py | ||
| rssbot/handler.py | ||
| rssbot/objects.py | ||
| rssbot/persist.py | ||
| rssbot/runtime.py | ||
| rssbot/client.py | ||
| rssbot/cmnd.py | ||
| rssbot/disk.py | ||
| rssbot/engine.py | ||
| rssbot/event.py | ||
| rssbot/find.py | ||
| rssbot/fleet.py | ||
| rssbot/log.py | ||
| rssbot/method.py | ||
| rssbot/object.py | ||
| rssbot/output.py | ||
| rssbot/paths.py | ||
| rssbot/thread.py | ||
| rssbot/timer.py | ||
| rssbot/utils.py | ||
| rssbot.egg-info/PKG-INFO | ||
@@ -16,6 +26,9 @@ rssbot.egg-info/SOURCES.txt | ||
| rssbot/modules/__init__.py | ||
| rssbot/modules/dbg.py | ||
| rssbot/modules/cmd.py | ||
| rssbot/modules/irc.py | ||
| rssbot/modules/log.py | ||
| rssbot/modules/lst.py | ||
| rssbot/modules/rss.py | ||
| rssbot/modules/thr.py | ||
| rssbot/modules/ver.py | ||
| rssbot/modules/srv.py | ||
| rssbot/modules/tdo.py | ||
| rssbot/modules/thr.py |
+51
-84
| # This file is placed in the Public Domain. | ||
| "main" | ||
| "main program" | ||
@@ -13,24 +13,21 @@ | ||
| from .clients import Client | ||
| from .command import Main, Commands | ||
| from .command import command, inits, parse, scan | ||
| from .handler import Event | ||
| from .persist import Workdir, pidname, skel, types | ||
| from .runtime import level | ||
| from .client import Client | ||
| from .cmnd import Commands, command, inits, parse, scan | ||
| from .event import Event | ||
| from .log import level | ||
| from .object import Default | ||
| from .paths import pidname, setwd | ||
| from . import modules as MODS | ||
| from . import modules as MODS | ||
| class Main(Default): | ||
| init = "" | ||
| level = "warn" | ||
| name = Default.__module__.split(".")[-2] | ||
| opts = Default() | ||
| verbose = False | ||
| version = 360 | ||
| Main.name = Main.__module__.split(".")[0] | ||
| def out(txt): | ||
| print(txt) | ||
| sys.stdout.flush() | ||
| "clients" | ||
| class CLI(Client): | ||
@@ -49,3 +46,2 @@ | ||
| def announce(self, txt): | ||
| #out(txt) | ||
| pass | ||
@@ -64,11 +60,29 @@ | ||
| "utilities" | ||
| def banner(): | ||
| def banner(mods): | ||
| tme = time.ctime(time.time()).replace(" ", " ") | ||
| out(f"{Main.name.upper()} {Main.version} since {tme} ({Main.level.upper()})") | ||
| out(f"loaded {".".join(dir(MODS))}") | ||
| out(f"loaded {".".join(dir(mods))}") | ||
| def forever(): | ||
| while True: | ||
| try: | ||
| time.sleep(0.1) | ||
| except (KeyboardInterrupt, EOFError): | ||
| print("") | ||
| sys.exit(1) | ||
| def out(txt): | ||
| print(txt) | ||
| sys.stdout.flush() | ||
| def ver(event): | ||
| event.reply(f"{Main.name.upper()} {Main.version}") | ||
| "daemon" | ||
| def check(txt): | ||
@@ -105,11 +119,2 @@ args = sys.argv[1:] | ||
| def forever(): | ||
| while True: | ||
| try: | ||
| time.sleep(0.1) | ||
| except (KeyboardInterrupt, EOFError): | ||
| print("") | ||
| sys.exit(1) | ||
| def pidfile(filename): | ||
@@ -132,26 +137,2 @@ if os.path.exists(filename): | ||
| def setwd(name, path=""): | ||
| Main.name = name | ||
| path = path or os.path.expanduser(f"~/.{name}") | ||
| Workdir.wdr = path | ||
| skel() | ||
| "commands" | ||
| def cmd(event): | ||
| event.reply(",".join(sorted([x for x in Commands.names if x not in Main.ignore]))) | ||
| def ls(event): | ||
| event.reply(",".join([x.split(".")[-1].lower() for x in types()])) | ||
| def srv(event): | ||
| import getpass | ||
| name = getpass.getuser() | ||
| event.reply(TXT % (Main.name.upper(), name, name, name, Main.name)) | ||
| "scripts" | ||
@@ -166,3 +147,3 @@ | ||
| pidfile(pidname(Main.name)) | ||
| Commands.add(cmd) | ||
| Commands.add(ver) | ||
| scan(MODS) | ||
@@ -181,7 +162,10 @@ inits(MODS, Main.init or "irc,rss") | ||
| setwd(Main.name) | ||
| Commands.add(cmd) | ||
| Commands.add(ls) | ||
| scan(MODS) | ||
| Commands.add(ver) | ||
| scan(MODS) | ||
| if "v" in Main.opts: | ||
| banner() | ||
| banner(MODS) | ||
| if "z" in Main.opts: | ||
| Commands.scan(MODS.log) | ||
| Commands.scan(MODS.tdo) | ||
| MODS.irc.Config.commands = True | ||
| for _mod, thr in inits(MODS, Main.init): | ||
@@ -201,5 +185,4 @@ if "w" in Main.opts: | ||
| setwd(Main.name) | ||
| Commands.add(cmd) | ||
| Commands.add(ls) | ||
| Commands.add(srv) | ||
| Commands.scan(MODS.srv) | ||
| Commands.add(ver) | ||
| scan(MODS) | ||
@@ -218,6 +201,6 @@ csl = CLI() | ||
| setwd(Main.name) | ||
| banner() | ||
| banner(MODS) | ||
| privileges() | ||
| pidfile(pidname(Main.name)) | ||
| Commands.add(cmd) | ||
| Commands.add(ver) | ||
| scan(MODS) | ||
@@ -228,19 +211,2 @@ inits(MODS, Main.init or "irc,rss") | ||
| "data" | ||
| TXT = """[Unit] | ||
| Description=%s | ||
| After=network-online.target | ||
| [Service] | ||
| Type=simple | ||
| User=%s | ||
| Group=%s | ||
| ExecStart=/home/%s/.local/bin/%s -s | ||
| [Install] | ||
| WantedBy=multi-user.target""" | ||
| "runtime" | ||
@@ -269,2 +235,3 @@ | ||
| def main(): | ||
@@ -271,0 +238,0 @@ if check("a"): |
@@ -7,11 +7,15 @@ # This file is placed in the Public Domain. | ||
| from . import irc, rss, thr, ver | ||
| from . import cmd, lst, thr | ||
| from . import irc, rss | ||
| from . import srv # noqa: F401 | ||
| from . import log, tdo | ||
| __all__= ( | ||
| 'irc', | ||
| 'rss', | ||
| 'thr', | ||
| 'ver' | ||
| ) | ||
| __all__ = ( | ||
| "cmd", | ||
| "irc", | ||
| "lst", | ||
| "rss", | ||
| "thr" | ||
| ) | ||
@@ -18,0 +22,0 @@ |
+143
-170
@@ -8,3 +8,2 @@ # This file is placed in the Public Domain. | ||
| import base64 | ||
| import queue | ||
| import os | ||
@@ -18,8 +17,14 @@ import socket | ||
| from ..clients import Client, Fleet | ||
| from ..command import Main, command | ||
| from ..handler import Event as IEvent | ||
| from ..objects import Default, Object, edit, fmt, keys | ||
| from ..persist import getpath, ident, last, write | ||
| from ..runtime import launch, rlog | ||
| from ..client import Client | ||
| from ..cmnd import command | ||
| from ..disk import write | ||
| from ..event import Event as IEvent | ||
| from ..find import last | ||
| from ..fleet import Fleet | ||
| from ..log import rlog | ||
| from ..method import edit, fmt | ||
| from ..object import Default, Object, keys | ||
| from ..output import Output | ||
| from ..paths import getpath, ident | ||
| from ..thread import launch | ||
@@ -52,7 +57,12 @@ | ||
| class Main: | ||
| name = Default.__module__.split(".")[-2] | ||
| class Config(Default): | ||
| channel = f'#{Main.name}' | ||
| channel = f"#{Main.name}" | ||
| commands = False | ||
| control = '!' | ||
| control = "!" | ||
| nick = Main.name | ||
@@ -63,4 +73,4 @@ password = "" | ||
| sasl = False | ||
| server = 'localhost' | ||
| servermodes = '' | ||
| server = "localhost" | ||
| servermodes = "" | ||
| sleep = 60 | ||
@@ -88,11 +98,11 @@ username = Main.name | ||
| super().__init__() | ||
| self.args = [] | ||
| self.args = [] | ||
| self.arguments = [] | ||
| self.command = "" | ||
| self.channel = "" | ||
| self.nick = "" | ||
| self.origin = "" | ||
| self.rawstr = "" | ||
| self.rest = "" | ||
| self.txt = "" | ||
| self.command = "" | ||
| self.channel = "" | ||
| self.nick = "" | ||
| self.origin = "" | ||
| self.rawstr = "" | ||
| self.rest = "" | ||
| self.txt = "" | ||
@@ -118,39 +128,5 @@ | ||
| "output" | ||
| "IRC" | ||
| class Output: | ||
| def __init__(self): | ||
| self.oqueue = queue.Queue() | ||
| self.ostop = threading.Event() | ||
| def oput(self, event): | ||
| self.oqueue.put(event) | ||
| def output(self): | ||
| while not self.ostop.is_set(): | ||
| event = self.oqueue.get() | ||
| if event is None: | ||
| self.oqueue.task_done() | ||
| break | ||
| self.display(event) | ||
| self.oqueue.task_done() | ||
| def start(self): | ||
| self.ostop.clear() | ||
| launch(self.output) | ||
| def stop(self): | ||
| self.ostop.set() | ||
| self.oqueue.put(None) | ||
| def wait(self): | ||
| self.oqueue.join() | ||
| super().wait() | ||
| "IRc" | ||
| class IRC(Output, Client): | ||
@@ -162,3 +138,3 @@ | ||
| self.buffer = [] | ||
| self.cache = Object() | ||
| self.cache = Default() | ||
| self.cfg = Config() | ||
@@ -183,14 +159,14 @@ self.channels = [] | ||
| self.state.pongcheck = False | ||
| self.state.sleep = self.cfg.sleep | ||
| self.state.sleep = self.cfg.sleep | ||
| self.state.stopkeep = False | ||
| self.zelf = '' | ||
| self.register('903', cb_h903) | ||
| self.register('904', cb_h903) | ||
| self.register('AUTHENTICATE', cb_auth) | ||
| self.register('CAP', cb_cap) | ||
| self.register('ERROR', cb_error) | ||
| self.register('LOG', cb_log) | ||
| self.register('NOTICE', cb_notice) | ||
| self.register('PRIVMSG', cb_privmsg) | ||
| self.register('QUIT', cb_quit) | ||
| self.zelf = "" | ||
| self.register("903", cb_h903) | ||
| self.register("904", cb_h903) | ||
| self.register("AUTHENTICATE", cb_auth) | ||
| self.register("CAP", cb_cap) | ||
| self.register("ERROR", cb_error) | ||
| self.register("LOG", cb_log) | ||
| self.register("NOTICE", cb_notice) | ||
| self.register("PRIVMSG", cb_privmsg) | ||
| self.register("QUIT", cb_quit) | ||
| self.register("366", cb_ready) | ||
@@ -217,3 +193,3 @@ self.ident = ident(self) | ||
| self.sock.connect((server, port)) | ||
| self.direct('CAP LS 302') | ||
| self.direct("CAP LS 302") | ||
| else: | ||
@@ -229,3 +205,6 @@ addr = socket.getaddrinfo(server, port, socket.AF_INET)[-1][-1] | ||
| self.events.connected.set() | ||
| rlog("debug", f"connected {self.cfg.server}:{self.cfg.port} {self.cfg.channel}") | ||
| rlog( | ||
| "debug", | ||
| f"connected {self.cfg.server}:{self.cfg.port} {self.cfg.channel}", | ||
| ) | ||
| return True | ||
@@ -242,7 +221,3 @@ return False | ||
| self.sock.shutdown(2) | ||
| except ( | ||
| ssl.SSLError, | ||
| OSError, | ||
| BrokenPipeError | ||
| ) as _ex: | ||
| except (ssl.SSLError, OSError, BrokenPipeError) as _ex: | ||
| pass | ||
@@ -253,2 +228,4 @@ | ||
| txt = evt.result.get(key) | ||
| if not txt: | ||
| continue | ||
| textlist = [] | ||
@@ -267,6 +244,3 @@ txtlist = wrapper.wrap(txt) | ||
| length = len(txtlist) - 3 | ||
| self.say( | ||
| evt.channel, | ||
| f"use !mre to show more (+{length})" | ||
| ) | ||
| self.say(evt.channel, f"use !mre to show more (+{length})") | ||
@@ -278,8 +252,8 @@ def docommand(self, cmd, *args): | ||
| elif len(args) == 1: | ||
| self.raw(f'{cmd.upper()} {args[0]}') | ||
| self.raw(f"{cmd.upper()} {args[0]}") | ||
| elif len(args) == 2: | ||
| txt = ' '.join(args[1:]) | ||
| self.raw(f'{cmd.upper()} {args[0]} :{txt}') | ||
| txt = " ".join(args[1:]) | ||
| self.raw(f"{cmd.upper()} {args[0]} :{txt}") | ||
| elif len(args) >= 3: | ||
| txt = ' '.join(args[2:]) | ||
| txt = " ".join(args[2:]) | ||
| self.raw("{cmd.upper()} {args[0]} {args[1]} :{txt}") | ||
@@ -301,8 +275,3 @@ if (time.time() - self.state.last) < 5.0: | ||
| break | ||
| except ( | ||
| socket.timeout, | ||
| ssl.SSLError, | ||
| OSError, | ||
| ConnectionResetError | ||
| ) as ex: | ||
| except (socket.timeout, ssl.SSLError, OSError, ConnectionResetError) as ex: | ||
| self.events.joined.set() | ||
@@ -315,5 +284,5 @@ self.state.error = str(ex) | ||
| self.events.joined.wait() | ||
| txt = str(txt).replace('\n', '') | ||
| txt = txt.replace(' ', ' ') | ||
| self.docommand('PRIVMSG', channel, txt) | ||
| txt = str(txt).replace("\n", "") | ||
| txt = txt.replace(" ", " ") | ||
| self.docommand("PRIVMSG", channel, txt) | ||
@@ -323,23 +292,23 @@ def event(self, txt): | ||
| cmd = evt.command | ||
| if cmd == 'PING': | ||
| if cmd == "PING": | ||
| self.state.pongcheck = True | ||
| self.docommand('PONG', evt.txt or '') | ||
| elif cmd == 'PONG': | ||
| self.docommand("PONG", evt.txt or "") | ||
| elif cmd == "PONG": | ||
| self.state.pongcheck = False | ||
| if cmd == '001': | ||
| if cmd == "001": | ||
| self.state.needconnect = False | ||
| if self.cfg.servermodes: | ||
| self.docommand(f'MODE {self.cfg.nick} {self.cfg.servermodes}') | ||
| self.docommand(f"MODE {self.cfg.nick} {self.cfg.servermodes}") | ||
| self.zelf = evt.args[-1] | ||
| elif cmd == "376": | ||
| self.joinall() | ||
| elif cmd == '002': | ||
| elif cmd == "002": | ||
| self.state.host = evt.args[2][:-1] | ||
| elif cmd == '366': | ||
| elif cmd == "366": | ||
| self.state.error = "" | ||
| self.events.joined.set() | ||
| elif cmd == '433': | ||
| elif cmd == "433": | ||
| self.state.error = txt | ||
| nck = self.cfg.nick = self.cfg.nick + '_' | ||
| self.docommand('NICK', nck) | ||
| nck = self.cfg.nick = self.cfg.nick + "_" | ||
| self.docommand("NICK", nck) | ||
| return evt | ||
@@ -349,3 +318,3 @@ | ||
| if channel not in dir(self.cache): | ||
| self.cache[channel] = [] | ||
| setattr(self.cache, channel, []) | ||
| chanlist = getattr(self.cache, channel) | ||
@@ -366,3 +335,3 @@ chanlist.extend(txtlist) | ||
| for channel in self.channels: | ||
| self.docommand('JOIN', channel) | ||
| self.docommand("JOIN", channel) | ||
@@ -379,3 +348,3 @@ def keep(self): | ||
| time.sleep(self.cfg.sleep) | ||
| self.docommand('PING', self.cfg.server) | ||
| self.docommand("PING", self.cfg.server) | ||
| if self.state.pongcheck: | ||
@@ -387,4 +356,4 @@ self.restart() | ||
| self.events.authed.wait() | ||
| self.direct(f'NICK {nck}') | ||
| self.direct(f'USER {nck} {server} {server} {nck}') | ||
| self.direct(f"NICK {nck}") | ||
| self.direct(f"USER {nck} {server} {server} {nck}") | ||
@@ -398,4 +367,4 @@ def oput(self, evt): | ||
| rawstr = str(txt) | ||
| rawstr = rawstr.replace('\u0001', '') | ||
| rawstr = rawstr.replace('\001', '') | ||
| rawstr = rawstr.replace("\u0001", "") | ||
| rawstr = rawstr.replace("\001", "") | ||
| rlog("debug", txt, IGNORE) | ||
@@ -405,3 +374,3 @@ obj = Event() | ||
| obj.rawstr = rawstr | ||
| obj.command = '' | ||
| obj.command = "" | ||
| obj.arguments = [] | ||
@@ -413,3 +382,3 @@ arguments = rawstr.split() | ||
| obj.origin = self.cfg.server | ||
| if obj.origin.startswith(':'): | ||
| if obj.origin.startswith(":"): | ||
| obj.origin = obj.origin[1:] | ||
@@ -423,3 +392,3 @@ if len(arguments) > 1: | ||
| for arg in arguments[2:]: | ||
| if arg.count(':') <= 1 and arg.startswith(':'): | ||
| if arg.count(":") <= 1 and arg.startswith(":"): | ||
| adding = True | ||
@@ -432,3 +401,3 @@ txtlist.append(arg[1:]) | ||
| obj.arguments.append(arg) | ||
| obj.txt = ' '.join(txtlist) | ||
| obj.txt = " ".join(txtlist) | ||
| else: | ||
@@ -438,9 +407,9 @@ obj.command = obj.origin | ||
| try: | ||
| obj.nick, obj.origin = obj.origin.split('!') | ||
| obj.nick, obj.origin = obj.origin.split("!") | ||
| except ValueError: | ||
| obj.nick = '' | ||
| target = '' | ||
| obj.nick = "" | ||
| target = "" | ||
| if obj.arguments: | ||
| target = obj.arguments[0] | ||
| if target.startswith('#'): | ||
| if target.startswith("#"): | ||
| obj.channel = target | ||
@@ -450,3 +419,3 @@ else: | ||
| if not obj.txt: | ||
| obj.txt = rawstr.split(':', 2)[-1] | ||
| obj.txt = rawstr.split(":", 2)[-1] | ||
| if not obj.txt and len(arguments) == 1: | ||
@@ -473,9 +442,9 @@ obj.txt = arguments[1] | ||
| except ( | ||
| OSError, | ||
| socket.timeout, | ||
| ssl.SSLError, | ||
| ssl.SSLZeroReturnError, | ||
| ConnectionResetError, | ||
| BrokenPipeError | ||
| ) as ex: | ||
| OSError, | ||
| socket.timeout, | ||
| ssl.SSLError, | ||
| ssl.SSLZeroReturnError, | ||
| ConnectionResetError, | ||
| BrokenPipeError, | ||
| ) as ex: | ||
| self.state.nrerror += 1 | ||
@@ -497,4 +466,4 @@ self.state.error = str(type(ex)) + " " + str(ex) | ||
| txt = txt[:500] | ||
| txt += '\r\n' | ||
| txt = bytes(txt, 'utf-8') | ||
| txt += "\r\n" | ||
| txt = bytes(txt, "utf-8") | ||
| if self.sock: | ||
@@ -504,9 +473,9 @@ try: | ||
| except ( | ||
| OSError, | ||
| ssl.SSLError, | ||
| ssl.SSLZeroReturnError, | ||
| ConnectionResetError, | ||
| BrokenPipeError, | ||
| socket.timeout | ||
| ) as ex: | ||
| OSError, | ||
| ssl.SSLError, | ||
| ssl.SSLZeroReturnError, | ||
| ConnectionResetError, | ||
| BrokenPipeError, | ||
| socket.timeout, | ||
| ) as ex: | ||
| rlog("debug", str(type(ex)) + " " + str(ex)) | ||
@@ -553,7 +522,7 @@ self.events.joined.set() | ||
| inbytes = self.sock.recv(512) | ||
| txt = str(inbytes, 'utf-8') | ||
| if txt == '': | ||
| txt = str(inbytes, "utf-8") | ||
| if txt == "": | ||
| raise ConnectionResetError | ||
| self.state.lastline += txt | ||
| splitted = self.state.lastline.split('\r\n') | ||
| splitted = self.state.lastline.split("\r\n") | ||
| for line in splitted[:-1]: | ||
@@ -574,7 +543,8 @@ self.buffer.append(line) | ||
| launch(self.keep) | ||
| launch(self.doconnect, | ||
| self.cfg.server or "localhost", | ||
| self.cfg.nick, | ||
| int(self.cfg.port) or 6667 | ||
| ) | ||
| launch( | ||
| self.doconnect, | ||
| self.cfg.server or "localhost", | ||
| self.cfg.nick, | ||
| int(self.cfg.port) or 6667, | ||
| ) | ||
@@ -585,3 +555,2 @@ def stop(self): | ||
| Output.stop(self) | ||
| #self.disconnect() | ||
@@ -597,3 +566,3 @@ def wait(self): | ||
| bot = Fleet.get(evt.orig) | ||
| bot.docommand(f'AUTHENTICATE {bot.cfg.password}') | ||
| bot.docommand(f"AUTHENTICATE {bot.cfg.password}") | ||
@@ -603,6 +572,6 @@ | ||
| bot = Fleet.get(evt.orig) | ||
| if bot.cfg.password and 'ACK' in evt.arguments: | ||
| bot.direct('AUTHENTICATE PLAIN') | ||
| if bot.cfg.password and "ACK" in evt.arguments: | ||
| bot.direct("AUTHENTICATE PLAIN") | ||
| else: | ||
| bot.direct('CAP REQ :sasl') | ||
| bot.direct("CAP REQ :sasl") | ||
@@ -614,3 +583,3 @@ | ||
| bot.state.error = evt.txt | ||
| rlog('debug', fmt(evt)) | ||
| rlog("debug", fmt(evt)) | ||
@@ -620,3 +589,3 @@ | ||
| bot = Fleet.get(evt.orig) | ||
| bot.direct('CAP END') | ||
| bot.direct("CAP END") | ||
| bot.events.authed.set() | ||
@@ -627,3 +596,3 @@ | ||
| bot = Fleet.get(evt.orig) | ||
| bot.direct('CAP END') | ||
| bot.direct("CAP END") | ||
| bot.events.authed.set() | ||
@@ -635,5 +604,7 @@ | ||
| def cb_log(evt): | ||
| pass | ||
| def cb_ready(evt): | ||
@@ -646,3 +617,3 @@ bot = Fleet.get(evt.orig) | ||
| bot = Fleet.get(evt.orig) | ||
| bot.events.logon,set() | ||
| bot.events.logon.set() | ||
@@ -652,5 +623,5 @@ | ||
| bot = Fleet.get(evt.orig) | ||
| if evt.txt.startswith('VERSION'): | ||
| txt = f'\001VERSION {Main.name.upper()} 140 - {bot.cfg.username}\001' | ||
| bot.docommand('NOTICE', evt.channel, txt) | ||
| if evt.txt.startswith("VERSION"): | ||
| txt = f"\001VERSION {Main.name.upper()} 140 - {bot.cfg.username}\001" | ||
| bot.docommand("NOTICE", evt.channel, txt) | ||
@@ -663,6 +634,8 @@ | ||
| if evt.txt: | ||
| if evt.txt[0] in ['!',]: | ||
| if evt.txt[0] in [ | ||
| "!", | ||
| ]: | ||
| evt.txt = evt.txt[1:] | ||
| elif evt.txt.startswith(f'{bot.cfg.nick}:'): | ||
| evt.txt = evt.txt[len(bot.cfg.nick)+1:] | ||
| elif evt.txt.startswith(f"{bot.cfg.nick}:"): | ||
| evt.txt = evt.txt[len(bot.cfg.nick) + 1 :] | ||
| else: | ||
@@ -693,8 +666,8 @@ return | ||
| event.reply( | ||
| fmt( | ||
| config, | ||
| keys(config), | ||
| skip='control,password,realname,sleep,username'.split(",") | ||
| ) | ||
| ) | ||
| fmt( | ||
| config, | ||
| keys(config), | ||
| skip="control,password,realname,sleep,username".split(","), | ||
| ) | ||
| ) | ||
| else: | ||
@@ -707,10 +680,10 @@ edit(config, event.sets) | ||
| if not event.channel: | ||
| event.reply('channel is not set.') | ||
| event.reply("channel is not set.") | ||
| return | ||
| bot = Fleet.get(event.orig) | ||
| if 'cache' not in dir(bot): | ||
| event.reply('bot is missing cache') | ||
| if "cache" not in dir(bot): | ||
| event.reply("bot is missing cache") | ||
| return | ||
| if event.channel not in dir(bot.cache): | ||
| event.reply(f'no output in {event.channel} cache.') | ||
| event.reply(f"no output in {event.channel} cache.") | ||
| return | ||
@@ -722,3 +695,3 @@ for _x in range(3): | ||
| if size != 0: | ||
| event.reply(f'{size} more in cache') | ||
| event.reply(f"{size} more in cache") | ||
@@ -728,10 +701,10 @@ | ||
| if len(event.args) != 2: | ||
| event.reply('pwd <nick> <password>') | ||
| event.reply("pwd <nick> <password>") | ||
| return | ||
| arg1 = event.args[0] | ||
| arg2 = event.args[1] | ||
| txt = f'\x00{arg1}\x00{arg2}' | ||
| enc = txt.encode('ascii') | ||
| txt = f"\x00{arg1}\x00{arg2}" | ||
| enc = txt.encode("ascii") | ||
| base = base64.b64encode(enc) | ||
| dcd = base.decode('ascii') | ||
| dcd = base.decode("ascii") | ||
| event.reply(dcd) |
+82
-76
@@ -24,7 +24,12 @@ # This file is placed in the Public Domain. | ||
| from ..clients import Fleet | ||
| from ..command import elapsed, spl | ||
| from ..objects import Default, Object, fmt, update | ||
| from ..persist import find, fntime, getpath, last, write | ||
| from ..runtime import Repeater, launch, rlog | ||
| from ..disk import write | ||
| from ..fleet import Fleet | ||
| from ..find import find, fntime, last | ||
| from ..log import rlog | ||
| from ..method import fmt | ||
| from ..object import Default, Object, update | ||
| from ..paths import getpath | ||
| from ..thread import launch | ||
| from ..timer import Repeater | ||
| from ..utils import elapsed, spl | ||
@@ -35,6 +40,6 @@ | ||
| fetchlock = _thread.allocate_lock() | ||
| fetchlock = _thread.allocate_lock() | ||
| importlock = _thread.allocate_lock() | ||
| errors = [] | ||
| skipped = [] | ||
| errors = [] | ||
| skipped = [] | ||
@@ -65,6 +70,6 @@ | ||
| Default.__init__(self) | ||
| self.display_list = 'title,link,author' | ||
| self.insertid = None | ||
| self.name = "" | ||
| self.rss = "" | ||
| self.display_list = "title,link,author" | ||
| self.insertid = None | ||
| self.name = "" | ||
| self.rss = "" | ||
@@ -90,7 +95,7 @@ | ||
| displaylist = "" | ||
| result = '' | ||
| result = "" | ||
| try: | ||
| displaylist = obj.display_list or 'title,link' | ||
| displaylist = obj.display_list or "title,link" | ||
| except AttributeError: | ||
| displaylist = 'title,link,author' | ||
| displaylist = "title,link,author" | ||
| for key in displaylist.split(","): | ||
@@ -102,7 +107,7 @@ if not key: | ||
| continue | ||
| data = data.replace('\n', ' ') | ||
| data = data.replace("\n", " ") | ||
| data = striphtml(data.rstrip()) | ||
| data = unescape(data) | ||
| result += data.rstrip() | ||
| result += ' - ' | ||
| result += " - " | ||
| return result[:-2].rstrip() | ||
@@ -122,4 +127,4 @@ | ||
| url = urllib.parse.urlparse(fed.link) | ||
| if url.path and not url.path == '/': | ||
| uurl = f'{url.scheme}://{url.netloc}/{url.path}' | ||
| if url.path and not url.path == "/": | ||
| uurl = f"{url.scheme}://{url.netloc}/{url.path}" | ||
| else: | ||
@@ -139,6 +144,6 @@ uurl = fed.link | ||
| return counter | ||
| txt = '' | ||
| feedname = getattr(feed, 'name', None) | ||
| txt = "" | ||
| feedname = getattr(feed, "name", None) | ||
| if feedname: | ||
| txt = f'[{feedname}] ' | ||
| txt = f"[{feedname}] " | ||
| for obj in result: | ||
@@ -152,3 +157,3 @@ txt2 = txt + self.display(obj) | ||
| thrs = [] | ||
| for _fn, feed in find('rss'): | ||
| for _fn, feed in find("rss"): | ||
| thrs.append(launch(self.fetch, feed, silent)) | ||
@@ -171,8 +176,8 @@ return thrs | ||
| def getitem(line, item): | ||
| lne = '' | ||
| index1 = line.find(f'<{item}>') | ||
| lne = "" | ||
| index1 = line.find(f"<{item}>") | ||
| if index1 == -1: | ||
| return lne | ||
| index1 += len(item) + 2 | ||
| index2 = line.find(f'</{item}>', index1) | ||
| index2 = line.find(f"</{item}>", index1) | ||
| if index2 == -1: | ||
@@ -190,7 +195,7 @@ return lne | ||
| while not stop: | ||
| index1 = text.find(f'<{token}', index) | ||
| index1 = text.find(f"<{token}", index) | ||
| if index1 == -1: | ||
| break | ||
| index1 += len(token) + 2 | ||
| index2 = text.find(f'</{token}>', index1) | ||
| index2 = text.find(f"</{token}>", index1) | ||
| if index2 == -1: | ||
@@ -204,3 +209,3 @@ break | ||
| @staticmethod | ||
| def parse(txt, toke="item", items='title,link'): | ||
| def parse(txt, toke="item", items="title,link"): | ||
| result = [] | ||
@@ -228,7 +233,7 @@ for line in Parser.getitems(txt, toke): | ||
| def getnames(line): | ||
| return [x.split('="')[0] for x in line.split()] | ||
| return [x.split('="')[0] for x in line.split()] | ||
| @staticmethod | ||
| def getvalue(line, attr): | ||
| lne = '' | ||
| lne = "" | ||
| index1 = line.find(f'{attr}="') | ||
@@ -240,10 +245,10 @@ if index1 == -1: | ||
| if index2 == -1: | ||
| index2 = line.find('/>', index1) | ||
| index2 = line.find("/>", index1) | ||
| if index2 == -1: | ||
| return lne | ||
| lne = line[index1:index2] | ||
| if 'CDATA' in lne: | ||
| lne = lne.replace('![CDATA[', '') | ||
| lne = lne.replace(']]', '') | ||
| #lne = lne[1:-1] | ||
| if "CDATA" in lne: | ||
| lne = lne.replace("![CDATA[", "") | ||
| lne = lne.replace("]]", "") | ||
| # lne = lne[1:-1] | ||
| return lne | ||
@@ -257,7 +262,7 @@ | ||
| while not stop: | ||
| index1 = line.find(f'<{token} ', index) | ||
| index1 = line.find(f"<{token} ", index) | ||
| if index1 == -1: | ||
| return result | ||
| index1 += len(token) + 2 | ||
| index2 = line.find('/>', index1) | ||
| index2 = line.find("/>", index1) | ||
| if index2 == -1: | ||
@@ -299,5 +304,5 @@ return result | ||
| def cdata(line): | ||
| if 'CDATA' in line: | ||
| lne = line.replace('![CDATA[', '') | ||
| lne = lne.replace(']]', '') | ||
| if "CDATA" in line: | ||
| lne = line.replace("![CDATA[", "") | ||
| lne = lne.replace("]]", "") | ||
| lne = lne[1:-1] | ||
@@ -319,8 +324,8 @@ return lne | ||
| if rest: | ||
| if 'link' not in items: | ||
| if "link" not in items: | ||
| items += ",link" | ||
| if url.endswith('atom'): | ||
| result = Parser.parse(str(rest.data, 'utf-8'), 'entry', items) or [] | ||
| if url.endswith("atom"): | ||
| result = Parser.parse(str(rest.data, "utf-8"), "entry", items) or [] | ||
| else: | ||
| result = Parser.parse(str(rest.data, 'utf-8'), 'item', items) or [] | ||
| result = Parser.parse(str(rest.data, "utf-8"), "item", items) or [] | ||
| return result | ||
@@ -331,12 +336,13 @@ | ||
| postarray = [ | ||
| ('submit', 'submit'), | ||
| ('url', url), | ||
| ("submit", "submit"), | ||
| ("url", url), | ||
| ] | ||
| postdata = urlencode(postarray, quote_via=quote_plus) | ||
| req = urllib.request.Request('http://tinyurl.com/create.php', | ||
| data=bytes(postdata, 'UTF-8')) | ||
| req.add_header('User-agent', useragent("rss fetcher")) | ||
| with urllib.request.urlopen(req) as htm: # nosec | ||
| req = urllib.request.Request( | ||
| "http://tinyurl.com/create.php", data=bytes(postdata, "UTF-8") | ||
| ) | ||
| req.add_header("User-agent", useragent("rss fetcher")) | ||
| with urllib.request.urlopen(req) as htm: # nosec | ||
| for txt in htm.readlines(): | ||
| line = txt.decode('UTF-8').strip() | ||
| line = txt.decode("UTF-8").strip() | ||
| i = re.search('data-clipboard-text="(.*?)"', line, re.M) | ||
@@ -351,4 +357,4 @@ if i: | ||
| req = urllib.request.Request(str(url)) | ||
| req.add_header('User-agent', useragent("rss fetcher")) | ||
| with urllib.request.urlopen(req) as response: # nosec | ||
| req.add_header("User-agent", useragent("rss fetcher")) | ||
| with urllib.request.urlopen(req) as response: # nosec | ||
| response.data = response.read() | ||
@@ -363,8 +369,8 @@ return response | ||
| def striphtml(text): | ||
| clean = re.compile('<.*?>') | ||
| return re.sub(clean, '', text) | ||
| clean = re.compile("<.*?>") | ||
| return re.sub(clean, "", text) | ||
| def unescape(text): | ||
| txt = re.sub(r'\s+', ' ', text) | ||
| txt = re.sub(r"\s+", " ", text) | ||
| return html.unescape(txt) | ||
@@ -374,3 +380,3 @@ | ||
| def useragent(txt): | ||
| return 'Mozilla/5.0 (X11; Linux x86_64) ' + txt | ||
| return "Mozilla/5.0 (X11; Linux x86_64) " + txt | ||
@@ -383,6 +389,6 @@ | ||
| if len(event.args) < 2: | ||
| event.reply('dpl <stringinurl> <item1,item2>') | ||
| event.reply("dpl <stringinurl> <item1,item2>") | ||
| return | ||
| setter = {'display_list': event.args[1]} | ||
| for fnm, rss in find("rss", {'rss': event.args[0]}): | ||
| setter = {"display_list": event.args[1]} | ||
| for fnm, rss in find("rss", {"rss": event.args[0]}): | ||
| if rss: | ||
@@ -404,4 +410,4 @@ update(rss, setter) | ||
| txt = f'<outline name="{name}" display_list="{obj.display_list}" xmlUrl="{obj.rss}"/>' | ||
| event.reply(" "*12 + txt) | ||
| event.reply(" "*8 + "</outline>") | ||
| event.reply(" " * 12 + txt) | ||
| event.reply(" " * 8 + "</outline>") | ||
| event.reply(" <body>") | ||
@@ -426,3 +432,3 @@ event.reply("</opml>") | ||
| with importlock: | ||
| for obj in prs.parse(txt, 'outline', "name,display_list,xmlUrl"): | ||
| for obj in prs.parse(txt, "outline", "name,display_list,xmlUrl"): | ||
| url = obj.xmlUrl | ||
@@ -433,3 +439,3 @@ if url in skipped: | ||
| continue | ||
| has = list(find("rss", {'rss': url}, matching=True)) | ||
| has = list(find("rss", {"rss": url}, matching=True)) | ||
| if has: | ||
@@ -453,5 +459,5 @@ skipped.append(url) | ||
| if len(event.args) != 2: | ||
| event.reply('nme <stringinurl> <name>') | ||
| event.reply("nme <stringinurl> <name>") | ||
| return | ||
| selector = {'rss': event.args[0]} | ||
| selector = {"rss": event.args[0]} | ||
| for fnm, rss in find("rss", selector): | ||
@@ -468,3 +474,3 @@ feed = Rss() | ||
| if len(event.args) != 1: | ||
| event.reply('rem <stringinurl>') | ||
| event.reply("rem <stringinurl>") | ||
| return | ||
@@ -484,3 +490,3 @@ for fnm, rss in find("rss"): | ||
| if len(event.args) != 1: | ||
| event.reply('res <stringinurl>') | ||
| event.reply("res <stringinurl>") | ||
| return | ||
@@ -501,15 +507,15 @@ for fnm, rss in find("rss", deleted=True): | ||
| nrs = 0 | ||
| for fnm, rss in find('rss'): | ||
| for fnm, rss in find("rss"): | ||
| nrs += 1 | ||
| elp = elapsed(time.time()-fntime(fnm)) | ||
| elp = elapsed(time.time() - fntime(fnm)) | ||
| txt = fmt(rss) | ||
| event.reply(f'{nrs} {txt} {elp}') | ||
| event.reply(f"{nrs} {txt} {elp}") | ||
| if not nrs: | ||
| event.reply('no feed found.') | ||
| event.reply("no feed found.") | ||
| return | ||
| url = event.args[0] | ||
| if 'http://' not in url and "https://" not in url: | ||
| event.reply('i need an url') | ||
| if "http://" not in url and "https://" not in url: | ||
| event.reply("i need an url") | ||
| return | ||
| for fnm, result in find("rss", {'rss': url}): | ||
| for fnm, result in find("rss", {"rss": url}): | ||
| if result: | ||
@@ -516,0 +522,0 @@ event.reply(f"{url} is known") |
@@ -11,6 +11,6 @@ # This file is placed in the Public Domain. | ||
| from ..command import STARTTIME, elapsed | ||
| from ..utils import elapsed | ||
| "commands" | ||
| STARTTIME = time.time() | ||
@@ -21,7 +21,7 @@ | ||
| for thread in sorted(threading.enumerate(), key=lambda x: x.name): | ||
| if str(thread).startswith('<_'): | ||
| if str(thread).startswith("<_"): | ||
| continue | ||
| if getattr(thread, 'state', None) and getattr(thread, "sleep", None): | ||
| if getattr(thread, "state", None) and getattr(thread, "sleep", None): | ||
| uptime = thread.sleep - int(time.time() - thread.state["latest"]) | ||
| elif getattr(thread, 'starttime', None): | ||
| elif getattr(thread, "starttime", None): | ||
| uptime = int(time.time() - thread.starttime) | ||
@@ -34,6 +34,6 @@ else: | ||
| lap = elapsed(uptime) | ||
| res.append(f'{txt}/{lap}') | ||
| res.append(f"{txt}/{lap}") | ||
| if res: | ||
| event.reply(' '.join(res)) | ||
| event.reply(" ".join(res)) | ||
| else: | ||
| event.reply('no threads') | ||
| event.reply("no threads") |
| # This file is placed in the Public Domain. | ||
| "clients" | ||
| import threading | ||
| import time | ||
| from .handler import Handler | ||
| "client" | ||
| class Client(Handler): | ||
| def __init__(self): | ||
| Handler.__init__(self) | ||
| self.olock = threading.RLock() | ||
| Fleet.add(self) | ||
| def announce(self, txt): | ||
| pass | ||
| def display(self, event): | ||
| with self.olock: | ||
| for tme in sorted(event.result): | ||
| self.dosay(event.channel, event.result[tme]) | ||
| def dosay(self, channel, txt): | ||
| self.say(channel, txt) | ||
| def raw(self, txt): | ||
| raise NotImplementedError("raw") | ||
| def say(self, channel, txt): | ||
| self.raw(txt) | ||
| "fleet" | ||
| class Fleet: | ||
| clients = {} | ||
| @staticmethod | ||
| def add(client): | ||
| Fleet.clients[repr(client)] = client | ||
| @staticmethod | ||
| def all(): | ||
| return Fleet.clients.values() | ||
| @staticmethod | ||
| def announce(txt): | ||
| for client in Fleet.all(): | ||
| client.announce(txt) | ||
| @staticmethod | ||
| def dispatch(evt): | ||
| client = Fleet.get(evt.orig) | ||
| client.put(evt) | ||
| @staticmethod | ||
| def display(evt): | ||
| client = Fleet.get(evt.orig) | ||
| client.display(evt) | ||
| @staticmethod | ||
| def first(): | ||
| clt = list(Fleet.all()) | ||
| res = None | ||
| if clt: | ||
| res = clt[0] | ||
| return res | ||
| @staticmethod | ||
| def get(orig): | ||
| return Fleet.clients.get(orig, None) | ||
| @staticmethod | ||
| def say(orig, channel, txt): | ||
| client = Fleet.get(orig) | ||
| if client: | ||
| client.say(channel, txt) | ||
| @staticmethod | ||
| def shutdown(): | ||
| for client in Fleet.all(): | ||
| client.stop() | ||
| @staticmethod | ||
| def wait(): | ||
| time.sleep(0.1) | ||
| for client in Fleet.all(): | ||
| client.wait() | ||
| "interface" | ||
| def __dir__(): | ||
| return ( | ||
| 'Client', | ||
| 'Fleet' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "commands" | ||
| import inspect | ||
| import time | ||
| from .clients import Fleet | ||
| from .objects import Default | ||
| from .runtime import launch | ||
| STARTTIME = time.time() | ||
| "config" | ||
| class Main(Default): | ||
| debug = False | ||
| gets = Default() | ||
| ignore = "" | ||
| init = "" | ||
| level = "warn" | ||
| name = Default.__module__.split(".")[-2] | ||
| opts = Default() | ||
| otxt = "" | ||
| sets = Default() | ||
| verbose = False | ||
| version = 647 | ||
| "commands" | ||
| class Commands: | ||
| cmds = {} | ||
| md5 = {} | ||
| names = {} | ||
| @staticmethod | ||
| def add(func, mod=None): | ||
| Commands.cmds[func.__name__] = func | ||
| if mod: | ||
| Commands.names[func.__name__] = mod.__name__.split(".")[-1] | ||
| @staticmethod | ||
| def get(cmd): | ||
| return Commands.cmds.get(cmd, None) | ||
| @staticmethod | ||
| def scan(mod): | ||
| for key, cmdz in inspect.getmembers(mod, inspect.isfunction): | ||
| if key.startswith("cb"): | ||
| continue | ||
| if 'event' in cmdz.__code__.co_varnames: | ||
| Commands.add(cmdz, mod) | ||
| "utilities" | ||
| def command(evt): | ||
| parse(evt) | ||
| func = Commands.get(evt.cmd) | ||
| if not func: | ||
| evt.ready() | ||
| return | ||
| func(evt) | ||
| Fleet.display(evt) | ||
| evt.ready() | ||
| def inits(pkg, names): | ||
| modz = [] | ||
| for name in sorted(spl(names)): | ||
| mod = getattr(pkg, name, None) | ||
| if not mod: | ||
| continue | ||
| if "init" in dir(mod): | ||
| thr = launch(mod.init) | ||
| modz.append((mod, thr)) | ||
| return modz | ||
| def parse(obj, txt=""): | ||
| if txt == "": | ||
| if "txt" in dir(obj): | ||
| txt = obj.txt | ||
| else: | ||
| txt = "" | ||
| args = [] | ||
| obj.args = [] | ||
| obj.cmd = "" | ||
| obj.gets = Default() | ||
| obj.index = None | ||
| obj.mod = "" | ||
| obj.opts = "" | ||
| obj.result = {} | ||
| obj.sets = Default() | ||
| obj.silent = Default() | ||
| obj.txt = txt | ||
| obj.otxt = obj.txt | ||
| _nr = -1 | ||
| for spli in obj.otxt.split(): | ||
| if spli.startswith("-"): | ||
| try: | ||
| obj.index = int(spli[1:]) | ||
| except ValueError: | ||
| obj.opts += spli[1:] | ||
| continue | ||
| if "-=" in spli: | ||
| key, value = spli.split("-=", maxsplit=1) | ||
| setattr(obj.silent, key, value) | ||
| setattr(obj.gets, key, value) | ||
| continue | ||
| if "==" in spli: | ||
| key, value = spli.split("==", maxsplit=1) | ||
| setattr(obj.gets, key, value) | ||
| continue | ||
| if "=" in spli: | ||
| key, value = spli.split("=", maxsplit=1) | ||
| if key == "mod": | ||
| if obj.mod: | ||
| obj.mod += f",{value}" | ||
| else: | ||
| obj.mod = value | ||
| continue | ||
| setattr(obj.sets, key, value) | ||
| continue | ||
| _nr += 1 | ||
| if _nr == 0: | ||
| obj.cmd = spli | ||
| continue | ||
| args.append(spli) | ||
| if args: | ||
| obj.args = args | ||
| obj.txt = obj.cmd or "" | ||
| obj.rest = " ".join(obj.args) | ||
| obj.txt = obj.cmd + " " + obj.rest | ||
| else: | ||
| obj.txt = obj.cmd or "" | ||
| def scan(pkg): | ||
| for modname in dir(pkg): | ||
| mod = getattr(pkg, modname) | ||
| Commands.scan(mod) | ||
| "utilities" | ||
| def elapsed(seconds, short=True): | ||
| txt = "" | ||
| nsec = float(seconds) | ||
| if nsec < 1: | ||
| return f"{nsec:.2f}s" | ||
| yea = 365*24*60*60 | ||
| week = 7*24*60*60 | ||
| nday = 24*60*60 | ||
| hour = 60*60 | ||
| minute = 60 | ||
| yeas = int(nsec/yea) | ||
| nsec -= yeas*yea | ||
| weeks = int(nsec/week) | ||
| nsec -= weeks*week | ||
| nrdays = int(nsec/nday) | ||
| nsec -= nrdays*nday | ||
| hours = int(nsec/hour) | ||
| nsec -= hours*hour | ||
| minutes = int(nsec/minute) | ||
| nsec -= int(minute*minutes) | ||
| sec = int(nsec) | ||
| if yeas: | ||
| txt += f"{yeas}y" | ||
| if weeks: | ||
| nrdays += weeks * 7 | ||
| if nrdays: | ||
| txt += f"{nrdays}d" | ||
| if short and txt: | ||
| return txt.strip() | ||
| if hours: | ||
| txt += f"{hours}h" | ||
| if minutes: | ||
| txt += f"{minutes}m" | ||
| if sec: | ||
| txt += f"{sec}s" | ||
| txt = txt.strip() | ||
| return txt | ||
| def spl(txt): | ||
| try: | ||
| result = txt.split(',') | ||
| except (TypeError, ValueError): | ||
| result = [txt, ] | ||
| return [x for x in result if x] | ||
| "interface" | ||
| def __dir__(): | ||
| return ( | ||
| 'STARTTIME', | ||
| 'Commands', | ||
| 'command', | ||
| 'elapsed', | ||
| 'inits', | ||
| 'load', | ||
| 'modules', | ||
| 'parse', | ||
| 'scan', | ||
| 'spl' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "event handler" | ||
| import queue | ||
| import threading | ||
| import time | ||
| import _thread | ||
| from .objects import Object | ||
| from .runtime import launch | ||
| "handler" | ||
| class Handler: | ||
| def __init__(self): | ||
| self.lock = _thread.allocate_lock() | ||
| self.cbs = {} | ||
| self.queue = queue.Queue() | ||
| self.ready = threading.Event() | ||
| self.stopped = threading.Event() | ||
| def available(self, event): | ||
| return event.type in self.cbs | ||
| def callback(self, event): | ||
| func = self.cbs.get(event.type, None) | ||
| if func: | ||
| event._thr = launch(func, event) | ||
| def loop(self): | ||
| while not self.stopped.is_set(): | ||
| event = self.poll() | ||
| if event is None: | ||
| break | ||
| event.orig = repr(self) | ||
| self.callback(event) | ||
| def poll(self): | ||
| return self.queue.get() | ||
| def put(self, event): | ||
| self.queue.put(event) | ||
| def register(self, typ, cbs): | ||
| self.cbs[typ] = cbs | ||
| def start(self, daemon=True): | ||
| self.stopped.clear() | ||
| launch(self.loop, daemon=daemon) | ||
| def stop(self): | ||
| self.stopped.set() | ||
| self.queue.put(None) | ||
| def wait(self): | ||
| pass | ||
| "event" | ||
| class Event(Object): | ||
| def __init__(self): | ||
| Object.__init__(self) | ||
| self._ready = threading.Event() | ||
| self._thr = None | ||
| self.channel = "" | ||
| self.ctime = time.time() | ||
| self.orig = "" | ||
| self.rest = "" | ||
| self.result = {} | ||
| self.type = "event" | ||
| self.txt = "" | ||
| def done(self): | ||
| self.reply("ok") | ||
| def ready(self): | ||
| self._ready.set() | ||
| def reply(self, txt): | ||
| self.result[time.time()] = txt | ||
| def wait(self, timeout=None): | ||
| self._ready.wait() | ||
| if self._thr: | ||
| self._thr.join() | ||
| "interface" | ||
| def __dir__(): | ||
| return ( | ||
| 'Event', | ||
| 'Handler' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "debug" | ||
| import time | ||
| from ..clients import Fleet | ||
| def dbg(event): | ||
| event.reply("raising exception") | ||
| raise Exception("yo!") | ||
| def brk(event): | ||
| event.reply("borking") | ||
| for clt in Fleet.all(): | ||
| if "sock" in dir(clt): | ||
| event.reply(f"shutdown on {clt.cfg.server}") | ||
| time.sleep(2.0) | ||
| try: | ||
| clt.sock.shutdown(2) | ||
| except OSError: | ||
| pass |
| # This file is placed in the Public Domain. | ||
| "version" | ||
| from ..command import Main | ||
| "commands" | ||
| def ver(event): | ||
| event.reply(f"{Main.name.upper()} {Main.version}") |
| # This file is placed in the Public Domain. | ||
| "clean namespace" | ||
| import json | ||
| "object" | ||
| class Object: | ||
| def __contains__(self, key): | ||
| return key in dir(self) | ||
| def __iter__(self): | ||
| return iter(self.__dict__) | ||
| def __len__(self): | ||
| return len(self.__dict__) | ||
| def __str__(self): | ||
| return str(self.__dict__) | ||
| "default" | ||
| class Default(Object): | ||
| def __getattr__(self, key): | ||
| if key not in self: | ||
| setattr(self, key, "") | ||
| return self.__dict__.get(key, "") | ||
| "methods" | ||
| def construct(obj, *args, **kwargs): | ||
| if args: | ||
| val = args[0] | ||
| if isinstance(val, zip): | ||
| update(obj, dict(val)) | ||
| elif isinstance(val, dict): | ||
| update(obj, val) | ||
| elif isinstance(val, Object): | ||
| update(obj, vars(val)) | ||
| if kwargs: | ||
| update(obj, kwargs) | ||
| def edit(obj, setter, skip=True): | ||
| for key, val in items(setter): | ||
| if skip and val == "": | ||
| continue | ||
| try: | ||
| setattr(obj, key, int(val)) | ||
| continue | ||
| except ValueError: | ||
| pass | ||
| try: | ||
| setattr(obj, key, float(val)) | ||
| continue | ||
| except ValueError: | ||
| pass | ||
| if val in ["True", "true"]: | ||
| setattr(obj, key, True) | ||
| elif val in ["False", "false"]: | ||
| setattr(obj, key, False) | ||
| else: | ||
| setattr(obj, key, val) | ||
| def fmt(obj, args=None, skip=None, plain=False, empty=False): | ||
| if args is None: | ||
| args = keys(obj) | ||
| if skip is None: | ||
| skip = [] | ||
| txt = "" | ||
| for key in args: | ||
| if key.startswith("__"): | ||
| continue | ||
| if key in skip: | ||
| continue | ||
| value = getattr(obj, key, None) | ||
| if value is None: | ||
| continue | ||
| if not empty and not value: | ||
| continue | ||
| if plain: | ||
| txt += f"{value} " | ||
| elif isinstance(value, str): | ||
| txt += f'{key}="{value}" ' | ||
| else: | ||
| txt += f'{key}={value} ' | ||
| return txt.strip() | ||
| def fqn(obj): | ||
| kin = str(type(obj)).split()[-1][1:-2] | ||
| if kin == "type": | ||
| kin = f"{obj.__module__}.{obj.__name__}" | ||
| return kin | ||
| def items(obj): | ||
| if isinstance(obj, dict): | ||
| return obj.items() | ||
| return obj.__dict__.items() | ||
| def keys(obj): | ||
| if isinstance(obj, dict): | ||
| return obj.keys() | ||
| return obj.__dict__.keys() | ||
| def update(obj, data): | ||
| if isinstance(data, dict): | ||
| return obj.__dict__.update(data) | ||
| obj.__dict__.update(vars(data)) | ||
| def values(obj): | ||
| if isinstance(obj, dict): | ||
| return obj.values() | ||
| return obj.__dict__.values() | ||
| "decoder/emcoder" | ||
| class Encoder(json.JSONEncoder): | ||
| def default(self, o): | ||
| if isinstance(o, dict): | ||
| return o.items() | ||
| if issubclass(type(o), Object): | ||
| return vars(o) | ||
| if isinstance(o, list): | ||
| return iter(o) | ||
| try: | ||
| return json.JSONEncoder.default(self, o) | ||
| except TypeError: | ||
| try: | ||
| return vars(o) | ||
| except TypeError: | ||
| return repr(o) | ||
| def dump(obj, fp, *args, **kw): | ||
| kw["cls"] = Encoder | ||
| json.dump(obj, fp, *args, **kw) | ||
| def dumps(obj, *args, **kw): | ||
| kw["cls"] = Encoder | ||
| return json.dumps(obj, *args, **kw) | ||
| def hook(objdict): | ||
| obj = Object() | ||
| construct(obj, objdict) | ||
| return obj | ||
| def load(fp, *args, **kw): | ||
| kw["object_hook"] = hook | ||
| return json.load(fp, *args, **kw) | ||
| def loads(s, *args, **kw): | ||
| kw["object_hook"] = hook | ||
| return json.loads(s, *args, **kw) | ||
| "interface" | ||
| def __dir__(): | ||
| return ( | ||
| 'Default', | ||
| 'Object', | ||
| 'construct', | ||
| 'dump', | ||
| 'dumps', | ||
| 'edit', | ||
| 'fmt', | ||
| 'fqn', | ||
| 'items', | ||
| 'keys', | ||
| 'load', | ||
| 'loads', | ||
| 'update', | ||
| 'values' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "persistence" | ||
| import datetime | ||
| import json | ||
| import os | ||
| import pathlib | ||
| import threading | ||
| import time | ||
| from .objects import Object, dump, fqn, items, load, update | ||
| lock = threading.RLock() | ||
| "cache" | ||
| class Cache: | ||
| objs = {} | ||
| @staticmethod | ||
| def add(path, obj): | ||
| Cache.objs[path] = obj | ||
| @staticmethod | ||
| def get(path): | ||
| return Cache.objs.get(path, None) | ||
| @staticmethod | ||
| def update(path, obj): | ||
| if not obj: | ||
| return | ||
| if path in Cache.objs: | ||
| update(Cache.objs[path], obj) | ||
| else: | ||
| Cache.add(path, obj) | ||
| "workdir" | ||
| class Workdir: | ||
| name = __file__.rsplit(os.sep, maxsplit=2)[-2] | ||
| wdr = "" | ||
| def getpath(obj): | ||
| return store(ident(obj)) | ||
| def ident(obj): | ||
| return os.path.join(fqn(obj),*str(datetime.datetime.now()).split()) | ||
| def long(name): | ||
| split = name.split(".")[-1].lower() | ||
| res = name | ||
| for names in types(): | ||
| if split == names.split(".")[-1].lower(): | ||
| res = names | ||
| break | ||
| return res | ||
| def pidname(name): | ||
| return os.path.join(Workdir.wdr, f"{name}.pid") | ||
| def skel(): | ||
| pth = pathlib.Path(store()) | ||
| pth.mkdir(parents=True, exist_ok=True) | ||
| return str(pth) | ||
| def store(pth=""): | ||
| return os.path.join(Workdir.wdr, "store", pth) | ||
| def strip(pth, nmr=2): | ||
| return os.path.join(pth.split(os.sep)[-nmr:]) | ||
| def types(): | ||
| return os.listdir(store()) | ||
| def wdr(pth): | ||
| return os.path.join(Workdir.wdr, pth) | ||
| "find" | ||
| def find(clz, selector=None, deleted=False, matching=False): | ||
| clz = long(clz) | ||
| if selector is None: | ||
| selector = {} | ||
| for pth in fns(clz): | ||
| obj = Cache.get(pth) | ||
| if not obj: | ||
| obj = Object() | ||
| read(obj, pth) | ||
| Cache.add(pth, obj) | ||
| if not deleted and isdeleted(obj): | ||
| continue | ||
| if selector and not search(obj, selector, matching): | ||
| continue | ||
| yield pth, obj | ||
| def fns(clz): | ||
| pth = store(clz) | ||
| for rootdir, dirs, _files in os.walk(pth, topdown=False): | ||
| for dname in dirs: | ||
| ddd = os.path.join(rootdir, dname) | ||
| for fll in os.listdir(ddd): | ||
| yield os.path.join(ddd, fll) | ||
| def fntime(daystr): | ||
| datestr = ' '.join(daystr.split(os.sep)[-2:]) | ||
| datestr = datestr.replace("_", " ") | ||
| if '.' in datestr: | ||
| datestr, rest = datestr.rsplit('.', 1) | ||
| else: | ||
| rest = '' | ||
| timed = time.mktime(time.strptime(datestr, '%Y-%m-%d %H:%M:%S')) | ||
| if rest: | ||
| timed += float('.' + rest) | ||
| return float(timed) | ||
| def isdeleted(obj): | ||
| return '__deleted__' in dir(obj) and obj.__deleted__ | ||
| def last(obj, selector=None): | ||
| if selector is None: | ||
| selector = {} | ||
| result = sorted(find(fqn(obj), selector), key=lambda x: fntime(x[0])) | ||
| res = "" | ||
| if result: | ||
| inp = result[-1] | ||
| update(obj, inp[-1]) | ||
| res = inp[0] | ||
| return res | ||
| def search(obj, selector, matching=False): | ||
| res = False | ||
| if not selector: | ||
| return res | ||
| for key, value in items(selector): | ||
| val = getattr(obj, key, None) | ||
| if not val: | ||
| continue | ||
| if matching and value == val: | ||
| res = True | ||
| elif str(value).lower() in str(val).lower() or value == "match": | ||
| res = True | ||
| else: | ||
| res = False | ||
| break | ||
| return res | ||
| "disk" | ||
| def cdir(path): | ||
| pth = pathlib.Path(path) | ||
| pth.parent.mkdir(parents=True, exist_ok=True) | ||
| def read(obj, path): | ||
| with lock: | ||
| with open(path, "r", encoding="utf-8") as fpt: | ||
| try: | ||
| update(obj, load(fpt)) | ||
| except json.decoder.JSONDecodeError as ex: | ||
| ex.add_note(path) | ||
| raise ex | ||
| def write(obj, path): | ||
| with lock: | ||
| cdir(path) | ||
| with open(path, "w", encoding="utf-8") as fpt: | ||
| dump(obj, fpt, indent=4) | ||
| return path | ||
| "interface" | ||
| def __dir__(): | ||
| return ( | ||
| 'Workdir', | ||
| 'cdir', | ||
| 'find', | ||
| 'fns', | ||
| 'fntime', | ||
| 'last', | ||
| 'long', | ||
| 'ident', | ||
| 'pidname', | ||
| 'read', | ||
| 'search', | ||
| 'skel', | ||
| 'store', | ||
| 'wdr', | ||
| 'write' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "runtime" | ||
| import logging | ||
| import queue | ||
| import sys | ||
| import time | ||
| import threading | ||
| import _thread | ||
| errorlock = threading.RLock() | ||
| launchlock = threading.RLock() | ||
| lock = threading.RLock() | ||
| "logging" | ||
| LEVELS = {'debug': logging.DEBUG, | ||
| 'info': logging.INFO, | ||
| 'warning': logging.WARNING, | ||
| 'warn': logging.WARNING, | ||
| 'error': logging.ERROR, | ||
| 'critical': logging.CRITICAL | ||
| } | ||
| def level(loglevel="debug"): | ||
| if loglevel != "none": | ||
| format_short = "%(message)-80s" | ||
| datefmt = '%H:%M:%S' | ||
| logging.basicConfig(stream=sys.stderr, datefmt=datefmt, format=format_short) | ||
| logging.getLogger().setLevel(LEVELS.get(loglevel)) | ||
| def rlog(loglevel, txt, ignore=None): | ||
| if ignore is None: | ||
| ignore = [] | ||
| for ign in ignore: | ||
| if ign in str(txt): | ||
| return | ||
| logging.log(LEVELS.get(loglevel), txt) | ||
| "threads" | ||
| class Thread(threading.Thread): | ||
| def __init__(self, func, thrname, *args, daemon=True, **kwargs): | ||
| super().__init__(None, self.run, thrname, (), daemon=daemon) | ||
| self.name = thrname or kwargs.get("name", name(func)) | ||
| self.queue = queue.Queue() | ||
| self.result = None | ||
| self.starttime = time.time() | ||
| self.stopped = threading.Event() | ||
| self.queue.put((func, args)) | ||
| def __iter__(self): | ||
| return self | ||
| def __next__(self): | ||
| for k in dir(self): | ||
| yield k | ||
| def run(self): | ||
| func, args = self.queue.get() | ||
| try: | ||
| self.result = func(*args) | ||
| except (KeyboardInterrupt, EOFError) as ex: | ||
| raise ex | ||
| except Exception as ex: | ||
| logging.exception(ex) | ||
| _thread.interrupt_main() | ||
| def join(self, timeout=None): | ||
| super().join(timeout) | ||
| return self.result | ||
| def launch(func, *args, **kwargs): | ||
| with launchlock: | ||
| nme = kwargs.get("name", None) | ||
| if not nme: | ||
| nme = name(func) | ||
| thread = Thread(func, nme, *args, **kwargs) | ||
| thread.start() | ||
| return thread | ||
| def name(obj): | ||
| typ = type(obj) | ||
| if '__builtins__' in dir(typ): | ||
| return obj.__name__ | ||
| if '__self__' in dir(obj): | ||
| return f'{obj.__self__.__class__.__name__}.{obj.__name__}' | ||
| if '__class__' in dir(obj) and '__name__' in dir(obj): | ||
| return f'{obj.__class__.__name__}.{obj.__name__}' | ||
| if '__class__' in dir(obj): | ||
| return f"{obj.__class__.__module__}.{obj.__class__.__name__}" | ||
| if '__name__' in dir(obj): | ||
| return f'{obj.__class__.__name__}.{obj.__name__}' | ||
| return "" | ||
| "repeater" | ||
| class Timy(threading.Timer): | ||
| def __init__(self, sleep, func, *args, **kwargs): | ||
| super().__init__(sleep, func) | ||
| self.name = kwargs.get("name", name(func)) | ||
| self.sleep = sleep | ||
| self.state = {} | ||
| self.state["latest"] = time.time() | ||
| self.state["starttime"] = time.time() | ||
| self.starttime = time.time() | ||
| class Timed: | ||
| def __init__(self, sleep, func, *args, thrname="", **kwargs): | ||
| self.args = args | ||
| self.func = func | ||
| self.kwargs = kwargs | ||
| self.sleep = sleep | ||
| self.name = thrname or kwargs.get("name", name(func)) | ||
| self.target = time.time() + self.sleep | ||
| self.timer = None | ||
| def run(self): | ||
| self.timer.latest = time.time() | ||
| self.func(*self.args) | ||
| def start(self): | ||
| self.kwargs["name"] = self.name | ||
| timer = Timy(self.sleep, self.run, *self.args, **self.kwargs) | ||
| timer.start() | ||
| self.timer = timer | ||
| def stop(self): | ||
| if self.timer: | ||
| self.timer.cancel() | ||
| class Repeater(Timed): | ||
| def run(self): | ||
| launch(self.start) | ||
| super().run() | ||
| "interface" | ||
| def __dir__(): | ||
| return ( | ||
| 'Repeater', | ||
| 'Thread', | ||
| 'Timed', | ||
| 'elapsed', | ||
| 'launch', | ||
| 'level', | ||
| 'name', | ||
| 'rlog', | ||
| 'spl' | ||
| ) |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
69947
3.88%35
59.09%2125
4.89%