├── .gitignore ├── Pipfile ├── Pipfile.lock ├── Procfile ├── README.md ├── app.json ├── app.py ├── runtime.txt ├── templates └── index.html ├── utils.py └── variables.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | dist 3 | build 4 | *.pyc 5 | *.pyo 6 | *.swp 7 | *.swa 8 | *.egg-info 9 | .env 10 | tconnectsync-check-output.log 11 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | flask = "*" 10 | tconnectsync = ">=2.1.3" 11 | flask-apscheduler = {git = "https://github.com/viniciuschiele/flask-apscheduler"} 12 | gunicorn = "*" 13 | 14 | [requires] 15 | python_version = "3.9" 16 | 17 | [scripts] 18 | web = "gunicorn app:app" 19 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "72781934f0fe0fe99080977e39d9066c8ef8758d3808786e3c057d029103a7ce" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.9" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "apscheduler": { 20 | "hashes": [ 21 | "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a", 22 | "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661" 23 | ], 24 | "markers": "python_version >= '3.6'", 25 | "version": "==3.10.4" 26 | }, 27 | "arrow": { 28 | "hashes": [ 29 | "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", 30 | "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85" 31 | ], 32 | "markers": "python_version >= '3.8'", 33 | "version": "==1.3.0" 34 | }, 35 | "beautifulsoup4": { 36 | "hashes": [ 37 | "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", 38 | "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed" 39 | ], 40 | "markers": "python_full_version >= '3.6.0'", 41 | "version": "==4.12.3" 42 | }, 43 | "blinker": { 44 | "hashes": [ 45 | "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01", 46 | "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83" 47 | ], 48 | "markers": "python_version >= '3.8'", 49 | "version": "==1.8.2" 50 | }, 51 | "bs4": { 52 | "hashes": [ 53 | "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925", 54 | "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc" 55 | ], 56 | "version": "==0.0.2" 57 | }, 58 | "certifi": { 59 | "hashes": [ 60 | "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", 61 | "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" 62 | ], 63 | "markers": "python_version >= '3.6'", 64 | "version": "==2024.8.30" 65 | }, 66 | "cffi": { 67 | "hashes": [ 68 | "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", 69 | "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", 70 | "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", 71 | "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", 72 | "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", 73 | "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", 74 | "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", 75 | "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", 76 | "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", 77 | "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", 78 | "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", 79 | "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", 80 | "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", 81 | "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", 82 | "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", 83 | "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", 84 | "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", 85 | "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", 86 | "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", 87 | "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", 88 | "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", 89 | "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", 90 | "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", 91 | "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", 92 | "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", 93 | "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", 94 | "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", 95 | "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", 96 | "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", 97 | "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", 98 | "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", 99 | "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", 100 | "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", 101 | "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", 102 | "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", 103 | "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", 104 | "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", 105 | "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", 106 | "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", 107 | "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", 108 | "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", 109 | "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", 110 | "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", 111 | "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", 112 | "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", 113 | "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", 114 | "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", 115 | "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", 116 | "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", 117 | "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", 118 | "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", 119 | "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", 120 | "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", 121 | "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", 122 | "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", 123 | "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", 124 | "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", 125 | "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", 126 | "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", 127 | "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", 128 | "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", 129 | "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", 130 | "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", 131 | "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", 132 | "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", 133 | "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", 134 | "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b" 135 | ], 136 | "markers": "python_version >= '3.8'", 137 | "version": "==1.17.1" 138 | }, 139 | "charset-normalizer": { 140 | "hashes": [ 141 | "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", 142 | "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", 143 | "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", 144 | "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", 145 | "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", 146 | "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", 147 | "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", 148 | "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", 149 | "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", 150 | "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", 151 | "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", 152 | "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", 153 | "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", 154 | "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", 155 | "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", 156 | "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", 157 | "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", 158 | "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", 159 | "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", 160 | "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", 161 | "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", 162 | "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", 163 | "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", 164 | "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", 165 | "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", 166 | "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", 167 | "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", 168 | "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", 169 | "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", 170 | "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", 171 | "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", 172 | "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", 173 | "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", 174 | "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", 175 | "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", 176 | "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", 177 | "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", 178 | "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", 179 | "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", 180 | "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", 181 | "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", 182 | "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", 183 | "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", 184 | "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", 185 | "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", 186 | "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", 187 | "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", 188 | "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", 189 | "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", 190 | "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", 191 | "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", 192 | "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", 193 | "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", 194 | "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", 195 | "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", 196 | "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", 197 | "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", 198 | "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", 199 | "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", 200 | "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", 201 | "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", 202 | "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", 203 | "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", 204 | "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", 205 | "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", 206 | "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", 207 | "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", 208 | "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", 209 | "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", 210 | "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", 211 | "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", 212 | "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", 213 | "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", 214 | "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", 215 | "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", 216 | "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", 217 | "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", 218 | "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", 219 | "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", 220 | "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", 221 | "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", 222 | "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", 223 | "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", 224 | "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", 225 | "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", 226 | "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", 227 | "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", 228 | "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", 229 | "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", 230 | "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" 231 | ], 232 | "markers": "python_full_version >= '3.7.0'", 233 | "version": "==3.3.2" 234 | }, 235 | "click": { 236 | "hashes": [ 237 | "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", 238 | "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" 239 | ], 240 | "markers": "python_version >= '3.7'", 241 | "version": "==8.1.7" 242 | }, 243 | "cryptography": { 244 | "hashes": [ 245 | "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494", 246 | "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806", 247 | "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d", 248 | "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062", 249 | "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2", 250 | "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", 251 | "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1", 252 | "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85", 253 | "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84", 254 | "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042", 255 | "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d", 256 | "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962", 257 | "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2", 258 | "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa", 259 | "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d", 260 | "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365", 261 | "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96", 262 | "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", 263 | "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d", 264 | "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d", 265 | "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c", 266 | "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", 267 | "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277", 268 | "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172", 269 | "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034", 270 | "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", 271 | "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289" 272 | ], 273 | "markers": "python_version >= '3.7'", 274 | "version": "==43.0.1" 275 | }, 276 | "dataclasses-json": { 277 | "hashes": [ 278 | "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", 279 | "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0" 280 | ], 281 | "markers": "python_version >= '3.7' and python_version < '4.0'", 282 | "version": "==0.6.7" 283 | }, 284 | "flask": { 285 | "hashes": [ 286 | "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3", 287 | "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842" 288 | ], 289 | "index": "pypi", 290 | "markers": "python_version >= '3.8'", 291 | "version": "==3.0.3" 292 | }, 293 | "flask-apscheduler": { 294 | "git": "https://github.com/viniciuschiele/flask-apscheduler", 295 | "markers": "python_version >= '3.8'", 296 | "ref": "d5b6cdd10415a413b2773b7750d56b7dfed68018" 297 | }, 298 | "gunicorn": { 299 | "hashes": [ 300 | "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", 301 | "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" 302 | ], 303 | "index": "pypi", 304 | "markers": "python_version >= '3.7'", 305 | "version": "==23.0.0" 306 | }, 307 | "idna": { 308 | "hashes": [ 309 | "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", 310 | "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" 311 | ], 312 | "markers": "python_version >= '3.6'", 313 | "version": "==3.10" 314 | }, 315 | "importlib-metadata": { 316 | "hashes": [ 317 | "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", 318 | "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7" 319 | ], 320 | "markers": "python_version < '3.10'", 321 | "version": "==8.5.0" 322 | }, 323 | "itsdangerous": { 324 | "hashes": [ 325 | "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", 326 | "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173" 327 | ], 328 | "markers": "python_version >= '3.8'", 329 | "version": "==2.2.0" 330 | }, 331 | "jinja2": { 332 | "hashes": [ 333 | "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", 334 | "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d" 335 | ], 336 | "markers": "python_version >= '3.7'", 337 | "version": "==3.1.4" 338 | }, 339 | "lxml": { 340 | "hashes": [ 341 | "sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e", 342 | "sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229", 343 | "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3", 344 | "sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5", 345 | "sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70", 346 | "sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15", 347 | "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002", 348 | "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd", 349 | "sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22", 350 | "sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf", 351 | "sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22", 352 | "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832", 353 | "sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727", 354 | "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e", 355 | "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30", 356 | "sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f", 357 | "sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f", 358 | "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51", 359 | "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4", 360 | "sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de", 361 | "sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875", 362 | "sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42", 363 | "sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e", 364 | "sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6", 365 | "sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391", 366 | "sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc", 367 | "sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b", 368 | "sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237", 369 | "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4", 370 | "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86", 371 | "sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f", 372 | "sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a", 373 | "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8", 374 | "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f", 375 | "sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903", 376 | "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03", 377 | "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e", 378 | "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99", 379 | "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7", 380 | "sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab", 381 | "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", 382 | "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22", 383 | "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492", 384 | "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b", 385 | "sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3", 386 | "sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be", 387 | "sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469", 388 | "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f", 389 | "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a", 390 | "sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c", 391 | "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a", 392 | "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4", 393 | "sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94", 394 | "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442", 395 | "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b", 396 | "sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84", 397 | "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c", 398 | "sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9", 399 | "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1", 400 | "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be", 401 | "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367", 402 | "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e", 403 | "sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21", 404 | "sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa", 405 | "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16", 406 | "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d", 407 | "sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe", 408 | "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83", 409 | "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba", 410 | "sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040", 411 | "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763", 412 | "sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8", 413 | "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff", 414 | "sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2", 415 | "sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a", 416 | "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b", 417 | "sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce", 418 | "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c", 419 | "sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577", 420 | "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8", 421 | "sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71", 422 | "sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512", 423 | "sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540", 424 | "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f", 425 | "sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2", 426 | "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a", 427 | "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce", 428 | "sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e", 429 | "sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2", 430 | "sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27", 431 | "sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1", 432 | "sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d", 433 | "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1", 434 | "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330", 435 | "sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920", 436 | "sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99", 437 | "sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff", 438 | "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18", 439 | "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff", 440 | "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c", 441 | "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179", 442 | "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080", 443 | "sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19", 444 | "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d", 445 | "sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70", 446 | "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32", 447 | "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a", 448 | "sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2", 449 | "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79", 450 | "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3", 451 | "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5", 452 | "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f", 453 | "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d", 454 | "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3", 455 | "sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b", 456 | "sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753", 457 | "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9", 458 | "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957", 459 | "sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033", 460 | "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb", 461 | "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656", 462 | "sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab", 463 | "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b", 464 | "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d", 465 | "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd", 466 | "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859", 467 | "sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11", 468 | "sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c", 469 | "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a", 470 | "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005", 471 | "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654", 472 | "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80", 473 | "sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e", 474 | "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec", 475 | "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7", 476 | "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965", 477 | "sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945", 478 | "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8" 479 | ], 480 | "markers": "python_version >= '3.6'", 481 | "version": "==5.3.0" 482 | }, 483 | "markupsafe": { 484 | "hashes": [ 485 | "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", 486 | "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", 487 | "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", 488 | "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", 489 | "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", 490 | "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", 491 | "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", 492 | "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", 493 | "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", 494 | "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", 495 | "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", 496 | "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", 497 | "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", 498 | "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", 499 | "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", 500 | "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", 501 | "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", 502 | "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", 503 | "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", 504 | "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", 505 | "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", 506 | "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", 507 | "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", 508 | "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", 509 | "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", 510 | "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", 511 | "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", 512 | "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", 513 | "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", 514 | "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", 515 | "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", 516 | "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", 517 | "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", 518 | "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", 519 | "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", 520 | "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", 521 | "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", 522 | "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", 523 | "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", 524 | "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", 525 | "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", 526 | "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", 527 | "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", 528 | "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", 529 | "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", 530 | "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", 531 | "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", 532 | "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", 533 | "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", 534 | "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", 535 | "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", 536 | "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", 537 | "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", 538 | "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", 539 | "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", 540 | "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", 541 | "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", 542 | "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", 543 | "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", 544 | "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68" 545 | ], 546 | "markers": "python_version >= '3.7'", 547 | "version": "==2.1.5" 548 | }, 549 | "marshmallow": { 550 | "hashes": [ 551 | "sha256:4972f529104a220bb8637d595aa4c9762afbe7f7a77d82dc58c1615d70c5823e", 552 | "sha256:71a2dce49ef901c3f97ed296ae5051135fd3febd2bf43afe0ae9a82143a494d9" 553 | ], 554 | "markers": "python_version >= '3.8'", 555 | "version": "==3.22.0" 556 | }, 557 | "mypy-extensions": { 558 | "hashes": [ 559 | "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", 560 | "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" 561 | ], 562 | "markers": "python_version >= '3.5'", 563 | "version": "==1.0.0" 564 | }, 565 | "oauthlib": { 566 | "hashes": [ 567 | "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", 568 | "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918" 569 | ], 570 | "markers": "python_version >= '3.6'", 571 | "version": "==3.2.2" 572 | }, 573 | "packaging": { 574 | "hashes": [ 575 | "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", 576 | "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" 577 | ], 578 | "markers": "python_version >= '3.8'", 579 | "version": "==24.1" 580 | }, 581 | "platformdirs": { 582 | "hashes": [ 583 | "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3", 584 | "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e" 585 | ], 586 | "markers": "python_version >= '3.7'", 587 | "version": "==3.11.0" 588 | }, 589 | "pycparser": { 590 | "hashes": [ 591 | "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", 592 | "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" 593 | ], 594 | "markers": "python_version >= '3.8'", 595 | "version": "==2.22" 596 | }, 597 | "pyjwt": { 598 | "hashes": [ 599 | "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", 600 | "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c" 601 | ], 602 | "markers": "python_version >= '3.8'", 603 | "version": "==2.9.0" 604 | }, 605 | "pypng": { 606 | "hashes": [ 607 | "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c", 608 | "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1" 609 | ], 610 | "version": "==0.20220715.0" 611 | }, 612 | "pysocks": { 613 | "hashes": [ 614 | "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299", 615 | "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", 616 | "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0" 617 | ], 618 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 619 | "version": "==1.7.1" 620 | }, 621 | "python-dateutil": { 622 | "hashes": [ 623 | "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", 624 | "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" 625 | ], 626 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 627 | "version": "==2.9.0.post0" 628 | }, 629 | "python-dotenv": { 630 | "hashes": [ 631 | "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", 632 | "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" 633 | ], 634 | "markers": "python_version >= '3.8'", 635 | "version": "==1.0.1" 636 | }, 637 | "pytz": { 638 | "hashes": [ 639 | "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", 640 | "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725" 641 | ], 642 | "version": "==2024.2" 643 | }, 644 | "qrcode": { 645 | "hashes": [ 646 | "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a", 647 | "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845" 648 | ], 649 | "markers": "python_version >= '3.7'", 650 | "version": "==7.4.2" 651 | }, 652 | "requests": { 653 | "hashes": [ 654 | "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", 655 | "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" 656 | ], 657 | "markers": "python_version >= '3.8'", 658 | "version": "==2.32.3" 659 | }, 660 | "requests-mock": { 661 | "hashes": [ 662 | "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563", 663 | "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401" 664 | ], 665 | "markers": "python_version >= '3.5'", 666 | "version": "==1.12.1" 667 | }, 668 | "requests-oauthlib": { 669 | "hashes": [ 670 | "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5", 671 | "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a" 672 | ], 673 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 674 | "version": "==1.3.1" 675 | }, 676 | "requests-oidc": { 677 | "hashes": [ 678 | "sha256:227fbcc561d0acb54469d62a7d4bde055a8eeee7968cdb98fa732e0d2313d814", 679 | "sha256:84ea777457067f8f67fe25534b9c153f3f9a45336c83e073a93ce735a86406ca" 680 | ], 681 | "markers": "python_version >= '3.7' and python_version < '4'", 682 | "version": "==0.6.2" 683 | }, 684 | "six": { 685 | "hashes": [ 686 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 687 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 688 | ], 689 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 690 | "version": "==1.16.0" 691 | }, 692 | "soupsieve": { 693 | "hashes": [ 694 | "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", 695 | "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9" 696 | ], 697 | "markers": "python_version >= '3.8'", 698 | "version": "==2.6" 699 | }, 700 | "tconnectsync": { 701 | "hashes": [ 702 | "sha256:1bdc955e95219b85a191029eabd460f522c7d672f21bea3d0c9b6cb02dc11e1f", 703 | "sha256:4b28ff1449cf8b6db70be01ad4e7d80ec4cc028a37fa73601e1a0871f81b336e" 704 | ], 705 | "index": "pypi", 706 | "markers": "python_version >= '3.7'", 707 | "version": "==2.1.3" 708 | }, 709 | "typer": { 710 | "hashes": [ 711 | "sha256:aa6c4a4e2329d868b80ecbaf16f807f2b54e192209d7ac9dd42691d63f7a54eb", 712 | "sha256:f714c2d90afae3a7929fcd72a3abb08df305e1ff61719381384211c4070af57f" 713 | ], 714 | "markers": "python_version >= '3.6'", 715 | "version": "==0.9.4" 716 | }, 717 | "types-python-dateutil": { 718 | "hashes": [ 719 | "sha256:250e1d8e80e7bbc3a6c99b907762711d1a1cdd00e978ad39cb5940f6f0a87f3d", 720 | "sha256:58cb85449b2a56d6684e41aeefb4c4280631246a0da1a719bdbe6f3fb0317446" 721 | ], 722 | "markers": "python_version >= '3.8'", 723 | "version": "==2.9.0.20241003" 724 | }, 725 | "typing-extensions": { 726 | "hashes": [ 727 | "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", 728 | "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" 729 | ], 730 | "markers": "python_version >= '3.8'", 731 | "version": "==4.12.2" 732 | }, 733 | "typing-inspect": { 734 | "hashes": [ 735 | "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", 736 | "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78" 737 | ], 738 | "version": "==0.9.0" 739 | }, 740 | "tzlocal": { 741 | "hashes": [ 742 | "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8", 743 | "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e" 744 | ], 745 | "markers": "python_version >= '3.8'", 746 | "version": "==5.2" 747 | }, 748 | "urllib3": { 749 | "hashes": [ 750 | "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", 751 | "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9" 752 | ], 753 | "markers": "python_version >= '3.8'", 754 | "version": "==2.2.3" 755 | }, 756 | "werkzeug": { 757 | "hashes": [ 758 | "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c", 759 | "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306" 760 | ], 761 | "markers": "python_version >= '3.8'", 762 | "version": "==3.0.4" 763 | }, 764 | "zipp": { 765 | "hashes": [ 766 | "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", 767 | "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29" 768 | ], 769 | "markers": "python_version >= '3.8'", 770 | "version": "==3.20.2" 771 | } 772 | }, 773 | "develop": {} 774 | } 775 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn app:app 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TConnectSync for Heroku 2 | 3 | > **Warning** 4 | > **Heroku discontinued their Free Tier effective November 28, 2022.** 5 | > 6 | > The instructions below will still work on paid Heroku plans. 7 | > For a free alternative, [you can migrate to fly.io](https://fly.io/launch/heroku). 8 | > 9 | > More detailed instructions for using Fly and other Heroku alternatives are not currently available but may be added at a later date. 10 | > 11 | > If you need support, [open an issue on tconnectsync with the `heroku` tag.](https://github.com/jwoglom/tconnectsync/issues/new/choose) 12 | 13 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/jwoglom/tconnectsync-heroku) 14 | 15 | This project allows [tconnectsync][tconnectsync] to run in Heroku. 16 | Unfortunately, the Heroku Free plan is no longer available, so you will need to configure billing in Heroku and pay $5/month ([see Heroku pricing details](https://www.heroku.com/pricing)). 17 | 18 | **Note:** GitHub Issues have been disabled in this repository. To report or investigate issues, [use the GitHub Issues page for tconnectsync](https://github.com/jwoglom/tconnectsync/issues). 19 | 20 | ## Setup 21 | 22 | Before beginning, make sure you have set up the t:connect 23 | [Android](https://play.google.com/store/apps/details?id=com.tandemdiabetes.tconnect&hl=en_US&gl=US), 24 | or [iPhone/iOS](https://apps.apple.com/us/app/t-connect-mobile/id1455916023) application. 25 | If you have not used either, you can find more information or create an account [at the t:connect website]([t:connect web](https://tconnect.tandemdiabetes.com/). 26 | 27 | You'll need the following information: 28 | 29 | * Your t:connect username and password 30 | * Your t:slim X2 pump serial number (visible in Settings > My Pump > Pump Info) 31 | * Your Nightscout site URL 32 | * Your Nightscout site secret 33 | 34 | If you have not yet set up a Nightscout site, [view the Nightscout documentation and set that up first](https://nightscout.github.io/nightscout/new_user/), then come back to these instructions. 35 | Either log in to your existing Heroku account or create a new one. 36 | 37 | Next, you'll be creating a new application inside your Heroku account. 38 | This will not be replacing your existing Heroku Nightscout app, if you have one; you will 39 | be creating a second app with a new name. 40 | **Start by clicking the Deploy button below:** 41 | 42 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/jwoglom/tconnectsync-heroku) 43 | 44 | For *App name*, you can enter any value. 45 | We recommend **`MyNightscoutSite-tconnect`**. 46 | 47 | The app name determines the URL to access your tconnectsync-heroku website, e.g. if you enter the app name `MyNightscoutSite-tconnect`, then the URL for tconnectsync-heroku will be `https://MyNightscoutSite-tconnect.herokuapp.com`. 48 | 49 | The remainder of the options are [tconnectsync environment variables][tconnect-installation]: 50 | 51 | * `TCONNECT_EMAIL` - Your t:connect email 52 | * `TCONNECT_PASSWORD` - Your t:connect password 53 | * `NS_URL` - Your Nightscout site URL (e.g. https://yournightscoutsite.herokuapp.com) 54 | * `NS_SECRET` - Your Nightscout `API_SECRET` value 55 | * `PUMP_SERIAL_NUMBER` - The numeric serial number of your pump. Enter only the number, do not include '#' 56 | * `TIMEZONE_NAME` - The timezone code in which your phone and pump's time is set. ([View a full list of valid values](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)) 57 | 58 | A new variable called `TCONNECTSYNC_HEROKU_SECRET` will be automatically generated for you. 59 | This secret password will be used to access API endpoints which trigger your pump data from tconnect 60 | to be synchronized to Nightscout, as well as view the current status of synchronization. 61 | (You can think of it like the tconnectsync equivalent to Nightscout's `API_SECRET`.) 62 | You can find its automatically generated value by going to Settings > Reveal Config Vars in your Heroku dashboard after setup. 63 | 64 | Once you've filled out the Create New App form and pressed *Deploy App*, you'll be taken to the Heroku dashboard. 65 | From here, you can click on **Open App** to see whether the tconnectsync-heroku server is running. 66 | 67 | 68 | 69 | If you see a page like the following, then you're ready to move on! 70 | 71 | 72 | 73 | If you get an error, then jump to the instructions in the **Testing** section below. 74 | 75 | Otherwise, continue reading below to set up tconnectsync-heroku by creating an account with a separate service, **UptimeRobot**. 76 | 77 | ## UptimeRobot 78 | 79 | Even though your Heroku app is already up and running, we need to set up a service called **UptimeRobot** 80 | which will send a request to your Heroku app to keep it alive so that it can continue to process 81 | your incoming t:connect data in the background. 82 | 83 | ### Creating an Account 84 | Create an account at [UptimeRobot][uptimerobot] by going to https://uptimerobot.com. 85 | 86 | 87 | 88 | 89 | 90 | Once you register, you will be prompted to verify your email address. 91 | Click the link you receive from UptimeRobot in your email. 92 | 93 | 94 | 95 | If you're asked to upgrade to the PRO plan, click **Maybe later** 96 | 97 | 98 | 99 | ### Setting Up UptimeRobot 100 | Now that you have an account and are logged in, you should see a page like the following: 101 | 102 | 103 | 104 | Click the **Add New Monitor** button. You'll see a popup like this: 105 | 106 | 107 | 108 | Enter the following, one at a time: 109 | 110 | * Monitor Type: **HTTP(s)** 111 | * Friendly Name: **tconnectsync** 112 | * Monitoring Interval: **every 30 minutes** 113 | * Monitor Timeout: **1 minute** 114 | 115 | Enter the following as the URL: 116 | ``` 117 | https://MyNightscoutSite-tconnect.herokuapp.com/run?secret=YOUR_TCONNECTSYNC_HEROKU_SECRET_VALUE 118 | ``` 119 | 120 | **MyNightscoutSite-tconnect** is the App Name you provided for tconnectsync-heroku. 121 | 122 | To get the value of **YOUR_TCONNECTSYNC_HEROKU_SECRET_VALUE**: 123 | 124 | * Go into your Heroku dashboard (https://dashboard.heroku.com) and select the app you created for tconnectsync: 125 | 126 | 127 | 128 | * Click on Settings > Reveal Config Vars. 129 | 130 | 131 | 132 | * Now find the row which has `TCONNECTSYNC_HEROKU_SECRET` displayed on the left, 133 | and copy the value contained in the right-most text box next to it. 134 | This is the value of the `TCONNECTSYNC_HEROKU_SECRET` environment variable. 135 | 136 | (If you'd like, you can change the value here to a different password. 137 | If so, then save settings and then restart the app in heroku via More > Restart All Dynos.) 138 | 139 | 140 | Your options should look similar to this within UptimeRobot (but with your actual tconnectsync-heroku URL and secret value): 141 | 142 | 143 | 144 | Click **Create Monitor**. 145 | 146 | 147 | You can optionally specify an _alert contact to notify_ if you would like to receive an email whenever tconnectsync-heroku experiences an error. 148 | If you don't specify one, you'll have to hit the button again. 149 | 150 | 151 | Now, every 30 minutes, tconnectsync will be triggered to synchronize your data between t:connect and Nightscout! 152 | 153 | ## Testing 154 | 155 | You can hit the following URL to verify that your tconnectsync options are 156 | specified correctly, and that both t:connect and Nightscout are accessible: 157 | 158 | ``` 159 | https://MyNightscoutSite-tconnect.herokuapp.com/check_login?secret=YOUR_TCONNECTSYNC_HEROKU_SECRET_VALUE 160 | ``` 161 | 162 | (Remember to replace `MyNightscoutSite-tconnect` and `YOUR_TCONNECTSYNC_HEROKU_SECRET_VALUE` with your own values like mentioned before.) 163 | 164 | There is a lot of debugging output on this page, so scroll to the very bottom. 165 | If it says **No API errors returned!** then you are all good to go! 166 | 167 | If the page doesn't load, you can also view the application-side error logs by going to the Heroku dashboard and clicking on **More > View logs**: 168 | 169 | 170 | 171 | **If you need help,** you can do one of the following: 172 | * Post in the **CGM in the Cloud** Facebook group 173 | * Open a GitHub Issue at https://github.com/jwoglom/tconnectsync/issues/new/choose with the `heroku` tag. 174 | 175 | When asking for help, please be ready to copy-and-paste the output of the `check_login` URL 176 | referenced above which contains diagnostic information which may help to solve your issue(s). 177 | 178 | **The remainder of the instructions below are for advanced use cases.** 179 | Once you've gotten to this point, wait 30 minutes to see if pump data starts appearing in Nightscout! 180 | 181 | ## Troubleshooting 182 | 183 | ### _I get a Heroku error page when clicking **Open App** from the Heroku dashboard_ 184 | 185 | View the application-side error logs by going to the Heroku dashboard and clicking on **More > View logs**: 186 | 187 | 188 | 189 | Check if there are any errors about invalid configuration options. 190 | If the problem is something else, follow the **If you need help** instructions above. 191 | 192 | ### _The `check_login` page gives a `Received ApiException in ControlIQApi: ControlIQ API HTTP 404 response` error_ 193 | 194 | Please verify that you've completed the following steps: 195 | 196 | * Have you created an account on the tconnect website? 197 | * With the tconnect credentials you've specified, can you: 198 | * Log in to the Android or iOS app, with your pump paired and sending data 199 | * Log in at https://tconnect.tandemdiabetes.com and see your pump information appear 200 | 201 | If tconnectsync still gives this error, try: 202 | 203 | * Downloading the [Windows or MacOS TConnect Uploader application](https://tconnect.tandemdiabetes.com/GettingStarted/Download.aspx) and plug your pump into your computer to upload your settings. 204 | * Resetting your password at https://tconnect.tandemdiabetes.com and setting it to the same password you use in the Android or iOS app 205 | 206 | If this doesn't help, follow the **If you need help** instructions above. 207 | 208 | ## Updating synchronization features 209 | 210 | By default, tconnectsync-heroku will upload the following data from your pump to Nightscout: 211 | * Basal events 212 | * Bolus events 213 | 214 | Tconnectsync supports additional so-called _synchronization features_ which enable it to send 215 | additional pump data into Nightscout. [You can see a list of these features here](https://github.com/jwoglom/tconnectsync#What-Gets-Synced). 216 | 217 | To set custom synchronization features, go into your Heroku Config Vars settings (Settings > Reveal Config Vars) 218 | and add a key and value with the following: 219 | 220 | * Key: `TCONNECTSYNC_HEROKU_FEATURES` 221 | * Value: a comma-separated list of features, without spaces. e.g., `BASAL,BOLUS,PUMP_EVENTS` 222 | 223 | If you don't set this value, tconnectsync-heroku will default to tconnectsync's default synchronization 224 | features (only basal and bolus data). 225 | 226 | ## Updating to a new version 227 | 228 | Updates are made frequently to tconnectsync to correct bugs or add new features. 229 | When some time has past after setting up tconnectsync-heroku and you want to ensure 230 | you are up to date, follow these instructions: 231 | 232 | To update your Heroku instance of tconnectsync, perform the following steps. 233 | This is a tconnectsync specific version of the "Deploy using Heroku Git" 234 | instructions which are present in the "Deploy" tab of your Heroku console. 235 | 236 | You should perform the following steps on a Linux or MacOS computer. 237 | If using Windows, install the [Windows Subsystem for Linux](https://ubuntu.com/wsl) and 238 | run these steps inside the Ubuntu Terminal. 239 | 240 | 1. [Install the Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) 241 | 2. In a terminal, clone your Heroku repository and pull the latest version of tconnectsync-heroku: 242 | ``` 243 | heroku git:clone -a YOUR_HEROKU_SITE_NAME 244 | cd YOUR_HEROKU_SITE_NAME 245 | git remote add upstream https://github.com/jwoglom/tconnectsync-heroku 246 | git pull -r upstream main 247 | ``` 248 | 3. Deploy the updated app to Heroku: 249 | ``` 250 | git push heroku main 251 | ``` 252 | 253 | ## Alternate Configuration Using Background Tasks 254 | What follows is an alternate option, which may be easier to set up but may not work for all setups: 255 | 256 | Go into your Heroku Config Vars settings (Settings > Reveal Config Vars) 257 | and add a key and value with the following: 258 | 259 | * TCONNECTSYNC_HEROKU_INTERVAL_MINS - The interval at which a scheduled task should 260 | be performed for a synchronization. This will **only** function while the Flask 261 | application is running inside Heroku. (Heroku will occasionally shut down the app 262 | after some period of inactivity.) 263 | 264 | For example: 265 | * Key: `TCONNECTSYNC_HEROKU_INTERVAL_MINS` 266 | * Value: `30` 267 | 268 | When the Flask application is started, so long as it is running the tconnect 269 | synchronization task will run at the given interval. 270 | 271 | Note that **Heroku may shut down your dyno if it has not processed requests in 272 | 30 minutes**. 273 | 274 | You should ideally set up UptimeRobot to ping your application at the index URL 275 | (**not** the run endpoint, since that will cause the synchronization to happen 276 | both triggered by UptimeRobot and the background task daemon itself). 277 | This would reflect a URL such as: 278 | 279 | ``` 280 | https://YOUR-TCONNECTSYNC-APP-NAME.herokuapp.com/ 281 | ``` 282 | 283 | 284 | [tconnectsync]: https://github.com/jwoglom/tconnectsync 285 | [tconnect-installation]: https://github.com/jwoglom/tconnectsync#installation 286 | [uptimerobot]: https://uptimerobot.com/ 287 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TConnectSync for Heroku", 3 | "description": "Flask wrapper for tconnectsync running in Heroku, which synchronizes t:connect data to Nightscout", 4 | "keywords": [ 5 | "tconnect", 6 | "nightscout", 7 | "diabetes" 8 | ], 9 | "repository": "https://github.com/jwoglom/tconnectsync-heroku", 10 | "logo": "https://images.squarespace-cdn.com/content/v1/55f63267e4b0b1dfc5acf418/1460133565328-8JPQA577SKLI3YY9QB9N/image-asset.png?format=1000w", 11 | "success_url": "/", 12 | "env": { 13 | "TCONNECT_EMAIL": { 14 | "description": "Email address for tconnect", 15 | "value": "" 16 | }, 17 | "TCONNECT_PASSWORD": { 18 | "description": "Password for tconnect", 19 | "value": "" 20 | }, 21 | "PUMP_SERIAL_NUMBER": { 22 | "description": "Tandem t:slim X2 pump serial number. Visible in the t:connect web UI and in Settings > My Pump > Pump Info on the pump. Enter only the number, do not include '#'", 23 | "value": "" 24 | }, 25 | "NS_URL": { 26 | "description": "URL of your Nightscout instance. Should be entered like https://mynightscouturl.herokuapp.com", 27 | "value": "" 28 | }, 29 | "NS_SECRET": { 30 | "description": "Secret key (API_SECRET) of your Nightscout instance", 31 | "value": "" 32 | }, 33 | "TIMEZONE_NAME": { 34 | "description": "Timezone of your phone and pump. In TZ database format: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones", 35 | "value": "America/New_York" 36 | }, 37 | "TCONNECTSYNC_HEROKU_SECRET": { 38 | "description": "A secret key used to trigger a tconnectsync run and view additional info. This is auto-generated.", 39 | "generator": "secret" 40 | }, 41 | "TCONNECTSYNC_HEROKU_INTERVAL_MINS": { 42 | "description": "How frequently to trigger tconnectsync on its own. Set to 0 or blank if tconnectsync should not be triggered in a background thread", 43 | "value": "0" 44 | } 45 | }, 46 | "formation": { 47 | "web": { 48 | "quantity": 1, 49 | "size": "eco" 50 | } 51 | }, 52 | "image": "heroku/python" 53 | } -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | """Wrapper for tconnectsync running in Heroku.""" 2 | 3 | from variables import secret, interval_mins, tconnect_secret 4 | from utils import token_required, call, setup, get_time_args, run_update, as_text, parse_features 5 | 6 | from threading import Thread 7 | from flask import Flask, request 8 | from flask.templating import render_template 9 | from flask_apscheduler import APScheduler 10 | from flask_apscheduler.auth import HTTPBasicAuth 11 | 12 | from tconnectsync.check import check_login 13 | from tconnectsync import __version__ as tconnectsync_version 14 | 15 | version = '0.3.1' 16 | print("Launching tconnectsync-heroku %s" % str(version)) 17 | print("Using tconnectsync %s" % str(tconnectsync_version)) 18 | 19 | app = Flask(__name__) 20 | scheduler = APScheduler() 21 | scheduler.api_enabled = True 22 | scheduler.auth = HTTPBasicAuth() 23 | scheduler.init_app(app) 24 | scheduler.start() 25 | 26 | @scheduler.authenticate 27 | def authenticate(auth): 28 | if not secret: 29 | return False 30 | 31 | return auth["password"] == secret 32 | 33 | @app.route('/') 34 | def index_route(): 35 | job = scheduler.get_job('update') 36 | return render_template('index.html', 37 | tconnectsyncheroku_version=version, 38 | tconnectsync_version=tconnectsync_version, 39 | job=job, 40 | interval_mins=interval_mins) 41 | 42 | """Runs the tconnectsync login check synchronously, returning the result.""" 43 | @app.route('/check_login') 44 | @token_required 45 | def check_login_route(): 46 | tconnect, _ = setup() 47 | time_start, time_end = get_time_args(request.values.get("days")) 48 | return as_text(*call(check_login, [tconnect, time_start, time_end, True])) 49 | 50 | """Runs tconnectsync synchronously, returning the result.""" 51 | @app.route('/update') 52 | @token_required 53 | def update_route(): 54 | days = request.values.get("days") 55 | pretend = bool(request.values.get("pretend")) 56 | features = parse_features(request.values.get("features")) 57 | return as_text(*run_update(days, pretend, features)) 58 | 59 | """Runs tconnectsync in the background, does not return an error if one occurs.""" 60 | @app.route('/run') 61 | @token_required 62 | def run_route(): 63 | days = request.values.get("days") 64 | pretend = bool(request.values.get("pretend")) 65 | features = parse_features(request.values.get("features")) 66 | Thread(target=run_update, kwargs={'days': days, 'pretend': pretend, 'features': features}).start() 67 | return 'Triggered job' 68 | 69 | if interval_mins: 70 | print("Creating scheduled task for every", interval_mins, "minutes") 71 | @scheduler.task('interval', id='update', seconds=interval_mins * 60, misfire_grace_time=300) 72 | def scheduled_update_task(): 73 | print('Running update scheduler task') 74 | run_update(1, False, parse_features(None)) 75 | print('Finished update scheduler task') 76 | 77 | if __name__ == '__main__': 78 | import os 79 | LISTEN_HOST = os.getenv('LISTEN_HOST', '0.0.0.0') 80 | LISTEN_PORT = int(os.getenv('LISTEN_PORT', '5000')) 81 | app.run(LISTEN_HOST, LISTEN_PORT) 82 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.9.13 2 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tconnectsync-heroku 5 | 6 | 7 |

