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
647
to
650
+11
rssbot/__init__.py
# This file is placed in the Public Domain.
__doc__ = __name__.upper()
from .object import * # noqa: F403
from .object import __dir__
__all__ = __dir__()
# This file is placed in the Public Domain.
"client"
import threading
from .fleet import Fleet
from .engine import Engine
class Client(Engine):
def __init__(self):
Engine.__init__(self)
self.olock = threading.RLock()
Fleet.add(self)
def announce(self, txt):
pass
def display(self, event):
with self.olock:
for tme in sorted(event.result):
self.dosay(event.channel, event.result[tme])
def dosay(self, channel, txt):
self.say(channel, txt)
def raw(self, txt):
raise NotImplementedError("raw")
def say(self, channel, txt):
self.raw(txt)
def __dir__():
return (
"Client",
)
# This file is placed in the Public Domain.
"command"
import inspect
from .fleet import Fleet
from .object import Default
from .thread import launch
from .utils import spl
class Commands:
cmds = {}
names = {}
@staticmethod
def add(func, mod=None):
Commands.cmds[func.__name__] = func
if mod:
Commands.names[func.__name__] = mod.__name__.split(".")[-1]
@staticmethod
def get(cmd):
return Commands.cmds.get(cmd, None)
@staticmethod
def scan(mod):
for key, cmdz in inspect.getmembers(mod, inspect.isfunction):
if key.startswith("cb"):
continue
if "event" in cmdz.__code__.co_varnames:
Commands.add(cmdz, mod)
def command(evt):
parse(evt)
func = Commands.get(evt.cmd)
if not func:
evt.ready()
return
func(evt)
Fleet.display(evt)
evt.ready()
def inits(pkg, names):
modz = []
for name in sorted(spl(names)):
mod = getattr(pkg, name, None)
if not mod:
continue
if "init" in dir(mod):
thr = launch(mod.init)
modz.append((mod, thr))
return modz
def scan(pkg):
mods = []
for modname in dir(pkg):
mod = getattr(pkg, modname)
Commands.scan(mod)
mods.append(mod)
return mods
def parse(obj, txt=""):
if txt == "":
if "txt" in dir(obj):
txt = obj.txt
else:
txt = ""
args = []
obj.args = []
obj.cmd = ""
obj.gets = Default()
obj.index = None
obj.mod = ""
obj.opts = ""
obj.result = {}
obj.sets = Default()
obj.silent = Default()
obj.txt = txt
obj.otxt = obj.txt
_nr = -1
for spli in obj.otxt.split():
if spli.startswith("-"):
try:
obj.index = int(spli[1:])
except ValueError:
obj.opts += spli[1:]
continue
if "-=" in spli:
key, value = spli.split("-=", maxsplit=1)
setattr(obj.silent, key, value)
setattr(obj.gets, key, value)
continue
if "==" in spli:
key, value = spli.split("==", maxsplit=1)
setattr(obj.gets, key, value)
continue
if "=" in spli:
key, value = spli.split("=", maxsplit=1)
if key == "mod":
if obj.mod:
obj.mod += f",{value}"
else:
obj.mod = value
continue
setattr(obj.sets, key, value)
continue
_nr += 1
if _nr == 0:
obj.cmd = spli
continue
args.append(spli)
if args:
obj.args = args
obj.txt = obj.cmd or ""
obj.rest = " ".join(obj.args)
obj.txt = obj.cmd + " " + obj.rest
else:
obj.txt = obj.cmd or ""
def __dir__():
return (
'Commands',
'command',
'parse',
'scan'
)
# This file is placed in the Public Domain.
"disk"
import json
import json.decoder
import pathlib
import threading
from .object import dump, load, update
lock = threading.RLock()
class Cache:
objs = {}
@staticmethod
def add(path, obj):
Cache.objs[path] = obj
@staticmethod
def get(path):
return Cache.objs.get(path, None)
@staticmethod
def update(path, obj):
if not obj:
return
if path in Cache.objs:
update(Cache.objs[path], obj)
else:
Cache.add(path, obj)
def cdir(path):
pth = pathlib.Path(path)
pth.parent.mkdir(parents=True, exist_ok=True)
def read(obj, path):
with lock:
with open(path, "r", encoding="utf-8") as fpt:
try:
update(obj, load(fpt))
except json.decoder.JSONDecodeError as ex:
ex.add_note(path)
raise ex
def write(obj, path):
with lock:
cdir(path)
with open(path, "w", encoding="utf-8") as fpt:
dump(obj, fpt, indent=4)
return path
def __dir__():
return (
"Cache",
"cdir",
"read",
"write"
)
# This file is placed in the Public Domain.
"callback engine"
import queue
import threading
import _thread
from .thread import launch
class Engine:
def __init__(self):
self.lock = _thread.allocate_lock()
self.cbs = {}
self.queue = queue.Queue()
self.ready = threading.Event()
self.stopped = threading.Event()
def available(self, event):
return event.type in self.cbs
def callback(self, event):
func = self.cbs.get(event.type, None)
if func:
event._thr = launch(func, event)
def loop(self):
while not self.stopped.is_set():
event = self.poll()
if event is None:
break
event.orig = repr(self)
self.callback(event)
def poll(self):
return self.queue.get()
def put(self, event):
self.queue.put(event)
def register(self, typ, cbs):
self.cbs[typ] = cbs
def start(self, daemon=True):
self.stopped.clear()
launch(self.loop, daemon=daemon)
def stop(self):
self.stopped.set()
self.queue.put(None)
def wait(self):
pass
def __dir__():
return (
"Engine",
)
# This file is placed in the Public Domain.
"event"
import threading
import time
from .object import Default
class Event(Default):
def __init__(self):
Default.__init__(self)
self._ready = threading.Event()
self._thr = None
self.ctime = time.time()
self.result = {}
self.type = "event"
def done(self):
self.reply("ok")
def ready(self):
self._ready.set()
def reply(self, txt):
self.result[time.time()] = txt
def wait(self, timeout=None):
self._ready.wait()
if self._thr:
self._thr.join()
def __dir__():
return (
"Event",
)
# This file is placed in the Public Domain.
"find"
import os
import time
from .disk import Cache, read
from .object import Object, items, update
from .paths import fqn, long, store
def find(clz, selector=None, deleted=False, matching=False):
clz = long(clz)
if selector is None:
selector = {}
for pth in fns(clz):
obj = Cache.get(pth)
if not obj:
obj = Object()
read(obj, pth)
Cache.add(pth, obj)
if not deleted and isdeleted(obj):
continue
if selector and not search(obj, selector, matching):
continue
yield pth, obj
def fns(clz):
pth = store(clz)
for rootdir, dirs, _files in os.walk(pth, topdown=False):
for dname in dirs:
ddd = os.path.join(rootdir, dname)
for fll in os.listdir(ddd):
yield os.path.join(ddd, fll)
def fntime(daystr):
datestr = " ".join(daystr.split(os.sep)[-2:])
datestr = datestr.replace("_", " ")
if "." in datestr:
datestr, rest = datestr.rsplit(".", 1)
else:
rest = ""
timed = time.mktime(time.strptime(datestr, "%Y-%m-%d %H:%M:%S"))
if rest:
timed += float("." + rest)
return float(timed)
def isdeleted(obj):
return "__deleted__" in dir(obj) and obj.__deleted__
def last(obj, selector=None):
if selector is None:
selector = {}
result = sorted(find(fqn(obj), selector), key=lambda x: fntime(x[0]))
res = ""
if result:
inp = result[-1]
update(obj, inp[-1])
res = inp[0]
return res
def search(obj, selector, matching=False):
res = False
if not selector:
return res
for key, value in items(selector):
val = getattr(obj, key, None)
if not val:
continue
if matching and value == val:
res = True
elif str(value).lower() in str(val).lower() or value == "match":
res = True
else:
res = False
break
return res
def __dir__():
return (
"find",
"fns",
"fntime",
"last",
"search"
)
# This file is placed in the Public Domain.
"fleet"
import time
class Fleet:
clients = {}
@staticmethod
def add(client):
Fleet.clients[repr(client)] = client
@staticmethod
def all():
return Fleet.clients.values()
@staticmethod
def announce(txt):
for client in Fleet.all():
client.announce(txt)
@staticmethod
def dispatch(evt):
client = Fleet.get(evt.orig)
client.put(evt)
@staticmethod
def display(evt):
client = Fleet.get(evt.orig)
client.display(evt)
@staticmethod
def first():
clt = list(Fleet.all())
res = None
if clt:
res = clt[0]
return res
@staticmethod
def get(orig):
return Fleet.clients.get(orig, None)
@staticmethod
def say(orig, channel, txt):
client = Fleet.get(orig)
if client:
client.say(channel, txt)
@staticmethod
def shutdown():
for client in Fleet.all():
client.stop()
@staticmethod
def wait():
time.sleep(0.1)
for client in Fleet.all():
client.wait()
def __dir__():
return (
"Fleet",
)
# This file is placed in the Public Domain.
"logging"
import logging
LEVELS = {
"debug": logging.DEBUG,
"info": logging.INFO,
"warning": logging.WARNING,
"warn": logging.WARNING,
"error": logging.ERROR,
"critical": logging.CRITICAL,
}
def level(loglevel="debug"):
if loglevel != "none":
format_short = "%(message)-80s"
datefmt = "%H:%M:%S"
logging.basicConfig(datefmt=datefmt, format=format_short, force=True)
logging.getLogger().setLevel(LEVELS.get(loglevel))
def rlog(loglevel, txt, ignore=None):
if ignore is None:
ignore = []
for ign in ignore:
if ign in str(txt):
return
logging.log(LEVELS.get(loglevel), txt)
def __dir__():
return (
"level",
"rlog"
)
# This file is placed in the Public Domain.
"methods"
from .object import items, keys
def edit(obj, setter, skip=True):
for key, val in items(setter):
if skip and val == "":
continue
try:
setattr(obj, key, int(val))
continue
except ValueError:
pass
try:
setattr(obj, key, float(val))
continue
except ValueError:
pass
if val in ["True", "true"]:
setattr(obj, key, True)
elif val in ["False", "false"]:
setattr(obj, key, False)
else:
setattr(obj, key, val)
def fmt(obj, args=None, skip=None, plain=False, empty=False):
if args is None:
args = keys(obj)
if skip is None:
skip = []
txt = ""
for key in args:
if key.startswith("__"):
continue
if key in skip:
continue
value = getattr(obj, key, None)
if value is None:
continue
if not empty and not value:
continue
if plain:
txt += f"{value} "
elif isinstance(value, str):
txt += f'{key}="{value}" '
else:
txt += f"{key}={value} "
return txt.strip()
def __dir__():
return (
"edit",
"fmt"
)
# This file is been placed in the Public Domain.
"available commands"
from ..cmnd import Commands
def cmd(event):
event.reply(",".join(sorted(Commands.cmds)))
# This file is placed in the Public Domain.
"log text"
import time
from ..disk import write
from ..find import find, fntime
from ..object import Object
from ..paths import ident, store
from ..utils import elapsed
class Log(Object):
def __init__(self):
super().__init__()
self.txt = ''
def log(event):
if not event.rest:
nmr = 0
for fnm, obj in find('log'):
lap = elapsed(time.time() - fntime(fnm))
event.reply(f'{nmr} {obj.txt} {lap}')
nmr += 1
if not nmr:
event.reply('no log')
return
obj = Log()
obj.txt = event.rest
write(obj, store(ident(obj)))
event.done()
# This file is been placed in the Public Domain.
"available types"
from ..paths import types
def ls(event):
event.reply(",".join([x.split(".")[-1].lower() for x in types()]))
# This file is placed in the Public Domain
"create service file"
from ..object import Default
class Main:
name = Default.__module__.split(".")[-2]
TXT = """[Unit]
Description=%s
After=network-online.target
[Service]
Type=simple
User=%s
Group=%s
ExecStart=/home/%s/.local/bin/%s -s
[Install]
WantedBy=multi-user.target"""
def srv(event):
import getpass
name = getpass.getuser()
event.reply(TXT % (Main.name.upper(), name, name, name, Main.name))
# This file is placed in the Public Domain.
"todo list"
import time
from ..disk import write
from ..find import find, fntime
from ..object import Object
from ..paths import ident, store
from ..utils import elapsed
class Todo(Object):
def __init__(self):
Object.__init__(self)
self.txt = ''
def dne(event):
if not event.args:
event.reply("dne <txt>")
return
selector = {'txt': event.args[0]}
nmr = 0
for fnm, obj in find('todo', selector):
nmr += 1
obj.__deleted__ = True
write(obj, fnm)
event.done()
break
if not nmr:
event.reply("nothing todo")
def tdo(event):
if not event.rest:
nmr = 0
for fnm, obj in find('todo'):
lap = elapsed(time.time()-fntime(fnm))
event.reply(f'{nmr} {obj.txt} {lap}')
nmr += 1
if not nmr:
event.reply("no todo")
return
obj = Todo()
obj.txt = event.rest
write(obj, store(ident(obj)))
event.done()
# This file is placed in the Public Domain.
"a clean namespace"
import json
class Object:
def __contains__(self, key):
return key in dir(self)
def __iter__(self):
return iter(self.__dict__)
def __len__(self):
return len(self.__dict__)
def __str__(self):
return str(self.__dict__)
def construct(obj, *args, **kwargs):
if args:
val = args[0]
if isinstance(val, zip):
update(obj, dict(val))
elif isinstance(val, dict):
update(obj, val)
elif isinstance(val, Object):
update(obj, vars(val))
if kwargs:
update(obj, kwargs)
def items(obj):
if isinstance(obj, dict):
return obj.items()
return obj.__dict__.items()
def keys(obj):
if isinstance(obj, dict):
return obj.keys()
return obj.__dict__.keys()
def update(obj, data):
if isinstance(data, dict):
return obj.__dict__.update(data)
obj.__dict__.update(vars(data))
def values(obj):
if isinstance(obj, dict):
return obj.values()
return obj.__dict__.values()
class Encoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, dict):
return o.items()
if issubclass(type(o), Object):
return vars(o)
if isinstance(o, list):
return iter(o)
try:
return json.JSONEncoder.default(self, o)
except TypeError:
try:
return vars(o)
except TypeError:
return repr(o)
def dump(obj, fp, *args, **kw):
kw["cls"] = Encoder
json.dump(obj, fp, *args, **kw)
def dumps(obj, *args, **kw):
kw["cls"] = Encoder
return json.dumps(obj, *args, **kw)
def hook(objdict):
obj = Object()
construct(obj, objdict)
return obj
def load(fp, *args, **kw):
kw["object_hook"] = hook
return json.load(fp, *args, **kw)
def loads(s, *args, **kw):
kw["object_hook"] = hook
return json.loads(s, *args, **kw)
class Default(Object):
def __getattr__(self, key):
if key not in self:
setattr(self, key, "")
return self.__dict__.get(key, "")
def __dir__():
return (
'Default',
'Object',
'construct',
'dump',
'dumps',
'items',
'keys',
'load',
'loads',
'update',
'values'
)
# This file is placed in the Public Domain.
"output"
import queue
import threading
from .thread import launch
class Output:
def __init__(self):
self.olock = threading.RLock()
self.oqueue = queue.Queue()
self.ostop = threading.Event()
def display(self, event):
with self.olock:
for tme in sorted(event.result):
self.dosay(event.channel, event.result[tme])
def dosay(self, channel, txt):
raise NotImplementedError("dosay")
def oput(self, event):
self.oqueue.put(event)
def output(self):
while not self.ostop.is_set():
event = self.oqueue.get()
if event is None:
self.oqueue.task_done()
break
self.display(event)
self.oqueue.task_done()
def start(self):
self.ostop.clear()
launch(self.output)
def stop(self):
self.ostop.set()
self.oqueue.put(None)
def wait(self):
self.oqueue.join()
def __dir__():
return (
"Output",
)
# This file is placed in the Public Domain.
"paths"
import datetime
import os
import pathlib
class Workdir:
name = __file__.rsplit(os.sep, maxsplit=2)[-2]
wdr = ""
def fqn(obj):
kin = str(type(obj)).split()[-1][1:-2]
if kin == "type":
kin = f"{obj.__module__}.{obj.__name__}"
return kin
def getpath(obj):
return store(ident(obj))
def ident(obj):
return os.path.join(fqn(obj), *str(datetime.datetime.now()).split())
def long(name):
split = name.split(".")[-1].lower()
res = name
for names in types():
if split == names.split(".")[-1].lower():
res = names
break
return res
def pidname(name):
return os.path.join(Workdir.wdr, f"{name}.pid")
def setwd(name, path=""):
path = path or os.path.expanduser(f"~/.{name}")
Workdir.wdr = path
skel()
def skel():
pth = pathlib.Path(store())
pth.mkdir(parents=True, exist_ok=True)
return str(pth)
def store(pth=""):
return os.path.join(Workdir.wdr, "store", pth)
def strip(pth, nmr=2):
return os.path.join(pth.split(os.sep)[-nmr:])
def types():
return os.listdir(store())
def wdr(pth):
return os.path.join(Workdir.wdr, pth)
def __dir__():
return (
"Workdir",
"fqn",
"getpath",
"long",
"ident",
"pidname",
"setwd",
"skel",
"store",
"wdr"
)
# This file is placed in the Public Domain.
"threads"
import logging
import queue
import time
import threading
import _thread
lock = threading.RLock()
class Thread(threading.Thread):
def __init__(self, func, thrname, *args, daemon=True, **kwargs):
super().__init__(None, self.run, thrname, (), daemon=daemon)
self.name = thrname or kwargs.get("name", name(func))
self.queue = queue.Queue()
self.result = None
self.starttime = time.time()
self.stopped = threading.Event()
self.queue.put((func, args))
def __iter__(self):
return self
def __next__(self):
yield from dir(self)
def run(self):
func, args = self.queue.get()
try:
self.result = func(*args)
except (KeyboardInterrupt, EOFError):
_thread.interrupt_main()
except Exception as ex:
logging.exception(ex)
_thread.interrupt_main()
def join(self, timeout=None):
super().join(timeout)
return self.result
def launch(func, *args, **kwargs):
with lock:
nme = kwargs.get("name", None)
if not nme:
nme = name(func)
thread = Thread(func, nme, *args, **kwargs)
thread.start()
return thread
def name(obj):
typ = type(obj)
if "__builtins__" in dir(typ):
return obj.__name__
if "__self__" in dir(obj):
return f"{obj.__self__.__class__.__name__}.{obj.__name__}"
if "__class__" in dir(obj) and "__name__" in dir(obj):
return f"{obj.__class__.__name__}.{obj.__name__}"
if "__class__" in dir(obj):
return f"{obj.__class__.__module__}.{obj.__class__.__name__}"
if "__name__" in dir(obj):
return f"{obj.__class__.__name__}.{obj.__name__}"
return ""
def __dir__():
return (
"Thread",
"launch",
"name"
)
# This file is placed in the Public Domain.
"timer/repeater"
import threading
import time
from .thread import launch, name
class Timy(threading.Timer):
def __init__(self, sleep, func, *args, **kwargs):
super().__init__(sleep, func)
self.name = kwargs.get("name", name(func))
self.sleep = sleep
self.state = {}
self.state["latest"] = time.time()
self.state["starttime"] = time.time()
self.starttime = time.time()
class Timed:
def __init__(self, sleep, func, *args, thrname="", **kwargs):
self.args = args
self.func = func
self.kwargs = kwargs
self.sleep = sleep
self.name = thrname or kwargs.get("name", name(func))
self.target = time.time() + self.sleep
self.timer = None
def run(self):
self.timer.latest = time.time()
self.func(*self.args)
def start(self):
self.kwargs["name"] = self.name
timer = Timy(self.sleep, self.run, *self.args, **self.kwargs)
timer.start()
self.timer = timer
def stop(self):
if self.timer:
self.timer.cancel()
class Repeater(Timed):
def run(self):
launch(self.start)
super().run()
def __dir__():
return (
"Repeater",
"Timed"
)
# This file is placed in the Public Domain.
"utilities"
def elapsed(seconds, short=True):
txt = ""
nsec = float(seconds)
if nsec < 1:
return f"{nsec:.2f}s"
yea = 365 * 24 * 60 * 60
week = 7 * 24 * 60 * 60
nday = 24 * 60 * 60
hour = 60 * 60
minute = 60
yeas = int(nsec / yea)
nsec -= yeas * yea
weeks = int(nsec / week)
nsec -= weeks * week
nrdays = int(nsec / nday)
nsec -= nrdays * nday
hours = int(nsec / hour)
nsec -= hours * hour
minutes = int(nsec / minute)
nsec -= int(minute * minutes)
sec = int(nsec)
if yeas:
txt += f"{yeas}y"
if weeks:
nrdays += weeks * 7
if nrdays:
txt += f"{nrdays}d"
if short and txt:
return txt.strip()
if hours:
txt += f"{hours}h"
if minutes:
txt += f"{minutes}m"
if sec:
txt += f"{sec}s"
txt = txt.strip()
return txt
def spl(txt):
try:
result = txt.split(",")
except (TypeError, ValueError):
result = [
txt,
]
return [x for x in result if x]
def __dir__():
return (
'elapsed',
'spl'
)
+1
-1
Metadata-Version: 2.4
Name: rssbot
Version: 647
Version: 650
Summary: 24/7 Feed Fetcher.

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

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

