Compare commits

...

13 Commits

14 changed files with 901 additions and 0 deletions

1
.envrc Normal file
View File

@ -0,0 +1 @@
use flake

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
__pycache__
secrets.py
runs
.ipynb_*
*.csv
.direnv

View File

@ -0,0 +1,69 @@
Providers are modules that export:
CONFIG = {
"config key": "optional | required",
...
}
update(config, state) -> list[items]
And optionally:
action_NAME(config, state, item) -> None
on_create(config, state, item) -> None
on_delete(config, state, item) -> None
Sources are folders in the data directory with:
config: user-modified configuration values, drawn from the provider
state: provider-modified data
*.item: feed items
config reserved keys:
"provider": the provider the source uses
"name": given to sources by default, individuates sources from same provider
GET /config
Edit subfeed configs
GET /config/[feedname]
Edit config values for feedname
GET /feed/[feedname]
Feed view for feedname
<!-- GET /items
Get item JSON
?include=[tags]
?exclude=[tags]
?limit=100 -->
CLI:
intake create [provider] [--config KEY VALUE] [-c KEY VALUE] ...
load the provider
verify that the provider's config is satisfied
create the source and set the config values
intake update [source] [--reset]
load the source's config and state
use config.provider to load the provider
verify the source config against the provider config
call provider.update(config, state) to get current items
merge new items into current items
if --reset, new items overwrite everything, including active
intake deactivate [source] [--tag TAG] [--title TITLE]
load each item in the source and deactivate it
intake add [--id ID] [--title TITLE] [--link LINK] [--time TIME] [--author AUTHOR] [--body BODY] [--tags TAGS] [--ttl TTL] [--ttd TTD] [--tts TTS]
create the item and add it to the default source
intake feed [--json]
Dump active item feed to stdout
intake action [source] [item] [action]
load config, state, item
verify item supports this action
load config.provider
verify config
verify provider supports this action
action_[action](config, state, item)

7
default.nix Normal file
View File

@ -0,0 +1,7 @@
(import (
fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }
) {
src = ./.;
}).defaultNix

43
flake.lock generated Normal file
View File

@ -0,0 +1,43 @@
{
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1659877975,
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1660162369,
"narHash": "sha256-pZukMP4zCA1FaBg0xHxf7KdE/Nv/C5YbDID7h2L8O7A=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "3a11db5f408095b8f08b098ec2066947f4b72ce2",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

33
flake.nix Normal file
View File

@ -0,0 +1,33 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = {self, nixpkgs, flake-utils}:
let
makeFlakeOutputs = system:
let
pname = "intake";
pkgs = nixpkgs.legacyPackages."${system}";
in
{
devShell = pkgs.mkShell {
buildInputs = with pkgs; [
python3Packages.poetry
];
};
defaultPackage = with pkgs.poetry2nix; mkPoetryApplication {
projectDir = builtins.path { path = ./server; name = pname; };
preferWheels = true;
};
defaultApp = flake-utils.lib.mkApp {
drv = self.defaultPackage."${system}";
};
};
in
with flake-utils.lib; eachSystem defaultSystems makeFlakeOutputs;
}

21
providers/echo.py Normal file
View File

@ -0,0 +1,21 @@
from intake import BaseSettings, Setting
class Settings(BaseSettings):
message = Setting(default="Hello, world!")
def update(config, state):
pass
def on_create(config, state, item):
pass
def on_delete(config, state, item):
pass
def action_tick(config, state, item):
pass

View File

@ -0,0 +1 @@
from .provider import Setting, BaseSettings

193
server/intake/cli.py Normal file
View File

