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
655
to
656
+42
rssbot/brokers.py
# This file is placed in the Public Domain.
"an object for a string."
class Broker:
objects = {}
def add(obj):
"add object to the broker, key is repr(obj)."
Broker.objects[repr(obj)] = obj
def broker(origin):
"return object by repr(obj)."
return Broker.objects.get(origin)
def objs(attr):
"return object with a certain attribute."
for obj in Broker.objects.values():
if attr in dir(obj):
yield obj
def like(txt):
"return all objects that have a substring in their key."
for orig in Broker.objects:
if orig.split()[0] in orig.split()[0]:
yield orig
def __dir__():
return (
'add',
'broker',
'like',
'objs'
)
# This file is placed in the Public Domain.
"configuration"
from .objects import Default
class Config(Default):
debug = False
gets = Default()
ignore = ""
init = ""
level = "info"
name = ""
opts = ""
sets = Default()
version = 0
def __dir__():
return (
'Config',
)
# This file is placed in the Public Domain.
# ruff: noqa F401
"definitions"
from .brokers import broker, add, like, objs
from .clients import Client, CLI, Output
from .command import Commands, cmds, command, enable, scan, scanner
from .configs import Config
from .handler import Handler
from .loggers import level
from .message import Message
from .methods import deleted, edit, fmt, fqn, parse, search
from .objects import Default, Object
from .objects import asdict , construct, items, keys, update, values
from .persist import attrs, cache, last, find, put, read, sync, write
from .repeats import Repeater, Timed
from .serials import dump, dumps, load, loads
from .statics import MONTH, SYSTEMD
from .threads import launch, name
from .timings import NoDate, date, day, elapsed, extract, fntime, hour, time
from .timings import parsetxt, today
from .utility import cdir, ident, md5sum, spl, where, wrapped
from .workdir import Workdir, getpath, long, moddir, pidname, skel, storage, kinds
def __dir__():
return (
'MONTH',
'SYSTEMD',
'Client',
'CLI',
'Commands',
'Config',
'Default',
'Handler',
'Message',
'Object',
'Output',
'Repeater',
'Timed',
'Workdir',
'add',
'asdict',
'cache',
'cdir',
'cmds',
'construct',
'command',
'date',
'day',
'deleted',
'dump',
'dumps',
'edit',
'elapsed',
'enable',
'extract',
'fntime',
'find',
'fmt',
'fqn',
'getpath',
'hour',
'ident',
'items',
'keys',
'kinds',
'last',
'launch',
'level',
'like',
'load',
'loads',
'long',
'moddir',
'md5sum',
'objs',
'parse',
'parsetxt',
'pidname',
'put',
'read',
'scan',
'scanner',
'search',
'skel',
'storage',
'sync',
'spl',
'time',
'today',
'update',
'values',
'where',
'wrapped',
'write'
)
__all__ = __dir__()
# This file is placed in the Public Domain.
"only message."
import threading
import time
from .brokers import broker
from .objects import Default
class Message(Default):
def __init__(self):
super().__init__()
self._ready = threading.Event()
self.result = {}
self.thr = None
self.args = []
self.index = 0
self.kind = "event"
self.orig = ""
def display(self):
"call display on originating client."
bot = broker(self.orig)
bot.display(self)
def ready(self):
"flag message as ready."
self._ready.set()
def reply(self, text):
"add text to result."
self.result[time.time()] = text
def wait(self, timeout=0.0):
"wait for completion."
if self.thr:
self.thr.join(timeout)
self._ready.wait(timeout or None)
def __dir__():
return (
'Message',
)
# This file is placed in the Public Domain.
"modules"
from . import atr as atr
from . import flt as flt
from . import fnd as fnd
from . import irc as irc
from . import lst as lst
from . import rss as rss
from . import sil as sil
from . import thr as thr
from . import upt as upt
def __dir__():
return (
'atr',
'flt',
'fnd',
'irc',
'lst',
'rss',
'sil',
'thr',
'upt',
)
# This file is placed in the Public Domain.
"fields"
from rssbot.defines import attrs, kinds
def atr(event):
if not event.rest:
res = sorted({x.split('.')[-1].lower() for x in kinds()})
if res:
event.reply(",".join(res))
else:
event.reply("no types")
return
itms = attrs(event.args[0])
if not itms:
event.reply("no fields")
else:
event.reply(",".join(itms))
# This file is placed in the Public Domain.
"realisation of serialisation"
import json
import types
class Encoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, dict):
return o.items()
if isinstance(o, list):
return iter(o)
if isinstance(o, types.MappingProxyType):
return dict(o)
try:
return json.JSONEncoder.default(self, o)
except TypeError:
try:
return vars(o)
except TypeError:
return repr(o)
def dump(*args, **kw):
"dump object to disk."
kw["cls"] = Encoder
return json.dump(*args, **kw)
def dumps(*args, **kw):
"dump object to string."
kw["cls"] = Encoder
return json.dumps(*args, **kw)
def load(s, *args, **kw):
"load object from disk."
return json.load(s, *args, **kw)
def loads(s, *args, **kw):
"load object from string."
return json.loads(s, *args, **kw)
def __dir__():
return (
'dump',
'dumps',
'load',
'loads'
)
# This file is placed in the Public Domain.
"by definition."
MONTH = {
'Jan': 1,
'Feb': 2,
'Mar': 3,
'Apr': 4,
'May': 5,
'Jun': 6,
'Jul': 7,
'Aug': 8,
'Sep': 9,
'Oct': 10,
'Nov': 11,
'Dec': 12
}
SYSTEMD = """[Unit]
Description=%s
After=multi-user.target
[Service]
Type=simple
User=%s
Group=%s
ExecStart=/home/%s/.local/bin/%s -s
[Install]
WantedBy=multi-user.target"""
TIMES = [
"%Y-%M-%D %H:%M:%S",
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%d",
"%d-%m-%Y",
"%d-%m",
"%m-%d"
]
def __dir__():
return (
'MONTH',
'SYSTEMD',
'TIMES'
)
# This file is placed in the Public Domain.
"things are repeating"
import datetime
import os
import re
import time
from .statics import MONTH, TIMES
class NoDate(Exception):
pass
def date(daystr):
"return date from string."
daystr = daystr.encode('utf-8', 'replace').decode("utf-8")
res = time.time()
for fmat in TIMES:
try:
res = time.mktime(time.strptime(daystr, fmat))
break
except ValueError:
pass
return res
def day(daystr):
"day part in a string."
days = None
month = None
yea = None
try:
ymdre = re.search(r'(\d+)-(\d+)-(\d+)', daystr)
if ymdre:
(days, month, yea) = ymdre.groups()
except ValueError:
try:
ymre = re.search(r'(\d+)-(\d+)', daystr)
if ymre:
(days, month) = ymre.groups()
yea = time.strftime("%Y", time.localtime())
except Exception as ex:
raise NoDate(daystr) from ex
if days:
days = int(days)
month = int(month)
yea = int(yea)
dte = f"{days} {MONTH[month]} {yea}"
return time.mktime(time.strptime(dte, r"%d %b %Y"))
raise NoDate(daystr)
def elapsed(seconds, short=True):
"seconds to string."
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
hou = 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 / hou)
nsec -= hours * hou
minutes = int(nsec / minute)
nsec -= minutes * minute
sec = int(nsec / 1)
nsec -= nsec - sec
if yeas:
txt += f"{yeas}y"
if weeks:
nrdays += weeks * 7
if nrdays:
txt += f"{nrdays}d"
if hours:
txt += f"{hours}h"
if short and txt:
return txt.strip()
if minutes:
txt += f"{minutes}m"
if sec:
txt += f"{sec}s"
txt = txt.strip()
return txt
def extract(daystr):
"extract date/time from string."
previous = ""
line = ""
daystr = str(daystr)
res = None
for word in daystr.split():
line = previous + " " + word
previous = word
try:
res = date(line.strip())
break
except ValueError:
res = None
line = ""
return res
def fntime(daystr):
"return time from path."
datestr = " ".join(daystr.split(os.sep)[-2:])
datestr = datestr.replace("_", " ")
if "." in datestr:
datestr, rest = datestr.rsplit(".", 1)
else:
rest = ""
timd = time.mktime(time.strptime(datestr, "%Y-%m-%d %H:%M:%S"))
if rest:
timd += float("." + rest)
return float(timd)
def hour(daystr):
"return hour in string."
try:
hmsre = re.search(r'(\d+):(\d+):(\d+)', str(daystr))
hours = 60 * 60 * (int(hmsre.group(1)))
hoursmin = hours + int(hmsre.group(2)) * 60
hmsres = hoursmin + int(hmsre.group(3))
except AttributeError:
pass
except ValueError:
pass
try:
hmre = re.search(r'(\d+):(\d+)', str(daystr))
hours = 60 * 60 * (int(hmre.group(1)))
hmsres = hours + int(hmre.group(2)) * 60
except AttributeError:
return 0
except ValueError:
return 0
return hmsres
def timed(txt):
"scan string for date/time."
try:
target = day(txt)
except NoDate:
target = extract(today())
hours = hour(txt)
if hours:
target += hours
return target
def parsetxt(txt):
"parse text for date/time."
seconds = 0
target = 0
txt = str(txt)
for word in txt.split():
if word.startswith("+"):
seconds = int(word[1:])
return time.time() + seconds
if word.startswith("-"):
seconds = int(word[1:])
return time.time() - seconds
if not target:
try:
target = day(txt)
except NoDate:
target = extract(today())
hours = hour(txt)
if hours:
target += hours
return target
def today():
"return start of the day."
return str(datetime.datetime.today()).split()[0]
def __dir__():
return (
'date',
'day',
'elapsed',
'extract',
'fntime',
'hour',
'time',
'parsetxt',
'today'
)
# This file is placed in the Public Domain.
"where objects are stored."
import os
import pathlib
from .utility import ident
class Workdir:
wdr = ""
def getpath(obj):
"return path for object."
return storage(ident(obj))
def long(name):
"match full qualified name by substring."
split = name.split(".")[-1].lower()
res = name
for names in kinds():
if split == names.split(".")[-1].lower():
res = names
break
return res
def moddir(modname: str = ""):
"return modules string."
return os.path.join(Workdir.wdr, modname or "mods")
def pidname(name: str):
"return name of pidfile."
return os.path.join(Workdir.wdr, f"{name}.pid")
def skel():
"create directories."
path = storage()
pth = pathlib.Path(path)
pth.mkdir(parents=True, exist_ok=True)
pth = pathlib.Path(moddir())
pth.mkdir(parents=True, exist_ok=True)
def storage(fnm: str = ""):
"return path to store."
return os.path.join(Workdir.wdr, "store", fnm)
def kinds():
"return stored types."
return os.listdir(storage())
def __dir__():
return (
'Workdir',
'getpath',
'long',
'moddir',
'pidname',
'skel',
'storage',
'types'
)
+1
-1
Metadata-Version: 2.4
Name: rssbot
Version: 655
Version: 656
Summary: 24/7 IRC Feed Fetcher, a contribution back to society. Public Domain.

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

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