description = "24/7 Feed Fetcher."
version = "647"
version = "650"
authors = [

@@ -15,0 +15,0 @@ {name = "Bart Thate",email = "rssbotd@gmail.com"},

Metadata-Version: 2.4
Name: rssbot
Version: 647
Version: 650
Summary: 24/7 Feed Fetcher.

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

README.rst
pyproject.toml
rssbot/__init__.py
rssbot/__main__.py
rssbot/clients.py
rssbot/command.py
rssbot/handler.py
rssbot/objects.py
rssbot/persist.py
rssbot/runtime.py
rssbot/client.py
rssbot/cmnd.py
rssbot/disk.py
rssbot/engine.py
rssbot/event.py
rssbot/find.py
rssbot/fleet.py
rssbot/log.py
rssbot/method.py
rssbot/object.py
rssbot/output.py
rssbot/paths.py
rssbot/thread.py
rssbot/timer.py
rssbot/utils.py
rssbot.egg-info/PKG-INFO

@@ -16,6 +26,9 @@ rssbot.egg-info/SOURCES.txt

rssbot/modules/__init__.py
rssbot/modules/dbg.py
rssbot/modules/cmd.py
rssbot/modules/irc.py
rssbot/modules/log.py
rssbot/modules/lst.py
rssbot/modules/rss.py
rssbot/modules/thr.py
rssbot/modules/ver.py
rssbot/modules/srv.py
rssbot/modules/tdo.py
rssbot/modules/thr.py
# This file is placed in the Public Domain.
"main"
"main program"

@@ -13,24 +13,21 @@

from .clients import Client
from .command import Main, Commands
from .command import command, inits, parse, scan
from .handler import Event
from .persist import Workdir, pidname, skel, types
from .runtime import level
from .client import Client
from .cmnd import Commands, command, inits, parse, scan
from .event import Event
from .log import level
from .object import Default
from .paths import pidname, setwd
from . import modules as MODS
from . import modules as MODS
class Main(Default):
init = ""
level = "warn"
name = Default.__module__.split(".")[-2]
opts = Default()
verbose = False
version = 360
Main.name = Main.__module__.split(".")[0]
def out(txt):
print(txt)
sys.stdout.flush()
"clients"
class CLI(Client):

@@ -49,3 +46,2 @@

def announce(self, txt):
#out(txt)
pass

@@ -64,11 +60,29 @@

"utilities"
def banner():
def banner(mods):
tme = time.ctime(time.time()).replace(" ", " ")
out(f"{Main.name.upper()} {Main.version} since {tme} ({Main.level.upper()})")
out(f"loaded {".".join(dir(MODS))}")
out(f"loaded {".".join(dir(mods))}")
def forever():
while True:
try:
time.sleep(0.1)
except (KeyboardInterrupt, EOFError):
print("")
sys.exit(1)
def out(txt):
print(txt)
sys.stdout.flush()
def ver(event):
event.reply(f"{Main.name.upper()} {Main.version}")
"daemon"
def check(txt):

@@ -105,11 +119,2 @@ args = sys.argv[1:]

def forever():
while True:
try:
time.sleep(0.1)
except (KeyboardInterrupt, EOFError):
print("")
sys.exit(1)
def pidfile(filename):

@@ -132,26 +137,2 @@ if os.path.exists(filename):

def setwd(name, path=""):
Main.name = name
path = path or os.path.expanduser(f"~/.{name}")
Workdir.wdr = path
skel()
"commands"
def cmd(event):
event.reply(",".join(sorted([x for x in Commands.names if x not in Main.ignore])))
def ls(event):
event.reply(",".join([x.split(".")[-1].lower() for x in types()]))
def srv(event):
import getpass
name = getpass.getuser()
event.reply(TXT % (Main.name.upper(), name, name, name, Main.name))
"scripts"

@@ -166,3 +147,3 @@

pidfile(pidname(Main.name))
Commands.add(cmd)
Commands.add(ver)
scan(MODS)

@@ -181,7 +162,10 @@ inits(MODS, Main.init or "irc,rss")

setwd(Main.name)
Commands.add(cmd)
Commands.add(ls)
scan(MODS)
Commands.add(ver)
scan(MODS)
if "v" in Main.opts:
banner()
banner(MODS)
if "z" in Main.opts:
Commands.scan(MODS.log)
Commands.scan(MODS.tdo)
MODS.irc.Config.commands = True
for _mod, thr in inits(MODS, Main.init):

@@ -201,5 +185,4 @@ if "w" in Main.opts:

setwd(Main.name)
Commands.add(cmd)
Commands.add(ls)
Commands.add(srv)
Commands.scan(MODS.srv)
Commands.add(ver)
scan(MODS)

@@ -218,6 +201,6 @@ csl = CLI()

setwd(Main.name)
banner()
banner(MODS)
privileges()
pidfile(pidname(Main.name))
Commands.add(cmd)
Commands.add(ver)
scan(MODS)

@@ -228,19 +211,2 @@ inits(MODS, Main.init or "irc,rss")

"data"
TXT = """[Unit]
Description=%s
After=network-online.target
[Service]
Type=simple
User=%s
Group=%s
ExecStart=/home/%s/.local/bin/%s -s
[Install]
WantedBy=multi-user.target"""
"runtime"

@@ -269,2 +235,3 @@

def main():

@@ -271,0 +238,0 @@ if check("a"):

@@ -7,11 +7,15 @@ # This file is placed in the Public Domain.

from . import irc, rss, thr, ver
from . import cmd, lst, thr
from . import irc, rss
from . import srv # noqa: F401
from . import log, tdo
__all__= (
'irc',
'rss',
'thr',
'ver'
)
__all__ = (
"cmd",
"irc",
"lst",
"rss",
"thr"
)

@@ -18,0 +22,0 @@

@@ -8,3 +8,2 @@ # This file is placed in the Public Domain.

import base64
import queue
import os

@@ -18,8 +17,14 @@ import socket

from ..clients import Client, Fleet
from ..command import Main, command
from ..handler import Event as IEvent
from ..objects import Default, Object, edit, fmt, keys
from ..persist import getpath, ident, last, write
from ..runtime import launch, rlog
from ..client import Client
from ..cmnd import command
from ..disk import write
from ..event import Event as IEvent
from ..find import last
from ..fleet import Fleet
from ..log import rlog
from ..method import edit, fmt
from ..object import Default, Object, keys
from ..output import Output
from ..paths import getpath, ident
from ..thread import launch

@@ -52,7 +57,12 @@

class Main:
name = Default.__module__.split(".")[-2]
class Config(Default):
channel = f'#{Main.name}'
channel = f"#{Main.name}"
commands = False
control = '!'
control = "!"
nick = Main.name

@@ -63,4 +73,4 @@ password = ""

sasl = False
server = 'localhost'
servermodes = ''
server = "localhost"
servermodes = ""
sleep = 60

@@ -88,11 +98,11 @@ username = Main.name

super().__init__()
self.args = []
self.args = []
self.arguments = []
self.command = ""
self.channel = ""
self.nick = ""
self.origin = ""
self.rawstr = ""
self.rest = ""
self.txt = ""
self.command = ""
self.channel = ""
self.nick = ""
self.origin = ""
self.rawstr = ""
self.rest = ""
self.txt = ""

@@ -118,39 +128,5 @@

"output"
"IRC"
class Output:
def __init__(self):
self.oqueue = queue.Queue()
self.ostop = threading.Event()
def oput(self, event):
self.oqueue.put(event)
def output(self):
while not self.ostop.is_set():
event = self.oqueue.get()
if event is None:
self.oqueue.task_done()
break
self.display(event)
self.oqueue.task_done()
def start(self):
self.ostop.clear()
launch(self.output)
def stop(self):
self.ostop.set()
self.oqueue.put(None)
def wait(self):
self.oqueue.join()
super().wait()
"IRc"
class IRC(Output, Client):

@@ -162,3 +138,3 @@

self.buffer = []
self.cache = Object()
self.cache = Default()
self.cfg = Config()

@@ -183,14 +159,14 @@ self.channels = []

self.state.pongcheck = False
self.state.sleep = self.cfg.sleep
self.state.sleep = self.cfg.sleep
self.state.stopkeep = False
self.zelf = ''
self.register('903', cb_h903)
self.register('904', cb_h903)
self.register('AUTHENTICATE', cb_auth)
self.register('CAP', cb_cap)
self.register('ERROR', cb_error)
self.register('LOG', cb_log)
self.register('NOTICE', cb_notice)
self.register('PRIVMSG', cb_privmsg)
self.register('QUIT', cb_quit)
self.zelf = ""
self.register("903", cb_h903)
self.register("904", cb_h903)
self.register("AUTHENTICATE", cb_auth)
self.register("CAP", cb_cap)
self.register("ERROR", cb_error)
self.register("LOG", cb_log)
self.register("NOTICE", cb_notice)
self.register("PRIVMSG", cb_privmsg)
self.register("QUIT", cb_quit)
self.register("366", cb_ready)

@@ -217,3 +193,3 @@ self.ident = ident(self)

self.sock.connect((server, port))
self.direct('CAP LS 302')
self.direct("CAP LS 302")
else:

@@ -229,3 +205,6 @@ addr = socket.getaddrinfo(server, port, socket.AF_INET)[-1][-1]

self.events.connected.set()
rlog("debug", f"connected {self.cfg.server}:{self.cfg.port} {self.cfg.channel}")
rlog(
"debug",
f"connected {self.cfg.server}:{self.cfg.port} {self.cfg.channel}",
)
return True

@@ -242,7 +221,3 @@ return False

self.sock.shutdown(2)
except (
ssl.SSLError,
OSError,
BrokenPipeError
) as _ex:
except (ssl.SSLError, OSError, BrokenPipeError) as _ex:
pass

@@ -253,2 +228,4 @@

txt = evt.result.get(key)
if not txt:
continue
textlist = []

@@ -267,6 +244,3 @@ txtlist = wrapper.wrap(txt)

length = len(txtlist) - 3
self.say(
evt.channel,
f"use !mre to show more (+{length})"
)
self.say(evt.channel, f"use !mre to show more (+{length})")

@@ -278,8 +252,8 @@ def docommand(self, cmd, *args):

elif len(args) == 1:
self.raw(f'{cmd.upper()} {args[0]}')
self.raw(f"{cmd.upper()} {args[0]}")
elif len(args) == 2:
txt = ' '.join(args[1:])
self.raw(f'{cmd.upper()} {args[0]} :{txt}')
txt = " ".join(args[1:])
self.raw(f"{cmd.upper()} {args[0]} :{txt}")
elif len(args) >= 3:
txt = ' '.join(args[2:])
txt = " ".join(args[2:])
self.raw("{cmd.upper()} {args[0]} {args[1]} :{txt}")

@@ -301,8 +275,3 @@ if (time.time() - self.state.last) < 5.0:

break
except (
socket.timeout,
ssl.SSLError,
OSError,
ConnectionResetError
) as ex:
except (socket.timeout, ssl.SSLError, OSError, ConnectionResetError) as ex:
self.events.joined.set()

@@ -315,5 +284,5 @@ self.state.error = str(ex)

self.events.joined.wait()
txt = str(txt).replace('\n', '')
txt = txt.replace(' ', ' ')
self.docommand('PRIVMSG', channel, txt)
txt = str(txt).replace("\n", "")
txt = txt.replace(" ", " ")
self.docommand("PRIVMSG", channel, txt)

@@ -323,23 +292,23 @@ def event(self, txt):

cmd = evt.command
if cmd == 'PING':
if cmd == "PING":
self.state.pongcheck = True
self.docommand('PONG', evt.txt or '')
elif cmd == 'PONG':
self.docommand("PONG", evt.txt or "")
elif cmd == "PONG":
self.state.pongcheck = False
if cmd == '001':
if cmd == "001":
self.state.needconnect = False
if self.cfg.servermodes:
self.docommand(f'MODE {self.cfg.nick} {self.cfg.servermodes}')
self.docommand(f"MODE {self.cfg.nick} {self.cfg.servermodes}")
self.zelf = evt.args[-1]
elif cmd == "376":
self.joinall()
elif cmd == '002':
elif cmd == "002":
self.state.host = evt.args[2][:-1]
elif cmd == '366':
elif cmd == "366":
self.state.error = ""
self.events.joined.set()
elif cmd == '433':
elif cmd == "433":
self.state.error = txt
nck = self.cfg.nick = self.cfg.nick + '_'
self.docommand('NICK', nck)
nck = self.cfg.nick = self.cfg.nick + "_"
self.docommand("NICK", nck)
return evt

@@ -349,3 +318,3 @@

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

@@ -366,3 +335,3 @@ chanlist.extend(txtlist)

for channel in self.channels:
self.docommand('JOIN', channel)
self.docommand("JOIN", channel)

@@ -379,3 +348,3 @@ def keep(self):

time.sleep(self.cfg.sleep)
self.docommand('PING', self.cfg.server)
self.docommand("PING", self.cfg.server)
if self.state.pongcheck:

@@ -387,4 +356,4 @@ self.restart()

self.events.authed.wait()
self.direct(f'NICK {nck}')
self.direct(f'USER {nck} {server} {server} {nck}')
self.direct(f"NICK {nck}")
self.direct(f"USER {nck} {server} {server} {nck}")

@@ -398,4 +367,4 @@ def oput(self, evt):

rawstr = str(txt)
rawstr = rawstr.replace('\u0001', '')
rawstr = rawstr.replace('\001', '')
rawstr = rawstr.replace("\u0001", "")
rawstr = rawstr.replace("\001", "")
rlog("debug", txt, IGNORE)

@@ -405,3 +374,3 @@ obj = Event()

obj.rawstr = rawstr
obj.command = ''
obj.command = ""
obj.arguments = []

@@ -413,3 +382,3 @@ arguments = rawstr.split()

obj.origin = self.cfg.server
if obj.origin.startswith(':'):
if obj.origin.startswith(":"):
obj.origin = obj.origin[1:]

@@ -423,3 +392,3 @@ if len(arguments) > 1:

for arg in arguments[2:]:
if arg.count(':') <= 1 and arg.startswith(':'):
if arg.count(":") <= 1 and arg.startswith(":"):
adding = True

@@ -432,3 +401,3 @@ txtlist.append(arg[1:])

obj.arguments.append(arg)
obj.txt = ' '.join(txtlist)
obj.txt = " ".join(txtlist)
else:

@@ -438,9 +407,9 @@ obj.command = obj.origin

try:
obj.nick, obj.origin = obj.origin.split('!')
obj.nick, obj.origin = obj.origin.split("!")
except ValueError:
obj.nick = ''
target = ''
obj.nick = ""
target = ""
if obj.arguments:
target = obj.arguments[0]
if target.startswith('#'):
if target.startswith("#"):
obj.channel = target

@@ -450,3 +419,3 @@ else:

if not obj.txt:
obj.txt = rawstr.split(':', 2)[-1]
obj.txt = rawstr.split(":", 2)[-1]
if not obj.txt and len(arguments) == 1:

@@ -473,9 +442,9 @@ obj.txt = arguments[1]

except (
OSError,
socket.timeout,
ssl.SSLError,
ssl.SSLZeroReturnError,
ConnectionResetError,
BrokenPipeError
) as ex:
OSError,
socket.timeout,
ssl.SSLError,
ssl.SSLZeroReturnError,
ConnectionResetError,
BrokenPipeError,
) as ex:
self.state.nrerror += 1

