diff --git a/.gitignore b/.gitignore index b7faf40..63f1fef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,207 +1 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[codz] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py.cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# UV -# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -#uv.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock -#poetry.toml - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. -# https://pdm-project.org/en/latest/usage/project/#working-with-version-control -#pdm.lock -#pdm.toml -.pdm-python -.pdm-build/ - -# pixi -# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. -#pixi.lock -# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one -# in the .venv directory. It is recommended not to include this directory in version control. -.pixi - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.envrc -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -# Abstra -# Abstra is an AI-powered process automation framework. -# Ignore directories containing user credentials, local state, and settings. -# Learn more at https://abstra.io/docs -.abstra/ - -# Visual Studio Code -# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore -# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore -# and can be added to the global gitignore or merged into this file. However, if you prefer, -# you could uncomment the following to ignore the entire vscode folder -# .vscode/ - -# Ruff stuff: -.ruff_cache/ - -# PyPI configuration file -.pypirc - -# Cursor -# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to -# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data -# refer to https://docs.cursor.com/context/ignore-files -.cursorignore -.cursorindexingignore - -# Marimo -marimo/_static/ -marimo/_lsp/ -__marimo__/ +*.lock diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 0d2e13b..0000000 --- a/Pipfile +++ /dev/null @@ -1,16 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -pyotp = "*" -cryptography = "*" -opencv-python = "*" -pyzbar = "*" -pygobject = "*" - -[dev-packages] - -[requires] -python_version = "3.12" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index f634fce..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,305 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "1bc617a94364686f1f2832093b9bedcf1b1ef8d00a5660bbbeaf9cf69f14217a" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.12" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "cffi": { - "hashes": [ - "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", - "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", - "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", - "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", - "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", - "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", - "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", - "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", - "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", - "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", - "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", - "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", - "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", - "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", - "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", - "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", - "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", - "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", - "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", - "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", - "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", - "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", - "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", - "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", - "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", - "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", - "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", - "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", - "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", - "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", - "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", - "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", - "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", - "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", - "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", - "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", - "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", - "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", - "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", - "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", - "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", - "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", - "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", - "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", - "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", - "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", - "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", - "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", - "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", - "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", - "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", - "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", - "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", - "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", - "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", - "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", - "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", - "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", - "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", - "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", - "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", - "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", - "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", - "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", - "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", - "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", - "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", - "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", - "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", - "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", - "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", - "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", - "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", - "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", - "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", - "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", - "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", - "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", - "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", - "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", - "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", - "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", - "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", - "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf" - ], - "markers": "python_full_version >= '3.9' and platform_python_implementation != 'PyPy'", - "version": "==2.0.0" - }, - "cryptography": { - "hashes": [ - "sha256:04911b149eae142ccd8c9a68892a70c21613864afb47aba92d8c7ed9cc001023", - "sha256:07c0eb6657c0e9cca5891f4e35081dbf985c8131825e21d99b4f440a8f496f36", - "sha256:0b507c8e033307e37af61cb9f7159b416173bdf5b41d11c4df2e499a1d8e007c", - "sha256:0c7ffe8c9b1fcbb07a26d7c9fa5e857c2fe80d72d7b9e0353dcf1d2180ae60ee", - "sha256:1a88634851d9b8de8bb53726f4300ab191d3b2f42595e2581a54b26aba71b7cc", - "sha256:1d3b3edd145953832e09607986f2bd86f85d1dc9c48ced41808b18009d9f30e5", - "sha256:1e3b6428a3d56043bff0bb85b41c535734204e599c1c0977e1d0f261b02f3ad5", - "sha256:1fd1a69086926b623ef8126b4c33d5399ce9e2f3fac07c9c734c2a4ec38b6d02", - "sha256:218abd64a2e72f8472c2102febb596793347a3e65fafbb4ad50519969da44470", - "sha256:21b6fc8c71a3f9a604f028a329e5560009cc4a3a828bfea5fcba8eb7647d88fe", - "sha256:27c53b4f6a682a1b645fbf1cd5058c72cf2f5aeba7d74314c36838c7cbc06e0f", - "sha256:2b9cad9cf71d0c45566624ff76654e9bae5f8a25970c250a26ccfc73f8553e2d", - "sha256:2fafb6aa24e702bbf74de4cb23bfa2c3beb7ab7683a299062b69724c92e0fa73", - "sha256:3f37aa12b2d91e157827d90ce78f6180f0c02319468a0aea86ab5a9566da644b", - "sha256:48b983089378f50cba258f7f7aa28198c3f6e13e607eaf10472c26320332ca9a", - "sha256:48c01988ecbb32979bb98731f5c2b2f79042a6c58cc9a319c8c2f9987c7f68f9", - "sha256:4a766d2a5d8127364fd936572c6e6757682fc5dfcbdba1632d4554943199f2fa", - "sha256:512c0250065e0a6b286b2db4bbcc2e67d810acd53eb81733e71314340366279e", - "sha256:5840f05518caa86b09d23f8b9405a7b6d5400085aa14a72a98fdf5cf1568c0d2", - "sha256:5e38f203160a48b93010b07493c15f2babb4e0f2319bbd001885adb3f3696d21", - "sha256:6b275e398ab3a7905e168c036aad54b5969d63d3d9099a0a66cc147a3cc983be", - "sha256:7282d8f092b5be7172d6472f29b0631f39f18512a3642aefe52c3c0e0ccfad5a", - "sha256:747b6f4a4a23d5a215aadd1d0b12233b4119c4313df83ab4137631d43672cc90", - "sha256:758cfc7f4c38c5c5274b55a57ef1910107436f4ae842478c4989abbd24bd5acb", - "sha256:8b16c1ede6a937c291d41176934268e4ccac2c6521c69d3f5961c5a1e11e039e", - "sha256:8b9bf67b11ef9e28f4d78ff88b04ed0929fcd0e4f70bb0f704cfc32a5c6311ee", - "sha256:8e2ad4d1a5899b7caa3a450e33ee2734be7cc0689010964703a7c4bcc8dd4fd0", - "sha256:9066cfd7f146f291869a9898b01df1c9b0e314bfa182cef432043f13fc462c92", - "sha256:91447f2b17e83c9e0c89f133119d83f94ce6e0fb55dd47da0a959316e6e9cfa1", - "sha256:97e83bf4f2f2c084d8dd792d13841d0a9b241643151686010866bbd076b19659", - "sha256:9bd26f2f75a925fdf5e0a446c0de2714f17819bf560b44b7480e4dd632ad6c46", - "sha256:9bdc25e4e01b261a8fda4e98618f1c9515febcecebc9566ddf4a70c63967043b", - "sha256:9ec3f2e2173f36a9679d3b06d3d01121ab9b57c979de1e6a244b98d51fea1b20", - "sha256:9f13b040649bc18e7eb37936009b24fd31ca095a5c647be8bb6aaf1761142bd1", - "sha256:a08e7401a94c002e79dc3bc5231b6558cd4b2280ee525c4673f650a37e2c7685", - "sha256:a61c154cc5488272a6c4b86e8d5beff4639cdb173d75325ce464d723cda0052b", - "sha256:bb7fb9cd44c2582aa5990cf61a4183e6f54eea3172e54963787ba47287edd135", - "sha256:bca3f0ce67e5a2a2cf524e86f44697c4323a86e0fd7ba857de1c30d52c11ede1", - "sha256:bda55e8dbe8533937956c996beaa20266a8eca3570402e52ae52ed60de1faca8", - "sha256:be939b99d4e091eec9a2bcf41aaf8f351f312cd19ff74b5c83480f08a8a43e0b", - "sha256:c4b93af7920cdf80f71650769464ccf1fb49a4b56ae0024173c24c48eb6b1612", - "sha256:cb5e8daac840e8879407acbe689a174f5ebaf344a062f8918e526824eb5d97af", - "sha256:d19f5f48883752b5ab34cff9e2f7e4a7f216296f33714e77d1beb03d108632b6", - "sha256:d30bc11d35743bf4ddf76674a0a369ec8a21f87aaa09b0661b04c5f6c46e8d7b", - "sha256:e12b61e0b86611e3f4c1756686d9086c1d36e6fd15326f5658112ad1f1cc8807", - "sha256:e6f6775eaaa08c0eec73e301f7592f4367ccde5e4e4df8e58320f2ebf161ea2c", - "sha256:e7155c0b004e936d381b15425273aee1cebc94f879c0ce82b0d7fecbf755d53a", - "sha256:e8633996579961f9b5a3008683344c2558d38420029d3c0bc7ff77c17949a4e1", - "sha256:f25a41f5b34b371a06dad3f01799706631331adc7d6c05253f5bca22068c7a34", - "sha256:f3e32ab7dd1b1ef67b9232c4cf5e2ee4cd517d4316ea910acaaa9c5712a1c663", - "sha256:f9b2dc7668418fb6f221e4bf701f716e05e8eadb4f1988a2487b11aedf8abe62", - "sha256:fab8f805e9675e61ed8538f192aad70500fa6afb33a8803932999b1049363a08", - "sha256:fe245cf4a73c20592f0f48da39748b3513db114465be78f0a36da847221bd1b4", - "sha256:ff798ad7a957a5021dcbab78dfff681f0cf15744d0e6af62bd6746984d9c9e9c" - ], - "index": "pypi", - "markers": "python_version >= '3.8' and python_full_version not in '3.9.0, 3.9.1'", - "version": "==46.0.2" - }, - "numpy": { - "hashes": [ - "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", - "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", - "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", - "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", - "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", - "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", - "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", - "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", - "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", - "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", - "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", - "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", - "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", - "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", - "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", - "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", - "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", - "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", - "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", - "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", - "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", - "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", - "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", - "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", - "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", - "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", - "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", - "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", - "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", - "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", - "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", - "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", - "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", - "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", - "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", - "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", - "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", - "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", - "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", - "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", - "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", - "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", - "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", - "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", - "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", - "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", - "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", - "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", - "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", - "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", - "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", - "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", - "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", - "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", - "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8" - ], - "markers": "python_version >= '3.9'", - "version": "==2.2.6" - }, - "opencv-python": { - "hashes": [ - "sha256:092c16da4c5a163a818f120c22c5e4a2f96e0db4f24e659c701f1fe629a690f9", - "sha256:51fd981c7df6af3e8f70b1556696b05224c4e6b6777bdd2a46b3d4fb09de1a92", - "sha256:812eb116ad2b4de43ee116fcd8991c3a687f099ada0b04e68f64899c09448e81", - "sha256:8b738389cede219405f6f3880b851efa3415ccd674752219377353f017d2994d", - "sha256:d98edb20aa932fd8ebd276a72627dad9dc097695b3d435a4257557bbb49a79d2", - "sha256:f9a1f08883257b95a5764bf517a32d75aec325319c8ed0f89739a57fae9e92a5", - "sha256:ff554d3f725b39878ac6a2e1fa232ec509c36130927afc18a1719ebf4fbf4357" - ], - "index": "pypi", - "markers": "python_version >= '3.6'", - "version": "==4.12.0.88" - }, - "pycairo": { - "hashes": [ - "sha256:026afd53b75291917a7412d9fe46dcfbaa0c028febd46ff1132d44a53ac2c8b6", - "sha256:082aef6b3a9dcc328fa648d38ed6b0a31c863e903ead57dd184b2e5f86790140", - "sha256:0fee15f5d72b13ba5fd065860312493dc1bca6ff2dce200ee9d704e11c94e60a", - "sha256:26ec5c6126781eb167089a123919f87baa2740da2cca9098be8b3a6b91cc5fbc", - "sha256:3ed16d48b8a79cc584cb1cb0ad62dfb265f2dda6d6a19ef5aab181693e19c83c", - "sha256:458877513eb2125513122e8aa9c938630e94bb0574f94f4fb5ab55eb23d6e9ac", - "sha256:53e6dbc98456f789965dad49ef89ce2c62f9a10fc96c8d084e14da0ffb73d8a6", - "sha256:6339979bfec8b58a06476094a9a5c104bd5a99932ddaff16ca0d9203d2f4482c", - "sha256:94f2ed204999ab95a0671a0fa948ffbb9f3d6fb8731fe787917f6d022d9c1c0f", - "sha256:957e0340ee1c279d197d4f7cfa96f6d8b48e453eec711fca999748d752468ff4", - "sha256:c00cfbb7f30eb7ca1d48886712932e2d91e8835a8496f4e423878296ceba573e", - "sha256:c6ae15392e28ebfc0b35d8dc05d395d3b6be4bad9ad4caecf0fa12c8e7150225", - "sha256:c8ab91a75025f984bc327ada335c787efb61c929ea0512063793cb36cee503d4", - "sha256:d0ab30585f536101ad6f09052fc3895e2a437ba57531ea07223d0e076248025d", - "sha256:d13352429d8a08a1cb3607767d23d2fb32e4c4f9faa642155383980ec1478c24", - "sha256:d50d190f5033992b55050b9f337ee42a45c3568445d5e5d7987bab96c278d8a6", - "sha256:da0d1e6d4842eed4d52779222c6e43d254244a486ca9fdab14e30042fd5bdf28", - "sha256:e955328c1a5147bf71ee94e206413ce15e12630296a79788fcd246c80e5337b8" - ], - "markers": "python_version >= '3.9'", - "version": "==1.28.0" - }, - "pycparser": { - "hashes": [ - "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", - "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934" - ], - "markers": "implementation_name != 'PyPy'", - "version": "==2.23" - }, - "pygobject": { - "hashes": [ - "sha256:a8da09134a0f7d56491cf2412145e35aa74e91d760e8f337096a1cda0b92bae7" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==3.54.3" - }, - "pyotp": { - "hashes": [ - "sha256:346b6642e0dbdde3b4ff5a930b664ca82abfa116356ed48cc42c7d6590d36f63", - "sha256:81c2e5865b8ac55e825b0358e496e1d9387c811e85bb40e71a3b29b288963612" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==2.9.0" - }, - "pyzbar": { - "hashes": [ - "sha256:13e3ee5a2f3a545204a285f41814d5c0db571967e8d4af8699a03afc55182a9c", - "sha256:4559628b8192feb25766d954b36a3753baaf5c97c03135aec7e4a026036b475d", - "sha256:8f4c5264c9c7c6b9f20d01efc52a4eba1ded47d9ba857a94130afe33703eb518" - ], - "index": "pypi", - "version": "==0.1.9" - } - }, - "develop": {} -} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c5c6e4c --- /dev/null +++ b/flake.nix @@ -0,0 +1,66 @@ +{ + description = "py2fa-gtk packaged the Nix way"; + + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; + + outputs = { self, nixpkgs }: + let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + + pythonEnv = pkgs.python312.withPackages (ps: with ps; [ + cryptography + opencv-python + pygobject3 + pyotp + pyzbar + ]); + + runtimeLibs = with pkgs; [ + gtk3 + zbar + gobject-introspection + ]; + + in + { + packages.${system}.default = pkgs.stdenv.mkDerivation { + name = "py2fa-gtk"; + + src = ./src; + + nativeBuildInputs = [ + pkgs.wrapGAppsHook3 + pkgs.makeWrapper + ]; + + buildInputs = runtimeLibs ++ [ pythonEnv ]; + + dontBuild = true; + + installPhase = '' + runHook preInstall + + # A. Create standard directory structure in the Nix Store + mkdir -p $out/bin $out/share/py2fa-gtk + + # B. Copy the python script to the store (Read-Only) + cp main.py $out/share/py2fa-gtk/main.py + + # C. Create the binary executable + # We wrap the python executable. + # 1. We tell it to run our specific script. + # 2. We inject the ZBar library path so pyzbar works. + makeWrapper ${pythonEnv}/bin/python3 $out/bin/py2fa-gtk \ + --add-flags "$out/share/py2fa-gtk/main.py" \ + --prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath [ pkgs.zbar ]}" + + runHook postInstall + ''; + + # wrapGAppsHook runs automatically after installPhase. + # It will see the binary in $out/bin/py2fa-gtk and double-wrap it + # to inject XDG_DATA_DIRS and GI_TYPELIB_PATH (fixing Pango/GTK). + }; + }; +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7204625 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "py2fa-gtk" +version = "0.1.1" +description = "Wannabe Authy replacement." +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "cryptography>=46.0.3", + "opencv-python>=4.12.0.88", + "pygobject>=3.54.5", + "pyotp>=2.9.0", + "pyzbar>=0.1.9", +] diff --git a/main.py b/src/main.py similarity index 74% rename from main.py rename to src/main.py index 1172e15..40be595 100755 --- a/main.py +++ b/src/main.py @@ -35,10 +35,15 @@ class PyTFAApp: self.add_button = Gtk.Button.new_with_label("Add Account") self.add_button.connect("clicked", self.on_add_account) header.pack_end(self.add_button) - + # Timer label self.timer_label = Gtk.Label() header.pack_start(self.timer_label) + + # Global timer progress bar anchored in header + self.timer_progress = Gtk.ProgressBar() + self.timer_progress.set_hexpand(True) + header.pack_start(self.timer_progress) # Create scrolled window for accounts scrolled = Gtk.ScrolledWindow() @@ -52,6 +57,7 @@ class PyTFAApp: # Password entry for encryption self.password = None self.account_widgets = {} # Store mapping of account to widgets + self.expanded_service = None # Load encrypted data self.load_encrypted_data() @@ -174,12 +180,20 @@ class PyTFAApp: else: confirm_entry = None + def trigger_ok(*_args): + dialog.response(Gtk.ResponseType.OK) + + password_entry.connect("activate", trigger_ok) + + if initial_setup and confirm_entry: + confirm_entry.connect("activate", trigger_ok) + dialog.show_all() response = dialog.run() - + if response == Gtk.ResponseType.OK: password = password_entry.get_text() - + if initial_setup: confirm = confirm_entry.get_text() if confirm_entry else "" if password != confirm: @@ -339,64 +353,103 @@ class PyTFAApp: box.set_margin_start(10) box.set_margin_end(10) row.add(box) - - # First row: Service name and buttons - top_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) - box.pack_start(top_box, False, False, 0) - - # Service label + + # Header with service name and arrow indicator + header_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + + arrow_image = Gtk.Image.new_from_icon_name("go-next-symbolic", Gtk.IconSize.BUTTON) + header_box.pack_start(arrow_image, False, False, 0) + service_label = Gtk.Label(label=account['service']) service_label.set_halign(Gtk.Align.START) service_label.set_hexpand(True) - top_box.pack_start(service_label, True, True, 0) - - # View secret button + header_box.pack_start(service_label, True, True, 0) + + header_event_box = Gtk.EventBox() + header_event_box.set_visible_window(False) + header_event_box.add(header_box) + box.pack_start(header_event_box, False, False, 0) + + # Details revealer containing buttons and code + details_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + + buttons_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + view_btn = Gtk.Button.new_from_icon_name("document-properties", Gtk.IconSize.BUTTON) view_btn.set_tooltip_text("View Secret") view_btn.connect("clicked", self.on_view_secret, account) - top_box.pack_start(view_btn, False, False, 0) - - # Rename button + buttons_box.pack_start(view_btn, False, False, 0) + rename_btn = Gtk.Button.new_from_icon_name("edit", Gtk.IconSize.BUTTON) rename_btn.set_tooltip_text("Rename Account") rename_btn.connect("clicked", self.on_rename_account, account) - top_box.pack_start(rename_btn, False, False, 0) - - # Delete button + buttons_box.pack_start(rename_btn, False, False, 0) + delete_btn = Gtk.Button.new_from_icon_name("edit-delete", Gtk.IconSize.BUTTON) delete_btn.set_tooltip_text("Delete Account") delete_btn.connect("clicked", self.on_delete_account, account) - top_box.pack_start(delete_btn, False, False, 0) - - # Second row: Code and progress bar - bottom_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - box.pack_start(bottom_box, False, False, 0) - - # Code label + buttons_box.pack_start(delete_btn, False, False, 0) + + details_box.pack_start(buttons_box, False, False, 0) + code_label = Gtk.Label(label="Generating...") code_label.set_halign(Gtk.Align.START) code_label.set_selectable(True) - - # Use monospace font for code font = Pango.FontDescription("Monospace 16") code_label.override_font(font) - - bottom_box.pack_start(code_label, False, False, 0) - - # Progress bar for timer - progress = Gtk.ProgressBar() - progress.set_hexpand(True) - bottom_box.pack_start(progress, True, True, 0) - + details_box.pack_start(code_label, False, False, 0) + + revealer = Gtk.Revealer() + revealer.set_transition_type(Gtk.RevealerTransitionType.SLIDE_DOWN) + revealer.add(details_box) + box.pack_start(revealer, False, False, 0) + + header_event_box.connect( + "button-press-event", + lambda _widget, _event, service=account['service']: self.toggle_account_row(service) + ) + self.accounts_list.add(row) - - # Store widget references for updating + self.account_widgets[account['service']] = { 'code_label': code_label, - 'progress_bar': progress + 'revealer': revealer, + 'arrow_image': arrow_image } - + self.accounts_list.show_all() + + if self.expanded_service and self.expanded_service not in self.account_widgets: + self.expanded_service = None + + for service, widgets in self.account_widgets.items(): + is_expanded = service == self.expanded_service + widgets['revealer'].set_reveal_child(is_expanded) + widgets['arrow_image'].set_from_icon_name( + "go-down-symbolic" if is_expanded else "go-next-symbolic", + Gtk.IconSize.BUTTON + ) + + def toggle_account_row(self, service): + """Expand or collapse the account row for the given service.""" + if service == self.expanded_service: + widgets = self.account_widgets.get(service) + if widgets: + widgets['revealer'].set_reveal_child(False) + widgets['arrow_image'].set_from_icon_name("go-next-symbolic", Gtk.IconSize.BUTTON) + self.expanded_service = None + return + + if self.expanded_service and self.expanded_service in self.account_widgets: + prev_widgets = self.account_widgets[self.expanded_service] + prev_widgets['revealer'].set_reveal_child(False) + prev_widgets['arrow_image'].set_from_icon_name("go-next-symbolic", Gtk.IconSize.BUTTON) + + widgets = self.account_widgets.get(service) + if widgets: + widgets['revealer'].set_reveal_child(True) + widgets['arrow_image'].set_from_icon_name("go-down-symbolic", Gtk.IconSize.BUTTON) + self.expanded_service = service def on_view_secret(self, widget, account): """Handle view secret button click""" @@ -502,19 +555,16 @@ class PyTFAApp: # Update global timer self.timer_label.set_label(f"Time remaining: {int(time_remaining)}s") - + self.timer_progress.set_fraction(time_remaining / 30.0) + for account in self.accounts: try: totp = pyotp.TOTP(account['secret']) code = totp.now() - + # Update the code label if it exists if account['service'] in self.account_widgets: self.account_widgets[account['service']]['code_label'].set_label(code) - - # Update progress bar - progress = time_remaining / 30.0 - self.account_widgets[account['service']]['progress_bar'].set_fraction(progress) except Exception as e: print(f"Error generating code for {account['service']}: {e}") if account['service'] in self.account_widgets: @@ -533,10 +583,12 @@ if __name__ == "__main__": app.run() # ENCRYPTED_DATA_BEGIN -# mZoIGF4HqcLjGtXOlcvolGdBQUFBQUJvOXZVNFZ3TjRvUEpsZXMzbTFndk1kNzRRdUtRT0hlM0NkSVVsYW1oOUdiOGF6U0IwN3NMd1k5d2dvM283S3lHNm9oS01oSXR4NVlfbWRqMUFZd1FlbXFQSURKUENMcldNeG9GdVVwbUVsWS0wdGVyR0hCcDduZVNNalhjeGlhZDFpNDZTOEc5elc5WE9pclVuVkhUVVZ1OEtTcXpjZHQzTmZmX3o0cU1SUnM0TWg2dz0= -# MRH8T2C7L4KboZkxOWKlmmdBQUFBQUJvOXZVNFlya1lUcldJRVF0NmYwbEVQWDBvRnZrTEE5NnJlTmszNFBMeVhqdjdNMHZwYmN6RWFidzBwUWtUVy1JTnphZTJRdWYyWTJlLVFlcG9QTlhDUzhhTjhuUU5Xb28xbm1maUdYWmI5V045ZEV4a0J0ZE9UX3FoZkRDYjc1cFlxZlU2UlJUemtDbzhxNElFNFdxYVNJM2xmQTJlQ1RISEMyYU1mYkYweHhrWEtGeG1fdTAwbVNHQmZjeU90SkdianJfbTV6ZGo5NklYTTFrYjBOOEZXRDVrQUE9PQ== -# b/XflIEz4lkcMVph0999TmdBQUFBQUJvOXZVNEV5dHVrMDR1eVNJVHA5bVRwY2FrUjc4aWlNMU9mbGF2a0hnXzFiaElUUDdwZnlLWE5rMU5jalphUmZKdE15VzhkZTNFUW1UZTFXMXJUeDFINm9acVBFb1FIUzdKTG16b1hMbVdqRmZRTGx2ZEgwOUc5RUVuNFIxZmJ0RzlHaUJRZmVDcU1rTFhCaFh3SnlTTkxhTGFFeVlrRWlUOWo1N20tNEhwbkluRDJldTRDRG1WZ0tiN1dlejdacHJheEpSZzRvV2c4cm5mMHEwVWJqakc1SzNSdEE9PQ== -# bdXDoFkfPrR87t3fp49FKmdBQUFBQUJvOXZVNGI0bGJ5bGRieVliNjRwQlBnbnJVZHlnMHhBbHRTZmF5TmJma0JncUktV1UxQUlpZGVhZ0d1UzJzWng4bW1KcTNld0E0QUFkd191TFhZdG1Ic2dzTWI4Z3JDb0FYSzh5Y09jUW5IN2U1bkNjVVlqRVdjeWtWZktzUnkxcHhrT0Y1cGxjVnhyMDRhT2IzRno2MjRKU0M3Zl9vT1JYOVlRLWJsQjNqR1VRY0JsV3I4VFlKUDVEMmVDWmVhX1VPbzdLQVZOUEhIRC1oNWVpSlpwejVheUFmYUE9PQ== -# 0kfRYRErfQmH/3PJyhe7JWdBQUFBQUJvOXZVNHZFSkxIc1NoSExuREdJbDdkS2trZFJjSVRsY0FQbXI3S0dxMFo2VC11MUkwTXFlRWYxTGpfYmVwTXhIR1VscWtrRmhaRUdwRExPN0F5SmJzcHJ0Yy1sTm9sR0ZfSEI5dnBpaEpBSHRUU25FR3dqOHVYLUxIMldrdEt1ZXBWMWdCUUNFZ3NCSlZ0aE5VNW1CUFpPdnk5YkFQSVdvVS1ObVdYYVVqQjdpOHF3aGZTWXBRZ2M0cHY1TXo2eFQxMDBmSEZ3eHdLUVBEcU9XS1ZiaWVKNkY0SEE9PQ== -# HWO6JmCH0Hcyr3d/9nOvI2dBQUFBQUJvOXZVNHYtS3B0NURmOHNDaGQ0bVVFc0ljZTRjQnVtM0hLb3c2X3ZETVFuTXVuMGszQ19vR25YX204eXRIMkFvZjVQWHBoZGhPeENUWjVKZXhtcVNIajBkSTJVUXg2UHdSNURBS0NVaklPaGZLZFYyZ1l2UE1zRGVINlEzZWlVaW1rU0o3R2xZTEpDTUhHY3ZtdUVFRElSUUhZZz09 +# Ewlit3P96YVgZ86uVZrX7GdBQUFBQUJvLXdxUkJHS0NJNDN3MUVqaEl0dl9WV3JwSjVxbEVpc2J0RVVvLXh0MEJWRVM5OU5URXVfd1ZzekhTOEtOZ1VlUFNuRnVoeWJhNG1TLWcySmhRWjN0TjAteHR4QlpkR0M5Q0Z5X2hNRG0zUzRtS3Q2NWQwZjBiSmszNEg1S3lJNVBKOC1GS3drc1hyaFU1aVV4MldvQVA5M1RhUHZwbS1ZaTJvdmxTQ1JTNEpoamlMND0= +# Qcd7zQyGRMZ1SYi/F8FwT2dBQUFBQUJvLXdxUjdOSnpSZEJfZFoxOEpPN2k4VERPYWcxc0xWc1FvTDZmSlBrcTJicVplRF9lX2dDSTkxaEI4SjVQUUhwbjM3SnlKTy1GVk1tSUI0dkFFUjVWenBXZEZJYzRXanNhUVNaSUtTaTZCUV9QTzlZV3BSLUZ1cjJxQzJBa0UxMmJPUDZVRUNwSGFyOVRlY245VjJTNkRLSzdZUEVvSEE0MlhiUlBtVE9sTkxnMEpMek5lVnU2NDQwX0lMV0NEdTkxUEI2TnFtWmduVmQ1TUpRRHZmay1QQllza1E9PQ== +# G7ULlxeW09qxA8SlSBKK1GdBQUFBQUJvLXdxUm5JNkFZVWVnclZqeVBPalp5QlFiNmluYkF6RUJfdGtGak12Qk5oVkZIOVZubmJybmN5VXcwLUd2ZVVsUmk1Snp1ZmQ5RXRPTEgxakVqazFaS0pKcHBDNklVRjk4QW43cExKU1JKejNhOUJkaWQzYWhrQVBQZVp0NFpJTjVnanlEYU1SdXZ4dTVoQ3pRZUtjTXhjdlRJcWJfMldpSFUzMU92UU5Lc2ZzMHJqY21aSkljanZaa0d0bnNuNnJQZ0R1d3FmeHREYnlTVTdKa3RRRlRkOFBGMmc9PQ== +# to+Bv630zSX8GD21uLYxImdBQUFBQUJvLXdxUndoRUItNlNnLW1DeEZiLWNRQWN0ME5xTFdiM1cwenBRNGplc1hyOS1UU1dRQWkzU1ozSTlZaTFQalhGWmhIZE05cjJYSFo1R2g0SkV4U0lMRFRFNkYwVGwwWGtiX1J6bUdZWkkxWjNiRzFHNExEWDVaQ1hGQ0VHZzR5akU3R3JQczJjTlRNdy1aYTFWNkF6ZGhfcm1ZSzZrazhjSjg4a256dzJ1aVJxMlNsa0ExMURKUFo3S2NGYnBiYk5RSE9VMWk5cF93Q1AyQ0stSGpEc0EzaDEteHc9PQ== +# +9TvLgxAJ5yWkfHKg28LsmdBQUFBQUJvLXdxUll4VFN0N2ZCR2FMY2E3T2QxMVJyUFd2OHhQMkZ1RGNVVEFTRFhxT0o3djVOb3dzdjctaVVhZTNQQmQ1N3hmc05hZ3Q4YXY1ZHp5Ny1QNmphVGgxUmRYTWFycTd1TEc0a0tVT19FMnJfZHE5S3RyeWNWUnB6SVFfTUc3aWwzV0NFMGhnemhtX2k3OWFoMEktZ1haSlZaS3lLaktzbU1qN1hSbXVWbV9SN3R0amxOR0VPekFHSzVfVkc3ckVrcFZiVTBwcVVraFhEQnRoQnZOUU44NG8tamc9PQ== +# 98ekR6qtjEkNyVWP7USeCWdBQUFBQUJvLXdxUmdFam5zYVBhTjNLSFljUDAtcm1BOGtoU041anlKUlRHZmg3Uk1jTTNBdGlFaThQWVEydFBZTFNnRmExU1BQejNUYzBRWThRQjQ0ZHRrWXV0R25mbFRsdnM5NlNiamJ0WWZfT3l3MkM0YTlZVGpGLUhlX2hXejZQQWZZMG4wbG5pdEpDbjVWSFdnZGQwM0NhWXlZWmlldz09 +# DuOwoPOc8aBVRxPRnYH7vWdBQUFBQUJvLXdxUkRfY0NMS3VNTmVzUGF2cG9La01WY3g4eGVGM3ZpQ0ZSS1lyOW9oaVJiUFBSY3dHY1cwQ2l6TEFwcllUNnNCM2pQRFI5TDlUM2EtUkhXZEhidGFZdlVzUzdLTVByWDZ4ZmQ2YjA1VFVzTWtMcXJ0N0FGU3p4ZGFoTm14amM1eTVBUnhoSGI3ZzJZVGJ3cFVGN3BWbDR3MTJabVEtX0l1b3h2cUZyX3dVMXM5aVNXUldyMHgtU2VQamdWUDNrZUE5Wkk3NVIwTlIxbW8yTnNoYkY4YlJ4TXc9PQ== +# GUAmOT2XETLoscmqGUYwdWdBQUFBQUJvLXdxUmphVXQ2V1ppS2w5c1JHTDNDYlkxSG9qdGJ1WWNnbXA3UDcwc3BaMExIRU81WTFwUkcxOG0zTjFFNlFBbzQzc2R2V1d1X0wtNUVoVklSTWxVRUF3Vk5CSDZmd3NZUXZSWXNETHlDSDhGcmtXVlp1Q1lWaTJPZlIyOGx4VXZMUXZOb25IeEJXeWtlc1V2RS1mUTBzTFNlYWNob18wTGFYei1pcmQtVEhfM1FaVDNXbXpSalZGb3JXZUpPVU5FcUl0Zm5mcUdRQ2hoWmNfdkczM1JNcjhiV09MOG9SbWU0cU9WZzVCNUxGSDhQak09 # ENCRYPTED_DATA_END diff --git a/qr2text.py b/src/qr2text.py similarity index 100% rename from qr2text.py rename to src/qr2text.py diff --git a/todo b/todo new file mode 100644 index 0000000..a9f8f2b --- /dev/null +++ b/todo @@ -0,0 +1 @@ +convert development to devenv