description = "24/7 IRC Feed Fetcher, a contribution back to society. Public Domain."
version = "655"
version = "656"
authors = [

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

Metadata-Version: 2.4
Name: rssbot
Version: 655
Version: 656
Summary: 24/7 IRC Feed Fetcher, a contribution back to society. Public Domain.

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

@@ -5,14 +5,20 @@ README.rst

bin/rssbot
rssbot/brokers.py
rssbot/clients.py
rssbot/command.py
rssbot/configs.py
rssbot/defines.py
rssbot/handler.py
rssbot/loggers.py
rssbot/marshal.py
rssbot/message.py
rssbot/methods.py
rssbot/objects.py
rssbot/package.py
rssbot/persist.py
rssbot/repeats.py
rssbot/serials.py
rssbot/statics.py
rssbot/threads.py
rssbot/timings.py
rssbot/utility.py
rssbot/workdir.py
rssbot.egg-info/PKG-INFO

@@ -22,2 +28,4 @@ rssbot.egg-info/SOURCES.txt

rssbot.egg-info/top_level.txt
rssbot/modules/__init__.py
rssbot/modules/atr.py
rssbot/modules/flt.py

@@ -27,3 +35,2 @@ rssbot/modules/fnd.py

rssbot/modules/lst.py
rssbot/modules/mod.py
rssbot/modules/rss.py

@@ -33,3 +40,2 @@ rssbot/modules/sil.py

rssbot/modules/upt.py
rssbot/network/__init__.py
tests/test_none.py
# This file is placed in the Public Domain.
"handle your own events"
import logging
import queue
import threading
import _thread
from .brokers import add
from .command import command
from .handler import Handler

@@ -12,20 +19,13 @@ from .threads import launch

class Config:
name = "tob"
opts = ""
sets: dict[str,str] = {}
version = 141
class Client(Handler):
def __init__(self):
Handler.__init__(self)
super().__init__()
self.olock = threading.RLock()
self.oqueue = queue.Queue()
self.silent = True
Fleet.add(self)
add(self)
def announce(self, text):
"announce text to all channels."
if not self.silent:

@@ -35,25 +35,40 @@ self.raw(text)

def display(self, event):
"display event results."
with self.olock:
for tme in sorted(event.result):
self.dosay(
event.channel,
event.result[tme]
)
for tme in event.result:
txt = event.result.get(tme)
self.dosay(event.channel, txt)
def dosay(self, channel, text):
"say called by display."
self.say(channel, text)
def raw(self, text):
"raw output."
raise NotImplementedError("raw")
def say(self, channel, text):
"say text in channel."
self.raw(text)
def wait(self):
self.oqueue.join()
"wait for output to finish."
try:
self.oqueue.join()
except Exception as ex:
logging.exception(ex)
_thread.interrupt_main()
class CLI(Client):
def __init__(self):
super().__init__()
self.register("command", command)
class Output(Client):
def output(self):
"output loop."
while True:

@@ -68,2 +83,3 @@ event = self.oqueue.get()

def start(self):
"start output loop."
launch(self.output)

@@ -73,2 +89,3 @@ super().start()

def stop(self):
"stop output loop."
self.oqueue.put(None)

@@ -78,54 +95,7 @@ super().stop()

class Fleet:
clients: dict[str, Client] = {}
@staticmethod
def add(client):
Fleet.clients[repr(client)] = client
@staticmethod
def all():
return Fleet.clients.values()
@staticmethod
def announce(text):
for client in Fleet.all():
client.announce(text)
@staticmethod
def display(event):
client = Fleet.get(event.orig)
if client:
client.display(event)
@staticmethod
def get(origin):
return Fleet.clients.get(origin, None)
@staticmethod
def like(origin):
for orig in Fleet.clients:
if origin.split()[0] in orig.split()[0]:
yield orig
@staticmethod
def say(orig, channel, txt):
client = Fleet.get(orig)
if client:
client.say(channel, txt)
@staticmethod
def shutdown():
for client in Fleet.all():
client.wait()
client.stop()
def __dir__():
return (
'CLI',
'Client',
'Config',
'Fleet',
'Output'
)
)
# This file is placed in the Public Domain.
import inspect
"write your own commands."
from typing import Callable
import inspect
from .clients import Fleet
from .methods import parse
from .utility import spl

