done too many things. hope this works.

This commit is contained in:
m 2020-12-17 17:07:58 +01:00
parent 2e53272001
commit 997986d892
29 changed files with 60496 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
app/main_temp.py
database/people*
database/task*
.mypy_cache

114
.vscode/.ropeproject/config.py vendored Normal file
View File

@ -0,0 +1,114 @@
# The default ``config.py``
# flake8: noqa
def set_prefs(prefs):
"""This function is called before opening the project"""
# Specify which files and folders to ignore in the project.
# Changes to ignored resources are not added to the history and
# VCSs. Also they are not returned in `Project.get_files()`.
# Note that ``?`` and ``*`` match all characters but slashes.
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
# '.svn': matches 'pkg/.svn' and all of its children
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject',
'.hg', '.svn', '_svn', '.git', '.tox']
# Specifies which files should be considered python files. It is
# useful when you have scripts inside your project. Only files
# ending with ``.py`` are considered to be python files by
# default.
# prefs['python_files'] = ['*.py']
# Custom source folders: By default rope searches the project
# for finding source folders (folders that should be searched
# for finding modules). You can add paths to that list. Note
# that rope guesses project source folders correctly most of the
# time; use this if you have any problems.
# The folders should be relative to project root and use '/' for
# separating folders regardless of the platform rope is running on.
# 'src/my_source_folder' for instance.
# prefs.add('source_folders', 'src')
# You can extend python path for looking up modules
# prefs.add('python_path', '~/python/')
# Should rope save object information or not.
prefs['save_objectdb'] = True
prefs['compress_objectdb'] = False
# If `True`, rope analyzes each module when it is being saved.
prefs['automatic_soa'] = True
# The depth of calls to follow in static object analysis
prefs['soa_followed_calls'] = 0
# If `False` when running modules or unit tests "dynamic object
# analysis" is turned off. This makes them much faster.
prefs['perform_doa'] = True
# Rope can check the validity of its object DB when running.
prefs['validate_objectdb'] = True
# How many undos to hold?
prefs['max_history_items'] = 32
# Shows whether to save history across sessions.
prefs['save_history'] = True
prefs['compress_history'] = False
# Set the number spaces used for indenting. According to
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's
# unit-tests use 4 spaces it is more reliable, too.
prefs['indent_size'] = 4
# Builtin and c-extension modules that are allowed to be imported
# and inspected by rope.
prefs['extension_modules'] = []
# Add all standard c-extensions to extension_modules list.
prefs['import_dynload_stdmods'] = True
# If `True` modules with syntax errors are considered to be empty.
# The default value is `False`; When `False` syntax errors raise
# `rope.base.exceptions.ModuleSyntaxError` exception.
prefs['ignore_syntax_errors'] = False
# If `True`, rope ignores unresolvable imports. Otherwise, they
# appear in the importing namespace.
prefs['ignore_bad_imports'] = False
# If `True`, rope will insert new module imports as
# `from <package> import <module>` by default.
prefs['prefer_module_from_imports'] = False
# If `True`, rope will transform a comma list of imports into
# multiple separate import statements when organizing
# imports.
prefs['split_imports'] = False
# If `True`, rope will remove all top-level import statements and
# reinsert them at the top of the module when making changes.
prefs['pull_imports_to_top'] = True
# If `True`, rope will sort imports alphabetically by module name instead
# of alphabetically by import statement, with from imports after normal
# imports.
prefs['sort_imports_alphabetically'] = False
# Location of implementation of
# rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
# case, you don't have to change this value, unless you're an rope expert.
# Change this value to inject you own implementations of interfaces
# listed in module rope.base.oi.type_hinting.providers.interfaces
# For example, you can add you own providers for Django Models, or disable
# the search type-hinting in a class hierarchy, etc.
prefs['type_hinting_factory'] = (
'rope.base.oi.type_hinting.factory.default_type_hinting_factory')
def project_opened(project):
"""This function is called after opening the project"""
# Do whatever you like here!

BIN
.vscode/.ropeproject/objectdb vendored Normal file

Binary file not shown.

13
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/*.pyc": true,
"**/__pycache__": true
},
"python.pythonPath": "/home/m/.local/share/virtualenvs/project_simplified-sadenydX"
}

1
LICENSE Normal file
View File

@ -0,0 +1 @@

76
Makefile Normal file
View File

@ -0,0 +1,76 @@
.DEFAULT_GOAL := help
.PHONY: clean pep8 black lint test install
PYLINT := pylint
PYTEST := pytest
PEP8 := flake8
BLACK := black
MYPY := python -m mypy
PIP := python -m pip
PIPENV := pipenv
RDBMS := sqlite3
TASK_DB := task
PEOPLE_DB := people
DB_SUFFIX := sqlite3
clean:
rm -fr build
rm -fr dist
find . -name '*.pyc' -exec rm -f {} \;
find . -name '*.pyo' -exec rm -f {} \;
find . -name '*~' -exec rm -f {} \;
#find . -regex "./telegram[0-9]*.\(jpg\|mp3\|mp4\|ogg\|png\|webp\)" -exec rm {} \;
pep8:
$(PEP8) telegram
black:
$(BLACK) .
lint:
$(PYLINT) --rcfile=setup.cfg app
mypy:
$(MYPY) app
test:
$(PYTEST) -v
install:
# $(PIP) install -r requirements.txt -r requirements-dev.txt
$(PIPENV) install
help:
#TODO
@echo "Available targets:"
@echo "- clean Clean up the source directory"
@echo "- pep8 Check style with flake8"
@echo "- lint Check style with pylint"
@echo "- black Check style with black"
@echo "- mypy Check type hinting with mypy"
@echo "- test Run tests using pytest"
@echo "- ensure_pipenv
@echo
@echo "Available variables:"
@echo "- PYLINT default: $(PYLINT)"
@echo "- PYTEST default: $(PYTEST)"
@echo "- PEP8 default: $(PEP8)"
@echo "- BLACK default: $(BLACK)"
@echo "- MYPY default: $(MYPY)"
@echo "- PIP default: $(PIP)"
@echo "- PIPENV default: $(PIPENV)"
pipenv:
@echo "pipenv already installed in `which pipenv`, pip already installed in `which pip`. Nothing to do." || \
echo "installing pipenv" && python -m ensurepip && python -m pip install --upgrade pipenv;
db:
#if not exist generate task.sqlite3 and people.sqlite3 databases based on schemas
sqlite3 database/$(TASK_DB).$(DB_SUFFIX) '.read database/schemas/$(TASK_DB).sql'
sqlite3 database/$(PEOPLE_DB).$(DB_SUFFIX) '.read database/schemas/$(PEOPLE_DB).sql'
run:
#TODO (supervisor)

20
Pipfile Normal file
View File

@ -0,0 +1,20 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
pylint = "*"
pytest = "*"
rope = "*"
ipykernel = "*"
mypy = "*"
[packages]
pysocks = "*"
sqlalchemy = "*"
botogram2 = "*"
python-telegram-bot = "*"
[requires]
python_version = "3.9"

719
Pipfile.lock generated Normal file
View File