tconnectsync-heroku

8 |

The tconnectsync-heroku server is running

9 | 13 | 14 | {% if interval_mins %} 15 |

Tconnectsync will automatically run approximately every {{ interval_mins }} min so long as the Flask application is running.

16 |

It will run next at {{ job.next_run_time }}.

17 | {% else %} 18 |

Tconnectsync must be triggered manually by an external service (e.g. UptimeRobot).

19 | {% endif %} 20 | 21 |

 

22 |

Need help? Open a new issue on GitHub and add the "setup help" tag.

23 | 24 | 25 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | from variables import secret, interval_mins, tconnect_secret, default_features 2 | 3 | import io 4 | import traceback 5 | import datetime 6 | 7 | from contextlib import redirect_stdout, redirect_stderr 8 | from functools import wraps 9 | from flask import request, make_response 10 | 11 | from tconnectsync.api import TConnectApi 12 | from tconnectsync.nightscout import NightscoutApi 13 | from tconnectsync.sync.tandemsource.heroku_helpers import run_oneshot 14 | from tconnectsync.features import DEFAULT_FEATURES, ALL_FEATURES 15 | 16 | def call(fn, args, **kwargs): 17 | print('call args:', args) 18 | s = io.StringIO() 19 | code = 200 20 | with redirect_stdout(s), redirect_stderr(s): 21 | try: 22 | fn(*args, **kwargs) 23 | except Exception: 24 | traceback.print_exc(file=s) 25 | code = 500 26 | 27 | out = s.getvalue() 28 | print('call output:', out) 29 | 30 | return out, code 31 | 32 | def as_text(*args): 33 | resp = make_response(*args) 34 | resp.mimetype = 'text/plain' 35 | return resp 36 | 37 | def token_required(f): 38 | @wraps(f) 39 | def decorated_function(*args, **kwargs): 40 | if not secret: 41 | return "secret is not configured in the settings, so this page is inaccessible", 403 42 | if request.values.get("secret") is None: 43 | return "secret not provided as a GET or POST argument", 403 44 | if request.values.get("secret") != secret: 45 | return "secret is invalid", 403 46 | return f(*args, **kwargs) 47 | return decorated_function 48 | 49 | def setup(): 50 | tconnect = TConnectApi(tconnect_secret.TCONNECT_EMAIL, tconnect_secret.TCONNECT_PASSWORD) 51 | nightscout = NightscoutApi(tconnect_secret.NS_URL, tconnect_secret.NS_SECRET) 52 | 53 | return tconnect, nightscout 54 | 55 | def get_time_args(days): 56 | if days: 57 | days = int(days) 58 | else: 59 | days = 1 60 | 61 | time_end = datetime.datetime.now() 62 | time_start = time_end - datetime.timedelta(days=days) 63 | 64 | return time_start, time_end 65 | 66 | def run_update(days, pretend, features): 67 | tconnect, nightscout = setup() 68 | time_start, time_end = get_time_args(days) 69 | 70 | if features is None: 71 | features = DEFAULT_FEATURES 72 | 73 | out, code = call(run_oneshot, [tconnect, nightscout, pretend, features, tconnect_secret, time_start, time_end]) 74 | 75 | print('Completed with', code) 76 | print(out) 77 | 78 | return out, code 79 | 80 | # In order, uses the features (in comma-delimited format) that are: 81 | # - specified in the query (from the given argument) 82 | # - specified as TCONNECTSYNC_HEROKU_FEATURES to tconnectsync-heroku 83 | # The default value is the tconnectsync DEFAULT_FEATURES 84 | def parse_features(features): 85 | ret = default_features.split(",") 86 | if features is not None and len(features) > 0: 87 | ret = features.split(",") 88 | 89 | ret = [i.strip() for i in ret] 90 | for f in ret: 91 | if f not in ALL_FEATURES: 92 | raise RuntimeError("The feature '%s' was not recognized by tconnectsync. It is not one of: %s" % (f, str(ALL_FEATURES))) 93 | return ret 94 | -------------------------------------------------------------------------------- /variables.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | secret = os.environ.get('TCONNECTSYNC_HEROKU_SECRET') 4 | if not secret: 5 | print("The TCONNECTSYNC_HEROKU_SECRET environment variable is undefined,") 6 | print("so the API endpoints will not be accessible.") 7 | secret = None 8 | 9 | interval_mins = os.environ.get('TCONNECTSYNC_HEROKU_INTERVAL_MINS') 10 | if interval_mins: 11 | interval_mins = int(interval_mins) 12 | if interval_mins < 1: 13 | interval_mins = None 14 | 15 | if not interval_mins: 16 | print("The TCONNECTSYNC_HEROKU_INTERVAL_MINS environment variable is undefined,") 17 | print("so tconnectsync will not run automatically") 18 | interval_mins = None 19 | 20 | try: 21 | from tconnectsync import secret as tconnect_secret 22 | except Exception as e: 23 | print("Tconnect environment variables are undefined.") 24 | print("Please follow the setup instructions at:") 25 | print("https://github.com/jwoglom/tconnectsync-heroku") 26 | raise e 27 | 28 | from tconnectsync.features import DEFAULT_FEATURES as tconnectsync_default_features 29 | 30 | default_features = os.environ.get('TCONNECTSYNC_HEROKU_FEATURES') 31 | if not default_features: 32 | default_features = ",".join(tconnectsync_default_features) 33 | print("The TCONNECTSYNC_HEROKU_FEATURES environment variable is undefined,") 34 | print("so the following default tconnectsync features will be used: %s" % default_features) 35 | --------------------------------------------------------------------------------