@@ -16,39 +16,57 @@

cmds: dict[str, Callable] = {}
names: dict[str, str] = {}
cmds = {}
names = {}
@staticmethod
def add(*args):
for func in args:
name = func.__name__
Commands.cmds[name] = func
Commands.names[name] = func.__module__.split(".")[-1]
@staticmethod
def get(cmd):
return Commands.cmds.get(cmd, None)
def cmds(cmd):
"return command."
return Commands.cmds.get(cmd, None)
def command(evt):
"command callback."
parse(evt, evt.text)
func = Commands.get(evt.cmd)
func = cmds(evt.cmd)
if func:
func(evt)
Fleet.display(evt)
evt.display()
evt.ready()
def enable(*args):
"add functions to commands."
for func in args:
name = func.__name__
Commands.cmds[name] = func
Commands.names[name] = func.__module__.split(".")[-1]
def scan(module):
"scan a module for command, function with event as first argument."
for key, cmdz in inspect.getmembers(module, inspect.isfunction):
if key.startswith("cb"):
if 'event' not in inspect.signature(cmdz).parameters:
continue
if 'event' in inspect.signature(cmdz).parameters:
Commands.add(cmdz)
enable(cmdz)
def scanner(pkg, names=None):
"scan package for commands."
if names is None:
names = ",".join(dir(pkg))
mods = []
for name in spl(names):
module = getattr(pkg, name, None)
if not module:
continue
scan(module)
return mods
def __dir__():
return (
'Comamnds',
'Commands',
'cmds',
'command',
'enable',
'scan'
)
# This file is placed in the Public Domain.
import queue
import threading
import time
"handle your own events"
from typing import Callable
import queue

@@ -15,43 +13,22 @@

class Event:
def __init__(self):
self._ready = threading.Event()
self._thr = None
self.channel = ""
self.ctime = time.time()
self.orig = ""
self.result = {}
self.type = "event"
def ready(self):
self._ready.set()
def reply(self, text):
self.result[time.time()] = text
def wait(self, timeout=None):
self._ready.wait()
if self._thr:
self._thr.join(timeout)
class Handler:
def __init__(self):
self.cbs: dict[str, Callable] = {}
self.cbs = {}
self.queue = queue.Queue()
def callback(self, event):
func = self.cbs.get(event.type, None)
if func:
name = event.text and event.text.split()[0]
event._thr = launch(func, event, name=name)
else:
"run callback function with event."
func = self.cbs.get(event.kind, None)
if not func:
event.ready()
return
name = event.text and event.text.split()[0]
event._thr = launch(func, event, name=name)
def loop(self):
"event loop."
while True:
event = self.poll()
if event is None:
if not event:
break

