rssbot
Advanced tools
| # This file is placed in the Public Domain. | ||
| import logging | ||
| LEVELS = { | ||
| 'debug': logging.DEBUG, | ||
| 'info': logging.INFO, | ||
| 'warning':logging. WARNING, | ||
| 'warn': logging.WARNING, | ||
| 'error': logging.ERROR, | ||
| 'critical': logging.CRITICAL | ||
| } | ||
| class Logging: | ||
| datefmt = "%H:%M:%S" | ||
| format = "%(module).3s %(message)s" | ||
| class Format(logging.Formatter): | ||
| def format(self, record): | ||
| record.module = record.module.upper() | ||
| return logging.Formatter.format(self, record) | ||
| def level(loglevel="debug"): | ||
| if loglevel != "none": | ||
| lvl = LEVELS.get(loglevel) | ||
| if not lvl: | ||
| return | ||
| logger = logging.getLogger() | ||
| for handler in logger.handlers: | ||
| logger.removeHandler(handler) | ||
| logger.setLevel(lvl) | ||
| formatter = Format(Logging.format, datefmt=Logging.datefmt) | ||
| ch = logging.StreamHandler() | ||
| ch.setFormatter(formatter) | ||
| logger.addHandler(ch) | ||
| def __dir__(): | ||
| return ( | ||
| 'LEVELS', | ||
| 'Logging', | ||
| 'level' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| import json | ||
| import types | ||
| class Encoder(json.JSONEncoder): | ||
| def default(self, o): | ||
| if isinstance(o, dict): | ||
| return o.items() | ||
| if isinstance(o, list): | ||
| return iter(o) | ||
| if isinstance(o, types.MappingProxyType): | ||
| return dict(o) | ||
| try: | ||
| return json.JSONEncoder.default(self, o) | ||
| except TypeError: | ||
| try: | ||
| return vars(o) | ||
| except TypeError: | ||
| return repr(o) | ||
| def dump(*args, **kw): | ||
| kw["cls"] = Encoder | ||
| return json.dump(*args, **kw) | ||
| def dumps(*args, **kw): | ||
| kw["cls"] = Encoder | ||
| return json.dumps(*args, **kw) | ||
| def load(s, *args, **kw): | ||
| return json.load(s, *args, **kw) | ||
| def loads(s, *args, **kw): | ||
| return json.loads(s, *args, **kw) | ||
| def __dir__(): | ||
| return ( | ||
| 'dump', | ||
| 'dumps', | ||
| 'load', | ||
| 'loads' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| import datetime | ||
| import json | ||
| import os | ||
| import pathlib | ||
| import threading | ||
| import time | ||
| from .marshal import dump, load | ||
| from .objects import Object, items, update | ||
| lock = threading.RLock() | ||
| class Cache: | ||
| objs = Object() | ||
| @staticmethod | ||
| def add(path, obj): | ||
| setattr(Cache.objs, path, obj) | ||
| @staticmethod | ||
| def get(path): | ||
| return getattr(Cache.objs, path, None) | ||
| @staticmethod | ||
| def update(path, obj): | ||
| setattr(Cache.objs, path, obj) | ||
| def deleted(obj): | ||
| return "__deleted__" in dir(obj) and obj.__deleted__ | ||
| def find(type=None, selector=None, removed=False, matching=False): | ||
| if selector is None: | ||
| selector = {} | ||
| for pth in fns(type): | ||
| obj = Cache.get(pth) | ||
| if not obj: | ||
| obj = Object() | ||
| read(obj, pth) | ||
| Cache.add(pth, obj) | ||
| if not removed and deleted(obj): | ||
| continue | ||
| if selector and not search(obj, selector, matching): | ||
| continue | ||
| yield pth, obj | ||
| def fns(type=None): | ||
| if type is not None: | ||
| type = type.lower() | ||
| path = store() | ||
| for rootdir, dirs, _files in os.walk(path, topdown=True): | ||
| for dname in dirs: | ||
| if dname.count("-") != 2: | ||
| continue | ||
| ddd = os.path.join(rootdir, dname) | ||
| if type and type not in ddd.lower(): | ||
| continue | ||
| 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 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 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 search(obj, selector, matching=False): | ||
| res = False | ||
| 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(): | ||
| res = True | ||
| else: | ||
| res = False | ||
| break | ||
| return res | ||
| def write(obj, path=None): | ||
| with lock: | ||
| if path is None: | ||
| path = getpath(obj) | ||
| cdir(path) | ||
| with open(path, "w", encoding="utf-8") as fpt: | ||
| dump(obj, fpt, indent=4) | ||
| Cache.update(path, obj) | ||
| return path | ||
| class Workdir: | ||
| wdr = "" | ||
| @staticmethod | ||
| def init(name): | ||
| Workdir.wdr = os.path.expanduser(f"~/.{name}") | ||
| def cdir(path): | ||
| pth = pathlib.Path(path) | ||
| pth.parent.mkdir(parents=True, exist_ok=True) | ||
| 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 moddir(modname=None): | ||
| return os.path.join(Workdir.wdr, modname or "mods") | ||
| def pidname(name): | ||
| assert Workdir.wdr | ||
| return os.path.join(Workdir.wdr, f"{name}.pid") | ||
| def skel(): | ||
| pth = pathlib.Path(store()) | ||
| pth.mkdir(parents=True, exist_ok=True) | ||
| pth = pathlib.Path(moddir()) | ||
| pth.mkdir(parents=True, exist_ok=True) | ||
| def store(fnm=""): | ||
| return os.path.join(Workdir.wdr, "store", fnm) | ||
| def types(): | ||
| return os.listdir(store()) | ||
| def __dir__(): | ||
| return ( | ||
| 'Cache', | ||
| 'Workdir', | ||
| 'cdir', | ||
| 'find', | ||
| 'fntime', | ||
| 'fqn', | ||
| 'fqn', | ||
| 'getpath', | ||
| 'ident', | ||
| 'read', | ||
| 'skel', | ||
| 'types', | ||
| 'write' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| import threading | ||
| import time | ||
| from .threads 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' | ||
| ) |
+2
-2
| Metadata-Version: 2.4 | ||
| Name: rssbot | ||
| Version: 653 | ||
| Summary: 24/7 Feed Fetcher | ||
| Version: 655 | ||
| Summary: 24/7 IRC Feed Fetcher, a contribution back to society. Public Domain. | ||
| Author-email: Bart Thate <rssbotd@gmail.com> | ||
@@ -6,0 +6,0 @@ License-Expression: Unlicense |
+2
-2
@@ -11,4 +11,4 @@ [build-system] | ||
| name = "rssbot" | ||
| description = "24/7 Feed Fetcher" | ||
| version = "653" | ||
| description = "24/7 IRC Feed Fetcher, a contribution back to society. Public Domain." | ||
| version = "655" | ||
| authors = [ | ||
@@ -15,0 +15,0 @@ {name = "Bart Thate",email = "rssbotd@gmail.com"}, |
| Metadata-Version: 2.4 | ||
| Name: rssbot | ||
| Version: 653 | ||
| Summary: 24/7 Feed Fetcher | ||
| Version: 655 | ||
| Summary: 24/7 IRC Feed Fetcher, a contribution back to society. Public Domain. | ||
| Author-email: Bart Thate <rssbotd@gmail.com> | ||
@@ -6,0 +6,0 @@ License-Expression: Unlicense |
@@ -5,14 +5,14 @@ README.rst | ||
| bin/rssbot | ||
| rssbot/brokers.py | ||
| rssbot/caching.py | ||
| rssbot/clients.py | ||
| rssbot/command.py | ||
| rssbot/handler.py | ||
| rssbot/loggers.py | ||
| rssbot/marshal.py | ||
| rssbot/methods.py | ||
| rssbot/objects.py | ||
| rssbot/package.py | ||
| rssbot/serials.py | ||
| rssbot/persist.py | ||
| rssbot/repeats.py | ||
| rssbot/threads.py | ||
| rssbot/utility.py | ||
| rssbot/workdir.py | ||
| rssbot.egg-info/PKG-INFO | ||
@@ -19,0 +19,0 @@ rssbot.egg-info/SOURCES.txt |
+66
-18
| # This file is placed in the Public Domain. | ||
| "client events" | ||
| import queue | ||
| import threading | ||
| import _thread | ||
| from .brokers import Fleet | ||
| from .handler import Handler | ||
@@ -17,2 +12,10 @@ from .threads import launch | ||
| class Config: | ||
| name = "tob" | ||
| opts = "" | ||
| sets: dict[str,str] = {} | ||
| version = 141 | ||
| class Client(Handler): | ||
@@ -27,5 +30,5 @@ | ||
| def announce(self, txt): | ||
| def announce(self, text): | ||
| if not self.silent: | ||
| self.raw(txt) | ||
| self.raw(text) | ||
@@ -40,12 +43,15 @@ def display(self, event): | ||
| def dosay(self, channel, txt): | ||
| self.say(channel, txt) | ||
| def dosay(self, channel, text): | ||
| self.say(channel, text) | ||
| def raw(self, txt): | ||
| def raw(self, text): | ||
| raise NotImplementedError("raw") | ||
| def say(self, channel, txt): | ||
| self.raw(txt) | ||
| def say(self, channel, text): | ||
| self.raw(text) | ||
| def wait(self): | ||
| self.oqueue.join() | ||
| class Output(Client): | ||
@@ -70,13 +76,55 @@ | ||
| def wait(self): | ||
| try: | ||
| self.oqueue.join() | ||
| except Exception: | ||
| _thread.interrupt_main() | ||
| class Fleet: | ||
| clients: dict[str, Client] = {} | ||
| @staticmethod | ||
| def add(client): | ||
| Fleet.clients[repr(client)] = client | ||
| @staticmethod | ||
| def all(): | ||
| return Fleet.clients.values() | ||
| @staticmethod | ||
| def announce(text): | ||
| for client in Fleet.all(): | ||
| client.announce(text) | ||
| @staticmethod | ||
| def display(event): | ||
| client = Fleet.get(event.orig) | ||
| if client: | ||
| client.display(event) | ||
| @staticmethod | ||
| def get(origin): | ||
| return Fleet.clients.get(origin, None) | ||
| @staticmethod | ||
| def like(origin): | ||
| for orig in Fleet.clients: | ||
| if origin.split()[0] in orig.split()[0]: | ||
| yield orig | ||
| @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.wait() | ||
| client.stop() | ||
| def __dir__(): | ||
| return ( | ||
| 'Client', | ||
| 'Config', | ||
| 'Fleet', | ||
| 'Output' | ||
| ) | ||
| ) |
+13
-45
| # This file is placed in the Public Domain. | ||
| "write your own commands" | ||
| import inspect | ||
| import inspect | ||
| from typing import Callable | ||
| from .brokers import Fleet | ||
| from .clients import Fleet | ||
| from .methods import parse | ||
| from .package import getmod, modules | ||
@@ -17,22 +16,14 @@ | ||
| cmds = {} | ||
| names = {} | ||
| cmds: dict[str, Callable] = {} | ||
| names: dict[str, str] = {} | ||
| @staticmethod | ||
| def add(func) -> None: | ||
| name = func.__name__ | ||
| modname = func.__module__.split(".")[-1] | ||
| Commands.cmds[name] = func | ||
| Commands.names[name] = modname | ||
| def add(*args): | ||
| for func in args: | ||
| name = func.__name__ | ||
| Commands.cmds[name] = func | ||
| Commands.names[name] = func.__module__.split(".")[-1] | ||
| @staticmethod | ||
| def get(cmd): | ||
| func = Commands.cmds.get(cmd, None) | ||
| if func: | ||
| return func | ||
| name = Commands.names.get(cmd, None) | ||
| if name: | ||
| module = getmod(name) | ||
| if module: | ||
| scan(module) | ||
| return Commands.cmds.get(cmd, None) | ||
@@ -42,3 +33,3 @@ | ||
| def command(evt): | ||
| parse(evt) | ||
| parse(evt, evt.text) | ||
| func = Commands.get(evt.cmd) | ||
@@ -59,30 +50,7 @@ if func: | ||
| def scanner(names=[]): | ||
| res = [] | ||
| for nme in sorted(modules()): | ||
| if names and nme not in names: | ||
| continue | ||
| module = getmod(nme) | ||
| if not module: | ||
| continue | ||
| scan(module) | ||
| res.append(module) | ||
| return res | ||
| def table(checksum=""): | ||
| tbl = getmod("tbl") | ||
| if tbl and "NAMES" in dir(tbl): | ||
| Commands.names.update(tbl.NAMES) | ||
| else: | ||
| scanner() | ||
| def __dir__(): | ||
| return ( | ||
| 'Commands', | ||
| 'Comamnds', | ||
| 'command', | ||
| 'scan', | ||
| 'scanner', | ||
| 'table' | ||
| 'scan' | ||
| ) |
+33
-43
| # This file is placed in the Public Domain. | ||
| "handle events" | ||
| import queue | ||
| import threading | ||
| import time | ||
| import _thread | ||
| from typing import Callable | ||
| from .threads import launch | ||
| class Event: | ||
| def __init__(self): | ||
| self._ready = threading.Event() | ||
| self._thr = None | ||
| self.channel = "" | ||
| self.ctime = time.time() | ||
| self.orig = "" | ||
| self.result = {} | ||
| self.type = "event" | ||
| def ready(self): | ||
| self._ready.set() | ||
| def reply(self, text): | ||
| self.result[time.time()] = text | ||
| def wait(self, timeout=None): | ||
| self._ready.wait() | ||
| if self._thr: | ||
| self._thr.join(timeout) | ||
| class Handler: | ||
| def __init__(self): | ||
| self.cbs = {} | ||
| self.cbs: dict[str, Callable] = {} | ||
| self.queue = queue.Queue() | ||
@@ -25,3 +47,3 @@ | ||
| if func: | ||
| name = event.txt and event.txt.split()[0] | ||
| name = event.text and event.text.split()[0] | ||
| event._thr = launch(func, event, name=name) | ||
@@ -33,10 +55,7 @@ else: | ||
| while True: | ||
| try: | ||
| event = self.poll() | ||
| if event is None: | ||
| break | ||
| event.orig = repr(self) | ||
| self.callback(event) | ||
| except (KeyboardInterrupt, EOFError): | ||
| _thread.interrupt_main() | ||
| event = self.poll() | ||
| if event is None: | ||
| break | ||
| event.orig = repr(self) | ||
| self.callback(event) | ||
@@ -59,31 +78,2 @@ def poll(self): | ||
| class Event: | ||
| def __init__(self): | ||
| self._ready = threading.Event() | ||
| self._thr = None | ||
| self.args = [] | ||
| self.channel = "" | ||
| self.ctime = time.time() | ||
| self.orig = "" | ||
| self.rest = "" | ||
| self.result = {} | ||
| self.txt = "" | ||
| self.type = "event" | ||
| def ready(self): | ||
| self._ready.set() | ||
| def reply(self, txt): | ||
| self.result[time.time()] = txt | ||
| def wait(self, timeout=None): | ||
| try: | ||
| self._ready.wait() | ||
| if self._thr: | ||
| self._thr.join(timeout) | ||
| except (KeyboardInterrupt, EOFError): | ||
| _thread.interrupt_main() | ||
| def __dir__(): | ||
@@ -90,0 +80,0 @@ return ( |
+28
-58
| # This file is placed in the Public Domain. | ||
| "object as the first argument" | ||
| from .objects import fqn, items | ||
| from .objects import items, keys | ||
| def deleted(obj): | ||
| return "__deleted__" in dir(obj) and obj.__deleted__ | ||
| def edit(obj, setter, skip=True): | ||
@@ -38,3 +31,3 @@ for key, val in items(setter): | ||
| if not args: | ||
| args = keys(obj) | ||
| args = obj.__dict__.keys() | ||
| txt = "" | ||
@@ -58,3 +51,3 @@ for key in args: | ||
| else: | ||
| txt += f"{key}={name(value, True)} " | ||
| txt += f"{key}={fqn(value)}((value))" | ||
| return txt.strip() | ||
@@ -81,21 +74,21 @@ | ||
| def parse(obj, txt=""): | ||
| if not txt: | ||
| if "txt" in dir(obj): | ||
| txt = obj.txt | ||
| def parse(obj, text): | ||
| data = { | ||
| "args": [], | ||
| "cmd": "", | ||
| "gets": {}, | ||
| "index": None, | ||
| "init": "", | ||
| "opts": "", | ||
| "otxt": text, | ||
| "rest": "", | ||
| "silent": {}, | ||
| "sets": {}, | ||
| "text": text | ||
| } | ||
| for k, v in data.items(): | ||
| setattr(obj, k, getattr(obj, k, v) or v) | ||
| args = [] | ||
| obj.args = getattr(obj, "args", []) | ||
| obj.cmd = getattr(obj, "cmd", "") | ||
| obj.gets = getattr(obj, "gets", "") | ||
| obj.index = getattr(obj, "index", None) | ||
| obj.inits = getattr(obj, "inits", "") | ||
| obj.mod = getattr(obj, "mod", "") | ||
| obj.opts = getattr(obj, "opts", "") | ||
| obj.result = getattr(obj, "result", "") | ||
| obj.sets = getattr(obj, "sets", {}) | ||
| obj.silent = getattr(obj, "silent", "") | ||
| obj.txt = txt or getattr(obj, "txt", "") | ||
| obj.otxt = obj.txt or getattr(obj, "otxt", "") | ||
| _nr = -1 | ||
| for spli in obj.otxt.split(): | ||
| nr = -1 | ||
| for spli in text.split(): | ||
| if spli.startswith("-"): | ||
@@ -118,12 +111,6 @@ try: | ||
| key, value = spli.split("=", maxsplit=1) | ||
| if key == "mod": | ||
| if obj.mod: | ||
| obj.mod += f",{value}" | ||
| else: | ||
| obj.mod = value | ||
| continue | ||
| obj.sets[key] = value | ||
| continue | ||
| _nr += 1 | ||
| if _nr == 0: | ||
| nr += 1 | ||
| if nr == 0: | ||
| obj.cmd = spli | ||
@@ -134,32 +121,15 @@ continue | ||
| obj.args = args | ||
| obj.txt = obj.cmd or "" | ||
| obj.text = obj.cmd or "" | ||
| obj.rest = " ".join(obj.args) | ||
| obj.txt = obj.cmd + " " + obj.rest | ||
| obj.text = obj.cmd + " " + obj.rest | ||
| else: | ||
| obj.txt = obj.cmd or "" | ||
| obj.text = obj.cmd or "" | ||
| def search(obj, selector, matching=False): | ||
| res = False | ||
| 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(): | ||
| res = True | ||
| else: | ||
| res = False | ||
| break | ||
| return res | ||
| def __dir__(): | ||
| return ( | ||
| 'deleted', | ||
| 'edit', | ||
| 'fmt', | ||
| 'parse', | ||
| 'search' | ||
| 'name', | ||
| 'parse' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| from ..clients import Fleet | ||
| from ..methods import fmt | ||
| from ..threads import name | ||
| from rssbot.clients import Fleet | ||
| from rssbot.methods import fmt | ||
| from rssbot.threads import name | ||
@@ -18,2 +18,2 @@ | ||
| return | ||
| event.reply(' | '.join([name(o) for o in Fleet.all()])) | ||
| event.reply(' | '.join([name(o) for o in Fleet.all()])) |
| # This file is placed in the Public Domain. | ||
| "find" | ||
| import time | ||
| from ..caching import find | ||
| from ..methods import fmt | ||
| from ..utility import elapsed, fntime | ||
| from ..workdir import long, skel, types | ||
| from rssbot.methods import fmt | ||
| from rssbot.persist import find, fntime, types | ||
| from rssbot.utility import elapsed | ||
| def fnd(event): | ||
| skel() | ||
| if not event.rest: | ||
@@ -26,5 +21,4 @@ res = sorted([x.split('.')[-1].lower() for x in types()]) | ||
| otype = event.args[0] | ||
| clz = long(otype) | ||
| nmr = 0 | ||
| for fnm, obj in list(find(clz, event.gets)): | ||
| for fnm, obj in list(find(otype, event.gets)): | ||
| event.reply(f"{nmr} {fmt(obj)} {elapsed(time.time()-fntime(fnm))}") | ||
@@ -31,0 +25,0 @@ nmr += 1 |
+93
-87
| # This file is placed in the Public Domain. | ||
| "internet relay chat" | ||
| import base64 | ||
@@ -17,52 +14,41 @@ import logging | ||
| from ..brokers import Fleet | ||
| from ..caching import last, write | ||
| from ..clients import Output | ||
| from ..command import command | ||
| from ..handler import Event as IEvent | ||
| from ..methods import edit, fmt | ||
| from ..objects import Object, keys | ||
| from ..threads import launch | ||
| from ..utility import LEVELS | ||
| from ..workdir import Workdir, getpath | ||
| from rssbot.clients import Config as Main | ||
| from rssbot.clients import Output | ||
| from rssbot.command import Fleet, command | ||
| from rssbot.handler import Event as IEvent | ||
| from rssbot.loggers import LEVELS | ||
| from rssbot.methods import edit, fmt | ||
| from rssbot.objects import Object, keys | ||
| from rssbot.persist import getpath, last, write | ||
| from rssbot.threads import launch | ||
| from rssbot.utility import where | ||
| IGNORE = ["PING", "PONG", "PRIVMSG"] | ||
| NAME = Workdir.name | ||
| IGNORE = ["PING", "PONG", "PRIVMSG"] | ||
| initlock = threading.RLock() | ||
| saylock = threading.RLock() | ||
| lock = threading.RLock() | ||
| def init(): | ||
| with initlock: | ||
| irc = IRC() | ||
| irc.start() | ||
| irc.events.joined.wait(30.0) | ||
| if irc.events.joined.is_set(): | ||
| logging.warning(fmt(irc.cfg, skip=["password", "realname", "username"])) | ||
| else: | ||
| irc.stop() | ||
| return irc | ||
| def init(cfg): | ||
| irc = IRC() | ||
| irc.start() | ||
| irc.events.joined.wait(30.0) | ||
| if irc.events.joined.is_set(): | ||
| logging.warning(fmt(irc.cfg, skip=["name", "password", "realname", "username"])) | ||
| else: | ||
| irc.stop() | ||
| return irc | ||
| 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) | ||
| class Config: | ||
| channel = f"#{NAME}" | ||
| channel = f"#{Main.name}" | ||
| commands = False | ||
| control = "!" | ||
| nick = NAME | ||
| name = Main.name | ||
| nick = Main.name | ||
| password = "" | ||
| port = 6667 | ||
| realname = NAME | ||
| realname = Main.name | ||
| sasl = False | ||
@@ -72,4 +58,5 @@ server = "localhost" | ||
| sleep = 60 | ||
| username = NAME | ||
| username = Main.name | ||
| users = False | ||
| version = 1 | ||
@@ -79,2 +66,3 @@ def __init__(self): | ||
| self.commands = Config.commands | ||
| self.name = Config.name | ||
| self.nick = Config.nick | ||
@@ -95,2 +83,3 @@ self.port = Config.port | ||
| self.channel = "" | ||
| self.gets = {} | ||
| self.nick = "" | ||
@@ -100,5 +89,10 @@ self.origin = "" | ||
| self.rest = "" | ||
| self.txt = "" | ||
| self.sets = {} | ||
| self.text = "" | ||
| def dosay(self, txt): | ||
| bot = Fleet.get(self.orig) | ||
| bot.dosay(self.channel, txt) | ||
| class TextWrap(textwrap.TextWrapper): | ||
@@ -158,5 +152,5 @@ | ||
| def announce(self, txt): | ||
| def announce(self, text): | ||
| for channel in self.channels: | ||
| self.say(channel, txt) | ||
| self.say(channel, text) | ||
@@ -193,3 +187,3 @@ def connect(self, server, port=6667): | ||
| def direct(self, txt): | ||
| with saylock: | ||
| with lock: | ||
| time.sleep(2.0) | ||
@@ -217,5 +211,5 @@ self.raw(txt) | ||
| _nr = -1 | ||
| for txt in textlist: | ||
| for text in textlist: | ||
| _nr += 1 | ||
| self.dosay(event.channel, txt) | ||
| self.dosay(event.channel, text) | ||
| if len(txtlist) > 3: | ||
@@ -226,3 +220,3 @@ length = len(txtlist) - 3 | ||
| def docommand(self, cmd, *args): | ||
| with saylock: | ||
| with lock: | ||
| if not args: | ||
@@ -259,5 +253,5 @@ self.raw(cmd) | ||
| def dosay(self, channel, txt): | ||
| def dosay(self, channel, text): | ||
| self.events.joined.wait() | ||
| txt = str(txt).replace("\n", "") | ||
| txt = str(text).replace("\n", "") | ||
| txt = txt.replace(" ", " ") | ||
@@ -271,3 +265,3 @@ self.docommand("PRIVMSG", channel, txt) | ||
| self.state.pongcheck = True | ||
| self.docommand("PONG", evt.txt or "") | ||
| self.docommand("PONG", evt.text or "") | ||
| elif cmd == "PONG": | ||
@@ -342,3 +336,3 @@ self.state.pongcheck = False | ||
| rawstr = rawstr.replace("\001", "") | ||
| logging.debug(txt) | ||
| rlog("debug", txt, IGNORE) | ||
| obj = Event() | ||
@@ -371,3 +365,3 @@ obj.args = [] | ||
| obj.arguments.append(arg) | ||
| obj.txt = " ".join(txtlist) | ||
| obj.text = " ".join(txtlist) | ||
| else: | ||
@@ -387,7 +381,7 @@ obj.command = obj.origin | ||
| obj.channel = obj.nick | ||
| if not obj.txt: | ||
| obj.txt = rawstr.split(":", 2)[-1] | ||
| if not obj.txt and len(arguments) == 1: | ||
| obj.txt = arguments[1] | ||
| splitted = obj.txt.split() | ||
| if not obj.text: | ||
| obj.text = rawstr.split(":", 2)[-1] | ||
| if not obj.text and len(arguments) == 1: | ||
| obj.text = arguments[1] | ||
| splitted = obj.text.split() | ||
| if len(splitted) > 1: | ||
@@ -398,3 +392,3 @@ obj.args = splitted[1:] | ||
| obj.orig = object.__repr__(self) | ||
| obj.txt = obj.txt.strip() | ||
| obj.text = obj.text.strip() | ||
| obj.type = obj.command | ||
@@ -431,11 +425,11 @@ return obj | ||
| def raw(self, txt): | ||
| txt = txt.rstrip() | ||
| rlog("info", txt, IGNORE) | ||
| txt = txt[:500] | ||
| txt += "\r\n" | ||
| txt = bytes(txt, "utf-8") | ||
| def raw(self, text): | ||
| text = text.rstrip() | ||
| rlog("debug", text, IGNORE) | ||
| text = text[:500] | ||
| text += "\r\n" | ||
| text = bytes(text, "utf-8") | ||
| if self.sock: | ||
| try: | ||
| self.sock.send(txt) | ||
| self.sock.send(text) | ||
| except ( | ||
@@ -479,7 +473,7 @@ OSError, | ||
| def say(self, channel, txt): | ||
| evt = Event() | ||
| evt.channel = channel | ||
| evt.reply(txt) | ||
| self.oput(evt) | ||
| def say(self, channel, text): | ||
| event = Event() | ||
| event.channel = channel | ||
| event.reply(text) | ||
| self.oput(event) | ||
@@ -491,6 +485,6 @@ def some(self): | ||
| inbytes = self.sock.recv(512) | ||
| txt = str(inbytes, "utf-8") | ||
| if txt == "": | ||
| text = str(inbytes, "utf-8") | ||
| if text == "": | ||
| raise ConnectionResetError | ||
| self.state.lastline += txt | ||
| self.state.lastline += text | ||
| splitted = self.state.lastline.split("\r\n") | ||
@@ -502,3 +496,2 @@ for line in splitted[:-1]: | ||
| def start(self): | ||
| last(self.cfg) | ||
| if self.cfg.channel not in self.channels: | ||
@@ -546,3 +539,3 @@ self.channels.append(self.cfg.channel) | ||
| bot.state.nrerror += 1 | ||
| bot.state.error = evt.txt | ||
| bot.state.error = evt.text | ||
| logging.debug(fmt(evt)) | ||
@@ -583,4 +576,4 @@ | ||
| bot = Fleet.get(evt.orig) | ||
| if evt.txt.startswith("VERSION"): | ||
| txt = f"\001VERSION {NAME.upper()} 140 - {bot.cfg.username}\001" | ||
| if evt.text.startswith("VERSION"): | ||
| txt = f"\001VERSION {Config.name.upper()} {Config.version} - {bot.cfg.username}\001" | ||
| bot.docommand("NOTICE", evt.channel, txt) | ||
@@ -593,14 +586,14 @@ | ||
| return | ||
| if evt.txt: | ||
| if evt.txt[0] in [ | ||
| if evt.text: | ||
| if evt.text[0] in [ | ||
| "!", | ||
| ]: | ||
| evt.txt = evt.txt[1:] | ||
| elif evt.txt.startswith(f"{bot.cfg.nick}:"): | ||
| evt.txt = evt.txt[len(bot.cfg.nick) + 1 :] | ||
| evt.text = evt.text[1:] | ||
| elif evt.text.startswith(f"{bot.cfg.nick}:"): | ||
| evt.text = evt.text[len(bot.cfg.nick) + 1 :] | ||
| else: | ||
| return | ||
| if evt.txt: | ||
| evt.txt = evt.txt[0].lower() + evt.txt[1:] | ||
| if evt.txt: | ||
| if evt.text: | ||
| evt.text = evt.text[0].lower() + evt.text[1:] | ||
| if evt.text: | ||
| launch(command, evt) | ||
@@ -613,3 +606,3 @@ | ||
| bot.state.nrerror += 1 | ||
| bot.state.error = evt.txt | ||
| bot.state.error = evt.text | ||
| if evt.orig and evt.orig in bot.zelf: | ||
@@ -630,3 +623,3 @@ bot.stop() | ||
| keys(config), | ||
| skip="control,password,realname,sleep,username".split(","), | ||
| skip="control,name,password,realname,sleep,username".split(","), | ||
| ) | ||
@@ -637,2 +630,3 @@ ) | ||
| write(config, fnm or getpath(config)) | ||
| event.reply("ok") | ||
@@ -670,1 +664,13 @@ | ||
| event.reply(dcd) | ||
| "utility" | ||
| 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) |
| # This file is been placed in the Public Domain. | ||
| "available types" | ||
| from rssbot.persist import types | ||
| from ..workdir import types | ||
| def lst(event): | ||
@@ -11,0 +8,0 @@ tps = types() |
| # This file is placed in the Public Domain. | ||
| "show modules" | ||
| from rssbot.package import modules | ||
| from ..package import modules | ||
| def mod(event): | ||
| event.reply(",".join(modules())) |
+21
-24
| # This file is placed in the Public Domain. | ||
| "rich site syndicate" | ||
| import html | ||
@@ -25,12 +22,15 @@ import html.parser | ||
| from ..caching import find, last, write | ||
| from ..clients import Fleet | ||
| from ..methods import fmt | ||
| from ..objects import Object, update | ||
| from ..threads import Repeater, launch | ||
| from ..utility import elapsed, fntime, spl | ||
| from ..workdir import getpath | ||
| from rssbot.clients import Fleet | ||
| from rssbot.methods import fmt | ||
| from rssbot.objects import Object, update | ||
| from rssbot.persist import find, fntime, getpath, last, write | ||
| from rssbot.repeats import Repeater | ||
| from rssbot.threads import launch | ||
| from rssbot.utility import elapsed, spl | ||
| def init(): | ||
| DEBUG = False | ||
| def init(cfg): | ||
| fetcher = Fetcher() | ||
@@ -45,8 +45,5 @@ fetcher.start() | ||
| DEBUG = False | ||
| fetchlock = _thread.allocate_lock() | ||
| importlock = _thread.allocate_lock() | ||
| errors = {} | ||
| errors: dict[str, float] = {} | ||
| skipped = [] | ||
@@ -144,3 +141,3 @@ | ||
| thrs = [] | ||
| for _fn, feed in find("rss"): | ||
| for _fn, feed in find("rss.Rss"): | ||
| thrs.append(launch(self.fetch, feed, silent)) | ||
@@ -364,3 +361,3 @@ return thrs | ||
| setter = {"display_list": event.args[1]} | ||
| for fnm, feed in find("rss", {"rss": event.args[0]}): | ||
| for fnm, feed in find("rss.Rss", {"rss": event.args[0]}): | ||
| if feed: | ||
@@ -376,3 +373,3 @@ update(feed, setter) | ||
| nrs = 0 | ||
| for _fn, ooo in find("rss"): | ||
| for _fn, ooo in find("rss.Rss"): | ||
| nrs += 1 | ||
@@ -410,3 +407,3 @@ obj = Rss() | ||
| continue | ||
| has = list(find("rss", {"rss": url}, matching=True)) | ||
| has = list(find("rss.Rss", {"rss": url}, matching=True)) | ||
| if has: | ||
@@ -433,3 +430,3 @@ skipped.append(url) | ||
| selector = {"rss": event.args[0]} | ||
| for fnm, fed in find("rss", selector): | ||
| for fnm, fed in find("rss.Rss", selector): | ||
| feed = Rss() | ||
@@ -447,3 +444,3 @@ update(feed, fed) | ||
| return | ||
| for fnm, fed in find("rss"): | ||
| for fnm, fed in find("rss.Rss"): | ||
| feed = Rss() | ||
@@ -464,3 +461,3 @@ update(feed, fed) | ||
| return | ||
| for fnm, fed in find("rss", removed=True): | ||
| for fnm, fed in find("rss.Rss", removed=True): | ||
| feed = Rss() | ||
@@ -479,3 +476,3 @@ update(feed, fed) | ||
| nrs = 0 | ||
| for fnm, fed in find("rss"): | ||
| for fnm, fed in find("rss.Rss"): | ||
| nrs += 1 | ||
@@ -492,3 +489,3 @@ elp = elapsed(time.time() - fntime(fnm)) | ||
| return | ||
| for fnm, result in find("rss", {"rss": url}): | ||
| for fnm, result in find("rss.Rss", {"rss": url}): | ||
| if result: | ||
@@ -495,0 +492,0 @@ event.reply(f"{url} is known") |
| # This file is placed in the Public Domain. | ||
| "enable silence mode" | ||
| from rssbot.command import Fleet | ||
| from ..brokers import Fleet | ||
| def sil(event): | ||
@@ -11,0 +8,0 @@ bot = Fleet.get(event.orig) |
| # This file is placed in the Public Domain. | ||
| "show running threads" | ||
| import threading | ||
@@ -11,3 +8,3 @@ import time | ||
| from ..utility import elapsed | ||
| from rssbot.utility import elapsed | ||
@@ -14,0 +11,0 @@ |
| # This file is placed in the Public Domain. | ||
| "uptime" | ||
| import time | ||
| from ..utility import elapsed | ||
| from rssbot.utility import elapsed | ||
@@ -12,0 +9,0 @@ |
+26
-6
| # This file is placed in the Public Domain. | ||
| "clean namespace" | ||
| import json | ||
| import types | ||
@@ -35,5 +36,14 @@ | ||
| 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() | ||
| if isinstance(obj, types.MappingProxyType): | ||
| return obj.items() | ||
| return obj.__dict__.items() | ||
@@ -48,7 +58,16 @@ | ||
| def update(obj, data, empty=True): | ||
| for key, value in items(data): | ||
| if not empty and not value: | ||
| continue | ||
| setattr(obj, key, value) | ||
| def update(obj, data={}, empty=True): | ||
| if isinstance(obj, type): | ||
| for k, v in items(data): | ||
| if isinstance(getattr(obj, k, None), types.MethodType): | ||
| continue | ||
| setattr(obj, k, v) | ||
| elif isinstance(obj, dict): | ||
| for k, v in items(data): | ||
| setattr(obj, k, v) | ||
| else: | ||
| for key, value in items(data): | ||
| if not empty and not value: | ||
| continue | ||
| setattr(obj, key, value) | ||
@@ -66,2 +85,3 @@ | ||
| 'construct', | ||
| 'fqn', | ||
| 'items', | ||
@@ -68,0 +88,0 @@ 'keys', |
+46
-78
| # This file is placed in the Public Domain. | ||
| "module management" | ||
| import inspect | ||
| import logging | ||
| import importlib | ||
| import importlib.util | ||
| import os | ||
| import sys | ||
| import threading | ||
| import _thread | ||
| from .threads import launch | ||
| from .utility import importer, md5sum | ||
| from .workdir import Workdir, j, moddir | ||
| from .utility import spl, where | ||
| NAME = Workdir.name | ||
| PATH = os.path.dirname(inspect.getfile(Workdir)) | ||
| lock = threading.RLock() | ||
| class Mods: | ||
| debug = False | ||
| dirs = {} | ||
| md5s = {} | ||
| dirs: dict[str, str] = {} | ||
| ignore: list[str] = [] | ||
| @staticmethod | ||
| def dir(name, path=None): | ||
| if path is not None: | ||
| Mods.dirs[name] = path | ||
| else: | ||
| Mods.dirs[NAME + "." + name] = j(PATH, name) | ||
| def add(name=None, path=None): | ||
| Mods.dirs[name] = path | ||
| @staticmethod | ||
| def init(name, ignore="", local=False): | ||
| if name: | ||
| pkg = importer(name) | ||
| if pkg: | ||
| Mods.add(name, pkg.__path__[0]) | ||
| if ignore: | ||
| Mods.ignore = spl(ignore) | ||
| if local: | ||
| Mods.add("mods", "mods") | ||
| def getmod(name): | ||
| for nme, path in Mods.dirs.items(): | ||
| mname = nme + "." + name | ||
| module = sys.modules.get(mname, None) | ||
| if module: | ||
| return module | ||
| pth = j(path, f"{name}.py") | ||
| if Mods.md5s: | ||
| if os.path.exists(pth) and name != "tbl": | ||
| md5 = Mods.md5s.get(name, None) | ||
| if md5sum(pth) != md5: | ||
| file = pth.split(os.sep)[-1] | ||
| logging.info("md5 error %s", file) | ||
| mod = importer(mname, pth) | ||
| if mod: | ||
| return mod | ||
| mname = "" | ||
| pth = "" | ||
| if name in Mods.ignore: | ||
| return | ||
| for packname, path in Mods.dirs.items(): | ||
| modpath = os.path.join(path, name + ".py") | ||
| if os.path.exists(modpath): | ||
| pth = modpath | ||
| mname = f"{packname}.{name}" | ||
| break | ||
| return sys.modules.get(mname, None) or importer(mname, pth) | ||
| def inits(names): | ||
| modz = [] | ||
| for name in modules(): | ||
| if name not in names: | ||
| continue | ||
| try: | ||
| module = getmod(name) | ||
| if module and "init" in dir(module): | ||
| thr = launch(module.init) | ||
| modz.append((module, thr)) | ||
| except Exception as ex: | ||
| logging.exception(ex) | ||
| _thread.interrupt_main() | ||
| return modz | ||
| def importer(name, pth=None): | ||
| if pth and os.path.exists(pth): | ||
| spec = importlib.util.spec_from_file_location(name, pth) | ||
| else: | ||
| spec = importlib.util.find_spec(name) | ||
| if not spec: | ||
| return | ||
| mod = importlib.util.module_from_spec(spec) | ||
| if not mod: | ||
| return | ||
| sys.modules[name] = mod | ||
| spec.loader.exec_module(mod) | ||
| return mod | ||
@@ -78,2 +66,4 @@ | ||
| for name, path in Mods.dirs.items(): | ||
| if name in Mods.ignore: | ||
| continue | ||
| if not os.path.exists(path): | ||
@@ -83,25 +73,7 @@ continue | ||
| x[:-3] for x in os.listdir(path) | ||
| if x.endswith(".py") and not x.startswith("__") | ||
| ]) | ||
| if x.endswith(".py") and not x.startswith("__") and x not in Mods.ignore | ||
| ]) | ||
| return sorted(mods) | ||
| def setdirs(network=False, mods=False): | ||
| Mods.dir("modules") | ||
| Mods.dir("local", moddir()) | ||
| if network: | ||
| Mods.dir("network") | ||
| if mods: | ||
| Mods.dir("mods", "mods") | ||
| def sums(checksum): | ||
| tbl = getmod("tbl") | ||
| if not tbl: | ||
| logging.info("no table") | ||
| return | ||
| if "MD5" in dir(tbl): | ||
| Mods.md5s.update(tbl.MD5) | ||
| def __dir__(): | ||
@@ -112,7 +84,3 @@ return ( | ||
| 'importer', | ||
| 'inits', | ||
| 'md5sum', | ||
| 'modules', | ||
| 'setdirs', | ||
| 'sums' | ||
| ) |
+12
-64
| # This file is placed in the Public Domain. | ||
| "non-blocking" | ||
| import logging | ||
@@ -35,66 +32,10 @@ import queue | ||
| def join(self, timeout=None): | ||
| result = None | ||
| try: | ||
| super().join(timeout) | ||
| result = self.result | ||
| except (KeyboardInterrupt, EOFError): | ||
| _thread.interrupt_main() | ||
| return result | ||
| super().join(timeout) | ||
| return self.result | ||
| 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() | ||
| self.result = func(*args) | ||
| 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 launch(func, *args, **kwargs): | ||
@@ -106,8 +47,15 @@ thread = Thread(func, *args, **kwargs) | ||
| def threadhook(args): | ||
| type, value, trace, thread = args | ||
| exc = value.with_traceback(trace) | ||
| if type not in (KeyboardInterrupt, EOFError): | ||
| logging.exception(exc) | ||
| _thread.interrupt_main() | ||
| def __dir__(): | ||
| return ( | ||
| 'Repeater', | ||
| 'Thread', | ||
| 'launch', | ||
| 'name' | ||
| 'threadhook' | ||
| ) |
+91
-71
| # This file is placed in the Public Domain. | ||
| "utilities" | ||
| import hashlib | ||
| import importlib.util | ||
| import logging | ||
| import os | ||
| import pathlib | ||
| import sys | ||
| import time | ||
| import _thread | ||
@@ -22,23 +17,37 @@ | ||
| "%d-%m", | ||
| "%m-%d", | ||
| "%m-%d" | ||
| ] | ||
| LEVELS = { | ||
| 'debug': logging.DEBUG, | ||
| 'info': logging.INFO, | ||
| 'warning': logging.WARNING, | ||
| 'warn': logging.WARNING, | ||
| 'error': logging.ERROR, | ||
| 'critical': logging.CRITICAL, | ||
| } | ||
| def check(text): | ||
| args = sys.argv[1:] | ||
| for arg in args: | ||
| if not arg.startswith("-"): | ||
| continue | ||
| for char in text: | ||
| if char in arg: | ||
| return True | ||
| return False | ||
| class Formatter(logging.Formatter): | ||
| def daemon(verbose=False): | ||
| pid = os.fork() | ||
| if pid != 0: | ||
| os._exit(0) | ||
| os.setsid() | ||
| pid2 = os.fork() | ||
| if pid2 != 0: | ||
| os._exit(0) | ||
| if not verbose: | ||
| with open('/dev/null', 'r', encoding="utf-8") as sis: | ||
| os.dup2(sis.fileno(), sys.stdin.fileno()) | ||
| with open('/dev/null', 'a+', encoding="utf-8") as sos: | ||
| os.dup2(sos.fileno(), sys.stdout.fileno()) | ||
| with open('/dev/null', 'a+', encoding="utf-8") as ses: | ||
| os.dup2(ses.fileno(), sys.stderr.fileno()) | ||
| os.umask(0) | ||
| os.chdir("/") | ||
| os.nice(10) | ||
| def format(self, record): | ||
| record.module = record.module.upper() | ||
| return logging.Formatter.format(self, record) | ||
| def elapsed(seconds, short=True): | ||
@@ -95,47 +104,12 @@ txt = "" | ||
| 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 forever(): | ||
| while True: | ||
| try: | ||
| time.sleep(0.1) | ||
| except (KeyboardInterrupt, EOFError): | ||
| break | ||
| def importer(name, pth): | ||
| if not os.path.exists(pth): | ||
| return | ||
| try: | ||
| spec = importlib.util.spec_from_file_location(name, pth) | ||
| if not spec or not spec.loader: | ||
| return | ||
| mod = importlib.util.module_from_spec(spec) | ||
| if not mod: | ||
| return | ||
| sys.modules[name] = mod | ||
| spec.loader.exec_module(mod) | ||
| logging.info("load %s", pth) | ||
| return mod | ||
| except Exception as ex: | ||
| logging.exception(ex) | ||
| _thread.interrupt_main() | ||
| def level(loglevel="debug"): | ||
| if loglevel != "none": | ||
| datefmt = "%H:%M:%S" | ||
| format_short = "%(module).3s %(message)-76s" | ||
| ch = logging.StreamHandler() | ||
| ch.setLevel(LEVELS.get(loglevel)) | ||
| formatter = Formatter(fmt=format_short, datefmt=datefmt) | ||
| ch.setFormatter(formatter) | ||
| logger = logging.getLogger() | ||
| logger.addHandler(ch) | ||
| def md5sum(path): | ||
| import hashlib | ||
| with open(path, "r", encoding="utf-8") as file: | ||
@@ -146,2 +120,20 @@ txt = file.read().encode("utf-8") | ||
| def pidfile(filename): | ||
| if os.path.exists(filename): | ||
| os.unlink(filename) | ||
| path2 = pathlib.Path(filename) | ||
| path2.parent.mkdir(parents=True, exist_ok=True) | ||
| with open(filename, "w", encoding="utf-8") as fds: | ||
| fds.write(str(os.getpid())) | ||
| def privileges(): | ||
| import getpass | ||
| import pwd | ||
| pwnam2 = pwd.getpwnam(getpass.getuser()) | ||
| os.setgid(pwnam2.pw_gid) | ||
| os.setuid(pwnam2.pw_uid) | ||
| def spl(txt): | ||
@@ -151,18 +143,46 @@ try: | ||
| except (TypeError, ValueError): | ||
| result = [ | ||
| txt, | ||
| ] | ||
| result = [] | ||
| return [x for x in result if x] | ||
| def where(obj): | ||
| import inspect | ||
| return os.path.dirname(inspect.getfile(obj)) | ||
| def wrapped(func): | ||
| try: | ||
| func() | ||
| except (KeyboardInterrupt, EOFError): | ||
| pass | ||
| def wrap(func): | ||
| import termios | ||
| old = None | ||
| try: | ||
| old = termios.tcgetattr(sys.stdin.fileno()) | ||
| except termios.error: | ||
| pass | ||
| try: | ||
| wrapped(func) | ||
| finally: | ||
| if old: | ||
| termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old) | ||
| def __dir__(): | ||
| return ( | ||
| 'cdir', | ||
| 'check', | ||
| 'daemon', | ||
| 'elapsed', | ||
| 'extract_date', | ||
| 'fntime', | ||
| 'importer', | ||
| 'level', | ||
| 'forever', | ||
| 'md5sum', | ||
| 'spl' | ||
| ) | ||
| 'pidfile', | ||
| 'privileges', | ||
| 'spl', | ||
| 'where', | ||
| 'wrap', | ||
| 'wrapped' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "client for a string" | ||
| 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 display(evt): | ||
| client = Fleet.get(evt.orig) | ||
| client.display(evt) | ||
| @staticmethod | ||
| def get(orig): | ||
| return Fleet.clients.get(orig, None) | ||
| @staticmethod | ||
| def say(orig, channel, txt): | ||
| client = Fleet.get(orig) | ||
| client.say(channel, txt) | ||
| @staticmethod | ||
| def shutdown(): | ||
| for client in Fleet.all(): | ||
| client.wait() | ||
| client.stop() | ||
| def __dir__(): | ||
| return ( | ||
| 'Fleet', | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "object for a string" | ||
| import json.decoder | ||
| import os | ||
| import threading | ||
| from .methods import deleted, search | ||
| from .objects import Object, update | ||
| from .serials import dump, load | ||
| from .workdir import cdir, fqn, getpath, j, long, store | ||
| from .utility import fntime | ||
| 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 path in Cache.objs: | ||
| update(Cache.objs[path], obj) | ||
| else: | ||
| Cache.add(path, obj) | ||
| def find(clz, selector=None, removed=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 removed and deleted(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 = j(rootdir, dname) | ||
| for fll in os.listdir(ddd): | ||
| yield j(ddd, fll) | ||
| 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 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=None): | ||
| with lock: | ||
| if path is None: | ||
| path = getpath(obj) | ||
| cdir(path) | ||
| with open(path, "w", encoding="utf-8") as fpt: | ||
| dump(obj, fpt, indent=4) | ||
| Cache.update(path, obj) | ||
| return path | ||
| def __dir__(): | ||
| return ( | ||
| 'Cache', | ||
| 'find', | ||
| 'last', | ||
| 'read', | ||
| 'write' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "json serializer" | ||
| from json import JSONEncoder | ||
| from json import dump as jdump | ||
| from json import dumps as jdumps | ||
| from json import load as load | ||
| from json import loads as loads | ||
| class Encoder(JSONEncoder): | ||
| def default(self, o): | ||
| if isinstance(o, dict): | ||
| return o.items() | ||
| if isinstance(o, list): | ||
| return iter(o) | ||
| try: | ||
| return JSONEncoder.default(self, o) | ||
| except TypeError: | ||
| try: | ||
| return vars(o) | ||
| except TypeError: | ||
| return repr(o) | ||
| def dump(obj, fp, *args, **kw): | ||
| kw["cls"] = Encoder | ||
| jdump(obj, fp, *args, **kw) | ||
| def dumps(obj, *args, **kw): | ||
| kw["cls"] = Encoder | ||
| return jdumps(obj, *args, **kw) | ||
| def __dir__(): | ||
| return ( | ||
| 'dump', | ||
| 'dumps', | ||
| 'load', | ||
| 'loads' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "working directory" | ||
| import datetime | ||
| import os | ||
| import pathlib | ||
| j = os.path.join | ||
| class Workdir: | ||
| name = __file__.rsplit(os.sep, maxsplit=2)[-2] | ||
| wdr = "" | ||
| def cdir(path): | ||
| pth = pathlib.Path(path) | ||
| pth.parent.mkdir(parents=True, exist_ok=True) | ||
| 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 j(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 moddir(): | ||
| assert Workdir.wdr | ||
| return j(Workdir.wdr, "mods") | ||
| def pidfile(filename): | ||
| if os.path.exists(filename): | ||
| os.unlink(filename) | ||
| path2 = pathlib.Path(filename) | ||
| path2.parent.mkdir(parents=True, exist_ok=True) | ||
| with open(filename, "w", encoding="utf-8") as fds: | ||
| fds.write(str(os.getpid())) | ||
| def pidname(name): | ||
| assert Workdir.wdr | ||
| return j(Workdir.wdr, f"{name}.pid") | ||
| def setwd(name, path=""): | ||
| path = path or os.path.expanduser(f"~/.{name}") | ||
| Workdir.wdr = Workdir.wdr or path | ||
| skel() | ||
| def skel(): | ||
| result = "" | ||
| if not os.path.exists(store()): | ||
| pth = pathlib.Path(store()) | ||
| pth.mkdir(parents=True, exist_ok=True) | ||
| pth = pathlib.Path(moddir()) | ||
| pth.mkdir(parents=True, exist_ok=True) | ||
| result = str(pth) | ||
| return result | ||
| def store(pth=""): | ||
| assert Workdir.wdr | ||
| return j(Workdir.wdr, "store", pth) | ||
| def strip(pth, nmr=2): | ||
| return j(pth.split(os.sep)[-nmr:]) | ||
| def types(): | ||
| skel() | ||
| return os.listdir(store()) | ||
| def __dir__(): | ||
| return ( | ||
| 'Workdir', | ||
| 'cdir', | ||
| 'fqn', | ||
| 'getpath', | ||
| 'ident', | ||
| 'j', | ||
| 'long', | ||
| 'moddir', | ||
| 'pidfile', | ||
| 'pidname', | ||
| 'setwd', | ||
| 'skel', | ||
| 'store', | ||
| 'strip', | ||
| 'types' | ||
| ) |
Sorry, the diff of this file is not supported yet
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
71452
-3.08%1979
-1.35%