@@ -497,4 +466,4 @@ self.state.error = str(type(ex)) + " " + str(ex)

txt = txt[:500]
txt += '\r\n'
txt = bytes(txt, 'utf-8')
txt += "\r\n"
txt = bytes(txt, "utf-8")
if self.sock:

@@ -504,9 +473,9 @@ try:

except (
OSError,
ssl.SSLError,
ssl.SSLZeroReturnError,
ConnectionResetError,
BrokenPipeError,
socket.timeout
) as ex:
OSError,
ssl.SSLError,
ssl.SSLZeroReturnError,
ConnectionResetError,
BrokenPipeError,
socket.timeout,
) as ex:
rlog("debug", str(type(ex)) + " " + str(ex))

@@ -553,7 +522,7 @@ self.events.joined.set()

inbytes = self.sock.recv(512)
txt = str(inbytes, 'utf-8')
if txt == '':
txt = str(inbytes, "utf-8")
if txt == "":
raise ConnectionResetError
self.state.lastline += txt
splitted = self.state.lastline.split('\r\n')
splitted = self.state.lastline.split("\r\n")
for line in splitted[:-1]:

@@ -574,7 +543,8 @@ self.buffer.append(line)

launch(self.keep)
launch(self.doconnect,
self.cfg.server or "localhost",
self.cfg.nick,
int(self.cfg.port) or 6667
)
launch(
self.doconnect,
self.cfg.server or "localhost",
self.cfg.nick,
int(self.cfg.port) or 6667,
)