@@ -62,14 +39,19 @@ event.orig = repr(self)

def poll(self):
"return an event to process."
return self.queue.get()
def put(self, event):
"put event on queue."
self.queue.put(event)
def register(self, type, callback):
self.cbs[type] = callback
def register(self, kind, callback):
"register callback."
self.cbs[kind] = callback
def start(self):
"start event handler loop."
launch(self.loop)
def stop(self):
"stop event handler loop."
self.queue.put(None)

@@ -80,4 +62,5 @@

return (
'Event',
'Handler'
)
'CLI',
'Client',
'Output'
)
# This file is placed in the Public Domain.
import logging
"log exceptions."
LEVELS = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warning':logging. WARNING,
'warn': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL
}
import logging
class Logging:
datefmt = "%H:%M:%S"
format = "%(module).3s %(message)s"
class Format(logging.Formatter):

@@ -30,22 +17,24 @@

def level(loglevel="debug"):
if loglevel != "none":
lvl = LEVELS.get(loglevel)
if not lvl:
return
logger = logging.getLogger()
for handler in logger.handlers:
logger.removeHandler(handler)
logger.setLevel(lvl)
formatter = Format(Logging.format, datefmt=Logging.datefmt)
ch = logging.StreamHandler()
ch.setFormatter(formatter)
logger.addHandler(ch)
class Log:
datefmt = "%H:%M:%S"
format = "%(module).3s %(message)s"
def level(loglevel):
"set log level."
formatter = Format(Log.format, Log.datefmt)
stream = logging.StreamHandler()
stream.setFormatter(formatter)
logging.basicConfig(
level=loglevel.upper(),
handlers=[stream,],
force=True
)
def __dir__():
return (
'LEVELS',
'Logging',
'Log',
'level'
)
)
# This file is placed in the Public Domain.
from .objects import fqn, items
"functions with an object as the first argument"
def edit(obj, setter, skip=True):
from .objects import Default, items
def deleted(obj):
"check whether obj had deleted flag set."
return "__deleted__" in dir(obj) and obj.__deleted__
def edit(obj, setter={}, skip=False):
"update object with dict."
for key, val in items(setter):

@@ -30,4 +39,5 @@ if skip and val == "":

def fmt(obj, args=[], skip=[], plain=False, empty=False):
if not args:
args = obj.__dict__.keys()
"format object info printable string."
if args == []:
args = list(obj.__dict__.keys())
txt = ""

@@ -52,28 +62,22 @@ for key in args:

txt += f"{key}={fqn(value)}((value))"
if txt == "":
txt = "{}"
return txt.strip()
def name(obj, short=False):
typ = type(obj)
res = ""
if "__builtins__" in dir(typ):
res = obj.__name__
elif "__self__" in dir(obj):
res = f"{obj.__self__.__class__.__name__}.{obj.__name__}"
elif "__class__" in dir(obj) and "__name__" in dir(obj):
res = f"{obj.__class__.__name__}.{obj.__name__}"
elif "__class__" in dir(obj):
res = f"{obj.__class__.__module__}.{obj.__class__.__name__}"
elif "__name__" in dir(obj):
res = f"{obj.__class__.__name__}.{obj.__name__}"
if short:
res = res.split(".")[-1]
return res
def fqn(obj):
"full qualified name."
kin = str(type(obj)).split()[-1][1:-2]
if kin == "type":
tpe = type(obj)
kin = f"{tpe.__module__}.{tpe.__name__}"
return kin
def parse(obj, text):
"parse text for command."
data = {
"args": [],
"cmd": "",
"gets": {},
"gets": Default(),
"index": None,

@@ -84,4 +88,4 @@ "init": "",

"rest": "",
"silent": {},
"sets": {},
"silent": Default(),
"sets": Default(),
"text": text

@@ -102,12 +106,12 @@ }

key, value = spli.split("-=", maxsplit=1)
obj.silent[key] = value
obj.gets[key] = value
setattr(obj.silent, key, value)
setattr(obj.gets, key. value)
continue
if "==" in spli:
key, value = spli.split("==", maxsplit=1)
obj.gets[key] = value
setattr(obj.gets, key, value)
continue
if "=" in spli:
key, value = spli.split("=", maxsplit=1)
obj.sets[key] = value
setattr(obj.sets, key, value)
continue

@@ -128,8 +132,28 @@ nr += 1

def search(obj, selector={}, matching=False):
"check whether object matches search criteria."
res = False
for key, value in items(selector):
val = getattr(obj, key, None)
if not val:
res = False
break
if matching and value != val:
res = False
break
if str(value).lower() not in str(val).lower():
res = False
break
res = True
return res
def __dir__():
return (
'deleted',
'edit',
'fmt',
'name',
'parse'
'fqn',
'parse',
'search'
)
# This file is placed in the Public Domain.
from rssbot.clients import Fleet
from rssbot.methods import fmt
from rssbot.threads import name
from rssbot.defines import fmt, name, objs
def flt(event):
clts = objs("announce")
if event.args:
clts = Fleet.all()
index = int(event.args[0])
if index < len(clts):
event.reply(fmt(list(Fleet.all())[index], empty=True))
event.reply(fmt(list(clts)[index]), empty=True)
else:
event.reply(f"only {len(clts)} clients in fleet.")
return
event.reply(' | '.join([name(o) for o in Fleet.all()]))
event.reply(' | '.join([name(o) for o in clts]))

@@ -7,5 +7,3 @@ # This file is placed in the Public Domain.

from rssbot.methods import fmt
from rssbot.persist import find, fntime, types
from rssbot.utility import elapsed
from rssbot.defines import elapsed, find, fmt, fntime, kinds

@@ -15,3 +13,3 @@

