From 406bd210bc58d27f1fd2aa9b361ba15a1e070d3d Mon Sep 17 00:00:00 2001 From: David Schultz Date: Thu, 13 May 2021 18:05:50 -0500 Subject: [PATCH] Initial commit --- .gitignore | 132 +++++++++++++++++++++++++++++++++++++++++++ LICENSE | 21 +++++++ README.md | 2 + VERSION | 1 + examples/example.py | 12 ++++ ircflags/__init__.py | 102 +++++++++++++++++++++++++++++++++ ircflags/py.typed | 0 setup.py | 23 ++++++++ 8 files changed, 293 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 VERSION create mode 100644 examples/example.py create mode 100644 ircflags/__init__.py create mode 100644 ircflags/py.typed create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ab30ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,132 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Ignore example access files +examples/*.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..336e0d7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 David Schultz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e2419ba --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# ircflags +easy IRC bot access control diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..8acdd82 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.1 diff --git a/examples/example.py b/examples/example.py new file mode 100644 index 0000000..225eac0 --- /dev/null +++ b/examples/example.py @@ -0,0 +1,12 @@ +from ircflags import Flags + +flags = Flags("access.json", "ABCDefg") + +flags.setFlags("lunchd", "+AeBf") +print(flags.getFlags("lunchd")) +flags.setFlags("lunchd", "+C", channel="#test") +print(flags.getFlags("lunchd", channel="#test")) +flags.setFlags("lunchd", "+*", channel="#test") +print(flags.getFlags("lunchd", channel="#test")) +flags.setFlags("lunchd", "-*", channel="#test") +print(flags.getFlags("lunchd", channel="#test")) diff --git a/ircflags/__init__.py b/ircflags/__init__.py new file mode 100644 index 0000000..6d17b64 --- /dev/null +++ b/ircflags/__init__.py @@ -0,0 +1,102 @@ +from typing import List, Set +from os.path import isfile +import json + +class Flags(object): + def __init__(self, + file: str, + validflags: str): + + if not validflags.isalpha(): + raise ValueError("Flags can only be letters of the alphabet") + + self.file = file + self.validflags = sorted(validflags) + self.database = {} + + self._from_file() + + def _serialize(self, value: Set[str]) -> List[str]: + return list(value) + + def _deserialize(self, value: List[str]) -> Set[str]: + return set(value) + + def _from_file(self): + if isfile(self.file): + with open(self.file) as db_file: + database = json.loads(db_file.read()) + + for t in database.keys(): + if not t in self.database.keys(): + self.database[t] = {} + self.database[t].update({ + k: self._deserialize(v) for k,v in database[t].items() + }) + + def _write(self): + database_serialized = { + t: { + k: self._serialize(v) for k,v in self.database[t].items() + } for t in self.database.keys() + } + data = json.dumps(database_serialized, indent=4, sort_keys=True) + with open(self.file, "w") as db_file: + db_file.write(data) + + def getFlags(self, + user: str, + default: str = '', + channel: str = None): + + user = user.lower() + + if channel and channel.lower() in self.database.keys() and user in self.database[channel]: + target = channel.lower() + else: + target = "global" + + try: + value = self.database[target][user] + except KeyError: + value = default + + return "".join(sorted(value)) + + def setFlags(self, + user: str, + value: str, + channel: str = None): + + user = user.lower() + + if channel: + target = channel.lower() + else: + target = "global" + + if not target in self.database.keys(): + self.database[target] = {} + + flags = {f for f in self.database[target].get(user, "")} + op = value[0] + if not op in "+-": + raise ValueError(f"{op} is not a valid operator") + for c in value[1:]: + if c in "+-": + op = c + elif c in self.validflags: + if op == "+" and not c in flags: + flags.add(c) + elif op == "-" and c in flags: + flags.remove(c) + elif c == "*": + if op == "+": + flags = set("".join(self.validflags)) + elif op == "-": + flags = set() + else: + raise ValueError(f"{c} is not a valid flag") + + self.database[target][user] = flags + self._write() diff --git a/ircflags/py.typed b/ircflags/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..cb91abb --- /dev/null +++ b/setup.py @@ -0,0 +1,23 @@ +import setuptools + +with open("VERSION", "r") as f: + version = f.read().strip() + +setuptools.setup( + name="ircflags", + version=version, + author="examknow", + author_email="me@zpld.me", + description="easy IRC bot access control", + url="https://github.com/examknow/ircflags", + packages=setuptools.find_packages(), + package_data={"ircflags": ["py.typed"]}, + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: Microsoft :: Windows" + ], + python_requires='>=3.6', +)