@@ -585,3 +555,2 @@ def stop(self):

Output.stop(self)
#self.disconnect()

@@ -597,3 +566,3 @@ def wait(self):

bot = Fleet.get(evt.orig)
bot.docommand(f'AUTHENTICATE {bot.cfg.password}')
bot.docommand(f"AUTHENTICATE {bot.cfg.password}")

@@ -603,6 +572,6 @@

bot = Fleet.get(evt.orig)
if bot.cfg.password and 'ACK' in evt.arguments:
bot.direct('AUTHENTICATE PLAIN')
if bot.cfg.password and "ACK" in evt.arguments:
bot.direct("AUTHENTICATE PLAIN")
else:
bot.direct('CAP REQ :sasl')
bot.direct("CAP REQ :sasl")

@@ -614,3 +583,3 @@

bot.state.error = evt.txt
rlog('debug', fmt(evt))
rlog("debug", fmt(evt))

@@ -620,3 +589,3 @@

bot = Fleet.get(evt.orig)
bot.direct('CAP END')
bot.direct("CAP END")
bot.events.authed.set()

@@ -627,3 +596,3 @@

bot = Fleet.get(evt.orig)
bot.direct('CAP END')
bot.direct("CAP END")
bot.events.authed.set()

@@ -635,5 +604,7 @@