if not event.rest:
res = sorted([x.split('.')[-1].lower() for x in types()])
res = sorted([x.split('.')[-1].lower() for x in kinds()])
if res:

@@ -24,3 +22,3 @@ event.reply(",".join(res))

nmr = 0
for fnm, obj in list(find(otype, event.gets)):
for fnm, obj in sorted(find(otype, event.gets), key=lambda x: fntime(x[0])):
event.reply(f"{nmr} {fmt(obj)} {elapsed(time.time()-fntime(fnm))}")

@@ -27,0 +25,0 @@ nmr += 1

@@ -14,21 +14,12 @@ # This file is placed in the Public Domain.

from rssbot.clients import Config as Main
from rssbot.clients import Output
from rssbot.command import Fleet, command
from rssbot.handler import Event as IEvent
from rssbot.loggers import LEVELS
from rssbot.methods import edit, fmt
from rssbot.objects import Object, keys
from rssbot.persist import getpath, last, write
from rssbot.threads import launch
from rssbot.utility import where
IGNORE = ["PING", "PONG", "PRIVMSG"]
from rssbot.defines import Config as Main
from rssbot.defines import Message, Object, Output
from rssbot.defines import broker, command, edit, fmt, getpath, last, launch
from rssbot.defines import keys, write
lock = threading.RLock()
def init(cfg):
def init():
irc = IRC()

@@ -38,3 +29,3 @@ irc.start()

if irc.events.joined.is_set():
logging.warning(fmt(irc.cfg, skip=["name", "password", "realname", "username"]))
logging.warning("%s", fmt(irc.cfg, skip=["name", "word", "realname", "username"]))
else:

@@ -45,10 +36,11 @@ irc.stop()

class Config:
class Config(Object):
channel = f"#{Main.name}"
commands = False
commands = True
control = "!"
ignore = ["PING", "PONG", "PRIVMSG"]
name = Main.name
nick = Main.name
password = ""
word = ""
port = 6667

@@ -65,2 +57,3 @@ realname = Main.name

def __init__(self):
super().__init__()
self.channel = Config.channel

@@ -75,5 +68,10 @@ self.commands = Config.commands

def __getattr__(self, name):
if name not in self:
return ""
return self.__getattribute__(name)
class Event(IEvent):
class Event(Message):
def __init__(self):

@@ -94,3 +92,3 @@ super().__init__()

def dosay(self, txt):
bot = Fleet.get(self.orig)
bot = broker(self.orig)
bot.dosay(self.channel, txt)

@@ -161,3 +159,3 @@

self.events.joined.clear()
if self.cfg.password:
if self.cfg.word or self.cfg.password:
logging.debug("using SASL")

@@ -199,3 +197,3 @@ self.cfg.sasl = True

def display(self, event):
for key in sorted(event.result, key=lambda x: x):
for key in sorted(event.result):
txt = event.result.get(key)

@@ -333,3 +331,3 @@ if not txt:

rawstr = rawstr.replace("\001", "")
rlog("debug", txt, IGNORE)
rlog(txt)
obj = Event()

@@ -349,3 +347,3 @@ obj.args = []

obj.command = arguments[1]
obj.type = obj.command
obj.kind = obj.command
if len(arguments) > 2:

@@ -389,3 +387,3 @@ txtlist = []

obj.text = obj.text.strip()
obj.type = obj.command
obj.kind = obj.command
return obj

@@ -423,3 +421,3 @@

text = text.rstrip()
rlog("debug", text, IGNORE)
rlog(text)
text = text[:500]

@@ -490,2 +488,3 @@ text += "\r\n"

def start(self):
last(self.cfg)
if self.cfg.channel not in self.channels:

@@ -518,9 +517,9 @@ self.channels.append(self.cfg.channel)

def cb_auth(evt):
bot = Fleet.get(evt.orig)
bot.docommand(f"AUTHENTICATE {bot.cfg.password}")
bot = broker(evt.orig)
bot.docommand(f"AUTHENTICATE {bot.cfg.word or bot.cfg.password}")
def cb_cap(evt):
bot = Fleet.get(evt.orig)
if bot.cfg.password and "ACK" in evt.arguments:
bot = broker(evt.orig)
if (bot.cfg.word or bot.cfg.password) and "ACK" in evt.arguments:
bot.direct("AUTHENTICATE PLAIN")

@@ -532,3 +531,3 @@ else:

def cb_error(evt):
bot = Fleet.get(evt.orig)
bot = broker(evt.orig)
bot.state.nrerror += 1

@@ -540,3 +539,3 @@ bot.state.error = evt.text

def cb_h903(evt):
bot = Fleet.get(evt.orig)
bot = broker(evt.orig)
bot.direct("CAP END")

@@ -547,3 +546,3 @@ bot.events.authed.set()

def cb_h904(evt):
bot = Fleet.get(evt.orig)
bot = broker(evt.orig)
bot.direct("CAP END")

@@ -562,3 +561,3 @@ bot.events.authed.set()

def cb_ready(evt):
bot = Fleet.get(evt.orig)
bot = broker(evt.orig)
bot.events.ready.set()

@@ -568,3 +567,3 @@

def cb_001(evt):
bot = Fleet.get(evt.orig)
bot = broker(evt.orig)
bot.events.logon.set()

@@ -574,3 +573,3 @@

def cb_notice(evt):
bot = Fleet.get(evt.orig)
bot = broker(evt.orig)
if evt.text.startswith("VERSION"):

@@ -582,3 +581,3 @@ txt = f"\001VERSION {Config.name.upper()} {Config.version} - {bot.cfg.username}\001"

def cb_privmsg(evt):
bot = Fleet.get(evt.orig)
bot = broker(evt.orig)
if not bot.cfg.commands:

@@ -602,3 +601,3 @@ return

def cb_quit(evt):
bot = Fleet.get(evt.orig)
bot = broker(evt.orig)
logging.debug("quit from %s", bot.cfg.server)

@@ -622,3 +621,3 @@ bot.state.nrerror += 1

