OAuth with Mastodon

This commit is contained in:
Johnny Gear 2026-03-05 11:55:33 -06:00
parent 25df720757
commit 7b6e3700e4
17 changed files with 745 additions and 199 deletions

View file

@ -14,6 +14,7 @@ flask = "*"
flask-login = "*" flask-login = "*"
flask-vite = "*" flask-vite = "*"
flask-wtf = "*" flask-wtf = "*"
requests = "*"
[dev-packages] [dev-packages]

442
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "4299d04ee438b2a337ab2ffa56188011c85dae01631485736b3c84bbe390b00e" "sha256": "924aa8ce8d78c1a3967b00781a33da88547c257d6c18cccdc8fc7d9ecfebf29e"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -175,6 +175,133 @@
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.9'",
"version": "==1.9.0" "version": "==1.9.0"
}, },
"certifi": {
"hashes": [
"sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa",
"sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"
],
"markers": "python_version >= '3.7'",
"version": "==2026.2.25"
},
"charset-normalizer": {
"hashes": [
"sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad",
"sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93",
"sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394",
"sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89",
"sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc",
"sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86",
"sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63",
"sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d",
"sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f",
"sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8",
"sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0",
"sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505",
"sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161",
"sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af",
"sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152",
"sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318",
"sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72",
"sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4",
"sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e",
"sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3",
"sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576",
"sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c",
"sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1",
"sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8",
"sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1",
"sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2",
"sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44",
"sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26",
"sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88",
"sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016",
"sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede",
"sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf",
"sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a",
"sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc",
"sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0",
"sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84",
"sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db",
"sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1",
"sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7",
"sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed",
"sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8",
"sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133",
"sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e",
"sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef",
"sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14",
"sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2",
"sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0",
"sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d",
"sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828",
"sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f",
"sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf",
"sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6",
"sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328",
"sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090",
"sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa",
"sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381",
"sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c",
"sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb",
"sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc",
"sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a",
"sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec",
"sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc",
"sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac",
"sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e",
"sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313",
"sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569",
"sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3",
"sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d",
"sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525",
"sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894",
"sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3",
"sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9",
"sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a",
"sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9",
"sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14",
"sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25",
"sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50",
"sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf",
"sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1",
"sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3",
"sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac",
"sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e",
"sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815",
"sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c",
"sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6",
"sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6",
"sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e",
"sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4",
"sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84",
"sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69",
"sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15",
"sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191",
"sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0",
"sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897",
"sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd",
"sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2",
"sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794",
"sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d",
"sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074",
"sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3",
"sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224",
"sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838",
"sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a",
"sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d",
"sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d",
"sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f",
"sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8",
"sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490",
"sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966",
"sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9",
"sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3",
"sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e",
"sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"
],
"markers": "python_version >= '3.7'",
"version": "==3.4.4"
},
"click": { "click": {
"hashes": [ "hashes": [
"sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a",
@ -185,12 +312,12 @@
}, },
"flask": { "flask": {
"hashes": [ "hashes": [
"sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb",
"sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c" "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.9'",
"version": "==3.1.2" "version": "==3.1.3"
}, },
"flask-login": { "flask-login": {
"hashes": [ "hashes": [
@ -773,11 +900,11 @@
}, },
"pytz": { "pytz": {
"hashes": [ "hashes": [
"sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1",
"sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00" "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a"
], ],
"index": "pypi", "index": "pypi",
"version": "==2025.2" "version": "==2026.1.post1"
}, },
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
@ -859,22 +986,31 @@
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==6.0.3" "version": "==6.0.3"
}, },
"scheduler": { "requests": {
"hashes": [ "hashes": [
"sha256:4575a12cd269e4e4896409836fd911560cb63fb7634360ee62aa2fa4ec495ffd", "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6",
"sha256:8f52ea6390757e4f42a8becbcb474e7744a3082ea5e1cdb0c972ea8b2c5d1891" "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.9'",
"version": "==0.8.8" "version": "==2.32.5"
},
"scheduler": {
"hashes": [
"sha256:b2234a069448a617abbc511b9e18f48c4deb3dcd9dcc51721d9c90ef2943b72a",
"sha256:db55040e0f1c0824a9b02ff37fcecdcd10c2ee1d5b86d1761648981a555a00f8"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
"version": "==0.8.11"
}, },
"typeguard": { "typeguard": {
"hashes": [ "hashes": [
"sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74", "sha256:44d2bf329d49a244110a090b55f5f91aa82d9a9834ebfd30bcc73651e4a8cc40",
"sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e" "sha256:f6f8ecbbc819c9bc749983cc67c02391e16a9b43b8b27f15dc70ed7c4a007274"
], ],
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.9'",
"version": "==4.4.4" "version": "==4.5.1"
}, },
"typing-extensions": { "typing-extensions": {
"hashes": [ "hashes": [
@ -884,13 +1020,21 @@
"markers": "python_version < '3.13'", "markers": "python_version < '3.13'",
"version": "==4.15.0" "version": "==4.15.0"
}, },
"werkzeug": { "urllib3": {
"hashes": [ "hashes": [
"sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc", "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed",
"sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67" "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"
], ],
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.9'",
"version": "==3.1.5" "version": "==2.6.3"
},
"werkzeug": {
"hashes": [
"sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25",
"sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131"
],
"markers": "python_version >= '3.9'",
"version": "==3.1.6"
}, },
"wtforms": { "wtforms": {
"hashes": [ "hashes": [
@ -902,139 +1046,137 @@
}, },
"yarl": { "yarl": {
"hashes": [ "hashes": [
"sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc",
"sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8", "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4",
"sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85",
"sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", "sha256:0e1fdaa14ef51366d7757b45bde294e95f6c8c049194e793eedb8387c86d5993",
"sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf", "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222",
"sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890", "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de",
"sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25",
"sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6", "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e",
"sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2",
"sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e",
"sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed", "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860",
"sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957",
"sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760",
"sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52",
"sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b", "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788",
"sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912",
"sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719",
"sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035",
"sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", "sha256:263cd4f47159c09b8b685890af949195b51d1aa82ba451c5847ca9bc6413c220",
"sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412",
"sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", "sha256:2a6940a074fb3c48356ed0158a3ca5699c955ee4185b4d7d619be3c327143e05",
"sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41",
"sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", "sha256:31c9921eb8bd12633b41ad27686bbb0b1a2a9b8452bfdf221e34f311e9942ed4",
"sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba", "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4",
"sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd",
"sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", "sha256:389871e65468400d6283c0308e791a640b5ab5c83bcee02a2f51295f95e09748",
"sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a",
"sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4",
"sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748", "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34",
"sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069",
"sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25",
"sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2",
"sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb",
"sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f",
"sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5",
"sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737", "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8",
"sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c",
"sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512",
"sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6",
"sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", "sha256:51430653db848d258336cfa0244427b17d12db63d42603a55f0d4546f50f25b5",
"sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9",
"sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072",
"sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5",
"sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b", "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277",
"sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a",
"sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", "sha256:5ec2f42d41ccbd5df0270d7df31618a8ee267bfa50997f5d720ddba86c4a83a6",
"sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae",
"sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea", "sha256:5f10fd85e4b75967468af655228fbfd212bdf66db1c0d135065ce288982eda26",
"sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2",
"sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4",
"sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70",
"sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f", "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723",
"sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c",
"sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba", "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9",
"sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9", "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5",
"sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0", "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e",
"sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c",
"sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4",
"sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", "sha256:75e3026ab649bf48f9a10c0134512638725b521340293f202a69b567518d94e0",
"sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2",
"sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b",
"sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda", "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7",
"sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", "sha256:80e6d33a3d42a7549b409f199857b4fb54e2103fc44fb87605b6663b7a7ff750",
"sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2",
"sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c", "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474",
"sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", "sha256:85e9beda1f591bc73e77ea1c51965c68e98dafd0fec72cdd745f77d727466716",
"sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7",
"sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123",
"sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147", "sha256:8c4fe09e0780c6c3bf2b7d4af02ee2394439d11a523bbcf095cf4747c2932007",
"sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595",
"sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca", "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe",
"sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea",
"sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598",
"sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679",
"sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8",
"sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60", "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83",
"sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6",
"sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f",
"sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94",
"sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51",
"sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e", "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120",
"sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467", "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039",
"sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1",
"sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859", "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05",
"sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", "sha256:ab5f043cb8a2d71c981c09c510da013bc79fd661f5c60139f00dd3c3cc4f2ffb",
"sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144",
"sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa",
"sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054", "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a",
"sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99",
"sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", "sha256:b39cb32a6582750b6cc77bfb3c49c0f8760dc18dc96ec9fb55fbb0f04e08b928",
"sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b", "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d",
"sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3",
"sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434",
"sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86",
"sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", "sha256:bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46",
"sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319",
"sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67",
"sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c",
"sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169",
"sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", "sha256:cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c",
"sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e", "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59",
"sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107",
"sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca", "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4",
"sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a",
"sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb", "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb",
"sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f",
"sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", "sha256:dbf507e9ef5688bada447a24d68b4b58dd389ba93b7afc065a2ba892bea54769",
"sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432",
"sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090",
"sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2", "sha256:dda608c88cf709b1d406bdfcd84d8d63cff7c9e577a403c6108ce8ce9dcc8764",
"sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", "sha256:debe9c4f41c32990771be5c22b56f810659f9ddf3d63f67abfdcaa2c6c9c5c1d",
"sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4",
"sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a", "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b",
"sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d",
"sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543",
"sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24",
"sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5",
"sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b",
"sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", "sha256:ed5f69ce7be7902e5c70ea19eb72d20abf7d725ab5d49777d696e32d4fc1811d",
"sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b", "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b",
"sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6",
"sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735",
"sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e",
"sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc", "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28",
"sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3",
"sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401",
"sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6",
"sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d"
"sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd",
"sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249"
], ],
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.10'",
"version": "==1.22.0" "version": "==1.23.0"
} }
}, },
"develop": {} "develop": {}

View file

@ -7,14 +7,29 @@ class BaseModel(Model):
class Meta: class Meta:
database = database database = database
# class MastodonServer(BaseModel):
# Order Configuration name = TextField(unique=True, null=False)
#
client_id = TextField(null=False)
client_secret = TextField(null=False)
class Meta:
table_name = 'mastodon_server'
class User(BaseModel): class User(BaseModel):
telegram_username = TextField(unique=True) telegram_username = TextField(unique=True)
telegram_chat_id = IntegerField() telegram_chat_id = IntegerField()
telegram_photo_url = TextField(null=True) telegram_photo_url = TextField(null=True)
mastodon_server = ForeignKeyField(
MastodonServer,
column_name='mastodon_server_id',
field='id',
null=True,
)
mastodon_username = TextField(null=True)
mastodon_access_token = TextField(null=True)
class Meta: class Meta:
table_name = 'user' table_name = 'user'
@ -28,6 +43,9 @@ class DomSubUsers(BaseModel):
(('dom_id', 'sub_id'), True), (('dom_id', 'sub_id'), True),
) )
#
# Order Configuration
#
class OrdersPool(BaseModel): class OrdersPool(BaseModel):
name = TextField() name = TextField()

View file

@ -1,6 +1,6 @@
import datetime import datetime
from .models import database, User, OrdersPool, DomSubUsers, Repeat, SkipDay, OrderStatus from .models import database, User, OrdersPool, DomSubUsers, Repeat, SkipDay, OrderStatus, MastodonServer
def initdb(): def initdb():
database.connect() database.connect()
@ -21,6 +21,33 @@ def user_get(username):
telegram_username=username telegram_username=username
) )
def user_mastodon_server_set(id, mastodon_server):
q = User.update(
mastodon_server=mastodon_server
).where(
User.id == id
)
return q.execute()
def user_mastodon_user_set(id, mastodon_username, mastodon_access_token):
q = User.update(
mastodon_username=mastodon_username,
mastodon_access_token=mastodon_access_token
).where(
User.id == id
)
return q.execute()
def mastodon_server_get(name):
return MastodonServer.get(name=name)
def mastodon_server_put(name, client_id, client_secret):
return MastodonServer.create(
name=name,
client_id=client_id,
client_secret=client_secret
)
def orders_pool_list(user_id): def orders_pool_list(user_id):
return OrdersPool.select().where(OrdersPool.user_id == user_id) return OrdersPool.select().where(OrdersPool.user_id == user_id)

View file

@ -1,16 +1,63 @@
import requests
from functools import wraps from functools import wraps
from flask import Blueprint, jsonify, abort, request from flask import Blueprint, jsonify, abort, request
from flask_login import login_required, current_user from flask_login import login_required, current_user
from db.models import database, OrdersPool, Order, OrderAddOn from db.models import database, OrdersPool, Order, OrderAddOn, MastodonServer
from db.queries import user_get, domsubusers_list, orders_pool_list, orders_pool from db.queries import user_get, domsubusers_list, orders_pool_list, orders_pool, mastodon_server_get, mastodon_server_put, user_mastodon_server_set
from settings import MASTODON_OAUTH_CLIENT_NAME, MASTODON_OAUTH_REDIRECT_URI, MASTODON_OAUTH_SCOPES, MASTODON_OAUTH_CLIENT_WEBSITE
api = Blueprint('api', __name__) api = Blueprint('api', __name__)
@api.route("/me") @api.route("/me")
@login_required @login_required
def me(): def me():
user = current_user.db_user
return jsonify({ return jsonify({
"username": current_user.db_user.telegram_username "username": user.telegram_username,
"telegram_photo_url": user.telegram_photo_url,
"mastodon_server": user.mastodon_server.name,
"mastodon_username": user.mastodon_username
})
@api.route('/mastodon_oauth')
@login_required
def mastodon_oauth():
server_name = request.args['server']
try:
server = mastodon_server_get(server_name)
except MastodonServer.DoesNotExist:
payload = {
'client_name': MASTODON_OAUTH_CLIENT_NAME,
'redirect_uris': MASTODON_OAUTH_REDIRECT_URI,
'scopes': MASTODON_OAUTH_SCOPES,
'website': MASTODON_OAUTH_CLIENT_WEBSITE
}
r = requests.post(f'https://{server_name}/api/v1/apps', data=payload)
if r.ok:
app = r.json()
import pdb; pdb.set_trace()
server = mastodon_server_put(
server_name,
app['client_id'],
app['client_secret']
)
else:
abort(500)
user_mastodon_server_set(current_user.db_user.id, server)
payload = {
"client_id": server.client_id,
"client_secret": server.client_secret,
"redirect_uri": MASTODON_OAUTH_REDIRECT_URI,
"grant_type": "client_credentials"
}
return jsonify({
"url": f"https://{server_name}/oauth/authorize?client_id={server.client_id}&scope={MASTODON_OAUTH_SCOPES}&redirect_uri={MASTODON_OAUTH_REDIRECT_URI}&response_type=code"
}) })
@api.route('/subs') @api.route('/subs')

View file

@ -1,15 +1,16 @@
import sys import sys
import requests
from pathlib import Path from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from flask import Flask, render_template, request, redirect, flash from flask import Flask, render_template, request, redirect, flash, abort
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from flask_wtf.csrf import CSRFProtect from flask_wtf.csrf import CSRFProtect
import hashlib import hashlib
import hmac import hmac
from settings import FLASK_SECRET_KEY, TELEGRAM_API_TOKEN, TELEGRAM_BOT_NAME, TELEGRAM_BOT_DOMAIN from settings import FLASK_SECRET_KEY, TELEGRAM_API_TOKEN, TELEGRAM_BOT_NAME, TELEGRAM_BOT_DOMAIN, MASTODON_OAUTH_REDIRECT_URI
from db.queries import user_get from db.queries import user_get, user_mastodon_user_set
from api import api from api import api
app = Flask(__name__) app = Flask(__name__)
@ -50,6 +51,7 @@ def index():
@app.route('/dashboard/', defaults={'path': ''}) @app.route('/dashboard/', defaults={'path': ''})
@app.route('/orders/<path:path>') @app.route('/orders/<path:path>')
@app.route('/profile', defaults={'path': ''})
@app.route('/dashboard/<path:path>') @app.route('/dashboard/<path:path>')
@login_required @login_required
def dashboard(path): def dashboard(path):
@ -70,6 +72,44 @@ def string_generator(data_incoming):
string_cat = '\n'.join(string_arr) string_cat = '\n'.join(string_arr)
return string_cat return string_cat
@app.route('/oauth')
@login_required
def mastodon_oauth():
code = request.args['code']
server = current_user.db_user.mastodon_server
payload = {
'grant_type': 'authorization_code',
'client_id': server.client_id,
'client_secret': server.client_secret,
'redirect_uri': MASTODON_OAUTH_REDIRECT_URI,
'code': code
}
token_request = requests.post(f"https://{server.name}/oauth/token", data=payload)
if not token_request.ok:
import pdb; pdb.set_trace()
abort(500)
access_token = token_request.json()['access_token']
headers = {
"Authorization": f"Bearer {access_token}"
}
verify_request = requests.get(f"https://{server.name}/api/v1/accounts/verify_credentials", headers=headers)
if not verify_request.ok:
import pdb; pdb.set_trace()
abort(500)
username = verify_request.json()['username']
user_mastodon_user_set(current_user.db_user.id, username, access_token)
return redirect('/profile')
@app.route('/login') @app.route('/login')
def login(): def login():
tg_data = { tg_data = {

View file

@ -63,9 +63,9 @@ export const Dashboard: React.FC = () => {
const { username } = useUserContext(); const { username } = useUserContext();
return ( return (
<Container> <>
<OrderSets orderSets={orderSets} username={username} /> <OrderSets orderSets={orderSets} username={username} />
{subs.length > 0 ? <SubsList subs={subs} /> : null} {subs.length > 0 ? <SubsList subs={subs} /> : null}
</Container> </>
); );
}; };

27
flask/vite/src/Header.tsx Normal file
View file

@ -0,0 +1,27 @@
import { Avatar, Text, Container, Flex, UnstyledButton } from "@mantine/core";
import React from "react";
import { useUserContext } from "./UserContext";
import { Outlet, useNavigate } from "react-router";
export const Header: React.FC = () => {
const { username, telegram_photo_url } = useUserContext();
const navigate = useNavigate();
const handleClick = React.useCallback(() => {
navigate("/profile");
}, []);
return (
<Container p="sm">
<Flex justify="flex-end">
<UnstyledButton onClick={handleClick}>
<Flex align="center" gap="sm">
<Text c="blue">{username}</Text>
<Avatar src={telegram_photo_url} />
</Flex>
</UnstyledButton>
</Flex>
<Outlet />
</Container>
);
};

View file

@ -218,7 +218,7 @@ export const OrderSet: React.FC = () => {
); );
return ( return (
<Container pb="xl"> <>
<Box mb="lg"> <Box mb="lg">
<Title order={1}>{orderSet?.name || "New Order Set"}</Title> <Title order={1}>{orderSet?.name || "New Order Set"}</Title>
<Link to={`/orders/${username}`}>Return to {username}</Link> <Link to={`/orders/${username}`}>Return to {username}</Link>
@ -377,6 +377,7 @@ export const OrderSet: React.FC = () => {
</Paper> </Paper>
</Affix> </Affix>
</form> </form>
</Container> <Box pb="xl"></Box>
</>
); );
}; };

View file

@ -0,0 +1,76 @@
import React from "react";
import { useUserContext } from "./UserContext";
import {
Avatar,
Box,
Button,
Flex,
Modal,
Paper,
TextInput,
Title,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { NavigateButton } from "./NavigateButton";
import { Link } from "react-router";
export const AuthorizeMastodonModal: React.FC<{
opened: boolean;
onClose: () => void;
}> = ({ opened, onClose }) => {
const [serverName, setServerName] = React.useState("rubber.social");
const [loading, setLoading] = React.useState(false);
const handleClick = React.useCallback(() => {
setLoading(true);
fetch(`/api/mastodon_oauth?server=${encodeURI(serverName)}`)
.then((response) => response.json())
.then((json) => {
if (json["url"]) {
window.location.href = json["url"];
}
});
}, []);
return (
<Modal opened={opened} onClose={onClose} title="Authorize with Mastodon">
<TextInput
label="Server Domain"
value={serverName}
onChange={(event) => setServerName(event.currentTarget.value)}
/>
<Button loading={loading} onClick={handleClick}>
Authorize
</Button>
</Modal>
);
};
export const Profile: React.FC = () => {
const { username, telegram_photo_url, mastodon_server, mastodon_username } =
useUserContext();
const [opened, { open, close }] = useDisclosure(false);
const mastodon_account = React.useMemo(
() => `@${mastodon_username}@${mastodon_server}`,
[mastodon_server, mastodon_username],
);
return (
<>
<Flex align="center" gap="md" mb="xl">
<Avatar src={telegram_photo_url} size="150" />
<Title>{username}</Title>
</Flex>
<Box mb="md">
<Link to={`/dashboard`}>Return to dashboard</Link>
</Box>
<Paper bg="gray.1">
<Title order={4}>Mastodon</Title>
<TextInput label="Account" w="50%" value={mastodon_account} />
<Button onClick={open}>Authorize with Mastodon</Button>
<AuthorizeMastodonModal opened={opened} onClose={close} />
</Paper>
</>
);
};

View file

@ -24,12 +24,10 @@ export const SubOrderSets: React.FC = () => {
const orderSets = useLoaderData<OrderSetProps["orderSets"]>(); const orderSets = useLoaderData<OrderSetProps["orderSets"]>();
return ( return (
<Container>
<OrderSets <OrderSets
username={sub_username} username={sub_username}
orderSets={orderSets} orderSets={orderSets}
linkBack={<Link to={`/dashboard/`}>Return to dashboard</Link>} linkBack={<Link to={`/dashboard/`}>Return to dashboard</Link>}
/> />
</Container>
); );
}; };

View file

@ -2,6 +2,9 @@ import React from "react";
export interface UserContextData { export interface UserContextData {
username?: string; username?: string;
telegram_photo_url?: string;
mastodon_server?: string;
mastodon_username?: string;
} }
const UserContext = React.createContext<UserContextData>({}); const UserContext = React.createContext<UserContextData>({});
@ -13,23 +16,17 @@ export const useUserContext = (): UserContextData => {
export const UserContextProvider: React.FC<{ children: React.ReactNode }> = ({ export const UserContextProvider: React.FC<{ children: React.ReactNode }> = ({
children, children,
}) => { }) => {
const [username, setUsername] = React.useState(); const [userProfile, setUserProfile] = React.useState<UserContextData>({});
React.useEffect(() => { React.useEffect(() => {
fetch(`/api/me`) fetch(`/api/me`)
.then((response) => response.json()) .then((response) => response.json())
.then(({ username }) => { .then((data) => {
setUsername(username); setUserProfile(data);
}); });
}, []); }, []);
return ( return (
<UserContext.Provider <UserContext.Provider value={userProfile}>{children}</UserContext.Provider>
value={{
username,
}}
>
{children}
</UserContext.Provider>
); );
}; };

View file

@ -19,6 +19,8 @@ import { Dashboard, subsListLoader } from "./Dashboard";
import { SubOrderSets, subOrderSetsLoader } from "./SubOrderSets"; import { SubOrderSets, subOrderSetsLoader } from "./SubOrderSets";
import { OrderSet, orderSetLoader, orderSetAction } from "./OrderSet"; import { OrderSet, orderSetLoader, orderSetAction } from "./OrderSet";
import { UserContextProvider } from "./UserContext"; import { UserContextProvider } from "./UserContext";
import { Header } from "./Header";
import { Profile } from "./Profile";
const theme = createTheme({ const theme = createTheme({
components: { components: {
@ -47,11 +49,19 @@ const theme = createTheme({
}); });
const router = createBrowserRouter([ const router = createBrowserRouter([
{
path: "/",
Component: Header,
children: [
{ {
path: "dashboard", path: "dashboard",
Component: Dashboard, Component: Dashboard,
loader: subsListLoader, loader: subsListLoader,
}, },
{
path: "profile",
Component: Profile,
},
{ {
path: "orders/:username", path: "orders/:username",
Component: SubOrderSets, Component: SubOrderSets,
@ -67,6 +77,8 @@ const router = createBrowserRouter([
loader: orderSetLoader, loader: orderSetLoader,
action: orderSetAction, action: orderSetAction,
}, },
],
},
]); ]);
ReactDOM.createRoot(document.getElementById("root")!).render( ReactDOM.createRoot(document.getElementById("root")!).render(

View file

@ -0,0 +1,54 @@
"""Peewee migrations -- 013_add_mastodon_server.py.
Some examples (model - class or model name)::
> Model = migrator.orm['table_name'] # Return model in current state by name
> Model = migrator.ModelClass # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.run(func, *args, **kwargs) # Run python function with the given args
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.add_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
> migrator.add_constraint(model, name, sql)
> migrator.drop_index(model, *col_names)
> migrator.drop_not_null(model, *field_names)
> migrator.drop_constraints(model, *constraints)
"""
from contextlib import suppress
import peewee as pw
from peewee_migrate import Migrator
with suppress(ImportError):
import playhouse.postgres_ext as pw_pext
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your migrations here."""
@migrator.create_model
class MastodonServer(pw.Model):
id = pw.AutoField()
name = pw.TextField(unique=True)
client_id = pw.TextField()
client_secret = pw.TextField()
class Meta:
table_name = "mastodon_server"
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your rollback migrations here."""
migrator.remove_model('mastodon_server')

View file

@ -0,0 +1,52 @@
"""Peewee migrations -- 014_add_user_mastodon.py.
Some examples (model - class or model name)::
> Model = migrator.orm['table_name'] # Return model in current state by name
> Model = migrator.ModelClass # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.run(func, *args, **kwargs) # Run python function with the given args
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.add_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
> migrator.add_constraint(model, name, sql)
> migrator.drop_index(model, *col_names)
> migrator.drop_not_null(model, *field_names)
> migrator.drop_constraints(model, *constraints)
"""
from contextlib import suppress
import peewee as pw
from peewee_migrate import Migrator
with suppress(ImportError):
import playhouse.postgres_ext as pw_pext
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your migrations here."""
migrator.add_fields(
'user',
mastodon_server=pw.ForeignKeyField(column_name='mastodon_server_id', field='id', model=migrator.orm['mastodon_server'], null=True),
mastodon_username=pw.TextField(null=True))
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your rollback migrations here."""
migrator.remove_fields('user', 'mastodon_server', 'mastodon_username')
migrator.drop_index('user', 'mastodon_server')

View file

@ -0,0 +1,49 @@
"""Peewee migrations -- 015_add_user_mastodon_access_token.py.
Some examples (model - class or model name)::
> Model = migrator.orm['table_name'] # Return model in current state by name
> Model = migrator.ModelClass # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.run(func, *args, **kwargs) # Run python function with the given args
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.add_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
> migrator.add_constraint(model, name, sql)
> migrator.drop_index(model, *col_names)
> migrator.drop_not_null(model, *field_names)
> migrator.drop_constraints(model, *constraints)
"""
from contextlib import suppress
import peewee as pw
from peewee_migrate import Migrator
with suppress(ImportError):
import playhouse.postgres_ext as pw_pext
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your migrations here."""
migrator.add_fields(
'user',
mastodon_access_token=pw.TextField(null=True))
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your rollback migrations here."""
migrator.remove_fields('user', 'mastodon_access_token')

View file

@ -13,6 +13,11 @@ MASTODON_INSTANCE = os.environ.get("MASTODON_INSTANCE")
MASTODON_ACCESS_TOKEN = os.environ.get('MASTODON_ACCESS_TOKEN') MASTODON_ACCESS_TOKEN = os.environ.get('MASTODON_ACCESS_TOKEN')
MASTODON_VISIBILITY = os.environ.get('MASTODON_VISIBILITY', 'direct') MASTODON_VISIBILITY = os.environ.get('MASTODON_VISIBILITY', 'direct')
MASTODON_OAUTH_SCOPES = os.environ.get('MASTODON_OAUTH_SCOPES', 'profile')
MASTODON_OAUTH_REDIRECT_URI = os.environ.get('MASTODON_OAUTH_REDIRECT_URI')
MASTODON_OAUTH_CLIENT_NAME = os.environ.get('MASTODON_OAUTH_CLIENT_NAME', 'Gear Orders Bot')
MASTODON_OAUTH_CLIENT_WEBSITE = os.environ.get('MASTODON_OAUTH_CLIENT_WEBSITE')
TELEGRAM_API_TOKEN = os.environ.get('TELEGRAM_API_TOKEN') TELEGRAM_API_TOKEN = os.environ.get('TELEGRAM_API_TOKEN')
TELEGRAM_CHAT_ID = int(os.environ.get('TELEGRAM_CHAT_ID')) TELEGRAM_CHAT_ID = int(os.environ.get('TELEGRAM_CHAT_ID'))
TELEGRAM_COMMAND_TIMEOUT = int(os.environ.get('TELEGRAM_COMMAND_TIMEOUT', 120)) TELEGRAM_COMMAND_TIMEOUT = int(os.environ.get('TELEGRAM_COMMAND_TIMEOUT', 120))