This commit is contained in:
TheK0tYaRa 2025-10-08 14:38:49 +03:00
commit 7650831193
4 changed files with 961 additions and 0 deletions

17
Pipfile Normal file
View file

@ -0,0 +1,17 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
pygobject = "*"
pyotp = "*"
cryptography = "*"
opencv-python = "*"
pyzbar = "*"
[dev-packages]
[requires]
python_version = "3.12"
python_full_version = "3.12.11"

306
Pipfile.lock generated Normal file
View file

@ -0,0 +1,306 @@
{
"_meta": {
"hash": {
"sha256": "a5b273fadf48fc35b4b1acb4d06208a304c14ef15dd9efd693542a6668d0f4e1"
},
"pipfile-spec": 6,
"requires": {
"python_full_version": "3.12.11",
"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": {}
}

537
main.py Executable file
View file

@ -0,0 +1,537 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GLib, Pango
import base64
import hashlib
import pyotp
import json
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import os
import re
import time
class PyTFAApp:
def __init__(self):
# Create main window
self.window = Gtk.Window(title="PyTFA - 2FA Authenticator")
self.window.set_default_size(500, 500)
self.window.set_border_width(10)
self.window.connect("destroy", Gtk.main_quit)
# Create main box
self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
self.window.add(self.main_box)
# Create header bar
header = Gtk.HeaderBar()
header.set_show_close_button(True)
header.props.title = "PyTFA - 2FA Authenticator"
self.window.set_titlebar(header)
# Add account button
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)
# Create scrolled window for accounts
scrolled = Gtk.ScrolledWindow()
self.main_box.pack_start(scrolled, True, True, 0)
# Create list box for accounts
self.accounts_list = Gtk.ListBox()
self.accounts_list.set_selection_mode(Gtk.SelectionMode.NONE)
scrolled.add(self.accounts_list)
# Password entry for encryption
self.password = None
self.account_widgets = {} # Store mapping of account to widgets
# Load encrypted data
self.load_encrypted_data()
# Start timer to update codes
GLib.timeout_add_seconds(1, self.update_codes)
def derive_key(self, password, salt):
"""Derive encryption key from password"""
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
)
return base64.urlsafe_b64encode(kdf.derive(password.encode()))
def encrypt_account(self, account, password):
"""Encrypt a single account"""
salt = os.urandom(16)
key = self.derive_key(password, salt)
f = Fernet(key)
encrypted = f.encrypt(json.dumps(account).encode())
return base64.b64encode(salt + encrypted).decode()
def decrypt_account(self, encrypted_data, password):
"""Decrypt a single account"""
try:
data = base64.b64decode(encrypted_data.encode())
salt, encrypted = data[:16], data[16:]
key = self.derive_key(password, salt)
f = Fernet(key)
decrypted = f.decrypt(encrypted)
return json.loads(decrypted.decode())
except:
return None
def load_encrypted_data(self):
"""Load encrypted data from the script itself"""
# Read the current script
with open(__file__, 'r') as f:
content = f.read()
# Find the encrypted data block
match = re.search(r'# ENCRYPTED_DATA_BEGIN\n(.*?)\n# ENCRYPTED_DATA_END', content, re.DOTALL)
if match:
encrypted_block = match.group(1).strip()
# Ask for password
self.show_password_dialog(encrypted_block)
else:
# No encrypted data found, initialize empty
self.accounts = []
self.show_password_dialog(initial_setup=True)
def save_encrypted_data(self):
"""Save encrypted data to the script itself"""
if not self.password or not self.accounts:
return
# Encrypt each account separately
encrypted_accounts = []
for account in self.accounts:
encrypted = self.encrypt_account(account, self.password)
encrypted_accounts.append(f"# {encrypted}")
encrypted_block = '# ENCRYPTED_DATA_BEGIN\n' + '\n'.join(encrypted_accounts) + '\n# ENCRYPTED_DATA_END'
# Read the current script
with open(__file__, 'r') as f:
content = f.read()
# Replace or add the encrypted data block
pattern = r'# ENCRYPTED_DATA_BEGIN\n.*\n# ENCRYPTED_DATA_END'
if re.search(pattern, content, re.DOTALL):
new_content = re.sub(pattern, encrypted_block, content, flags=re.DOTALL)
else:
# Add the encrypted data block at the end
new_content = content + '\n' + encrypted_block
# Write back to the script
with open(__file__, 'w') as f:
f.write(new_content)
def show_password_dialog(self, encrypted_block=None, initial_setup=False):
"""Show password entry dialog"""
dialog = Gtk.Dialog(
title="PyTFA Password" if not initial_setup else "Set PyTFA Password",
parent=self.window,
flags=0
)
dialog.add_buttons(
Gtk.STOCK_OK, Gtk.ResponseType.OK,
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL
)
dialog.set_default_size(300, 150)
box = dialog.get_content_area()
box.set_spacing(10)
box.set_margin_top(10)
box.set_margin_bottom(10)
box.set_margin_start(10)
box.set_margin_end(10)
label = Gtk.Label(label="Enter password to decrypt your 2FA accounts:" if not initial_setup
else "Set a password to encrypt your 2FA accounts:")
box.add(label)
password_entry = Gtk.Entry()
password_entry.set_visibility(False)
password_entry.set_placeholder_text("Password")
box.add(password_entry)
# For initial setup, add a confirmation field
if initial_setup:
confirm_entry = Gtk.Entry()
confirm_entry.set_visibility(False)
confirm_entry.set_placeholder_text("Confirm Password")
box.add(confirm_entry)
else:
confirm_entry = None
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:
error_dialog = Gtk.MessageDialog(
parent=self.window,
flags=0,
message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.OK,
text="Passwords do not match!"
)
error_dialog.run()
error_dialog.destroy()
dialog.destroy()
self.show_password_dialog(initial_setup=True)
return
self.password = password
self.accounts = []
else:
self.password = password
# Try to decrypt the accounts
self.accounts = []
for line in encrypted_block.split('\n'):
line = line.strip()
if line.startswith('# '):
encrypted_data = line[2:].strip()
account = self.decrypt_account(encrypted_data, password)
if account:
self.accounts.append(account)
if not self.accounts:
error_dialog = Gtk.MessageDialog(
parent=self.window,
flags=0,
message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.OK,
text="Incorrect password or no accounts found!"
)
error_dialog.run()
error_dialog.destroy()
dialog.destroy()
self.show_password_dialog(encrypted_block)
return
self.update_accounts_list()
else:
if not initial_setup:
Gtk.main_quit()
else:
# Can't proceed without a password
error_dialog = Gtk.MessageDialog(
parent=self.window,
flags=0,
message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.OK,
text="Password is required!"
)
error_dialog.run()
error_dialog.destroy()
dialog.destroy()
self.show_password_dialog(initial_setup=True)
return
dialog.destroy()
def on_add_account(self, widget):
"""Handle add account button click"""
dialog = Gtk.Dialog(
title="Add 2FA Account",
parent=self.window,
flags=0
)
dialog.add_buttons(
Gtk.STOCK_OK, Gtk.ResponseType.OK,
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL
)
dialog.set_default_size(400, 200)
box = dialog.get_content_area()
box.set_spacing(10)
box.set_margin_top(10)
box.set_margin_bottom(10)
box.set_margin_start(10)
box.set_margin_end(10)
# Service name entry
service_label = Gtk.Label(label="Service Name:")
service_label.set_halign(Gtk.Align.START)
box.add(service_label)
service_entry = Gtk.Entry()
service_entry.set_placeholder_text("e.g., GitHub, Google")
box.add(service_entry)
# Secret key entry
secret_label = Gtk.Label(label="Secret Key:")
secret_label.set_halign(Gtk.Align.START)
box.add(secret_label)
secret_entry = Gtk.Entry()
secret_entry.set_placeholder_text("Base32 secret key")
box.add(secret_entry)
dialog.show_all()
response = dialog.run()
if response == Gtk.ResponseType.OK:
service = service_entry.get_text().strip()
secret = secret_entry.get_text().strip().replace(" ", "")
if service and secret:
# Check for duplicates
duplicate_service = any(acc['service'] == service for acc in self.accounts)
duplicate_secret = any(acc['secret'] == secret for acc in self.accounts)
if duplicate_service:
self.show_error_dialog("Service name already exists!")
elif duplicate_secret:
self.show_error_dialog("Secret already exists for another account!")
else:
# Add the account
account = {
'service': service,
'secret': secret
}
self.accounts.append(account)
self.update_accounts_list()
self.save_encrypted_data()
dialog.destroy()
def show_error_dialog(self, message):
"""Show an error dialog"""
dialog = Gtk.MessageDialog(
parent=self.window,
flags=0,
message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.OK,
text=message
)
dialog.run()
dialog.destroy()
def update_accounts_list(self):
"""Update the accounts list in the UI"""
# Clear current list and widget mapping
for child in self.accounts_list.get_children():
self.accounts_list.remove(child)
self.account_widgets = {}
# Add accounts to list
for account in self.accounts:
row = Gtk.ListBoxRow()
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
box.set_margin_top(10)
box.set_margin_bottom(10)
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
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
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
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
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
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)
self.accounts_list.add(row)
# Store widget references for updating
self.account_widgets[account['service']] = {
'code_label': code_label,
'progress_bar': progress
}
self.accounts_list.show_all()
def on_view_secret(self, widget, account):
"""Handle view secret button click"""
dialog = Gtk.Dialog(
title=f"Secret for {account['service']}",
parent=self.window,
flags=0
)
dialog.add_buttons(Gtk.STOCK_OK, Gtk.ResponseType.OK)
dialog.set_default_size(400, 100)
box = dialog.get_content_area()
box.set_spacing(10)
box.set_margin_top(10)
box.set_margin_bottom(10)
box.set_margin_start(10)
box.set_margin_end(10)
# Secret label
secret_label = Gtk.Label(label=account['secret'])
secret_label.set_selectable(True)
# Use monospace font for secret
font = Pango.FontDescription("Monospace 12")
secret_label.override_font(font)
box.add(secret_label)
dialog.show_all()
dialog.run()
dialog.destroy()
def on_rename_account(self, widget, account):
"""Handle rename account button click"""
dialog = Gtk.Dialog(
title=f"Rename {account['service']}",
parent=self.window,
flags=0
)
dialog.add_buttons(
Gtk.STOCK_OK, Gtk.ResponseType.OK,
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL
)
dialog.set_default_size(400, 100)
box = dialog.get_content_area()
box.set_spacing(10)
box.set_margin_top(10)
box.set_margin_bottom(10)
box.set_margin_start(10)
box.set_margin_end(10)
# Service name entry
service_label = Gtk.Label(label="New Service Name:")
service_label.set_halign(Gtk.Align.START)
box.add(service_label)
service_entry = Gtk.Entry()
service_entry.set_text(account['service'])
box.add(service_entry)
dialog.show_all()
response = dialog.run()
if response == Gtk.ResponseType.OK:
new_service = service_entry.get_text().strip()
if new_service and new_service != account['service']:
# Check for duplicates
duplicate = any(acc['service'] == new_service for acc in self.accounts if acc != account)
if duplicate:
self.show_error_dialog("Service name already exists!")
else:
# Update the account
account['service'] = new_service
self.update_accounts_list()
self.save_encrypted_data()
dialog.destroy()
def on_delete_account(self, widget, account):
"""Handle account deletion"""
dialog = Gtk.MessageDialog(
parent=self.window,
flags=0,
message_type=Gtk.MessageType.QUESTION,
buttons=Gtk.ButtonsType.YES_NO,
text=f"Delete account for {account['service']}?"
)
response = dialog.run()
dialog.destroy()
if response == Gtk.ResponseType.YES:
self.accounts = [acc for acc in self.accounts if acc != account]
self.update_accounts_list()
self.save_encrypted_data()
def update_codes(self):
"""Update all TOTP codes and timer"""
current_time = time.time()
time_remaining = 30 - (current_time % 30)
# Update global timer
self.timer_label.set_label(f"Time remaining: {int(time_remaining)}s")
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:
self.account_widgets[account['service']]['code_label'].set_label("Invalid")
return True # Continue timeout
def run(self):
"""Run the application"""
self.window.show_all()
Gtk.main()
# Create and run the application
if __name__ == "__main__":
app = PyTFAApp()
app.run()
# ENCRYPTED_DATA_BEGIN
# a1c1Nlor366L/O/A+uqZ0WdBQUFBQUJvNWt3THhjbDAwM0QwZHVTaW0wUWxfbHFuZ3ZfeEJFVEh0bVNBeGxKTy1CWGVfRDN1aFRrM2pnT01CTnFrS1NUVUtPYWR4Qm5BRUdZOUR4bzluTHVRdURETlYwYzhTajFuRUg2SHMtOXIwYUJrYnE2SURycWR0MVNjelZBbVVraVNuMm5YWDNfZXNXbkktLTNveHVpSEdWLVBPMHM0bHNCd3dfMlJUdmtseWwtMFliTT0=
# ENCRYPTED_DATA_END