keys(config),
skip="control,name,password,realname,sleep,username".split(","),
skip="control,name,word,realname,sleep,username".split(",")
)

@@ -636,3 +635,3 @@ )

return
bot = Fleet.get(event.orig)
bot = broker(event.orig)
if "cache" not in dir(bot):

@@ -668,8 +667,6 @@ event.reply("bot is missing cache")

def rlog(loglevel, txt, ignore=None):
if ignore is None:
ignore = []
for ign in ignore:
def rlog(txt):
for ign in Config.ignore:
if ign in str(txt):
return
logging.log(LEVELS.get(loglevel), txt)
logging.debug(txt)
# This file is been placed in the Public Domain.
from rssbot.persist import types
from rssbot.defines import kinds
def lst(event):
tps = types()
tps = kinds()
if tps:
event.reply(",".join([x.split(".")[-1].lower() for x in tps]))
event.reply(",".join({x.split(".")[-1].lower() for x in tps}))
else:
event.reply("no data yet.")

@@ -22,15 +22,8 @@ # This file is placed in the Public Domain.

from rssbot.clients import Fleet
from rssbot.methods import fmt
from rssbot.objects import Object, update
from rssbot.persist import find, fntime, getpath, last, write
from rssbot.repeats import Repeater
from rssbot.threads import launch
from rssbot.utility import elapsed, spl
from rssbot.defines import Config, Object, Repeater
from rssbot.defines import elapsed, fmt, find, last, launch, objs
from rssbot.defines import fntime, getpath, spl, update, write
DEBUG = False
def init(cfg):
def init():
fetcher = Fetcher()

@@ -41,3 +34,3 @@ fetcher.start()

else:
logging.warning("since %s", time.ctime(time.time()))
logging.warning("since %s", time.ctime(time.time()).replace(" ", " "))
return fetcher

@@ -48,3 +41,5 @@

importlock = _thread.allocate_lock()
errors: dict[str, float] = {}
errors = {}
skipped = []

@@ -136,3 +131,3 @@

txt2 = txt + self.display(obj)
for bot in Fleet.all():
for bot in objs("announce"):
bot.announce(txt2)

@@ -290,3 +285,3 @@ return counter

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

@@ -490,3 +485,3 @@ try:

feed.rss = event.args[0]
write(feed)
fnm = write(feed)
event.reply("ok")

@@ -496,3 +491,3 @@

def syn(event):
if DEBUG:
if Config.debug:
return

@@ -499,0 +494,0 @@ fetcher = Fetcher()

# This file is placed in the Public Domain.
from rssbot.command import Fleet
from rssbot.defines import broker
def sil(event):
bot = Fleet.get(event.orig)
bot = broker(event.orig)
bot.silent = True

@@ -14,4 +14,4 @@ event.reply("ok")

def lou(event):
bot = Fleet.get(event.orig)
bot = broker(event.orig)
bot.silent = False
event.reply("ok")

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

from rssbot.utility import elapsed
from rssbot.defines import elapsed

@@ -23,5 +23,5 @@

elif getattr(thread, "starttime", None):
uptime = int(time.time() - thread.starttime)
uptime = time.time() - thread.starttime
else:
uptime = int(time.time() - STARTTIME)
uptime = time.time() - STARTTIME
result.append((uptime, thread.name))

@@ -28,0 +28,0 @@ res = []

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

from rssbot.utility import elapsed
from rssbot.defines import elapsed

@@ -10,0 +10,0 @@

# This file is placed in the Public Domain.
import json
"a clean namespace"
import types
class Reserved(Exception):
pass
class Object:

@@ -24,2 +31,3 @@

def construct(obj, *args, **kwargs):
"object contructor."
if args:

@@ -37,10 +45,14 @@ val = args[0]

def fqn(obj):
kin = str(type(obj)).split()[-1][1:-2]
if kin == "type":
kin = f"{obj.__module__}.{obj.__name__}"
return kin
def asdict(obj):
"return object as dictionary."
res = {}
for key in dir(obj):
if key.startswith("_"):
continue
res[key] = getattr(obj, key)
return res
def items(obj):
"return object's key,valye pairs."
if isinstance(obj, dict):

@@ -50,16 +62,23 @@ return obj.items()

return obj.items()
return obj.__dict__.items()
res = []
for key in dir(obj):
if key.startswith("_"):
continue
res.append((key, getattr(obj, key)))
return res
def keys(obj):
"return object keys."
if isinstance(obj, dict):
return obj.keys()
return obj.__dict__.keys()
def update(obj, data={}, empty=True):
def update(obj, data, empty=True):
"update object,"
if isinstance(obj, type):
for k, v in items(data):
if isinstance(getattr(obj, k, None), types.MethodType):
continue
raise Reserved(k)
setattr(obj, k, v)

@@ -75,14 +94,26 @@ elif isinstance(obj, dict):