@ -0,0 +1,719 @@
{
"_meta": {
"hash": {
"sha256": "bb7b18648e6827e9eb42541151bf912a9d985a8d298d32ee5531991941996e53"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.9"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"apscheduler": {
"hashes": [
"sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244",
"sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"
],
"version": "==3.6.3"
},
"botogram2": {
"hashes": [
"sha256:42834167d178fa85eb5ccd130ee7b371ca2da5d8320d05823848192f4a6e8767",
"sha256:d7d58c8df5dd6a5cc6d3164a9f8c0f45969253aada10bbf17b191e6cb5b84ca9"
],
"index": "pypi",
"version": "==0.6.1"
},
"certifi": {
"hashes": [
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
],
"version": "==2020.12.5"
},
"cffi": {
"hashes": [
"sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e",
"sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d",
"sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a",
"sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec",
"sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362",
"sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668",
"sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c",
"sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b",
"sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06",
"sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698",
"sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2",
"sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c",
"sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7",
"sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009",
"sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03",
"sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b",
"sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909",
"sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53",
"sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35",
"sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26",
"sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b",
"sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01",
"sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb",
"sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293",
"sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd",
"sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d",
"sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3",
"sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d",
"sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e",
"sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca",
"sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d",
"sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775",
"sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375",
"sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b",
"sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b",
"sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"
],
"version": "==1.14.4"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"cryptography": {
"hashes": [
"sha256:0003a52a123602e1acee177dc90dd201f9bb1e73f24a070db7d36c588e8f5c7d",
"sha256:0e85aaae861d0485eb5a79d33226dd6248d2a9f133b81532c8f5aae37de10ff7",
"sha256:594a1db4511bc4d960571536abe21b4e5c3003e8750ab8365fafce71c5d86901",
"sha256:69e836c9e5ff4373ce6d3ab311c1a2eed274793083858d3cd4c7d12ce20d5f9c",
"sha256:788a3c9942df5e4371c199d10383f44a105d67d401fb4304178020142f020244",
"sha256:7e177e4bea2de937a584b13645cab32f25e3d96fc0bc4a4cf99c27dc77682be6",
"sha256:83d9d2dfec70364a74f4e7c70ad04d3ca2e6a08b703606993407bf46b97868c5",
"sha256:84ef7a0c10c24a7773163f917f1cb6b4444597efd505a8aed0a22e8c4780f27e",
"sha256:9e21301f7a1e7c03dbea73e8602905a4ebba641547a462b26dd03451e5769e7c",
"sha256:9f6b0492d111b43de5f70052e24c1f0951cb9e6022188ebcb1cc3a3d301469b0",
"sha256:a69bd3c68b98298f490e84519b954335154917eaab52cf582fa2c5c7efc6e812",
"sha256:b4890d5fb9b7a23e3bf8abf5a8a7da8e228f1e97dc96b30b95685df840b6914a",
"sha256:c366df0401d1ec4e548bebe8f91d55ebcc0ec3137900d214dd7aac8427ef3030",
"sha256:dc42f645f8f3a489c3dd416730a514e7a91a59510ddaadc09d04224c098d3302"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==3.3.1"
},
"decorator": {
"hashes": [
"sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760",
"sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"
],
"version": "==4.4.2"
},
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10"
},
"logbook": {
"hashes": [
"sha256:0cf2cdbfb65a03b5987d19109dacad13417809dcf697f66e1a7084fb21744ea9",
"sha256:2dc85f1510533fddb481e97677bb7bca913560862734c0b3b289bfed04f78c92",
"sha256:56ee54c11df3377314cedcd6507638f015b4b88c0238c2e01b5eb44fd3a6ad1b",
"sha256:66f454ada0f56eae43066f604a222b09893f98c1adc18df169710761b8f32fe8",
"sha256:7c533eb728b3d220b1b5414ba4635292d149d79f74f6973b4aa744c850ca944a",
"sha256:8f76a2e7b1f72595f753228732f81ce342caf03babc3fed6bbdcf366f2f20f18",
"sha256:94e2e11ff3c2304b0d09a36c6208e5ae756eb948b210e5cbd63cd8d27f911542",
"sha256:97fee1bd9605f76335b169430ed65e15e457a844b2121bd1d90a08cf7e30aba0",
"sha256:e18f7422214b1cf0240c56f884fd9c9b4ff9d0da2eabca9abccba56df7222f66"
],
"version": "==1.5.3"
},
"pycparser": {
"hashes": [
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.20"
},
"pysocks": {
"hashes": [
"sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299",
"sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5",
"sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"
],
"index": "pypi",
"version": "==1.7.1"
},
"python-telegram-bot": {
"hashes": [
"sha256:5feebb08ed08d7b71ceb4c05372b9f6a21d83994b5018db111509765881c8282",
"sha256:9a887a5057d1eb4fede9d04f736d53365cdb1cda1035c0f4fecb645cfd59c456"
],
"index": "pypi",
"version": "==13.1"
},
"pytz": {
"hashes": [
"sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268",
"sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"
],
"version": "==2020.4"
},
"requests": {
"hashes": [
"sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8",
"sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.25.0"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0"
},
"sqlalchemy": {
"hashes": [
"sha256:009e8388d4d551a2107632921320886650b46332f61dc935e70c8bcf37d8e0d6",
"sha256:0157c269701d88f5faf1fa0e4560e4d814f210c01a5b55df3cab95e9346a8bcc",
"sha256:0a92745bb1ebbcb3985ed7bda379b94627f0edbc6c82e9e4bac4fb5647ae609a",
"sha256:0cca1844ba870e81c03633a99aa3dc62256fb96323431a5dec7d4e503c26372d",
"sha256:166917a729b9226decff29416f212c516227c2eb8a9c9f920d69ced24e30109f",
"sha256:1f5f369202912be72fdf9a8f25067a5ece31a2b38507bb869306f173336348da",
"sha256:2909dffe5c9a615b7e6c92d1ac2d31e3026dc436440a4f750f4749d114d88ceb",
"sha256:2b5dafed97f778e9901b79cc01b88d39c605e0545b4541f2551a2fd785adc15b",
"sha256:2e9bd5b23bba8ae8ce4219c9333974ff5e103c857d9ff0e4b73dc4cb244c7d86",
"sha256:3aa6d45e149a16aa1f0c46816397e12313d5e37f22205c26e06975e150ffcf2a",
"sha256:4bdbdb8ca577c6c366d15791747c1de6ab14529115a2eb52774240c412a7b403",
"sha256:53fd857c6c8ffc0aa6a5a3a2619f6a74247e42ec9e46b836a8ffa4abe7aab327",
"sha256:5cdfe54c1e37279dc70d92815464b77cd8ee30725adc9350f06074f91dbfeed2",
"sha256:5d92c18458a4aa27497a986038d5d797b5279268a2de303cd00910658e8d149c",
"sha256:632b32183c0cb0053194a4085c304bc2320e5299f77e3024556fa2aa395c2a8b",
"sha256:7c735c7a6db8ee9554a3935e741cf288f7dcbe8706320251eb38c412e6a4281d",
"sha256:7cd40cb4bc50d9e87b3540b23df6e6b24821ba7e1f305c1492b0806c33dbdbec",
"sha256:84f0ac4a09971536b38cc5d515d6add7926a7e13baa25135a1dbb6afa351a376",
"sha256:8dcbf377529a9af167cbfc5b8acec0fadd7c2357fc282a1494c222d3abfc9629",
"sha256:950f0e17ffba7a7ceb0dd056567bc5ade22a11a75920b0e8298865dc28c0eff6",
"sha256:9e379674728f43a0cd95c423ac0e95262500f9bfd81d33b999daa8ea1756d162",
"sha256:b15002b9788ffe84e42baffc334739d3b68008a973d65fad0a410ca5d0531980",
"sha256:b6f036ecc017ec2e2cc2a40615b41850dc7aaaea6a932628c0afc73ab98ba3fb",
"sha256:bad73f9888d30f9e1d57ac8829f8a12091bdee4949b91db279569774a866a18e",
"sha256:bbc58fca72ce45a64bb02b87f73df58e29848b693869e58bd890b2ddbb42d83b",
"sha256:bca4d367a725694dae3dfdc86cf1d1622b9f414e70bd19651f5ac4fb3aa96d61",
"sha256:be41d5de7a8e241864189b7530ca4aaf56a5204332caa70555c2d96379e18079",
"sha256:bf53d8dddfc3e53a5bda65f7f4aa40fae306843641e3e8e701c18a5609471edf",
"sha256:c092fe282de83d48e64d306b4bce03114859cdbfe19bf8a978a78a0d44ddadb1",
"sha256:c3ab23ee9674336654bf9cac30eb75ac6acb9150dc4b1391bec533a7a4126471",
"sha256:ce64a44c867d128ab8e675f587aae7f61bd2db836a3c4ba522d884cd7c298a77",
"sha256:d05cef4a164b44ffda58200efcb22355350979e000828479971ebca49b82ddb1",
"sha256:d2f25c7f410338d31666d7ddedfa67570900e248b940d186b48461bd4e5569a1",
"sha256:d3b709d64b5cf064972b3763b47139e4a0dc4ae28a36437757f7663f67b99710",
"sha256:e32e3455db14602b6117f0f422f46bc297a3853ae2c322ecd1e2c4c04daf6ed5",
"sha256:ed53209b5f0f383acb49a927179fa51a6e2259878e164273ebc6815f3a752465",
"sha256:f605f348f4e6a2ba00acb3399c71d213b92f27f2383fc4abebf7a37368c12142",
"sha256:fcdb3755a7c355bc29df1b5e6fb8226d5c8b90551d202d69d0076a8a5649d68b"
],
"index": "pypi",
"version": "==1.3.20"
},
"tornado": {
"hashes": [
"sha256:0a00ff4561e2929a2c37ce706cb8233b7907e0cdc22eab98888aca5dd3775feb",
"sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c",
"sha256:1e8225a1070cd8eec59a996c43229fe8f95689cb16e552d130b9793cb570a288",
"sha256:20241b3cb4f425e971cb0a8e4ffc9b0a861530ae3c52f2b0434e6c1b57e9fd95",
"sha256:25ad220258349a12ae87ede08a7b04aca51237721f63b1808d39bdb4b2164558",
"sha256:33892118b165401f291070100d6d09359ca74addda679b60390b09f8ef325ffe",
"sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791",
"sha256:3447475585bae2e77ecb832fc0300c3695516a47d46cefa0528181a34c5b9d3d",
"sha256:34ca2dac9e4d7afb0bed4677512e36a52f09caa6fded70b4e3e1c89dbd92c326",
"sha256:3e63498f680547ed24d2c71e6497f24bca791aca2fe116dbc2bd0ac7f191691b",
"sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4",
"sha256:6196a5c39286cc37c024cd78834fb9345e464525d8991c21e908cc046d1cc02c",
"sha256:61b32d06ae8a036a6607805e6720ef00a3c98207038444ba7fd3d169cd998910",
"sha256:6286efab1ed6e74b7028327365cf7346b1d777d63ab30e21a0f4d5b275fc17d5",
"sha256:65d98939f1a2e74b58839f8c4dab3b6b3c1ce84972ae712be02845e65391ac7c",
"sha256:66324e4e1beede9ac79e60f88de548da58b1f8ab4b2f1354d8375774f997e6c0",
"sha256:6c77c9937962577a6a76917845d06af6ab9197702a42e1346d8ae2e76b5e3675",
"sha256:70dec29e8ac485dbf57481baee40781c63e381bebea080991893cd297742b8fd",
"sha256:7250a3fa399f08ec9cb3f7b1b987955d17e044f1ade821b32e5f435130250d7f",
"sha256:748290bf9112b581c525e6e6d3820621ff020ed95af6f17fedef416b27ed564c",
"sha256:7da13da6f985aab7f6f28debab00c67ff9cbacd588e8477034c0652ac141feea",
"sha256:8f959b26f2634a091bb42241c3ed8d3cedb506e7c27b8dd5c7b9f745318ddbb6",
"sha256:9de9e5188a782be6b1ce866e8a51bc76a0fbaa0e16613823fc38e4fc2556ad05",
"sha256:a48900ecea1cbb71b8c71c620dee15b62f85f7c14189bdeee54966fbd9a0c5bd",
"sha256:b87936fd2c317b6ee08a5741ea06b9d11a6074ef4cc42e031bc6403f82a32575",
"sha256:c77da1263aa361938476f04c4b6c8916001b90b2c2fdd92d8d535e1af48fba5a",
"sha256:cb5ec8eead331e3bb4ce8066cf06d2dfef1bfb1b2a73082dfe8a161301b76e37",
"sha256:cc0ee35043162abbf717b7df924597ade8e5395e7b66d18270116f8745ceb795",
"sha256:d14d30e7f46a0476efb0deb5b61343b1526f73ebb5ed84f23dc794bdb88f9d9f",
"sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32",
"sha256:d3d20ea5782ba63ed13bc2b8c291a053c8d807a8fa927d941bd718468f7b950c",
"sha256:d3f7594930c423fd9f5d1a76bee85a2c36fd8b4b16921cae7e965f22575e9c01",
"sha256:dcef026f608f678c118779cd6591c8af6e9b4155c44e0d1bc0c87c036fb8c8c4",
"sha256:e0791ac58d91ac58f694d8d2957884df8e4e2f6687cdf367ef7eb7497f79eaa2",
"sha256:e385b637ac3acaae8022e7e47dfa7b83d3620e432e3ecb9a3f7f58f150e50921",
"sha256:e519d64089b0876c7b467274468709dadf11e41d65f63bba207e04217f47c085",
"sha256:e7229e60ac41a1202444497ddde70a48d33909e484f96eb0da9baf8dc68541df",
"sha256:ed3ad863b1b40cd1d4bd21e7498329ccaece75db5a5bf58cd3c9f130843e7102",
"sha256:f0ba29bafd8e7e22920567ce0d232c26d4d47c8b5cf4ed7b562b5db39fa199c5",
"sha256:fa2ba70284fa42c2a5ecb35e322e68823288a4251f9ba9cc77be04ae15eada68",
"sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5"
],
"markers": "python_version >= '3.5'",
"version": "==6.1"
},
"tzlocal": {
"hashes": [
"sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44",
"sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"
],
"version": "==2.1"
},
"urllib3": {
"hashes": [
"sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.2"
}
},
"develop": {
"astroid": {
"hashes": [
"sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703",
"sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"
],
"markers": "python_version >= '3.5'",
"version": "==2.4.2"
},
"attrs": {
"hashes": [
"sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
"sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.3.0"
},
"backcall": {
"hashes": [
"sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e",
"sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"
],
"version": "==0.2.0"
},
"decorator": {
"hashes": [
"sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760",
"sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"
],
"version": "==4.4.2"
},
"iniconfig": {
"hashes": [
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
],
"version": "==1.1.1"
},
"ipykernel": {
"hashes": [
"sha256:63b4b96c513e1138874934e3e783a8e5e13c02b9036e37107bfe042ac8955005",
"sha256:e20ceb7e52cb4d250452e1230be76e0b2323f33bd46c6b2bc7abb6601740e182"
],
"index": "pypi",
"version": "==5.4.2"
},
"ipython": {
"hashes": [
"sha256:c987e8178ced651532b3b1ff9965925bfd445c279239697052561a9ab806d28f",
"sha256:cbb2ef3d5961d44e6a963b9817d4ea4e1fa2eb589c371a470fed14d8d40cbd6a"
],
"markers": "python_version >= '3.7'",
"version": "==7.19.0"
},
"ipython-genutils": {
"hashes": [
"sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8",
"sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"
],
"version": "==0.2.0"
},
"isort": {
"hashes": [
"sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7",
"sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"
],
"markers": "python_version >= '3.6' and python_version < '4.0'",
"version": "==5.6.4"
},
"jedi": {
"hashes": [
"sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20",
"sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==0.17.2"
},
"jupyter-client": {
"hashes": [
"sha256:49e390b36fe4b4226724704ea28d9fb903f1a3601b6882ce3105221cd09377a1",
"sha256:c958d24d6eacb975c1acebb68ac9077da61b5f5c040f22f6849928ad7393b950"
],
"markers": "python_version >= '3.5'",
"version": "==6.1.7"
},
"jupyter-core": {
"hashes": [
"sha256:0a451c9b295e4db772bdd8d06f2f1eb31caeec0e81fbb77ba37d4a3024e3b315",
"sha256:aa1f9496ab3abe72da4efe0daab0cb2233997914581f9a071e07498c6add8ed3"
],
"markers": "python_version >= '3.6'",
"version": "==4.7.0"
},
"lazy-object-proxy": {
"hashes": [
"sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d",
"sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449",
"sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08",
"sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a",
"sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50",
"sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd",
"sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239",
"sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb",
"sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea",
"sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e",
"sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156",
"sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142",
"sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442",
"sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62",
"sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db",
"sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531",
"sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383",
"sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a",
"sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357",
"sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4",
"sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.4.3"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
],
"version": "==0.6.1"
},
"mypy": {
"hashes": [
"sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324",
"sha256:0d34d6b122597d48a36d6c59e35341f410d4abfa771d96d04ae2c468dd201abc",
"sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802",
"sha256:2842d4fbd1b12ab422346376aad03ff5d0805b706102e475e962370f874a5122",
"sha256:2b21ba45ad9ef2e2eb88ce4aeadd0112d0f5026418324176fd494a6824b74975",
"sha256:72060bf64f290fb629bd4a67c707a66fd88ca26e413a91384b18db3876e57ed7",
"sha256:af4e9ff1834e565f1baa74ccf7ae2564ae38c8df2a85b057af1dbbc958eb6666",
"sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669",
"sha256:c614194e01c85bb2e551c421397e49afb2872c88b5830e3554f0519f9fb1c178",
"sha256:cf4e7bf7f1214826cf7333627cb2547c0db7e3078723227820d0a2490f117a01",
"sha256:da56dedcd7cd502ccd3c5dddc656cb36113dd793ad466e894574125945653cea",
"sha256:e86bdace26c5fe9cf8cb735e7cedfe7850ad92b327ac5d797c656717d2ca66de",
"sha256:e97e9c13d67fbe524be17e4d8025d51a7dca38f90de2e462243ab8ed8a9178d1",
"sha256:eea260feb1830a627fb526d22fbb426b750d9f5a47b624e8d5e7e004359b219c"
],
"index": "pypi",
"version": "==0.790"
},
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
"version": "==0.4.3"
},
"packaging": {
"hashes": [
"sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858",
"sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.8"
},
"parso": {
"hashes": [
"sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea",
"sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.7.1"
},
"pexpect": {
"hashes": [
"sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937",
"sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"
],
"markers": "sys_platform != 'win32'",
"version": "==4.8.0"
},
"pickleshare": {
"hashes": [
"sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca",
"sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"
],
"version": "==0.7.5"
},
"pluggy": {
"hashes": [
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.13.1"
},
"prompt-toolkit": {
"hashes": [
"sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c",
"sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63"
],
"markers": "python_full_version >= '3.6.1'",
"version": "==3.0.8"
},
"ptyprocess": {
"hashes": [
"sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0",
"sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"
],
"version": "==0.6.0"
},
"py": {
"hashes": [
"sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
"sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.10.0"
},
"pygments": {
"hashes": [
"sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716",
"sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"
],
"markers": "python_version >= '3.5'",
"version": "==2.7.3"
},
"pylint": {
"hashes": [
"sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210",
"sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f"
],
"index": "pypi",
"version": "==2.6.0"
},
"pyparsing": {
"hashes": [
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
"pytest": {
"hashes": [
"sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8",
"sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"
],
"index": "pypi",
"version": "==6.2.1"
},
"python-dateutil": {
"hashes": [
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.1"
},
"pyzmq": {
"hashes": [
"sha256:03638e46d486dd1c118e03c8bf9c634bdcae679600eac6573ae1e54906de7c2f",
"sha256:0af84f34f27b5c6a0e906c648bdf46d4caebf9c8e6e16db0728f30a58141cad6",
"sha256:0e554fd390021edbe0330b67226325a820b0319c5b45e1b0a59bf22ccc36e793",
"sha256:1e9b75a119606732023a305d1c214146c09a91f8116f6aff3e8b7d0a60b6f0ff",
"sha256:225774a48ed7414c0395335e7123ef8c418dbcbe172caabdc2496133b03254c2",
"sha256:2742e380d186673eee6a570ef83d4568741945434ba36d92b98d36cdbfedbd44",
"sha256:309d763d89ec1845c0e0fa14e1fb6558fd8c9ef05ed32baec27d7a8499cc7bb0",
"sha256:46250789730489009fe139cbf576679557c070a6a3628077d09a4153d52fd381",
"sha256:4d9259a5eb3f71abbaf61f165cacf42240bfeea3783bebd8255341abdfe206f1",
"sha256:523d542823cabb94065178090e05347bd204365f6e7cb260f0071c995d392fc2",
"sha256:53706f4a792cdae422121fb6a5e65119bad02373153364fc9d004cf6a90394de",
"sha256:5efe02bdcc5eafcac0aab531292294298f0ab8d28ed43be9e507d0e09173d1a4",
"sha256:63ee08e35be72fdd7568065a249a5b5cf51a2e8ab6ee63cf9f73786fcb9e710b",
"sha256:6e24907857c80dc67692e31f5bf3ad5bf483ee0142cec95b3d47e2db8c43bdda",
"sha256:7113eb93dcd0a5750c65d123ed0099e036a3a3f2dcb48afedd025ffa125c983b",
"sha256:824ad5888331aadeac772bce27e1c2fbcab82fade92edbd234542c4e12f0dca9",
"sha256:895695be380f0f85d2e3ec5ccf68a93c92d45bd298567525ad5633071589872c",
"sha256:b62113eeb9a0649cebed9b21fd578f3a0175ef214a2a91dcb7b31bbf55805295",
"sha256:bc7dd697356b31389d5118b9bcdef3e8d8079e8181800c4e8d72dccd56e1ff68",
"sha256:bf755905a7d30d2749079611b9a89924c1f2da2695dc09ce221f42122c9808e3",
"sha256:c63fafd2556d218368c51d18588f8e6f8d86d09d493032415057faf6de869b34",
"sha256:c95dda497a7c1b1e734b5e8353173ca5dd7b67784d8821d13413a97856588057",
"sha256:cc09c5cd1a4332611c8564d65e6a432dc6db3e10793d0254da9fa1e31d9ffd6d",
"sha256:cfa54a162a7b32641665e99b2c12084555afe9fc8fe80ec8b2f71a57320d10e1",
"sha256:d81184489369ec325bd50ba1c935361e63f31f578430b9ad95471899361a8253",
"sha256:d92c7f41a53ece82b91703ea433c7d34143248cf0cead33aa11c5fc621c764bf",
"sha256:dc2f48b575dff6edefd572f1ac84cf0c3f18ad5fcf13384de32df740a010594a",
"sha256:f0beef935efe78a63c785bb21ed56c1c24448511383e3994927c8bb2caf5e714",
"sha256:f110a4d3f8f01209eec304ed542f6c8054cce9b0f16dfe3d571e57c290e4e133"
],
"markers": "python_version >= '3.5'",
"version": "==20.0.0"
},
"rope": {
"hashes": [
"sha256:786b5c38c530d4846aa68a42604f61b4e69a493390e3ca11b88df0fbfdc3ed04"
],
"index": "pypi",
"version": "==0.18.0"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0"
},
"toml": {
"hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2"
},
"tornado": {
"hashes": [
"sha256:0a00ff4561e2929a2c37ce706cb8233b7907e0cdc22eab98888aca5dd3775feb",
"sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c",
"sha256:1e8225a1070cd8eec59a996c43229fe8f95689cb16e552d130b9793cb570a288",
"sha256:20241b3cb4f425e971cb0a8e4ffc9b0a861530ae3c52f2b0434e6c1b57e9fd95",
"sha256:25ad220258349a12ae87ede08a7b04aca51237721f63b1808d39bdb4b2164558",
"sha256:33892118b165401f291070100d6d09359ca74addda679b60390b09f8ef325ffe",
"sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791",
"sha256:3447475585bae2e77ecb832fc0300c3695516a47d46cefa0528181a34c5b9d3d",
"sha256:34ca2dac9e4d7afb0bed4677512e36a52f09caa6fded70b4e3e1c89dbd92c326",
"sha256:3e63498f680547ed24d2c71e6497f24bca791aca2fe116dbc2bd0ac7f191691b",
"sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4",
"sha256:6196a5c39286cc37c024cd78834fb9345e464525d8991c21e908cc046d1cc02c",
"sha256:61b32d06ae8a036a6607805e6720ef00a3c98207038444ba7fd3d169cd998910",
"sha256:6286efab1ed6e74b7028327365cf7346b1d777d63ab30e21a0f4d5b275fc17d5",
"sha256:65d98939f1a2e74b58839f8c4dab3b6b3c1ce84972ae712be02845e65391ac7c",
"sha256:66324e4e1beede9ac79e60f88de548da58b1f8ab4b2f1354d8375774f997e6c0",
"sha256:6c77c9937962577a6a76917845d06af6ab9197702a42e1346d8ae2e76b5e3675",
"sha256:70dec29e8ac485dbf57481baee40781c63e381bebea080991893cd297742b8fd",
"sha256:7250a3fa399f08ec9cb3f7b1b987955d17e044f1ade821b32e5f435130250d7f",
"sha256:748290bf9112b581c525e6e6d3820621ff020ed95af6f17fedef416b27ed564c",
"sha256:7da13da6f985aab7f6f28debab00c67ff9cbacd588e8477034c0652ac141feea",
"sha256:8f959b26f2634a091bb42241c3ed8d3cedb506e7c27b8dd5c7b9f745318ddbb6",
"sha256:9de9e5188a782be6b1ce866e8a51bc76a0fbaa0e16613823fc38e4fc2556ad05",
"sha256:a48900ecea1cbb71b8c71c620dee15b62f85f7c14189bdeee54966fbd9a0c5bd",
"sha256:b87936fd2c317b6ee08a5741ea06b9d11a6074ef4cc42e031bc6403f82a32575",
"sha256:c77da1263aa361938476f04c4b6c8916001b90b2c2fdd92d8d535e1af48fba5a",
"sha256:cb5ec8eead331e3bb4ce8066cf06d2dfef1bfb1b2a73082dfe8a161301b76e37",
"sha256:cc0ee35043162abbf717b7df924597ade8e5395e7b66d18270116f8745ceb795",
"sha256:d14d30e7f46a0476efb0deb5b61343b1526f73ebb5ed84f23dc794bdb88f9d9f",
"sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32",
"sha256:d3d20ea5782ba63ed13bc2b8c291a053c8d807a8fa927d941bd718468f7b950c",
"sha256:d3f7594930c423fd9f5d1a76bee85a2c36fd8b4b16921cae7e965f22575e9c01",
"sha256:dcef026f608f678c118779cd6591c8af6e9b4155c44e0d1bc0c87c036fb8c8c4",
"sha256:e0791ac58d91ac58f694d8d2957884df8e4e2f6687cdf367ef7eb7497f79eaa2",
"sha256:e385b637ac3acaae8022e7e47dfa7b83d3620e432e3ecb9a3f7f58f150e50921",
"sha256:e519d64089b0876c7b467274468709dadf11e41d65f63bba207e04217f47c085",
"sha256:e7229e60ac41a1202444497ddde70a48d33909e484f96eb0da9baf8dc68541df",
"sha256:ed3ad863b1b40cd1d4bd21e7498329ccaece75db5a5bf58cd3c9f130843e7102",
"sha256:f0ba29bafd8e7e22920567ce0d232c26d4d47c8b5cf4ed7b562b5db39fa199c5",
"sha256:fa2ba70284fa42c2a5ecb35e322e68823288a4251f9ba9cc77be04ae15eada68",
"sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5"
],
"markers": "python_version >= '3.5'",
"version": "==6.1"
},
"traitlets": {
"hashes": [
"sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396",
"sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"
],
"markers": "python_version >= '3.7'",
"version": "==5.0.5"
},
"typed-ast": {
"hashes": [
"sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
"sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
"sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d",
"sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
"sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
"sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
"sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c",
"sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
"sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
"sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
"sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
"sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
"sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
"sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d",
"sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
"sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
"sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c",
"sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
"sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395",
"sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
"sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
"sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
"sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
"sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
"sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072",
"sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298",
"sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91",
"sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
"sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f",
"sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
],
"version": "==1.4.1"
},
"typing-extensions": {
"hashes": [
"sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
"sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
"sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
],
"version": "==3.7.4.3"
},
"wcwidth": {
"hashes": [
"sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784",
"sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"
],
"version": "==0.2.5"
},
"wrapt": {
"hashes": [
"sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"
],
"version": "==1.12.1"
}
}
}

1
README.md Normal file
View File

@ -0,0 +1 @@

1
app/.tmp/update Normal file
View File

@ -0,0 +1 @@
{'update_id': 916766066, 'message': {'message_id': 138, 'date': 1607818585, 'chat': {'id': 1276927356, 'type': 'private', 'first_name': 'Hikikomena'}, 'text': '/test ciao a tutti i giorni @jufcxdgh @gvcfyjjh #ferran #documentary #ferran $ruff www.example.com altro testo', 'entities': [{'type': 'bot_command', 'offset': 0, 'length': 5}, {'type': 'mention', 'offset': 28, 'length': 9}, {'type': 'mention', 'offset': 38, 'length': 9}, {'type': 'hashtag', 'offset': 48, 'length': 7}, {'type': 'hashtag', 'offset': 56, 'length': 12}, {'type': 'hashtag', 'offset': 69, 'length': 7}, {'type': 'url', 'offset': 83, 'length': 15}], 'caption_entities': [], 'photo': [], 'new_chat_members': [], 'new_chat_photo': [], 'delete_chat_photo': False, 'group_chat_created': False, 'supergroup_chat_created': False, 'channel_chat_created': False, 'from': {'id': 1276927356, 'first_name': 'Hikikomena', 'is_bot': False, 'language_code': 'en'}}}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

142
app/database_interface.py Normal file
View File

@ -0,0 +1,142 @@
import sqlite3
from sqlite3 import Error as DBError
from itertools import repeat
from telegram import Message
# db connections
task_db: sqlite3.Connection = sqlite3.connect('database/task.sqlite3',check_same_thread=False)
people_db: sqlite3.Connection = sqlite3.connect('database/people.sqlite3',check_same_thread=False)
def place_from_group_id(group_id: int, *, db=task_db) -> str:
result = db.execute("SELECT place \
FROM places_group_id \
WHERE telegram_group_id = ? \
ORDER BY inserted DESC \
LIMIT 1",
(group_id,)
).fetchone()
place_name = result[0] if result else ""
return place_name
def insert_task_in_db(*, db: sqlite3.Connection = task_db,
task_table="task", participants_table="participants",
task_type: str, chat_id: int, participants: list[int], message:Message) -> int:
"""insert task in db and returns the task id"""
#TODO rethink interface for decoupling
#try:
place = place_from_group_id(message.chat.id)
#except sqlite3.Error as e:
#TODO
# raise e
#TODO check existing
insert_tuple = (task_type, place, message.text, message.message_id)
task_insertion = f"""INSERT INTO {task_table}
(type, place, creation_message, creation_message_id, creation_chat_id
)
VALUES
(?,?,?,?,?)""",\
(task_type, place, message.text, message.message_id, chat_id)
db.commit()
#try:
db.execute(*task_insertion)
db.commit()
#except sqlite3.Error as e:
#TODO
#raise e
try:
task_id = db.execute("SELECT last_insert_rowid();").fetchone()[0]
except sqlite3.Error as e:
#TODO
raise e
participant_insertion = [(task_id, participant) for participant in participants]
try:
db.executemany(f"INSERT INTO {participants_table} VALUES (?,?)", participant_insertion)
except sqlite3.Error as e:
#TODO
raise e
return task_id
def task_id_from_message(original_message_id: int, original_chat_id: int,
db: sqlite3.Connection=task_db) -> int:
task_id_query = db.execute("""
SELECT id
FROM task
WHERE creation_message_id = ? AND
creation_chat_id = ? ;""",
(original_message_id, original_chat_id)
).fetchone()
task_id = task_id_query[0]
return task_id
def delete_task_in_db(*, deleting_message_id: int, deleting_chat_id: int, deleting_message_text: str,
original_message_id: int, original_chat_id: int, task_id: int=-1,
db=task_db) -> int:
"""mark deleted (update) in db the task specified.
you can specify a task by referring by task_id or by the creation message.
returns the id of the task deleted"""
db.execute("""
UPDATE task
SET deleted = TRUE,
deletion_time = CURRENT_TIME,
deletion_date = CURRENT_DATE,
deletion_message = ? ,
deletion_message_id = ? ,
deletion_chat_id = ?
WHERE (creation_message_id = ? AND
creation_chat_id = ?)
OR id = ? ;""",
(deleting_message_text, deleting_message_id, deleting_chat_id,
original_message_id, original_chat_id, task_id))
db.commit()
#fetch the id of the last task deleted
if not task_id:
task_id: int = task_id_from_message(original_message_id=original_message_id,
original_chat_id=original_chat_id)
return task_id
def fetch_person_id_from_telegram_id(*, telegram_id: int, db=people_db) -> int:
raise NotImplementedError
def insert_new_group(*, group_id:int, place:str, db=task_db) -> bool:
db.execute("INSERT INTO places_group_id (place, telegram_group_id) \
VALUES (?,?);",
(place, group_id)
)
db.commit()
return True
def list_places(db=task_db) -> list[str]:
place_tuples = db.execute("""
SELECT DITINCT place
FROM places_group_id
""").fetchall()
return [place[0] for place in place_tuples]

163
app/helpers.py Normal file
View File

@ -0,0 +1,163 @@
"""business logic and database"""
from typing import Union, Optional
from collections import defaultdict #, namedtuple
from telegram import Update, Message, MessageEntity, User, Chat, ChatMember
from telegram.ext import CallbackContext
import database_interface as db
from sqlite3 import Error as DBError
InternaldictType = dict[MessageEntity, str]
ClassifiedEntitiesdictType = dict[str, list[InternaldictType]]
def classify_entities(entities: InternaldictType) -> ClassifiedEntitiesdictType:
"""classify entities by type and returns a dictionary with types as keys"""
internaldict: InternaldictType = InternaldictType()
internaldictkeys = ("entity", "text")
entities_bytype: ClassifiedEntitiesdictType = defaultdict(list)
for entity, text in entities.items():
internaldict = dict(zip(internaldictkeys,
[entity, text]))
entities_bytype[entity.type].append(internaldict)
return entities_bytype
def is_group(chat: Chat) -> bool:
return chat.type in (Chat.GROUP, Chat.SUPERGROUP)
def is_private(chat: Chat) -> bool:
return chat.type == Chat.PRIVATE
def extract_entities_text(type_name: str, *, message: Message, entities: ClassifiedEntitiesdictType) -> list[str]:
if not entities:
entities = classify_entities(message.entities)
entities_of_type = entities[type_name]
return [entity["text"] for entity in entities_of_type]
def telegram_id_of(reference: Union[MessageEntity, User], nobot: bool = True) -> Optional[int]:
"""numerical telegram id of mention if it is not a bot"""
if type(reference) == MessageEntity:
user = reference.user
elif type(reference) != User:
raise NotImplementedError("supported only MessageEntity and User argument type")
user = reference
return user.id if not user.is_bot else None
def person_id_of(telegram_id: int) -> int:
"""for stats reports"""
raise NotImplementedError
def insert_task(update: Update) -> int:
message = update.message
if not is_group(update.effective_chat):
raise ValueError
# split command and determine participants, task type and place
entities_bytype = classify_entities(message.parse_entities()) # TODO include captions to images etc"or message.parse_caption_entities())"
task_kind: str = entities_bytype[MessageEntity.BOT_COMMAND][0]["text"][1:]
chat_id: int = update.effective_chat.id #NOTE TBD permit use of a h/cashtag for signaling that the task is in either place?
# participants_id: list[int] = list()
# for mention in entities_bytype[MessageEntity.MENTION]:
# user = mention["entity"].user
# if user and not user.is_bot:
# try:
# participants_id.append(user.id)
# except AttributeError: # the user does not exists
# pass
participants: list[User] = get_participants(entities_bytype)
participants_id: list[int] = get_ids_of(participants)
participants_id_unique: list[int] = remove_duplicates(participants_id)
# insert in db
inserted_task_id: int = db.insert_task_in_db(task_type=task_kind, chat_id=chat_id,
participants=participants_id_unique, message=message)
return inserted_task_id
def remove_duplicates(in_list: Union[list, tuple]) -> list: # to export
return list(set(in_list))
def get_participants(entities_bytype: ClassifiedEntitiesdictType) -> list[User]:
"""get unique users form a list of classified"""
participants: list = []
participants.append(entities_bytype[MessageEntity.MENTION])
participants.append(entities_bytype[MessageEntity.TEXT_MENTION])
users = map(lambda d_entity_text: d_entity_text["entity"].user , participants)
unique_users = remove_duplicates(users)
return unique_users
def get_ids_of(users: list[User]) -> list[int]:
return list(map(lambda user: user.id))
def get_administrators_id(chat: Chat) -> list[int]: #util
return [admin.id for admin in chat.get_administrators()]
def is_admin(user: Union[User, int], chat: Chat) -> bool:
test = False
if type(user) == User:
test = user in [member.user for member in chat.get_administrators()]
elif type(user) == int: # user is a user_id
test = user in get_administrators_id(chat)
return test
def dump_db():
raise NotImplementedError
def is_from_admin(message: Message) -> bool:
return is_admin(user=message.from_user, chat=message.chat)
def delete_task(*, deleting_message: Message, original_message: Message, task_id: int=0,) -> int:
return db.delete_task_in_db(deleting_message_id=deleting_message.message_id,
deleting_chat_id=deleting_message.chat.id,
deleting_message_text=deleting_message.text,
original_message_id=original_message.message_id,
original_chat_id=original_message.chat.id)
def command_name_from_message(message: Message) -> str:
return classify_entities(message.entities)[MessageEntity.BOT_COMMAND][0]["text"]
def place_name_from_message(message: Message) -> str:
command = command_name_from_message(message)
return message.text.replace(command, "").casefold().strip()
def register_group_as_place(message: Message) -> bool:
success = True
place_name = place_name_from_message(message)
db.insert_new_group(group_id=message.chat.id, place=place_name)
return success
def list_places() -> list[str]:
return db.list_places()

38
app/main.py Normal file
View File

@ -0,0 +1,38 @@
import logging
import sqlite3
from telegram.ext import CommandHandler, Updater
import text_commands
# logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO,
filename="logs/app/info")
# authentication and proxy
with open("secrets/proxy") as proxyfile:
proxy_url = proxyfile.readline().strip()
with open("secrets/telegram_token_test") as tokenfile:
telegram_bot_token = tokenfile.readline().strip()
updater_request_kwargs = {'proxy_url': proxy_url} if proxy_url else dict()
updater = Updater(token=telegram_bot_token, request_kwargs=updater_request_kwargs)
del telegram_bot_token
del proxy_url
# generate text commands
for function_name, function in text_commands.get_commands().items() :
updater.dispatcher.add_handler(CommandHandler(function_name, function))
print("command registered: " + function_name)
# bot run
if __name__ == "__main__":
updater.start_polling()
updater.idle()

314
app/text_commands.py Normal file
View File

@ -0,0 +1,314 @@
"""Collection of all text commands (commands that starts with / ).
Decorate a function with @command for marking it as a text command.
Functions here should be only for user interaction (messages etc).
"""
from sqlite3 import Error as DBError
from typing import Union, Optional
from functools import wraps
from textwrap import dedent
from telegram import Update, Message, MessageEntity, User, Chat, ChatMember
from telegram.ext import CallbackContext
import helpers as h
# flags and conf
# TODO make a conf file
DEVELOPMENT = True
# decorator functions
__command_dict__ = dict()
def command(func, command_name: str = ""):
"""functions decorated will be counted as '/' commands"""
if not command_name:
command_name = func.__name__
global __command_dict__
__command_dict__[command_name] = func
#@wraps(func)
#def inner(func):
# return func
return func
# def command(command_name: str = ""):
# def inner(function):
# if not command_name:
# command_name = function.__name__
# global __command_dict__
# __command_dict__[command_name] = function
# @wraps(function)
# def wrapper(*args, **kwargs):
# function(*args, **kwargs)
# return wrapper
# return inner
def get_commands() -> dict:
return __command_dict__
def nullfunc(*, phony):
return
def admin_only(func): # decorator
"""decorator: the command is only to be used by admins. it implies it is only in groups"""
@wraps(func)
def inner(update: Update, context: CallbackContext) -> None:
user: User = update.effective_user
chat: Chat = update.effective_chat
if h.is_admin(user, chat) and h.is_group(chat):
return func
else:
return nullfunc
return inner
def group_only(func): # decorator
"""decorator: the command is only to be used in groups"""
@wraps(func)
def inner(update: Update, context: CallbackContext) -> None:
if h.is_group(update.effective_chat):
return func
else:
return nullfunc
return inner
def private_only(func): # decorator
"""decorator: the command is only to be used in private chats"""
@wraps(func)
def inner(update: Update, context: CallbackContext) -> None:
if h.is_private(update.effective_chat):
return func
else:
return nullfunc
return inner
def dev_only(func): # decorator
"""decorator: the command will be available only durong development"""
if DEVELOPMENT:
return func
else:
return nullfunc
# common strings
reply_text_to_not_admins = "This command is for administrators only. You're not an administrator. Nothing done."
# commands
# def insert_task_command_wrapper(message: Message, command_name: str) -> None:
# """creates the reply text for the insertion command"""
# task_type: str = command_name
# try: #TODO logging
# h.insert_task(message)
# except ValueError: #not a group
# reply_text = "This command must be used only in group chats. Nothing done."
# except DBError: # db error
# reply_text = "Database error, please retry."
# except: # other
# reply_text = "Unknown error, please retry."
# else: # success
# reply_text = f"{task_type.title()} task inserted." #TODO private message to users?
# #TODO user mentions?
@command
def delete(update: Update, context: CallbackContext) -> None:
"""mark as deleted the task in the message replied_to"""
# TODO add syntax for deletion comment (i.e. "/delete because reasons")
# TODO add syntax for deletion of task number without responding (and comment, like "/delete 56 because reasons")
success = True
deleted_task_id = 0
message = update.message
if h.is_from_admin(message):
original_message: Message = message.reply_to_message
deleted_task_id = h.delete_task(deleting_message=message, original_message=original_message)
else:
success = False
update.message.reply_text(f"Task {deleted_task_id} deleted" if success else "Task not deleted")
return
@command
def start(update: Update, context: CallbackContext) -> None:
context.bot.send_message(chat_id=update.effective_chat.id, text="I'm a bot, please talk to me!")
@command
def test(update: Update, context: CallbackContext) -> None:
#update.message.reply_text("ok")
reply = ""
ent = None
try :
ent = h.classify_entities(update.message)
reply += str(ent)
except Exception as e1:
reply += str(e1)
try:
with open("app/.tmp/update", "tw") as f:
f.write(str(update))
except Exception as e2:
reply += str(e2)
print(reply or "None")
update.message.reply_text(reply or "None")
@command
@admin_only
def dump(update: Update, context: CallbackContext) -> None:
"""dumps db"""
reply_text = h.dump_db()
update.message.reply_text(reply_text)
@command
def admins(update: Update, context: CallbackContext) -> None:
admins: list[ChatMember] = update.message.chat.get_administrators()
admins_names = [admin.user.username or admin.user.firstname for admin in admins]
reply_text = "Admins: \n" + ", ".join("@"+name for name in admins_names)
update.message.reply_text(reply_text)
@command
def register_person(update: Update, context: CallbackContext) -> None:
"""associate a person id with the telegram id.
multiple telegram accounts for a single person are allowed,
while multiple people with a single shared account is not supported.
admin only"""
#TODO
reply_text = reply_text_to_not_admins or "Not implemented. Nothing done."
update.message.reply_text(reply_text)
raise NotImplementedError
@command
def help(update: Update, context: CallbackContext) -> None:
usage = """Usage: use a command of (/use, /cleaning, /construction, /assistance, /meeting) for inserting a task.
The command must be followed by @mentions of all the participants.
Except for that rule, you can write what you want in the message."""
if "admin" in update.message.text:
usage += """
Admin-only commands:
/register_group name: register a group chat as the official work chat for the place.
/delete: respond with that command to a task for deleting it"""
if "dev" in update.message.text:
usage += """
Dev-only usage:
/chat_number
/test"""
update.message.reply_text(dedent(usage))
## group task commands
@command
def insert_task(update: Update, context: CallbackContext, task_type: str) -> None:
task_id: int = 0
reply_text = ""
try:
task_id = h.insert_task(update)
reply_text = f"OK, {task_type} task n.{task_id} registered."
except Exception as e:
reply_text = "Task not inserted. Nothing done." + str(e)
update.message.reply_text(reply_text)
@command
def assistance(update: Update, context: CallbackContext) -> None:
insert_task(update, context, "assistance")
@command
def construction(update: Update, context: CallbackContext) -> None:
insert_task(update, context, "construction")
@command
def use(update: Update, context: CallbackContext) -> None:
insert_task(update, context, "use")
@command
def cleaning(update: Update, context: CallbackContext) -> None:
insert_task(update, context, "cleaning")
@command #dev
def chat_number(update: Update, context: CallbackContext) -> None:
chat_id = update.effective_chat.id
context.bot.send_message(chat_id=chat_id, text=f"{chat_id}")
@command
def register_group(update: Update, context: CallbackContext) -> None:
"""Admin can associate a group to a place name.
Substituting the precedent group:place pair or creating another place.
type this command followed by the name that you want to assign to the place.
Spaces and capitalization are ignored"""
message = update.message
place = h.place_name_from_message(messagedef insert_task_command_wrapper(message: Message, command_name: str) -> None:
# """creates the reply text for the insertion command"""
# task_type: str = command_name
# try: #TODO logging
# h.insert_task(message)
# except ValueError: #not a group
# reply_text = "This command must be used only in group chats. Nothing done."
# except DBError: # db error
# reply_text = "Database error, please retry."
# except: # other
# reply_text = "Unknown error, please retry."
# else: # success
# reply_text = f"{task_type.title()} task inserted." #TODO private message to users?
# #TODO user mentions?
)
# check if it is a new place, in this case put a wanring #TBD
existing_places = h.list_places()
if place not in existing_places:
warning = dedent(f"""Warning: the place name you inserted, '{place}', does not exists yet.
The places currently registered were {existing_places}.
Inserting it anyway... \n""" )
success = h.register_group_as_place(message)
if success:
reply_text = f"Current group registered as {place} group "
else:
reply_text = "Failed to register. Nothing done"
reply_text = warning + reply_text
message.reply_to_message(reply_text)

View File

@ -0,0 +1,21 @@
-- db not for disclosure, it maps telegram accounts to phisical people
PRAGMA foreign_keys = ON;
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS person (
id INTEGER PRIMARY KEY,
alias TEXT UNIQUE NOT NULL,
name TEXT -- real name
);
CREATE TABLE IF NOT EXISTS tg_account (
id INTEGER PRIMARY KEY,
person_id INT,
disabled BOOL DEFAULT FALSE,
registration_timestamp TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (person_id) REFERENCES person (id)
); --TODO date on changed
COMMIT;

37
database/schemas/task.sql Normal file
View File

@ -0,0 +1,37 @@
-- db for public discolsure without personal informations
PRAGMA foreign_keys = ON;
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS task (
id INTEGER PRIMARY KEY,
creation_time TEXT DEFAULT CURRENT_TIME,
creation_date TEXT DEFAULT CURRENT_DATE,
creation_message TEXT NOT NULL, -- full text message, same as description
creation_message_id INT NOT NULL,
creation_chat_id INT NOT NULL,
deleted BOOL DEFAULT FALSE,
deletion_time TEXT DEFAULT "",
deletion_date TEXT DEFAULT "",
deletion_message TEXT DEFAULT "",
deletion_message_id INT DEFAULT 0,
deletion_chat_id INT DEFAULT 0,
type TEXT NOT NULL,
place TEXT,
FOREIGN KEY (place) REFERENCES places_group_id (place)
);
CREATE TABLE IF NOT EXISTS participants (
task_id INT,
telegram_id int,
FOREIGN KEY (task_id) REFERENCES task (id)
);
CREATE TABLE IF NOT EXISTS places_group_id (
place TEXT NOT NULL,
inserted TEXT DEFAULT CURRENT_TIMESTAMP, -- group has migrated, only last timestamp is considered
telegram_group_id INT NOT NULL
);
COMMIT;

View File

@ -0,0 +1,119 @@
PRAGMA foreign_keys = ON;
BEGIN TRANSACTION;
CREATE TABLE person (
name TEXT,
pseudonym TEXT unique NOT NULL,
id INTEGER PRIMARY KEY, --TODO see if it is ok to PK the pseudonym
suspended BOOL -- to suspend account
);
CREATE TABLE person_pseudonyms (
person_id INTEGER,
telegram_username TEXT NOT NULL,
first_seen TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
FOREIGN KEY (person_id) REFERENCES person (id),
);
CREATE TRIGGER --invalidate
CREATE TABLE role (
person_id INTEGER,
is_admin BOOL DEFAULT false,
FOREIGN KEY (person_id)
REFERENCES person(id)
);
CREATE TABLE zone_group (
person_id INTEGER,
name text,
FOREIGN KEY (name) REFERENCES zone (name),
FOREIGN KEY (person_id) REFERENCES person (id)
);
CREATE TABLE telegram_account (
account_id INT PRIMARY KEY, -- a person can have multiple accounts. multiple people cannot share a telegram account
person_id INTEGER,
FOREIGN KEY (person_id) REFERENCES person(id)
);
CREATE TABLE telegram_private_chat (
chat_id INT PRIMARY KEY,
account_id INTEGER,
FOREIGN KEY (account_id) REFERENCES telegram_account(account_id)
);
--CREATE TABLE telegram_group_chat (
CREATE TABLE work_type ( -- for limiting the type of possible works
type TEXT
);
INSERT INTO work_type (type) VALUES ("cleaning"), ("helping"), ("meeting"), ("construction"), ("use");
CREATE TABLE work_unit (
id INTEGER PRIMARY KEY,
creation_time TEXT NOT NULL DEFAULT CURRENT_TIME,
creation_date TEXT NOT NULL DEFAULT CURRENT_DATE,
last_mod_time TEXT NOT NULL DEFAULT CURRENT_TIME, -- maybe set up a different table with the history?
last_mod_date TEXT NOT NULL DEFAULT CURRENT_DATE,
value REAL NOT NULL DEFAULT 0.0,
value_sharing_mode TEXT CHECK (value_sharing_mode=="divided" or value_sharing_mode=="multiplied" or value_sharing_mode ISNULL),
place_name TEXT,
zone_name TEXT,
type TEXT,
description TEXT NOT NULL, -- maybe remove constraint NOT NULL
FOREIGN KEY (place_name) REFERENCES places (name),
FOREIGN KEY (zone_name) REFERENCES zone (name),
FOREIGN KEY (type) REFERENCES work_type (type) -- check if a valid work type
);
CREATE TRIGGER work_unit_last_mod_time AFTER UPDATE
ON work_unit
BEGIN
UPDATE work_unit
SET last_mod_date = date(),
last_mod_time = time();
END;
-- views
CREATE VIEW places_full_name AS
SELECT place.name, zone.name
FROM place
JOIN zone
ON place.zone_name = zone.name;
CREATE VIEW admins AS
SELECT id AS user_id
FROM person
JOIN role
ON id = person_id
WHERE is_admin;
CREATE VIEW users AS
SELECT id AS user_id
FROM person
JOIN role
ON id = person_id
WHERE NOT is_admin;
-- work_unit audit ?
-- CREATE TABLE work_unit_audit (
-- event TEXT NOT NULL, -- event type: UPDATE, INSERT, DELETE TODO how to determine event type? from the presence of both new and old values?
-- new and old values for every
-- CREATE TABLE telegram_account_audit (
-- CREATE TRIGGER work_unit_audit_insert AFTER INSERT
-- CREATE TRIGGER work_unit_audit_update AFTER UPDATE
-- CREATE TRIGGER work_unit_audit_delete AFTER DELETE
COMMIT;

58709
logs/app/info Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
-426107194

View File

@ -0,0 +1 @@
-332060019

1
secrets/proxy Normal file
View File

@ -0,0 +1 @@
socks5://127.0.0.1:9050/

View File

@ -0,0 +1 @@
1276341418:AAFsMadgVZGJTiEdjLBybV0un4-1CybmrhI