101
qr2text.py Executable file
View file

@ -0,0 +1,101 @@
#!/usr/bin/env python3
import sys
import cv2
from pyzbar import pyzbar
import re
def extract_totp_from_qr(image_path):
"""Extract TOTP secret from QR code image"""
try:
# Read the image
image = cv2.imread(image_path)
if image is None:
print(f"Error: Could not load image from {image_path}")
return None
# Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Decode QR codes
decoded_objects = pyzbar.decode(gray)
if not decoded_objects:
print("No QR code found in the image")
return None
# Process decoded objects
for obj in decoded_objects:
data = obj.data.decode('utf-8')
# Try to extract TOTP information
totp_info = extract_totp_info(data)
if totp_info:
return totp_info
print("No TOTP data found in QR code")
return None
except Exception as e:
print(f"Error processing QR code: {str(e)}")
return None
def extract_totp_info(data):
"""Extract TOTP information from QR code data"""
# Check if it's a TOTP URI
if data.startswith('otpauth://totp/'):
# Extract secret
secret_match = re.search(r'secret=([^&]+)', data)
secret = secret_match.group(1) if secret_match else None
# Extract issuer
issuer_match = re.search(r'issuer=([^&]+)', data)
issuer = issuer_match.group(1) if issuer_match else None
# Extract account name
account_match = re.search(r'totp/([^?]+)', data)
account = account_match.group(1) if account_match else None
if account and ':' in account:
# Some formats use "issuer:account"
parts = account.split(':', 1)
if not issuer:
issuer = parts[0]
account = parts[1] if len(parts) > 1 else parts[0]
return {
'secret': secret,
'issuer': issuer,
'account': account,
'full_uri': data
}
# Check if it's just a base32 secret
elif re.match(r'^[A-Z2-7]+=*$', data):
return {
'secret': data,
'note': 'Raw TOTP secret'
}
return None
def main():
if len(sys.argv) != 2:
print("Usage: qr_to_totp.py <image_path>")
sys.exit(1)
image_path = sys.argv[1]
result = extract_totp_from_qr(image_path)
if result:
#print("TOTP Information Extracted:")
for key, value in result.items():
print(f"{key}: {value}")
# For easy copying, print just the secret
#if 'secret' in result:
# print(f"\nSecret only: {result['secret']}")
else:
sys.exit(1)
if __name__ == "__main__":
main()