New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

rssbot

Package Overview
Dependencies
Maintainers
1
Versions
62
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rssbot - pypi Package Compare versions

Comparing version
651
to
652
bin/rssbot

Sorry, the diff of this file is not supported yet

+234
# This file is placed in the Public Domain.
"commands"
import hashlib
import importlib
import importlib.util
import inspect
import logging
import os
import sys
import threading
import _thread
from .methods import j, rlog, spl
from .handler import Fleet
lock = threading.RLock()
class Commands:
cmds = {}
debug = False
md5s = {}
mod = j(os.path.dirname(__file__), "modules")
package = __name__.split(".")[0] + "." + "modules"
names = {}
@staticmethod
def add(func) -> None:
name = func.__name__
modname = func.__module__.split(".")[-1]
Commands.cmds[name] = func
Commands.names[name] = modname
@staticmethod
def get(cmd):
func = Commands.cmds.get(cmd, None)
if func:
return func
name = Commands.names.get(cmd, None)
if not name:
return
module = getmod(name)
if not module:
return
scan(module)
if Commands.debug:
module.DEBUG = True
return Commands.cmds.get(cmd, None)
def command(evt):
parse(evt)
func = Commands.get(evt.cmd)
if func:
func(evt)
Fleet.display(evt)
evt.ready()
"modules"
def getmod(name, path=None):
with lock:
mname = Commands.package + "." + name
module = sys.modules.get(mname, None)
if module:
return module
if not path:
path = Commands.mod
pth = j(path, f"{name}.py")
if not os.path.exists(pth):
return
if name != "tbl" and md5sum(pth) != Commands.md5s.get(name, None):
rlog("warn", f"md5 error on {pth.split(os.sep)[-1]}")
return importer(mname, pth)
def modules():
if not os.path.exists(Commands.mod):
return {}
return {
x[:-3] for x in os.listdir(Commands.mod)
if x.endswith(".py") and not x.startswith("__")
}
def scan(module):
for key, cmdz in inspect.getmembers(module, inspect.isfunction):
if key.startswith("cb"):
continue
if 'event' in inspect.signature(cmdz).parameters:
Commands.add(cmdz)
def scanner(names=None):
res = []
for nme in sorted(modules()):
if names and nme not in spl(names):
continue
module = getmod(nme)
if not module:
continue
scan(module)
res.append(module)
return res
def table(checksum=""):
pth = j(Commands.mod, "tbl.py")
if os.path.exists(pth):
if checksum and md5sum(pth) != checksum:
rlog("warn", "table checksum error.")
tbl = getmod("tbl")
if tbl:
if "NAMES" in dir(tbl):
Commands.names.update(tbl.NAMES)
if "MD5" in dir(tbl):
Commands.md5s.update(tbl.MD5)
else:
scanner()
"utilities"
def importer(name, pth):
try:
spec = importlib.util.spec_from_file_location(name, pth)
if not spec:
rlog("info", f"misiing {pth}")
return
module = importlib.util.module_from_spec(spec)
if not module:
rlog("info", f"{pth} not importable")
return
sys.modules[name] = module
spec.loader.exec_module(module)
rlog("info", f"load {pth}")
return module
except Exception as ex:
logging.exception(ex)
_thread.interrupt_main()
def md5sum(path):
with open(path, "r", encoding="utf-8") as file:
txt = file.read().encode("utf-8")
return hashlib.md5(txt).hexdigest()
def parse(obj, txt=None):
if txt is None:
if "txt" in dir(obj):
txt = obj.txt
else:
txt = ""
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():
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)
obj.silent[key] = value
obj.gets[key] = value
continue
if "==" in spli:
key, value = spli.split("==", maxsplit=1)
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
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 ""
"interface"
def __dir__():
return (
'Commands',
'command',
'getmod',
'importer',
'md5sum',
'modules',
'parse',
'scan',
'scanner',
'table'
)
# This file is placed in the Public Domain.
"event handler"
import queue
import threading
import time
import _thread
from .methods import fqn
from .runtime import launch
class Handler:
def __init__(self):
self.cbs = {}
self.type = fqn(self)
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, name=event.txt and event.txt.split()[0])
else:
event.ready()
def loop(self):
while not self.stopped.is_set():
try:
event = self.poll()
if event is None or self.stopped.is_set():
break
event.orig = repr(self)
self.callback(event)
except (KeyboardInterrupt, EOFError):
_thread.interrupt_main()
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
"clients"
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)
class Output(Client):
def __init__(self):
Client.__init__(self)
self.olock = threading.RLock()
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, daemon=True):
self.ostop.clear()
launch(self.output, daemon=daemon)
super().start()
def stop(self):
self.ostop.set()
self.oqueue.put(None)
super().stop()
def wait(self):
try:
self.oqueue.join()
except Exception:
_thread.interrupt_main()
"list of clients"
class Fleet:
clients = {}
@staticmethod
def add(client):
Fleet.clients[repr(client)] = client
@staticmethod
def all():
return list(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()
"event"
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 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):
try:
self._ready.wait()
if self._thr:
self._thr.join()
except (KeyboardInterrupt, EOFError):
_thread.interrupt_main()
"interface"
def __dir__():
return (
'Client',
'Event',
'Fleet',
'Handler'
'Output'
)
# This file is placed in the Public Domain.
"methods"
import hashlib
import importlib
import importlib.util
import logging
import os
import sys
import time
import _thread
from .objects import items, keys
j = os.path.join
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, newline=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} "
if newline:
txt += "\n"
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 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
"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 extract_date(daystr):
daystr = daystr.encode('utf-8', 'replace').decode("utf-8")
res = time.time()
for format in FORMATS:
try:
res = time.mktime(time.strptime(daystr, format))
break
except ValueError:
pass
return res
def level(loglevel="debug"):
if loglevel != "none":
format_short = "%(asctime)-8s %(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 spl(txt):
try:
result = txt.split(",")
except (TypeError, ValueError):
result = [
txt,
]
return [x for x in result if x]
"data"
FORMATS = [
"%Y-%M-%D %H:%M:%S",
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%d",
"%d-%m-%Y",
"%d-%m",
"%m-%d",
]
LEVELS = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'warn': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL,
}
"interface"
def __dir__():
return (
'edit',
'elapsed',
'extract_date',
'fmt',
'fqn',
'j',
'level',
'rlog',
'search',
'spl'
)
# This file is placed in the Public Domain.
NAMES = {
"cfg": "irc",
"dpl": "rss",
"exp": "rss",
"imp": "rss",
"mre": "irc",
"nme": "rss",
"pwd": "irc",
"rem": "rss",
"res": "rss",
"rss": "rss",
"syn": "rss"
}
MD5 = {
"irc": "fc8ddf8baeadfe46b1bed8bf47efecab",
"rss": "b60c574764b55d7c766cd56122946904",
"tbl": "d41d8cd98f00b204e9800998ecf8427e",
}
# 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, empty=True):
for key, value in items(data):
if not empty and not value:
continue
setattr(obj, key, value)
def values(obj):
if isinstance(obj, dict):
return obj.values()
return obj.__dict__.values()
"decoder/encoder"
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 (
'Object',
'construct',
'dump',
'dumps',
'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 .methods import fqn, j, search
from .objects import Object, 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=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
"paths"
class Workdir:
name = __file__.rsplit(os.sep, maxsplit=2)[-2]
wdr = os.path.expanduser(f"~/.{name}")
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():
return j(Workdir.wdr, "mods")
def pidname(name):
return j(Workdir.wdr, f"{name}.pid")
def setwd(name, path=""):
path = path or os.path.expanduser(f"~/.{name}")
Workdir.wdr = path
skel()
def skel():
if os.path.exists(store()):
return
pth = pathlib.Path(store())
pth.mkdir(parents=True, exist_ok=True)
pth = pathlib.Path(moddir())
pth.mkdir(parents=True, exist_ok=True)
return str(pth)
def store(pth=""):
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 wdr(pth):
return j(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 = j(rootdir, dname)
for fll in os.listdir(ddd):
yield j(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
"interface"
def __dir__():
return (
'Cache',
'Workdir',
'cdir',
'find',
'fntime',
'last',
'long',
'pidname',
'read',
'setwd',
'store',
'strip',
'types',
'write'
)
# This file is placed in the Public Domain.
"runtime"
import logging
import queue
import threading
import time
import _thread
class Thread(threading.Thread):
def __init__(self, func, *args, daemon=True, **kwargs):
super().__init__(None, self.run, None, (), daemon=daemon)
self.name = 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):
try:
super().join(timeout)
return self.result
except (KeyboardInterrupt, EOFError):
_thread.interrupt_main()
"timer/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()
"utilities"
def launch(func, *args, **kwargs):
thread = Thread(func, *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 ""
"interface"
def __dir__():
return (
'Pool',
'Repeater',
'Thread',
'Timed',
'launch',
'name'
)
# This file is placed in the Public Domain.
import setuptools
if __name__ == "__main__":
setuptools.setup(scripts=["bin/rssbot"])
# This file is placed in the Public Domain.
"engine"
import unittest
class TestNone(unittest.TestCase):
def testnone(self):
self.assertTrue(True)
+1
-1
Metadata-Version: 2.4
Name: rssbot
Version: 651
Version: 652
Summary: 24/7 Feed Fetcher.

@@ -5,0 +5,0 @@ Author-email: Bart Thate <rssbotd@gmail.com>

@@ -12,3 +12,3 @@ [build-system]

description = "24/7 Feed Fetcher."
version = "651"
version = "652"
authors = [

@@ -27,6 +27,2 @@ {name = "Bart Thate",email = "rssbotd@gmail.com"},

[project.scripts]
"rssbot" = "rssbot.__main__:main"
[project.urls]

@@ -33,0 +29,0 @@ "home" = "https://pypi.org/project/rssbot"

Metadata-Version: 2.4
Name: rssbot
Version: 651
Version: 652
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/auto.py
rssbot/client.py
rssbot/cmnd.py
rssbot/disk.py
rssbot/engine.py
rssbot/event.py
rssbot/find.py
rssbot/fleet.py
rssbot/method.py
rssbot/object.py
rssbot/output.py
rssbot/parse.py
rssbot/paths.py
rssbot/pool.py
rssbot/serial.py
rssbot/thread.py
rssbot/timer.py
rssbot/utils.py
setup.py
bin/rssbot
rssbot/command.py
rssbot/handler.py
rssbot/methods.py
rssbot/objects.py
rssbot/persist.py
rssbot/runtime.py
rssbot.egg-info/PKG-INFO
rssbot.egg-info/SOURCES.txt
rssbot.egg-info/dependency_links.txt
rssbot.egg-info/entry_points.txt
rssbot.egg-info/top_level.txt
rssbot/modules/__init__.py
rssbot/modules/cmd.py
rssbot/modules/dbg.py
rssbot/modules/fnd.py
rssbot/modules/irc.py
rssbot/modules/log.py
rssbot/modules/lst.py
rssbot/modules/rss.py
rssbot/modules/srv.py
rssbot/modules/tdo.py
rssbot/modules/thr.py
rssbot/modules/tbl.py
tests/test_none.py

@@ -16,26 +16,19 @@ # This file is placed in the Public Domain.

from ..auto import Auto
from ..cmnd import command
from ..disk import write
from ..event import Event as IEvent
from ..find import last
from ..fleet import Fleet
from ..method import edit, fmt
from ..object import Object, keys
from ..output import Output
from ..paths import getpath, ident
from ..thread import launch
from ..utils import rlog
from rssbot.command import command
from rssbot.handler import Event as IEvent
from rssbot.handler import Fleet, Output
from rssbot.methods import edit, fmt, rlog
from rssbot.objects import Object, keys
from rssbot.persist import Workdir, getpath, last, write
from rssbot.runtime import launch
IGNORE = ["PING", "PONG", "PRIVMSG"]
NAME = Workdir.name
initlock = threading.RLock()
saylock = threading.RLock()
saylock = threading.RLock()
"init"
def init():

@@ -47,3 +40,3 @@ with initlock:

if irc.events.joined.is_set():
rlog("debug", fmt(irc.cfg, skip=["password", "realname", "username"]))
rlog("warn", f"irc {fmt(irc.cfg, skip=["password", "realname", "username"])} channels {",".join(irc.channels)}")
else:

@@ -54,19 +47,11 @@ irc.stop()

"config"
class Config:
class Main:
name = Auto.__module__.split(".")[-2]
class Config(Auto):
channel = f"#{Main.name}"
channel = f"#{NAME}"
commands = False
control = "!"
nick = Main.name
nick = NAME
password = ""
port = 6667
realname = Main.name
realname = NAME
sasl = False

@@ -76,7 +61,6 @@ server = "localhost"

sleep = 60
username = Main.name
username = NAME
users = False
def __init__(self):
Auto.__init__(self)
self.channel = Config.channel

@@ -91,5 +75,2 @@ self.commands = Config.commands

"event"
class Event(IEvent):

@@ -110,5 +91,2 @@

"wrapper"
class TextWrap(textwrap.TextWrapper):

@@ -129,5 +107,2 @@

"IRC"
class IRC(Output):

@@ -138,3 +113,3 @@

self.buffer = []
self.cache = Auto()
self.cache = {}
self.cfg = Config()

@@ -148,3 +123,2 @@ self.channels = []

self.events.ready = threading.Event()
self.idents = []
self.sock = None

@@ -173,3 +147,2 @@ self.state = Object()

self.register("366", cb_ready)
self.ident = ident(self)

@@ -309,5 +282,5 @@ def announce(self, txt):

def extend(self, channel, txtlist):
if channel not in dir(self.cache):
setattr(self.cache, channel, [])
chanlist = getattr(self.cache, channel)
if channel not in self.cache:
self.cache[channel] = []
chanlist = self.cache.get(channel)
chanlist.extend(txtlist)

@@ -318,3 +291,3 @@

try:
che = getattr(self.cache, channel, None)
che = self.cache.get(channel, None)
if che:

@@ -351,4 +324,4 @@ txt = che.pop(0)

def oput(self, event):
if event.channel and event.channel not in dir(self.cache):
setattr(self.cache, event.channel, [])
if event.channel and event.channel not in self.cache:
self.cache[event.channel] = []
self.oqueue.put_nowait(event)

@@ -488,4 +461,4 @@

def size(self, chan):
if chan in dir(self.cache):
return len(getattr(self.cache, chan, []))
if chan in self.cache:
return len(self.cache.get(chan, []))
return 0

@@ -594,3 +567,3 @@

if evt.txt.startswith("VERSION"):
txt = f"\001VERSION {Main.name.upper()} 140 - {bot.cfg.username}\001"
txt = f"\001VERSION {NAME.upper()} 140 - {bot.cfg.username}\001"
bot.docommand("NOTICE", evt.channel, txt)

@@ -654,3 +627,3 @@

return
if event.channel not in dir(bot.cache):
if event.channel not in bot.cache:
event.reply(f"no output in {event.channel} cache.")

@@ -657,0 +630,0 @@ return

@@ -24,14 +24,17 @@ # This file is placed in the Public Domain.

from ..auto import Auto
from ..disk import write
from ..fleet import Fleet
from ..find import find, fntime, last
from ..method import fmt
from ..object import Object, update
from ..paths import getpath
from ..thread import launch
from ..timer import Repeater
from ..utils import elapsed, rlog, spl
from rssbot.methods import elapsed, fmt, rlog, spl
from rssbot.handler import Fleet
from rssbot.objects import Object, update
from rssbot.persist import find, fntime, getpath, last, write
from rssbot.runtime import Repeater, launch
def init():
fetcher = Fetcher()
fetcher.start()
if fetcher.seenfn:
rlog("warn", f"rss since {elapsed(time.time()-fntime(fetcher.seenfn))}")
return fetcher
DEBUG = False

@@ -42,29 +45,16 @@

importlock = _thread.allocate_lock()
errors = []
errors = {}
skipped = []
"init"
class Feed:
def init():
fetcher = Fetcher()
fetcher.start()
return fetcher
"classes"
class Feed(Auto):
def __init__(self):
Auto.__init__(self)
self.link = ""
self.name = ""
class Rss(Auto):
class Rss:
def __init__(self):
Auto.__init__(self)
self.display_list = "title,link,author"

@@ -76,3 +66,3 @@ self.insertid = None

class Urls(Auto):
class Urls:

@@ -82,5 +72,2 @@ pass

"fetcher"
class Fetcher(Object):

@@ -134,3 +121,3 @@

if self.dosave:
write(fed, getpath(fed))
write(fed)
result.append(fed)

@@ -166,5 +153,2 @@ setattr(self.seen, feed.rss, urls)

"parser"
class Parser:

@@ -221,3 +205,3 @@

"opml"
"OPML"

@@ -307,3 +291,3 @@

result = [Object(), Object()]
if DEBUG or url in errors:
if DEBUG or url in errors and (time.time() - errors[url]) < 600:
return result

@@ -314,3 +298,3 @@ try:

rlog("error", f"{url} {ex}")
errors.append(url)
errors[url] = time.time()
return result

@@ -381,6 +365,6 @@ if rest:

setter = {"display_list": event.args[1]}
for fnm, rss in find("rss", {"rss": event.args[0]}):
if rss:
update(rss, setter)
write(rss, fnm)
for fnm, feed in find("rss", {"rss": event.args[0]}):
if feed:
update(feed, setter)
write(feed, fnm)
event.done()

@@ -431,7 +415,7 @@

continue
rss = Rss()
update(rss, obj)
rss.rss = obj.xmlUrl
rss.insertid = insertid
write(rss, getpath(rss))
feed = Rss()
update(feed, obj)
feed.rss = obj.xmlUrl
feed.insertid = insertid
write(feed)
nrs += 1

@@ -449,5 +433,5 @@ if nrskip:

selector = {"rss": event.args[0]}
for fnm, rss in find("rss", selector):
for fnm, fed in find("rss", selector):
feed = Rss()
update(feed, rss)
update(feed, fed)
if feed:

@@ -463,5 +447,5 @@ feed.name = str(event.args[1])

return
for fnm, rss in find("rss"):
feed = Auto()
update(feed, rss)
for fnm, fed in find("rss"):
feed = Rss()
update(feed, fed)
if event.args[0] not in feed.rss:

@@ -471,3 +455,3 @@ continue

feed.__deleted__ = True
write(feed, fnm)
write(feed)
event.done()

@@ -481,5 +465,5 @@ break

return
for fnm, rss in find("rss", deleted=True):
feed = Auto()
update(feed, rss)
for fnm, fed in find("rss", deleted=True):
feed = Rss()
update(feed, fed)
if event.args[0] not in feed.rss:

@@ -496,6 +480,6 @@ continue

nrs = 0
for fnm, rss in find("rss"):
for fnm, fed in find("rss"):
nrs += 1
elp = elapsed(time.time() - fntime(fnm))
txt = fmt(rss)
txt = fmt(fed)
event.reply(f"{nrs} {txt} {elp}")

@@ -513,5 +497,5 @@ if not nrs:

return
rss = Rss()
rss.rss = event.args[0]
write(rss, getpath(rss))
feed = Rss()
feed.rss = event.args[0]
write(feed)
event.done()

@@ -533,5 +517,2 @@

"data"
TEMPLATE = """<opml version="1.0">

@@ -538,0 +519,0 @@ <head>

[console_scripts]
rssbot = rssbot.__main__:main
# This file is placed in the Public Domain.
__doc__ = __name__.upper()
# This file is placed in the Public Domain.
"main"
import os
import pathlib
import sys
import time
from .auto import Auto
from .client import Client
from .cmnd import Commands, command, scan
from .event import Event
from .parse import parse
from .paths import pidname, setwd
from .thread import launch
from .utils import level, spl
from . import modules as MODS
class Main(Auto):
init = ""
level = "warn"
name = Auto.__module__.split(".")[-2]
opts = Auto()
verbose = False
version = 651
class CLI(Client):
def __init__(self):
Client.__init__(self)
self.register("command", command)
def raw(self, txt):
out(txt.encode('utf-8', 'replace').decode("utf-8"))
class Console(CLI):
def announce(self, txt):
pass
def callback(self, event):
super().callback(event)
event.wait()
def poll(self):
evt = Event()
evt.txt = input("> ")
evt.type = "command"
return evt
"daemon"
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 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)
"commands"
def ver(event):
event.reply(f"{Main.name.upper()} {Main.version}")
"utilities"
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))}")
def check(txt):
args = sys.argv[1:]
for arg in args:
if not arg.startswith("-"):
continue
for char in txt:
if char in arg:
return True
return False
def forever():
while True:
try:
time.sleep(0.1)
except (KeyboardInterrupt, EOFError):
print("")
sys.exit(1)
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 out(txt):
print(txt)
sys.stdout.flush()
"scripts"
def background():
daemon("-v" in sys.argv)
privileges()
level(Main.level or "debug")
setwd(Main.name)
pidfile(pidname(Main.name))
Commands.add(ver)
scan(MODS)
inits(MODS, Main.init or "irc,rss")
forever()
def console():
import readline # noqa: F401
parse(Main, " ".join(sys.argv[1:]))
Main.init = Main.sets.init or Main.init
Main.verbose = Main.sets.verbose or Main.verbose
Main.level = Main.sets.level or Main.level or "warn"
level(Main.level)
setwd(Main.name)
Commands.add(ver)
scan(MODS)
if "v" in Main.opts:
banner(MODS)
for _mod, thr in inits(MODS, Main.init):
if "w" in Main.opts:
thr.join(30.0)
csl = Console()
csl.start()
forever()
def control():
if len(sys.argv) == 1:
return
parse(Main, " ".join(sys.argv[1:]))
level(Main.level or "warn")
setwd(Main.name)
Commands.scan(MODS.srv)
Commands.add(ver)
scan(MODS)
csl = CLI()
evt = Event()
evt.orig = repr(csl)
evt.type = "command"
evt.txt = Main.otxt
command(evt)
evt.wait()
def service():
level(Main.level or "warn")
setwd(Main.name)
banner(MODS)
privileges()
pidfile(pidname(Main.name))
Commands.add(ver)
scan(MODS)
inits(MODS, Main.init or "irc,rss")
forever()
"runtime"
def wrapped(func):
try:
func()
except (KeyboardInterrupt, EOFError):
out("")
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 main():
if check("a"):
Main.init = ",".join(dir(MODS))
if check("v"):
setattr(Main.opts, "v", True)
if check("c"):
wrap(console)
elif check("d"):
background()
elif check("s"):
wrapped(service)
else:
wrapped(control)
if __name__ == "__main__":
main()
# This file is placed in the Public Domain.
"auto construct"
from .object import Object
class Auto(Object):
def __getattr__(self, key):
if key not in self:
setattr(self, key, "")
return self.__dict__.get(key, "")
def __dir__():
return (
'Auto',
)
# 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',
)
# This file is placed in the Public Domain.
"command"
import inspect
from .fleet import Fleet
from .parse import parse
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 func:
func(evt)
Fleet.display(evt)
evt.ready()
def scan(pkg):
mods = []
for modname in dir(pkg):
mod = getattr(pkg, modname)
Commands.scan(mod)
mods.append(mod)
return mods
def __dir__():
return (
'Commands',
'command',
'scan'
)
# This file is placed in the Public Domain.
"disk"
import json
import json.decoder
import pathlib
import threading
from .object import update
from .serial import dump, load
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)
Cache.update(path, obj)
return path
def __dir__():
return (
'Cache',
'cdir',
'read',
'write'
)
# This file is placed in the Public Domain.
"callback engine"
import queue
import threading
from .thread import launch
class Engine:
def __init__(self):
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 .auto import Auto
class Event(Auto):
def __init__(self):
Auto.__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.
"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 placed in the Public Domain.
"modules"
from . import cmd, lst, thr
from . import irc, rss
from . import dbg, srv # noqa: F401
from . import fnd
__all__ = (
"cmd",
"fnd",
"irc",
"lst",
"rss",
"thr"
)
def __dir__():
return __all__
# This file is been placed in the Public Domain.
"available commands"
from ..cmnd import Commands
def cmd(event):
event.reply(",".join(sorted(Commands.names)))
# This file is placed in the Public Domain.
"debug"
import time
from ..client 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.
"find"
import time
from ..find import find, fntime
from ..method import fmt
from ..paths import long, skel, types
from ..utils import elapsed
def fnd(event):
skel()
if not event.rest:
res = sorted([x.split('.')[-1].lower() for x in types()])
if res:
event.reply(",".join(res))
return
otype = event.args[0]
clz = long(otype)
nmr = 0
for fnm, obj in list(find(clz, event.gets)):
event.reply(f"{nmr} {fmt(obj)} {elapsed(time.time()-fntime(fnm))}")
nmr += 1
if not nmr:
event.reply("no result")
# 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 ..auto import Auto
class Main:
name = Auto.__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()
# This file is placed in the Public Domain.
"running threads"
import threading
import time
from ..utils import elapsed
STARTTIME = time.time()
def thr(event):
result = []
for thread in sorted(threading.enumerate(), key=lambda x: x.name):
if str(thread).startswith("<_"):
continue
if getattr(thread, "state", None) and getattr(thread, "sleep", None):
uptime = thread.sleep - int(time.time() - thread.state["latest"])
elif getattr(thread, "starttime", None):
uptime = int(time.time() - thread.starttime)
else:
uptime = int(time.time() - STARTTIME)
result.append((uptime, thread.name))
res = []
for uptime, txt in sorted(result, key=lambda x: x[0]):
lap = elapsed(uptime)
res.append(f"{txt}/{lap}")
if res:
event.reply(" ".join(res))
else:
event.reply("no threads")
# This file is placed in the Public Domain.
"a clean namespace"
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()
def __dir__():
return (
'Object',
'construct',
'items',
'keys',
'update',
'values'
)
# This file is placed in the Public Domain.
"output"
import queue
import threading
from .client import Client
from .thread import launch
class Output(Client):
def __init__(self):
Client.__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)
super().start()
def stop(self):
self.ostop.set()
self.oqueue.put(None)
super.stop()
def wait(self):
self.oqueue.join()
def __dir__():
return (
'Output',
)
# This file is placed in the Public Domain.
"argparse"
from .auto import Auto
def parse(obj, txt=""):
if txt == "":
if "txt" in dir(obj):
txt = obj.txt
else:
txt = ""
args = []
obj.args = []
obj.cmd = ""
obj.gets = Auto()
obj.index = None
obj.mod = ""
obj.opts = ""
obj.result = {}
obj.sets = Auto()
obj.silent = Auto()
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 (
'parse',
)
# 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',
'strip',
'wdr'
)
# This file is placed in the Public Domain.
"client pool"
import os
import threading
from .fleet import Fleet
from .output import Output
class Pool:
clients = []
lock = threading.RLock()
nrcpu = os.cpu_count()
nrlast = 0
@staticmethod
def put(evt):
with Pool.lock:
if not Pool.clients:
for task in range(Pool.nrcpu):
clt = Output()
clt.start()
Pool.clients = list(Fleet.all())
if Pool.nrlast >= Pool.nrcpu:
Pool.nrlast = 0
print(Pool.clients)
Pool.clients[Pool.nrlast].put(evt)
Pool.nrlast += 1
def __dir__():
return (
'Pool',
)
# This file is placed in the Public Domain.
"decoder/encoder"
import json
from .object import Object, construct
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)
def __dir__():
return (
'dump',
'dumps',
'load',
'loads'
)
# 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:
thread = Thread(func, None, *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"
import logging
LEVELS = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'warn': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL,
}
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 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 spl(txt):
try:
result = txt.split(",")
except (TypeError, ValueError):
result = [
txt,
]
return [x for x in result if x]
def __dir__():
return (
'elapsed',
'level',
'rlog',
'spl'
)