def values(obj):
"return object's values/"
if isinstance(obj, dict):
return obj.values()
return obj.__dict__.values()
res = []
for key in dir(obj):
if key.startswith("_"):
continue
res.append(getattr(obj, key))
return res
class Default(Object):
def __getattr__(self, key):
return self.__dict__.get(key, "")
def __dir__():
return (
'Default',
'Object',
'asdict',
'construct',
'fqn',
'items',

@@ -89,0 +120,0 @@ 'keys',

# This file is placed in the Public Domain.
import datetime
"persistence through storage"
import os
import json
import os
import pathlib
import threading
import time
from .marshal import dump, load
from .objects import Object, items, update
from .methods import deleted, fqn, search
from .objects import Object, keys, update
from .serials import dump, load
from .timings import fntime
from .utility import cdir
from .workdir import getpath, long, storage

@@ -21,30 +25,27 @@

objs = Object()
objects = {}
@staticmethod
def add(path, obj):
setattr(Cache.objs, path, obj)
@staticmethod
def get(path):
return getattr(Cache.objs, path, None)
def attrs(kind):
"show attributes for kind of objects."
objs = list(find(kind))
if objs:
return list(keys(objs[0][1]))
return []
@staticmethod
def update(path, obj):
setattr(Cache.objs, path, obj)
def cache(path):
"return object from cache."
return Cache.objects.get(path, None)
def deleted(obj):
return "__deleted__" in dir(obj) and obj.__deleted__
def find(type=None, selector=None, removed=False, matching=False):
if selector is None:
selector = {}
for pth in fns(type):
obj = Cache.get(pth)
def find(kind, selector={}, removed=False, matching=False):
"locate objects by matching atributes."
fullname = long(kind)
for pth in fns(fullname):
obj = cache(pth)
if not obj:
obj = Object()
read(obj, pth)
Cache.add(pth, obj)
put(pth, obj)
if not removed and deleted(obj):

@@ -57,6 +58,5 @@ continue

def fns(type=None):
if type is not None:
type = type.lower()
path = store()
def fns(kind):
"return file names by kind of object."
path = storage(kind)
for rootdir, dirs, _files in os.walk(path, topdown=True):

@@ -67,4 +67,2 @@ for dname in dirs:

ddd = os.path.join(rootdir, dname)
if type and type not in ddd.lower():
continue
for fll in os.listdir(ddd):

@@ -74,18 +72,4 @@ yield os.path.join(ddd, fll)

def fntime(daystr):
datestr = " ".join(daystr.split(os.sep)[-2:])
datestr = datestr.replace("_", " ")
if "." in datestr:
datestr, rest = datestr.rsplit(".", 1)
else:
rest = ""
timed = time.mktime(time.strptime(datestr, "%Y-%m-%d %H:%M:%S"))
if rest:
timed += float("." + rest)
return float(timed)
def last(obj, selector=None):
if selector is None:
selector = {}
def last(obj, selector={}):
"return last saved version."
result = sorted(

@@ -103,3 +87,9 @@ find(fqn(obj), selector),

def put(path, obj):
"put object into cache."
Cache.objects[path] = obj
def read(obj, path):
"read object from path."
with lock:

@@ -114,21 +104,14 @@ with open(path, "r", encoding="utf-8") as fpt:

def search(obj, selector, matching=False):
res = False
for key, value in items(selector):
val = getattr(obj, key, None)
if not val:
continue
if matching and value == val:
res = True
elif str(value).lower() in str(val).lower():
res = True
else:
res = False
break
return res
def sync(path, obj):
"update cached object."
try:
update(Cache.objects[path], obj)
except KeyError:
put(path, obj)
def write(obj, path=None):
def write(obj, path=""):
"write object to disk."
with lock:
if path is None:
if path == "":
path = getpath(obj)

@@ -138,75 +121,18 @@ cdir(path)

dump(obj, fpt, indent=4)
Cache.update(path, obj)
sync(path, obj)
return path
class Workdir:
wdr = ""
@staticmethod
def init(name):
Workdir.wdr = os.path.expanduser(f"~/.{name}")
def cdir(path):
pth = pathlib.Path(path)
pth.parent.mkdir(parents=True, exist_ok=True)
def fqn(obj):
kin = str(type(obj)).split()[-1][1:-2]
if kin == "type":
kin = f"{obj.__module__}.{obj.__name__}"
return kin
def getpath(obj):
return store(ident(obj))
def ident(obj):
return os.path.join(fqn(obj), *str(datetime.datetime.now()).split())
def moddir(modname=None):
return os.path.join(Workdir.wdr, modname or "mods")
def pidname(name):
assert Workdir.wdr
return os.path.join(Workdir.wdr, f"{name}.pid")
def skel():
pth = pathlib.Path(store())
pth.mkdir(parents=True, exist_ok=True)
pth = pathlib.Path(moddir())
pth.mkdir(parents=True, exist_ok=True)
def store(fnm=""):
return os.path.join(Workdir.wdr, "store", fnm)
def types():
return os.listdir(store())
def __dir__():
return (
'Cache',
'Workdir',
'cdir',
'attrs',
'cache',
'find',
'fntime',
'fqn',
'fqn',
'getpath',
'ident',
'fns',
'last',
'put',
'read',
'skel',
'types',
'sync',
'write'
)
# This file is placed in the Public Domain.
"things are repeating."
import threading

@@ -35,2 +38,3 @@ import time

def run(self):
"run timed function."
self.timer.latest = time.time()

@@ -40,2 +44,3 @@ self.func(*self.args)

def start(self):
"start timer."
self.kwargs["name"] = self.name

@@ -47,2 +52,3 @@ timer = Timy(self.sleep, self.run, *self.args, **self.kwargs)

def stop(self):
"stop timer."
if self.timer:

@@ -55,2 +61,3 @@ self.timer.cancel()

def run(self):
"run function and launch timer for next run."
launch(self.start)

@@ -63,3 +70,3 @@ super().run()

'Repeater',
'Timed'
)
'Timed',
)
# This file is placed in the Public Domain.
"make it not blocking"
import inspect
import logging

@@ -11,5 +15,2 @@ import queue

from .methods import name
class Thread(threading.Thread):

@@ -19,2 +20,3 @@

super().__init__(None, self.run, None, (), daemon=daemon)
self.event = None
self.name = kwargs.get("name", name(func))

@@ -33,23 +35,47 @@ self.queue = queue.Queue()