@ -0,0 +1,193 @@
import argparse
import inspect
import os
from signal import signal, SIGPIPE, SIG_DFL
import sys
from intake.provider import (
AttributeTypeError,
FunctionSignatureError,
load_provider,
RequiredAttributeMissingError,
verify_action,
verify_oncreate,
verify_ondelete,
verify_settings,
verify_update,
)
from intake.source import initialize_source
def command_create(args):
"""Create a source."""
parser = argparse.ArgumentParser(
prog="intake create",
description=command_create.__doc__)
parser.add_argument("provider",
help="Provider to create the source from",
metavar="provider")
parser.add_argument("--setting", "-s",
help="Define a provider setting",
nargs=2,
action="append",
metavar="key value",
dest="settings",
default=[])
parser.add_argument("--data",
help="Data folder path",
metavar="path",
type=os.path.abspath)
parser.add_argument("--path",
nargs="+",
help="Additional paths to add to INTAKEPATH",
metavar="path",
type=os.path.abspath,
default=[])
args = parser.parse_args()
ret = 0
settings = {key: value for key, value in args.settings}
initialize_source(args.data, args.path, **settings)
def command_test(args):
"""Check for errors or misconfigurations."""
parser = argparse.ArgumentParser(
prog="intake test",
description=command_test.__doc__)
parser.add_argument("--provider",
nargs="+",
help="Providers to test.",
metavar="name",
dest="providers",
default=[])
parser.add_argument("--path",
nargs="+",
help="Additional paths to add to INTAKEPATH",
metavar="path",
type=os.path.abspath,
default=[])
args = parser.parse_args(args)
ret = 0
search_path = args.path
print("INTAKEPATH:")
for path in search_path:
print(f" {path}")
for provider_name in args.providers:
print(f"Checking provider {provider_name}")
provider = load_provider(search_path, provider_name)
if not provider:
print(" x Not found")
ret = 1
continue
# Settings class
try:
verify_settings(provider)
print(" o Settings")
except RequiredAttributeMissingError:
print(" x Missing Settings class")
ret = 1
except AttributeTypeError:
print(" x Settings class does not inherit from intake.BaseSettings")
ret = 1
# update function
try:
verify_update(provider)
print(" o update")
except RequiredAttributeMissingError:
print(" x Missing update(config, state)")
ret = 1
except AttributeTypeError:
print(" x update is not callable")
ret = 1
except FunctionSignatureError:
print(" x update does not have signature (config, state)")
ret = 1
# on-create hook
try:
if verify_oncreate(provider):
print(" o on_create")
except AttributeTypeError:
print(" x on_create is not callable")
ret = 1
except FunctionSignatureError:
print(" x on_create does not have signature (config, state, item)")
ret = 1
# on-delete hook
try:
if verify_ondelete(provider):
print(" o on_delete")
except AttributeTypeError:
print(" x on_delete is not callable")
ret = 1
except FunctionSignatureError:
print(" x on_delete does not have signature (config, state, item)")
ret = 1
# actions
actions = [name for name in vars(provider) if name.startswith("action_")]
for action_name in actions:
try:
if verify_action(provider, action_name):
print(f" o {action_name}")
except AttributeTypeError:
print(f" x {action_name} is not callable")
ret = 1
except FunctionSignatureError:
print(f" x {action_name} does not have signature (config, state, item)")
ret = 1
print("Done")
return ret
def command_help(args):
"""Print this help message and exit."""
print_usage()
return 0
def main():
"""CLI entry point"""
# Enable piping
signal(SIGPIPE, SIG_DFL)
# Get the available commands by reflection
cli = sys.modules[__name__]
commands = {
name[8:]: func
for name, func in vars(cli).items()
if name.startswith("command_")
}
descriptions = "\n".join([
f" {name} - {func.__doc__}"
for name, func in commands.items()])
# Set up the top-level parser
parser = argparse.ArgumentParser(
description=f"Available commands:\n{descriptions}\n",
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("command",
nargs="?",
default="help",
help="The command to execute",
choices=commands,
metavar="command")
parser.add_argument("args",
nargs=argparse.REMAINDER,
help="Command arguments",
metavar="args")
# Pass the parser's help printer to command_help via global
global print_usage
print_usage = parser.print_help
args = parser.parse_args()
# Execute command
if args.command:
sys.exit(commands[args.command](args.args))
else:
parser.print_usage()
sys.exit(0)

193
server/intake/provider.py Normal file
View File

@ -0,0 +1,193 @@
import importlib.util
import inspect
import os
import sys
class SettingMissingError(Exception):
"""
No value was provided for a required setting.
"""
def __init__(self, missing_names):
super().__init__("Missing required settings: {}".format(", ".join(missing_names)))
self.missing_names = missing_names
class Setting:
"""
A setting value declared by a provider and defined by a source.
"""
def __init__(self, required=False, default=None):
self.required = required
self.value = default
class BaseSettings:
"""
Base class for provider settings.
"""
name = Setting(required=True)
provider = Setting(required=True)
def __init__(self, **kwargs):
setting_names = [name for name in dir(self) if not name.startswith("__")]
missing = []
for setting_name in setting_names:
setting: Setting = getattr(self, setting_name)
if setting_name in kwargs:
setting.value = kwargs[setting_name]
elif setting.required:
missing.append(setting_name)
if missing:
raise SettingMissingError(missing)
def issetting(obj):
return isinstance(obj, Setting)
class chdir:
"""
A context manager that changes the working directory inside the context.
"""
def __init__(self, path):
self.cwd = os.getcwd()
os.chdir(path)
def __enter__(self):
pass
def __exit__(self, *args):
os.chdir(self.cwd)
class add_to_sys_path:
"""
A context manager that adds a path to sys.path, allowing imports from it.
"""
def __init__(self, path):
self.path = path
self.not_present = path not in sys.path
if self.not_present:
sys.path.insert(0, path)
def __enter__(self):
pass
def __exit__(self, *args):
if self.not_present:
sys.path.remove(self.path)
def load_provider(search_paths, provider_name):
"""
Load a provider on the search path. If the provider cannot be found,
return None.
"""
for search_path in search_paths:
with chdir(search_path), add_to_sys_path(search_path):
# Check if the provider is on this path.
provider_filename = f"{provider_name}.py"
if not os.path.isfile(provider_filename):
continue
# Import the provider by file path.
spec = importlib.util.spec_from_file_location(provider_name, provider_filename)
provider_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(provider_module)
provider = importlib.import_module(provider_name)
return provider
return None
class ProviderError(Exception):
"""A provider's attributes are incorrect."""
class RequiredAttributeMissingError(ProviderError):
"""A provider is missing a required attribute."""
class AttributeTypeError(ProviderError):
"""A provider has an attribute with a wrong type."""
class FunctionSignatureError(ProviderError):
"""A provider has a function attribute with the wrong signature."""
def verify_settings(provider):
"""
Verify that a provider's Settings is valid.
"""
if not hasattr(provider, "Settings"):
raise RequiredAttributeMissingError()
settings = getattr(provider, "Settings")
if not issubclass(settings, BaseSettings):
raise AttributeTypeError()
return True
def verify_update(provider):
"""
Verify that a provider's update() is valid.
"""
if not hasattr(provider, "update"):
raise RequiredAttributeMissingError()
update = getattr(provider, "update")
if not callable(update):
raise AttributeTypeError()
update_sig = inspect.signature(update)
if list(update_sig.parameters) != ["config", "state"]:
raise FunctionSignatureError()
return True
def verify_oncreate(provider):
"""
Verify that a provider's on_create() is valid.
"""
if not hasattr(provider, "on_create"):
return False
on_create = getattr(provider, "on_create")
if not callable(on_create):
raise AttributeTypeError()
create_sig = inspect.signature(on_create)
if list(create_sig.parameters) != ["config", "state", "item"]:
raise FunctionSignatureError()
return True
def verify_ondelete(provider):
"""
Verify that a provider's on_delete() is valid.
"""
if not hasattr(provider, "on_delete"):
return False
on_delete = getattr(provider, "on_delete")
if not callable(on_delete):
raise AttributeTypeError()
delete_sig = inspect.signature(on_delete)
if list(delete_sig.parameters) != ["config", "state", "item"]:
raise FunctionSignatureError()
return True
def verify_action(provider, name):
"""
Verify that a provider's action_*() is valid.
"""
action_name = name if name.startswith("action_") else f"action_{name}"
if not hasattr(provider, "action_name"):
return False
action = getattr(provider, action_name)
if not callable(action):
raise AttributeTypeError()
action_sig = inspect.signature(action)
if list(action_sig.parameters) != ["config", "state", "item"]:
raise FunctionSignatureError()
return True

50
server/intake/source.py Normal file
View File

@ -0,0 +1,50 @@
import inspect
import json
import os
from intake.provider import (
load_provider,
issetting,
SettingMissingError,
verify_settings,
)
def initialize_source(data_path, search_paths, **setting_kvs):
"""
Create a source's data folder and store its settings.
"""
# Verify the base required settings
if "name" not in setting_kvs:
raise KeyError("Missing name in settings")
if "provider" not in setting_kvs:
raise KeyError("Missing provider in settings")
# Check if the directory exists already
source_name = setting_kvs.get("name")
source_path = os.path.join(data_path, source_name)
if os.path.exists(source_path):
raise FileExistsError(source_path)
# Load the provider
provider_name = setting_kvs.get("provider")
provider = load_provider(search_paths, provider_name)
if not provider:
raise FileNotFoundError(provider_name)
# Verify that the provider has settings
verify_settings(provider)
# Check that all the required settings are populated
settings = inspect.getmembers(provider.Settings, issetting)
for name, setting in settings:
if setting.required and name not in setting_kvs:
raise SettingMissingError()
# Create the directory
os.mkdir(source_path)
# Create the settings file
setting_json = {}
for name, setting in settings:
setting_json[name] = setting_kvs.get(name, setting.default)
with open(os.path.join(source_path, ".settings"), 'w') as f:
json.dump(setting_json, f, indent=2)
# Create the state file
with open(os.path.join(source_path, ".state"), 'w') as f:
json.dump({}, f, indent=2)

257
server/poetry.lock generated Normal file
View File

@ -0,0 +1,257 @@
[[package]]
name = "certifi"
version = "2022.6.15"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "charset-normalizer"
version = "2.1.0"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
python-versions = ">=3.6.0"
[package.extras]
unicode_backport = ["unicodedata2"]
[[package]]
name = "click"
version = "8.1.3"
description = "Composable command line interface toolkit"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "colorama"
version = "0.4.5"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "feedparser"
version = "6.0.10"
description = "Universal feed parser, handles RSS 0.9x, RSS 1.0, RSS 2.0, CDF, Atom 0.3, and Atom 1.0 feeds"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
sgmllib3k = "*"
[[package]]
name = "flask"
version = "2.2.2"
description = "A simple framework for building complex web applications."
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
click = ">=8.0"
itsdangerous = ">=2.0"
Jinja2 = ">=3.0"
Werkzeug = ">=2.2.2"
[package.extras]
async = ["asgiref (>=3.2)"]
dotenv = ["python-dotenv"]
[[package]]
name = "idna"
version = "3.3"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "itsdangerous"
version = "2.1.2"
description = "Safely pass data to untrusted environments and back."
category = "main"
optional = false
python-versions = ">=3.7"
[[package]]
name = "jinja2"
version = "3.1.2"
description = "A very fast and expressive template engine."
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]]
name = "markupsafe"
version = "2.1.1"
description = "Safely add untrusted strings to HTML/XML markup."
category = "main"
optional = false
python-versions = ">=3.7"
[[package]]
name = "requests"
version = "2.28.1"
description = "Python HTTP for Humans."
category = "main"
optional = false
python-versions = ">=3.7, <4"
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2,<3"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<1.27"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "sgmllib3k"
version = "1.0.0"
description = "Py3k port of sgmllib."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "urllib3"
version = "1.26.11"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
[package.extras]
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "werkzeug"
version = "2.2.2"
description = "The comprehensive WSGI web application library."
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
MarkupSafe = ">=2.1.1"
[package.extras]
watchdog = ["watchdog"]
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
content-hash = "d214f7eef1fcb2260a812ea5bbd90854fbcf180a682003b33576d37dcbb20eb3"
[metadata.files]
certifi = [
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
]
charset-normalizer = [
{file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"},
{file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"},
]
click = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
]
colorama = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
]
feedparser = [
{file = "feedparser-6.0.10-py3-none-any.whl", hash = "sha256:79c257d526d13b944e965f6095700587f27388e50ea16fd245babe4dfae7024f"},
{file = "feedparser-6.0.10.tar.gz", hash = "sha256:27da485f4637ce7163cdeab13a80312b93b7d0c1b775bef4a47629a3110bca51"},
]
flask = [
{file = "Flask-2.2.2-py3-none-any.whl", hash = "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"},
{file = "Flask-2.2.2.tar.gz", hash = "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b"},
]
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
]
itsdangerous = [
{file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
{file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
]
jinja2 = [
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
]
markupsafe = [
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
{file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
{file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
{file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
{file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
{file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
]
requests = [
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
]
sgmllib3k = [
{file = "sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9"},
]
urllib3 = [
{file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"},
{file = "urllib3-1.26.11.tar.gz", hash = "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"},
]
werkzeug = [
{file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"},
{file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"},
]

20
server/pyproject.toml Normal file
View File

@ -0,0 +1,20 @@
[tool.poetry]
name = "intake"
version = "0.1.0"
description = ""
authors = ["Jaculabilis <jaculabilis@git.alogoulogoi.com>"]
[tool.poetry.dependencies]
python = "^3.10"
Flask = "^2.2.2"
requests = "^2.28.1"
feedparser = "^6.0.10"
[tool.poetry.dev-dependencies]
[tool.poetry.scripts]
intake = "intake.cli:main"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

7
shell.nix Normal file
View File

@ -0,0 +1,7 @@
(import (
fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }
) {
src = ./.;
}).shellNix