def cb_log(evt):
pass
def cb_ready(evt):

@@ -646,3 +617,3 @@ bot = Fleet.get(evt.orig)

bot = Fleet.get(evt.orig)
bot.events.logon,set()
bot.events.logon.set()

@@ -652,5 +623,5 @@

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

@@ -663,6 +634,8 @@

if evt.txt:
if evt.txt[0] in ['!',]:
if evt.txt[0] in [
"!",
]:
evt.txt = evt.txt[1:]
elif evt.txt.startswith(f'{bot.cfg.nick}:'):
evt.txt = evt.txt[len(bot.cfg.nick)+1:]
elif evt.txt.startswith(f"{bot.cfg.nick}:"):
evt.txt = evt.txt[len(bot.cfg.nick) + 1 :]
else:

@@ -693,8 +666,8 @@ return

event.reply(
fmt(
config,
keys(config),
skip='control,password,realname,sleep,username'.split(",")
)
)
fmt(
config,
keys(config),
skip="control,password,realname,sleep,username".split(","),
)
)
else:

@@ -707,10 +680,10 @@ edit(config, event.sets)

if not event.channel:
event.reply('channel is not set.')
event.reply("channel is not set.")
return
bot = Fleet.get(event.orig)
if 'cache' not in dir(bot):
event.reply('bot is missing cache')
if "cache" not in dir(bot):
event.reply("bot is missing cache")
return
if event.channel not in dir(bot.cache):
event.reply(f'no output in {event.channel} cache.')
event.reply(f"no output in {event.channel} cache.")
return

@@ -722,3 +695,3 @@ for _x in range(3):

if size != 0:
event.reply(f'{size} more in cache')
event.reply(f"{size} more in cache")

@@ -728,10 +701,10 @@

if len(event.args) != 2:
event.reply('pwd <nick> <password>')
event.reply("pwd <nick> <password>")
return
arg1 = event.args[0]
arg2 = event.args[1]
txt = f'\x00{arg1}\x00{arg2}'
enc = txt.encode('ascii')
txt = f"\x00{arg1}\x00{arg2}"
enc = txt.encode("ascii")
base = base64.b64encode(enc)
dcd = base.decode('ascii')
dcd = base.decode("ascii")
event.reply(dcd)

@@ -24,7 +24,12 @@ # This file is placed in the Public Domain.

from ..clients import Fleet
from ..command import elapsed, spl
from ..objects import Default, Object, fmt, update
from ..persist import find, fntime, getpath, last, write
from ..runtime import Repeater, launch, rlog
from ..disk import write
from ..fleet import Fleet
from ..find import find, fntime, last
from ..log import rlog
from ..method import fmt
from ..object import Default, Object, update
from ..paths import getpath
from ..thread import launch
from ..timer import Repeater
from ..utils import elapsed, spl

@@ -35,6 +40,6 @@

fetchlock = _thread.allocate_lock()
fetchlock = _thread.allocate_lock()
importlock = _thread.allocate_lock()
errors = []
skipped = []
errors = []
skipped = []

@@ -65,6 +70,6 @@

Default.__init__(self)
self.display_list = 'title,link,author'
self.insertid = None
self.name = ""
self.rss = ""
self.display_list = "title,link,author"
self.insertid = None
self.name = ""
self.rss = ""

@@ -90,7 +95,7 @@

displaylist = ""
result = ''
result = ""
try:
displaylist = obj.display_list or 'title,link'
displaylist = obj.display_list or "title,link"
except AttributeError:
displaylist = 'title,link,author'
displaylist = "title,link,author"
for key in displaylist.split(","):

@@ -102,7 +107,7 @@ if not key:

continue
data = data.replace('\n', ' ')
data = data.replace("\n", " ")
data = striphtml(data.rstrip())
data = unescape(data)
result += data.rstrip()
result += ' - '
result += " - "
return result[:-2].rstrip()

@@ -122,4 +127,4 @@

url = urllib.parse.urlparse(fed.link)
if url.path and not url.path == '/':
uurl = f'{url.scheme}://{url.netloc}/{url.path}'
if url.path and not url.path == "/":
uurl = f"{url.scheme}://{url.netloc}/{url.path}"
else:

@@ -139,6 +144,6 @@ uurl = fed.link

return counter
txt = ''
feedname = getattr(feed, 'name', None)
txt = ""
feedname = getattr(feed, "name", None)
if feedname:
txt = f'[{feedname}] '
txt = f"[{feedname}] "
for obj in result:

@@ -152,3 +157,3 @@ txt2 = txt + self.display(obj)

thrs = []
for _fn, feed in find('rss'):
for _fn, feed in find("rss"):
thrs.append(launch(self.fetch, feed, silent))