def join(self, timeout=None):
super().join(timeout)
return self.result
def join(self, timeout=0.0):
"join thread and return result."
try:
super().join(timeout or None)
return self.result
except (KeyboardInterrupt, EOFError) as ex:
if self.event:
self.event.ready()
raise ex
def run(self):
"run function."
func, args = self.queue.get()
self.result = func(*args)
if args and hasattr(args[0], "ready"):
self.event = args[0]
try:
self.result = func(*args)
except (KeyboardInterrupt, EOFError):
if self.event:
self.event.ready()
_thread.interrupt_main()
except Exception as ex:
if self.event:
self.event.ready()
logging.exception(ex)
_thread.interrupt_main()
def launch(func, *args, **kwargs):
thread = Thread(func, *args, **kwargs)
thread.start()
return thread
"run function in a thread."
try:
thread = Thread(func, *args, **kwargs)
thread.start()
return thread
except (KeyboardInterrupt, EOFError):
_thread.interrupt_main()
def threadhook(args):
type, value, trace, thread = args
exc = value.with_traceback(trace)
if type not in (KeyboardInterrupt, EOFError):
logging.exception(exc)
_thread.interrupt_main()
def name(obj):
"return string of function/method."
if inspect.ismethod(obj):
return f"{obj.__self__.__class__.__name__}.{obj.__name__}"
if inspect.isfunction(obj):
return repr(obj).split()[1]
return repr(obj)

@@ -61,3 +87,3 @@

'launch',
'threadhook'
)
'name'
)
# This file is placed in the Public Domain.
import logging
"dumpyard"
import datetime
import inspect
import os
import pathlib
import sys
import time
FORMATS = [
"%Y-%M-%D %H:%M:%S",
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%d",
"%d-%m-%Y",
"%d-%m",
"%m-%d"
]
from .methods import fqn
def check(text):
args = sys.argv[1:]
for arg in args:
if not arg.startswith("-"):
continue
for char in text:
if char in arg:
return True
return False
def cdir(path):
"create directory."
pth = pathlib.Path(path)
pth.parent.mkdir(parents=True, exist_ok=True)
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 ident(obj):
"return ident string for object."
return os.path.join(fqn(obj), *str(datetime.datetime.now()).split())
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 fmat in FORMATS:
try:
res = time.mktime(time.strptime(daystr, fmat))
break
except ValueError:
pass
return res
def forever():
while True:
try:
time.sleep(0.1)
except (KeyboardInterrupt, EOFError):
break
def md5sum(path):
"return md5 of a file."
import hashlib
with open(path, "r", encoding="utf-8") as file:
txt = file.read().encode("utf-8")
return hashlib.md5(txt).hexdigest()
return hashlib.md5(txt, usedforsecurity=False).hexdigest()
def pidfile(filename):
if os.path.exists(filename):
os.unlink(filename)
path2 = pathlib.Path(filename)
path2.parent.mkdir(parents=True, exist_ok=True)
with open(filename, "w", encoding="utf-8") as fds:
fds.write(str(os.getpid()))
def privileges():
import getpass
import pwd
pwnam2 = pwd.getpwnam(getpass.getuser())
os.setgid(pwnam2.pw_gid)
os.setuid(pwnam2.pw_uid)
def spl(txt):
"return list from command seperated string."
try:

@@ -145,3 +45,3 @@ result = txt.split(",")

def where(obj):
import inspect
"return path where object is defined."
return os.path.dirname(inspect.getfile(obj))

@@ -151,2 +51,3 @@

def wrapped(func):
"wrap function in a try/except, silence ctrl-c/ctrl-d."
try:

@@ -158,30 +59,10 @@ func()

def wrap(func):
import termios
old = None
try:
old = termios.tcgetattr(sys.stdin.fileno())
except termios.error:
pass
try:
wrapped(func)
finally:
if old:
termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old)
def __dir__():
return (
'check',
'daemon',
'elapsed',
'extract_date',
'forever',
'cdir',
'ident',
'md5sum',
'pidfile',
'privileges',
'spl',
'where',
'wrap',
'wrapped'
)
)
# This file is placed in the Public Domain.
import json
import types
class Encoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, dict):
return o.items()
if isinstance(o, list):
return iter(o)
if isinstance(o, types.MappingProxyType):
return dict(o)
try:
return json.JSONEncoder.default(self, o)
except TypeError:
try:
return vars(o)
except TypeError:
return repr(o)
def dump(*args, **kw):
kw["cls"] = Encoder
return json.dump(*args, **kw)
def dumps(*args, **kw):
kw["cls"] = Encoder
return json.dumps(*args, **kw)
def load(s, *args, **kw):
return json.load(s, *args, **kw)
def loads(s, *args, **kw):
return json.loads(s, *args, **kw)
def __dir__():
return (
'dump',
'dumps',
'load',
'loads'
)
# This file is placed in the Public Domain.
from rssbot.package import modules
def mod(event):
event.reply(",".join(modules()))
# This file is placed in the Public Domain.
"network"
# This file is placed in the Public Domain.
import importlib
import importlib.util
import os
import sys
from .utility import spl, where
class Mods:
dirs: dict[str, str] = {}
ignore: list[str] = []
@staticmethod
def add(name=None, path=None):
Mods.dirs[name] = path
@staticmethod
def init(name, ignore="", local=False):
if name:
pkg = importer(name)
if pkg:
Mods.add(name, pkg.__path__[0])
if ignore:
Mods.ignore = spl(ignore)
if local:
Mods.add("mods", "mods")
def getmod(name):
mname = ""
pth = ""
if name in Mods.ignore:
return
for packname, path in Mods.dirs.items():
modpath = os.path.join(path, name + ".py")
if os.path.exists(modpath):
pth = modpath
mname = f"{packname}.{name}"
break
return sys.modules.get(mname, None) or importer(mname, pth)
def importer(name, pth=None):
if pth and os.path.exists(pth):
spec = importlib.util.spec_from_file_location(name, pth)
else:
spec = importlib.util.find_spec(name)
if not spec:
return
mod = importlib.util.module_from_spec(spec)
if not mod:
return
sys.modules[name] = mod
spec.loader.exec_module(mod)
return mod
def modules():
mods = []
for name, path in Mods.dirs.items():
if name in Mods.ignore:
continue
if not os.path.exists(path):
continue
mods.extend([
x[:-3] for x in os.listdir(path)
if x.endswith(".py") and not x.startswith("__") and x not in Mods.ignore
])
return sorted(mods)
def __dir__():
return (
'Mods',
'getmod',
'importer',
'modules',
)

Sorry, the diff of this file is not supported yet