@@ -171,8 +176,8 @@ return thrs

def getitem(line, item):
lne = ''
index1 = line.find(f'<{item}>')
lne = ""
index1 = line.find(f"<{item}>")
if index1 == -1:
return lne
index1 += len(item) + 2
index2 = line.find(f'</{item}>', index1)
index2 = line.find(f"</{item}>", index1)
if index2 == -1:

@@ -190,7 +195,7 @@ return lne

while not stop:
index1 = text.find(f'<{token}', index)
index1 = text.find(f"<{token}", index)
if index1 == -1:
break
index1 += len(token) + 2
index2 = text.find(f'</{token}>', index1)
index2 = text.find(f"</{token}>", index1)
if index2 == -1:

@@ -204,3 +209,3 @@ break

@staticmethod
def parse(txt, toke="item", items='title,link'):
def parse(txt, toke="item", items="title,link"):
result = []

@@ -228,7 +233,7 @@ for line in Parser.getitems(txt, toke):

def getnames(line):
return [x.split('="')[0] for x in line.split()]
return [x.split('="')[0] for x in line.split()]
@staticmethod
def getvalue(line, attr):
lne = ''
lne = ""
index1 = line.find(f'{attr}="')

@@ -240,10 +245,10 @@ if index1 == -1:

if index2 == -1:
index2 = line.find('/>', index1)
index2 = line.find("/>", index1)
if index2 == -1:
return lne
lne = line[index1:index2]
if 'CDATA' in lne:
lne = lne.replace('![CDATA[', '')
lne = lne.replace(']]', '')
#lne = lne[1:-1]
if "CDATA" in lne:
lne = lne.replace("![CDATA[", "")
lne = lne.replace("]]", "")
# lne = lne[1:-1]
return lne

@@ -257,7 +262,7 @@

while not stop:
index1 = line.find(f'<{token} ', index)
index1 = line.find(f"<{token} ", index)
if index1 == -1:
return result
index1 += len(token) + 2
index2 = line.find('/>', index1)
index2 = line.find("/>", index1)
if index2 == -1:

@@ -299,5 +304,5 @@ return result

def cdata(line):
if 'CDATA' in line:
lne = line.replace('![CDATA[', '')
lne = lne.replace(']]', '')
if "CDATA" in line:
lne = line.replace("![CDATA[", "")
lne = lne.replace("]]", "")
lne = lne[1:-1]

@@ -319,8 +324,8 @@ return lne

if rest:
if 'link' not in items:
if "link" not in items:
items += ",link"
if url.endswith('atom'):
result = Parser.parse(str(rest.data, 'utf-8'), 'entry', items) or []
if url.endswith("atom"):
result = Parser.parse(str(rest.data, "utf-8"), "entry", items) or []
else:
result = Parser.parse(str(rest.data, 'utf-8'), 'item', items) or []
result = Parser.parse(str(rest.data, "utf-8"), "item", items) or []
return result

@@ -331,12 +336,13 @@

postarray = [
('submit', 'submit'),
('url', url),
("submit", "submit"),
("url", url),
]
postdata = urlencode(postarray, quote_via=quote_plus)
req = urllib.request.Request('http://tinyurl.com/create.php',
data=bytes(postdata, 'UTF-8'))
req.add_header('User-agent', useragent("rss fetcher"))
with urllib.request.urlopen(req) as htm: # nosec
req = urllib.request.Request(
"http://tinyurl.com/create.php", data=bytes(postdata, "UTF-8")
)
req.add_header("User-agent", useragent("rss fetcher"))
with urllib.request.urlopen(req) as htm: # nosec
for txt in htm.readlines():
line = txt.decode('UTF-8').strip()
line = txt.decode("UTF-8").strip()
i = re.search('data-clipboard-text="(.*?)"', line, re.M)

@@ -351,4 +357,4 @@ if i:

req = urllib.request.Request(str(url))
req.add_header('User-agent', useragent("rss fetcher"))
with urllib.request.urlopen(req) as response: # nosec
req.add_header("User-agent", useragent("rss fetcher"))
with urllib.request.urlopen(req) as response: # nosec
response.data = response.read()

@@ -363,8 +369,8 @@ return response

def striphtml(text):
clean = re.compile('<.*?>')
return re.sub(clean, '', text)
clean = re.compile("<.*?>")
return re.sub(clean, "", text)
def unescape(text):
txt = re.sub(r'\s+', ' ', text)
txt = re.sub(r"\s+", " ", text)
return html.unescape(txt)

@@ -374,3 +380,3 @@

def useragent(txt):
return 'Mozilla/5.0 (X11; Linux x86_64) ' + txt
return "Mozilla/5.0 (X11; Linux x86_64) " + txt

@@ -383,6 +389,6 @@

if len(event.args) < 2:
event.reply('dpl <stringinurl> <item1,item2>')
event.reply("dpl <stringinurl> <item1,item2>")
return
setter = {'display_list': event.args[1]}
for fnm, rss in find("rss", {'rss': event.args[0]}):
setter = {"display_list": event.args[1]}
for fnm, rss in find("rss", {"rss": event.args[0]}):
if rss:

@@ -404,4 +410,4 @@ update(rss, setter)

txt = f'<outline name="{name}" display_list="{obj.display_list}" xmlUrl="{obj.rss}"/>'
event.reply(" "*12 + txt)
event.reply(" "*8 + "</outline>")
event.reply(" " * 12 + txt)
event.reply(" " * 8 + "</outline>")
event.reply(" <body>")

@@ -426,3 +432,3 @@ event.reply("</opml>")

with importlock:
for obj in prs.parse(txt, 'outline', "name,display_list,xmlUrl"):
for obj in prs.parse(txt, "outline", "name,display_list,xmlUrl"):
url = obj.xmlUrl

@@ -433,3 +439,3 @@ if url in skipped:

continue
has = list(find("rss", {'rss': url}, matching=True))
has = list(find("rss", {"rss": url}, matching=True))
if has:

@@ -453,5 +459,5 @@ skipped.append(url)

if len(event.args) != 2:
event.reply('nme <stringinurl> <name>')
event.reply("nme <stringinurl> <name>")
return
selector = {'rss': event.args[0]}
selector = {"rss": event.args[0]}
for fnm, rss in find("rss", selector):

@@ -468,3 +474,3 @@ feed = Rss()

if len(event.args) != 1:
event.reply('rem <stringinurl>')
event.reply("rem <stringinurl>")
return

@@ -484,3 +490,3 @@ for fnm, rss in find("rss"):

if len(event.args) != 1:
event.reply('res <stringinurl>')
event.reply("res <stringinurl>")
return

@@ -501,15 +507,15 @@ for fnm, rss in find("rss", deleted=True):

nrs = 0
for fnm, rss in find('rss'):
for fnm, rss in find("rss"):
nrs += 1
elp = elapsed(time.time()-fntime(fnm))
elp = elapsed(time.time() - fntime(fnm))
txt = fmt(rss)
event.reply(f'{nrs} {txt} {elp}')
event.reply(f"{nrs} {txt} {elp}")
if not nrs:
event.reply('no feed found.')
event.reply("no feed found.")
return
url = event.args[0]
if 'http://' not in url and "https://" not in url:
event.reply('i need an url')
if "http://" not in url and "https://" not in url:
event.reply("i need an url")
return
for fnm, result in find("rss", {'rss': url}):
for fnm, result in find("rss", {"rss": url}):
if result:

@@ -516,0 +522,0 @@ event.reply(f"{url} is known")

@@ -11,6 +11,6 @@ # This file is placed in the Public Domain.

from ..command import STARTTIME, elapsed
from ..utils import elapsed
"commands"
STARTTIME = time.time()

@@ -21,7 +21,7 @@

for thread in sorted(threading.enumerate(), key=lambda x: x.name):
if str(thread).startswith('<_'):
if str(thread).startswith("<_"):
continue
if getattr(thread, 'state', None) and getattr(thread, "sleep", None):
if getattr(thread, "state", None) and getattr(thread, "sleep", None):
uptime = thread.sleep - int(time.time() - thread.state["latest"])
elif getattr(thread, 'starttime', None):
elif getattr(thread, "starttime", None):
uptime = int(time.time() - thread.starttime)

@@ -34,6 +34,6 @@ else:

lap = elapsed(uptime)
res.append(f'{txt}/{lap}')
res.append(f"{txt}/{lap}")
if res:
event.reply(' '.join(res))
event.reply(" ".join(res))
else:
event.reply('no threads')
event.reply("no threads")
# This file is placed in the Public Domain.
"clients"
import threading
import time
from .handler import Handler
"client"
class Client(Handler):
def __init__(self):
Handler.__init__(self)
self.olock = threading.RLock()
Fleet.add(self)
def announce(self, txt):
pass
def display(self, event):
with self.olock:
for tme in sorted(event.result):
self.dosay(event.channel, event.result[tme])
def dosay(self, channel, txt):
self.say(channel, txt)
def raw(self, txt):
raise NotImplementedError("raw")
def say(self, channel, txt):
self.raw(txt)
"fleet"
class Fleet:
clients = {}
@staticmethod
def add(client):
Fleet.clients[repr(client)] = client
@staticmethod
def all():
return Fleet.clients.values()
@staticmethod
def announce(txt):
for client in Fleet.all():
client.announce(txt)
@staticmethod
def dispatch(evt):
client = Fleet.get(evt.orig)
client.put(evt)
@staticmethod
def display(evt):
client = Fleet.get(evt.orig)
client.display(evt)
@staticmethod
def first():
clt = list(Fleet.all())
res = None
if clt:
res = clt[0]
return res
@staticmethod
def get(orig):
return Fleet.clients.get(orig, None)
@staticmethod
def say(orig, channel, txt):
client = Fleet.get(orig)
if client:
client.say(channel, txt)
@staticmethod
def shutdown():
for client in Fleet.all():
client.stop()
@staticmethod
def wait():
time.sleep(0.1)
for client in Fleet.all():
client.wait()
"interface"
def __dir__():
return (
'Client',
'Fleet'
)
# This file is placed in the Public Domain.
"commands"
import inspect
import time
from .clients import Fleet
from .objects import Default
from .runtime import launch
STARTTIME = time.time()
"config"
class Main(Default):
debug = False
gets = Default()
ignore = ""
init = ""
level = "warn"
name = Default.__module__.split(".")[-2]
opts = Default()
otxt = ""
sets = Default()
verbose = False
version = 647
"commands"
class Commands:
cmds = {}
md5 = {}
names = {}
@staticmethod
def add(func, mod=None):
Commands.cmds[func.__name__] = func
if mod:
Commands.names[func.__name__] = mod.__name__.split(".")[-1]
@staticmethod
def get(cmd):
return Commands.cmds.get(cmd, None)
@staticmethod
def scan(mod):
for key, cmdz in inspect.getmembers(mod, inspect.isfunction):
if key.startswith("cb"):
continue
if 'event' in cmdz.__code__.co_varnames:
Commands.add(cmdz, mod)
"utilities"
def command(evt):
parse(evt)
func = Commands.get(evt.cmd)
if not func:
evt.ready()
return
func(evt)
Fleet.display(evt)
evt.ready()
def inits(pkg, names):
modz = []
for name in sorted(spl(names)):
mod = getattr(pkg, name, None)
if not mod:
continue
if "init" in dir(mod):
thr = launch(mod.init)
modz.append((mod, thr))
return modz
def parse(obj, txt=""):
if txt == "":
if "txt" in dir(obj):
txt = obj.txt
else:
txt = ""
args = []
obj.args = []
obj.cmd = ""
obj.gets = Default()
obj.index = None
obj.mod = ""
obj.opts = ""
obj.result = {}
obj.sets = Default()
obj.silent = Default()
obj.txt = txt
obj.otxt = obj.txt
_nr = -1
for spli in obj.otxt.split():
if spli.startswith("-"):
try:
obj.index = int(spli[1:])
except ValueError:
obj.opts += spli[1:]
continue
if "-=" in spli:
key, value = spli.split("-=", maxsplit=1)
setattr(obj.silent, key, value)
setattr(obj.gets, key, value)
continue
if "==" in spli:
key, value = spli.split("==", maxsplit=1)
setattr(obj.gets, key, value)
continue
if "=" in spli:
key, value = spli.split("=", maxsplit=1)
if key == "mod":
if obj.mod:
obj.mod += f",{value}"
else:
obj.mod = value
continue
setattr(obj.sets, key, value)
continue
_nr += 1
if _nr == 0:
obj.cmd = spli
continue
args.append(spli)
if args:
obj.args = args
obj.txt = obj.cmd or ""
obj.rest = " ".join(obj.args)
obj.txt = obj.cmd + " " + obj.rest
else:
obj.txt = obj.cmd or ""
def scan(pkg):
for modname in dir(pkg):
mod = getattr(pkg, modname)
Commands.scan(mod)
"utilities"
def elapsed(seconds, short=True):
txt = ""
nsec = float(seconds)
if nsec < 1:
return f"{nsec:.2f}s"
yea = 365*24*60*60
week = 7*24*60*60
nday = 24*60*60
hour = 60*60
minute = 60
yeas = int(nsec/yea)
nsec -= yeas*yea
weeks = int(nsec/week)
nsec -= weeks*week
nrdays = int(nsec/nday)
nsec -= nrdays*nday
hours = int(nsec/hour)
nsec -= hours*hour
minutes = int(nsec/minute)
nsec -= int(minute*minutes)
sec = int(nsec)
if yeas:
txt += f"{yeas}y"
if weeks:
nrdays += weeks * 7
if nrdays:
txt += f"{nrdays}d"
if short and txt:
return txt.strip()
if hours:
txt += f"{hours}h"
if minutes:
txt += f"{minutes}m"
if sec:
txt += f"{sec}s"
txt = txt.strip()
return txt
def spl(txt):
try:
result = txt.split(',')
except (TypeError, ValueError):
result = [txt, ]
return [x for x in result if x]
"interface"
def __dir__():
return (
'STARTTIME',
'Commands',
'command',
'elapsed',
'inits',
'load',
'modules',
'parse',
'scan',
'spl'
)
# This file is placed in the Public Domain.
"event handler"
import queue
import threading
import time
import _thread
from .objects import Object
from .runtime import launch
"handler"
class Handler:
def __init__(self):
self.lock = _thread.allocate_lock()
self.cbs = {}
self.queue = queue.Queue()
self.ready = threading.Event()
self.stopped = threading.Event()
def available(self, event):
return event.type in self.cbs
def callback(self, event):
func = self.cbs.get(event.type, None)
if func:
event._thr = launch(func, event)
def loop(self):
while not self.stopped.is_set():
event = self.poll()
if event is None:
break
event.orig = repr(self)
self.callback(event)
def poll(self):
return self.queue.get()
def put(self, event):
self.queue.put(event)
def register(self, typ, cbs):
self.cbs[typ] = cbs
def start(self, daemon=True):
self.stopped.clear()
launch(self.loop, daemon=daemon)
def stop(self):
self.stopped.set()
self.queue.put(None)
def wait(self):
pass
"event"
class Event(Object):
def __init__(self):
Object.__init__(self)
self._ready = threading.Event()
self._thr = None
self.channel = ""
self.ctime = time.time()
self.orig = ""
self.rest = ""
self.result = {}
self.type = "event"
self.txt = ""
def done(self):
self.reply("ok")
def ready(self):
self._ready.set()
def reply(self, txt):
self.result[time.time()] = txt
def wait(self, timeout=None):
self._ready.wait()
if self._thr:
self._thr.join()
"interface"
def __dir__():
return (
'Event',
'Handler'
)
# This file is placed in the Public Domain.
"debug"
import time
from ..clients import Fleet
def dbg(event):
event.reply("raising exception")
raise Exception("yo!")
def brk(event):
event.reply("borking")
for clt in Fleet.all():
if "sock" in dir(clt):
event.reply(f"shutdown on {clt.cfg.server}")
time.sleep(2.0)
try:
clt.sock.shutdown(2)
except OSError:
pass
# This file is placed in the Public Domain.
"version"
from ..command import Main
"commands"
def ver(event):
event.reply(f"{Main.name.upper()} {Main.version}")
# This file is placed in the Public Domain.
"clean namespace"
import json
"object"
class Object:
def __contains__(self, key):
return key in dir(self)
def __iter__(self):
return iter(self.__dict__)
def __len__(self):
return len(self.__dict__)
def __str__(self):
return str(self.__dict__)
"default"
class Default(Object):
def __getattr__(self, key):
if key not in self:
setattr(self, key, "")
return self.__dict__.get(key, "")
"methods"
def construct(obj, *args, **kwargs):
if args:
val = args[0]
if isinstance(val, zip):
update(obj, dict(val))
elif isinstance(val, dict):
update(obj, val)
elif isinstance(val, Object):
update(obj, vars(val))
if kwargs:
update(obj, kwargs)
def edit(obj, setter, skip=True):
for key, val in items(setter):
if skip and val == "":
continue
try:
setattr(obj, key, int(val))
continue
except ValueError:
pass
try:
setattr(obj, key, float(val))
continue
except ValueError:
pass
if val in ["True", "true"]:
setattr(obj, key, True)
elif val in ["False", "false"]:
setattr(obj, key, False)
else:
setattr(obj, key, val)
def fmt(obj, args=None, skip=None, plain=False, empty=False):
if args is None:
args = keys(obj)
if skip is None:
skip = []
txt = ""
for key in args:
if key.startswith("__"):
continue
if key in skip:
continue
value = getattr(obj, key, None)
if value is None:
continue
if not empty and not value:
continue
if plain:
txt += f"{value} "
elif isinstance(value, str):
txt += f'{key}="{value}" '
else:
txt += f'{key}={value} '
return txt.strip()
def fqn(obj):
kin = str(type(obj)).split()[-1][1:-2]
if kin == "type":
kin = f"{obj.__module__}.{obj.__name__}"
return kin
def items(obj):
if isinstance(obj, dict):
return obj.items()
return obj.__dict__.items()
def keys(obj):
if isinstance(obj, dict):
return obj.keys()
return obj.__dict__.keys()
def update(obj, data):
if isinstance(data, dict):
return obj.__dict__.update(data)
obj.__dict__.update(vars(data))
def values(obj):
if isinstance(obj, dict):
return obj.values()
return obj.__dict__.values()
"decoder/emcoder"
class Encoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, dict):
return o.items()
if issubclass(type(o), Object):
return vars(o)
if isinstance(o, list):
return iter(o)
try:
return json.JSONEncoder.default(self, o)
except TypeError:
try:
return vars(o)
except TypeError:
return repr(o)
def dump(obj, fp, *args, **kw):
kw["cls"] = Encoder
json.dump(obj, fp, *args, **kw)
def dumps(obj, *args, **kw):
kw["cls"] = Encoder
return json.dumps(obj, *args, **kw)
def hook(objdict):
obj = Object()
construct(obj, objdict)
return obj
def load(fp, *args, **kw):
kw["object_hook"] = hook
return json.load(fp, *args, **kw)
def loads(s, *args, **kw):
kw["object_hook"] = hook
return json.loads(s, *args, **kw)
"interface"
def __dir__():
return (
'Default',
'Object',
'construct',
'dump',
'dumps',
'edit',
'fmt',
'fqn',
'items',
'keys',
'load',
'loads',
'update',
'values'
)
# This file is placed in the Public Domain.
"persistence"
import datetime
import json
import os
import pathlib
import threading
import time
from .objects import Object, dump, fqn, items, load, update
lock = threading.RLock()
"cache"
class Cache:
objs = {}
@staticmethod
def add(path, obj):
Cache.objs[path] = obj
@staticmethod
def get(path):
return Cache.objs.get(path, None)
@staticmethod
def update(path, obj):
if not obj:
return
if path in Cache.objs:
update(Cache.objs[path], obj)
else:
Cache.add(path, obj)
"workdir"
class Workdir:
name = __file__.rsplit(os.sep, maxsplit=2)[-2]
wdr = ""
def getpath(obj):
return store(ident(obj))
def ident(obj):
return os.path.join(fqn(obj),*str(datetime.datetime.now()).split())
def long(name):
split = name.split(".")[-1].lower()
res = name
for names in types():
if split == names.split(".")[-1].lower():
res = names
break
return res
def pidname(name):
return os.path.join(Workdir.wdr, f"{name}.pid")
def skel():
pth = pathlib.Path(store())
pth.mkdir(parents=True, exist_ok=True)
return str(pth)
def store(pth=""):
return os.path.join(Workdir.wdr, "store", pth)
def strip(pth, nmr=2):
return os.path.join(pth.split(os.sep)[-nmr:])
def types():
return os.listdir(store())
def wdr(pth):
return os.path.join(Workdir.wdr, pth)
"find"
def find(clz, selector=None, deleted=False, matching=False):
clz = long(clz)
if selector is None:
selector = {}
for pth in fns(clz):
obj = Cache.get(pth)
if not obj:
obj = Object()
read(obj, pth)
Cache.add(pth, obj)
if not deleted and isdeleted(obj):
continue
if selector and not search(obj, selector, matching):
continue
yield pth, obj
def fns(clz):
pth = store(clz)
for rootdir, dirs, _files in os.walk(pth, topdown=False):
for dname in dirs:
ddd = os.path.join(rootdir, dname)
for fll in os.listdir(ddd):
yield os.path.join(ddd, fll)
def fntime(daystr):
datestr = ' '.join(daystr.split(os.sep)[-2:])
datestr = datestr.replace("_", " ")
if '.' in datestr:
datestr, rest = datestr.rsplit('.', 1)
else:
rest = ''
timed = time.mktime(time.strptime(datestr, '%Y-%m-%d %H:%M:%S'))
if rest:
timed += float('.' + rest)
return float(timed)
def isdeleted(obj):
return '__deleted__' in dir(obj) and obj.__deleted__
def last(obj, selector=None):
if selector is None:
selector = {}
result = sorted(find(fqn(obj), selector), key=lambda x: fntime(x[0]))
res = ""
if result:
inp = result[-1]
update(obj, inp[-1])
res = inp[0]
return res
def search(obj, selector, matching=False):
res = False
if not selector:
return res
for key, value in items(selector):
val = getattr(obj, key, None)
if not val:
continue
if matching and value == val:
res = True
elif str(value).lower() in str(val).lower() or value == "match":
res = True
else:
res = False
break
return res
"disk"
def cdir(path):
pth = pathlib.Path(path)
pth.parent.mkdir(parents=True, exist_ok=True)
def read(obj, path):
with lock:
with open(path, "r", encoding="utf-8") as fpt:
try:
update(obj, load(fpt))
except json.decoder.JSONDecodeError as ex:
ex.add_note(path)
raise ex
def write(obj, path):
with lock:
cdir(path)
with open(path, "w", encoding="utf-8") as fpt:
dump(obj, fpt, indent=4)
return path
"interface"
def __dir__():
return (
'Workdir',
'cdir',
'find',
'fns',
'fntime',
'last',
'long',
'ident',
'pidname',
'read',
'search',
'skel',
'store',
'wdr',
'write'
)
# This file is placed in the Public Domain.
"runtime"
import logging
import queue
import sys
import time
import threading
import _thread
errorlock = threading.RLock()
launchlock = threading.RLock()
lock = threading.RLock()
"logging"
LEVELS = {'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'warn': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL
}
def level(loglevel="debug"):
if loglevel != "none":
format_short = "%(message)-80s"
datefmt = '%H:%M:%S'
logging.basicConfig(stream=sys.stderr, datefmt=datefmt, format=format_short)
logging.getLogger().setLevel(LEVELS.get(loglevel))
def rlog(loglevel, txt, ignore=None):
if ignore is None:
ignore = []
for ign in ignore:
if ign in str(txt):
return
logging.log(LEVELS.get(loglevel), txt)
"threads"
class Thread(threading.Thread):
def __init__(self, func, thrname, *args, daemon=True, **kwargs):
super().__init__(None, self.run, thrname, (), daemon=daemon)
self.name = thrname or kwargs.get("name", name(func))
self.queue = queue.Queue()
self.result = None
self.starttime = time.time()
self.stopped = threading.Event()
self.queue.put((func, args))
def __iter__(self):
return self
def __next__(self):
for k in dir(self):
yield k
def run(self):
func, args = self.queue.get()
try:
self.result = func(*args)
except (KeyboardInterrupt, EOFError) as ex:
raise ex
except Exception as ex:
logging.exception(ex)
_thread.interrupt_main()
def join(self, timeout=None):
super().join(timeout)
return self.result
def launch(func, *args, **kwargs):
with launchlock:
nme = kwargs.get("name", None)
if not nme:
nme = name(func)
thread = Thread(func, nme, *args, **kwargs)
thread.start()
return thread
def name(obj):
typ = type(obj)
if '__builtins__' in dir(typ):
return obj.__name__
if '__self__' in dir(obj):
return f'{obj.__self__.__class__.__name__}.{obj.__name__}'
if '__class__' in dir(obj) and '__name__' in dir(obj):
return f'{obj.__class__.__name__}.{obj.__name__}'
if '__class__' in dir(obj):
return f"{obj.__class__.__module__}.{obj.__class__.__name__}"
if '__name__' in dir(obj):
return f'{obj.__class__.__name__}.{obj.__name__}'
return ""
"repeater"
class Timy(threading.Timer):
def __init__(self, sleep, func, *args, **kwargs):
super().__init__(sleep, func)
self.name = kwargs.get("name", name(func))
self.sleep = sleep
self.state = {}
self.state["latest"] = time.time()
self.state["starttime"] = time.time()
self.starttime = time.time()
class Timed:
def __init__(self, sleep, func, *args, thrname="", **kwargs):
self.args = args
self.func = func
self.kwargs = kwargs
self.sleep = sleep
self.name = thrname or kwargs.get("name", name(func))
self.target = time.time() + self.sleep
self.timer = None
def run(self):
self.timer.latest = time.time()
self.func(*self.args)
def start(self):
self.kwargs["name"] = self.name
timer = Timy(self.sleep, self.run, *self.args, **self.kwargs)
timer.start()
self.timer = timer
def stop(self):
if self.timer:
self.timer.cancel()
class Repeater(Timed):
def run(self):
launch(self.start)
super().run()
"interface"
def __dir__():
return (
'Repeater',
'Thread',
'Timed',
'elapsed',
'launch',
'level',
'name',
'rlog',
'spl'
)