diff --git a/.env.example b/.env.example index cc70644c1..a9faad0b6 100644 --- a/.env.example +++ b/.env.example @@ -5,7 +5,7 @@ QUART_DEBUG=true HOST=127.0.0.1 PORT=5000 -LNBITS_SITE_TITLE=LNbits + LNBITS_ALLOWED_USERS="" LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" LNBITS_DATA_FOLDER="./data" @@ -13,6 +13,11 @@ LNBITS_DISABLED_EXTENSIONS="amilk" LNBITS_FORCE_HTTPS=true LNBITS_SERVICE_FEE="0.0" +# Change theme +LNBITS_SITE_TITLE=LNbits +# Choose from mint, flamingo, quasar, autumn, monochrome +LNBITS_THEME_OPTIONS="mint, flamingo, quasar, autumn, monochrome, salvador" + # Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, LndWallet (gRPC), # LndRestWallet, CLightningWallet, LNbitsWallet, SparkWallet LNBITS_BACKEND_WALLET_CLASS=VoidWallet diff --git a/Pipfile b/Pipfile index ae59e0b12..951ea9bfa 100644 --- a/Pipfile +++ b/Pipfile @@ -17,7 +17,6 @@ shortuuid = "*" quart = "*" quart-cors = "*" quart-compress = "*" -secure = "*" typing-extensions = "*" httpx = "*" quart-trio = "*" @@ -35,3 +34,4 @@ pytest = "*" pytest-cov = "*" mypy = "latest" pytest-trio = "*" +trio-typing = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 0dc59dc54..81b4d6898 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e12af74353e8bea3f97bf2aea16a1ba0a6e4c3a08042ce7368187a06e7791e2c" + "sha256": "8c4056a80c682fac834266c11892573ce53807226c0810e4564976656ea5ff45" }, "pipfile-spec": 6, "requires": { @@ -18,10 +18,19 @@ "default": { "aiofiles": { "hashes": [ - "sha256:bd3019af67f83b739f8e4053c6c0512a7f545b9a8d91aaeab55e6e0f9d123c27", - "sha256:e0281b157d3d5d59d803e3f4557dcc9a3dff28a4dd4829a9ff478adae50ca092" + "sha256:a1c4fc9b2ff81568c83e21392a82f344ea9d23da906e4f6a52662764545e19d4", + "sha256:c67a6823b5f23fcab0a2595a289cec7d8c863ffcb4322fb8cd6b90400aedfdbc" ], - "version": "==0.6.0" + "markers": "python_version >= '3.6' and python_version < '4.0'", + "version": "==0.7.0" + }, + "anyio": { + "hashes": [ + "sha256:41c4be842c284222b197a625d76a7ab85adf9d52788f563172fe180c2744b6c1", + "sha256:89e19b1498c8a6f12277e0bd2949597e445aa1b14361fbab2c36943639ef5190" + ], + "markers": "python_full_version >= '3.6.2'", + "version": "==3.2.0" }, "async-generator": { "hashes": [ @@ -33,11 +42,11 @@ }, "attrs": { "hashes": [ - "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", - "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.3.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" }, "bech32": { "hashes": [ @@ -99,41 +108,40 @@ }, "cerberus": { "hashes": [ - "sha256:7aff49bc793e58a88ac14bffc3eca0f67e077881d3c62c621679a621294dd174", - "sha256:eec10585c33044fb7c69650bc5b68018dac0443753337e2b07684ee0f3c83329" + "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c" ], "index": "pypi", - "version": "==1.3.3" + "version": "==1.3.4" }, "certifi": { "hashes": [ - "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", - "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" + "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", + "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" ], - "version": "==2020.12.5" + "version": "==2021.5.30" }, "click": { "hashes": [ - "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", - "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", + "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==7.1.2" + "markers": "python_version >= '3.6'", + "version": "==8.0.1" }, "ecdsa": { "hashes": [ - "sha256:881fa5e12bb992972d3d1b3d4dfbe149ab76a89f13da02daa5ea1ec7dea6e747", - "sha256:cfc046a2ddd425adbd1a78b3c46f0d1325c657811c0f45ecc3a0a6236c1e50ff" + "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676", + "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa" ], "index": "pypi", - "version": "==0.16.1" + "version": "==0.17.0" }, "embit": { "hashes": [ - "sha256:7c4264d7ede8e2c114db10585270874c9df809c68d2e21db918872e3245b5f2b" + "sha256:d67fc0f7fbdb7588c3eb24441bf8e05770056260bc8e5537399a1b3ce5ccf12a" ], "index": "pypi", - "version": "==0.2.1" + "version": "==0.4.2" }, "environs": { "hashes": [ @@ -169,19 +177,19 @@ }, "httpcore": { "hashes": [ - "sha256:37ae835fb370049b2030c3290e12ed298bf1473c41bb72ca4aa78681eba9b7c9", - "sha256:93e822cd16c32016b414b789aeff4e855d0ccbfc51df563ee34d4dbadbb3bcdc" + "sha256:b0d16f0012ec88d8cc848f5a55f8a03158405f4bca02ee49bc4ca2c1fda49f3e", + "sha256:db4c0dcb8323494d01b8c6d812d80091a31e520033e7b0120883d6f52da649ff" ], "markers": "python_version >= '3.6'", - "version": "==0.12.3" + "version": "==0.13.6" }, "httpx": { "hashes": [ - "sha256:cc2a55188e4b25272d2bcd46379d300f632045de4377682aa98a8a6069d55967", - "sha256:d379653bd457e8257eb0df99cb94557e4aac441b7ba948e333be969298cac272" + "sha256:979afafecb7d22a1d10340bafb403cf2cb75aff214426ff206521fc79d26408c", + "sha256:9f99c15d33642d38bce8405df088c1c4cfd940284b4290cacbfb02e64f4877c6" ], "index": "pypi", - "version": "==0.17.1" + "version": "==0.18.2" }, "hypercorn": { "extras": [ @@ -196,34 +204,34 @@ }, "hyperframe": { "hashes": [ - "sha256:742d2a4bc3152a340a49d59f32e33ec420aa8e7054c1444ef5c7efff255842f1", - "sha256:a51026b1591cac726fc3d0b7994fbc7dc5efab861ef38503face2930fd7b2d34" + "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15", + "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914" ], "markers": "python_full_version >= '3.6.1'", - "version": "==6.0.0" + "version": "==6.0.1" }, "idna": { "hashes": [ - "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16", - "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1" + "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", + "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" ], - "version": "==3.1" + "version": "==3.2" }, "itsdangerous": { "hashes": [ - "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", - "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c", + "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.1.0" + "markers": "python_version >= '3.6'", + "version": "==2.0.1" }, "jinja2": { "hashes": [ - "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", - "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" + "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4", + "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==2.11.3" + "markers": "python_version >= '3.6'", + "version": "==3.0.1" }, "lnurl": { "hashes": [ @@ -235,69 +243,87 @@ }, "markupsafe": { "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", - "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f", - "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014", - "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85", - "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850", - "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1", - "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", - "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5", - "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c", - "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be", - "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621" + "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", + "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", + "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", + "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", + "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", + "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", + "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", + "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", + "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", + "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", + "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", + "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", + "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", + "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", + "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", + "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", + "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", + "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", + "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", + "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", + "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", + "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", + "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", + "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", + "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", + "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", + "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", + "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", + "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", + "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", + "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", + "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", + "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", + "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.1.1" + "markers": "python_version >= '3.6'", + "version": "==2.0.1" }, "marshmallow": { "hashes": [ - "sha256:0dd42891a5ef288217ed6410917f3c6048f585f8692075a0052c24f9bfff9dfd", - "sha256:16e99cb7f630c0ef4d7d364ed0109ac194268dde123966076ab3dafb9ae3906b" + "sha256:8050475b70470cc58f4441ee92375db611792ba39ca1ad41d39cad193ea9e040", + "sha256:b45cde981d1835145257b4a3c5cb7b80786dcf5f50dd2990749a50c16cb48e01" ], "markers": "python_version >= '3.5'", - "version": "==3.11.1" + "version": "==3.12.1" + }, + "mypy": { + "hashes": [ + "sha256:0190fb77e93ce971954c9e54ea61de2802065174e5e990c9d4c1d0f54fbeeca2", + "sha256:0756529da2dd4d53d26096b7969ce0a47997123261a5432b48cc6848a2cb0bd4", + "sha256:2f9fedc1f186697fda191e634ac1d02f03d4c260212ccb018fabbb6d4b03eee8", + "sha256:353aac2ce41ddeaf7599f1c73fed2b75750bef3b44b6ad12985a991bc002a0da", + "sha256:3f12705eabdd274b98f676e3e5a89f247ea86dc1af48a2d5a2b080abac4e1243", + "sha256:4efc67b9b3e2fddbe395700f91d5b8deb5980bfaaccb77b306310bd0b9e002eb", + "sha256:517e7528d1be7e187a5db7f0a3e479747307c1b897d9706b1c662014faba3116", + "sha256:68a098c104ae2b75e946b107ef69dd8398d54cb52ad57580dfb9fc78f7f997f0", + "sha256:746e0b0101b8efec34902810047f26a8c80e1efbb4fc554956d848c05ef85d76", + "sha256:8be7bbd091886bde9fcafed8dd089a766fa76eb223135fe5c9e9798f78023a20", + "sha256:9236c21194fde5df1b4d8ebc2ef2c1f2a5dc7f18bcbea54274937cae2e20a01c", + "sha256:9ef5355eaaf7a23ab157c21a44c614365238a7bdb3552ec3b80c393697d974e1", + "sha256:9f1d74eeb3f58c7bd3f3f92b8f63cb1678466a55e2c4612bf36909105d0724ab", + "sha256:a26d0e53e90815c765f91966442775cf03b8a7514a4e960de7b5320208b07269", + "sha256:ae94c31bb556ddb2310e4f913b706696ccbd43c62d3331cd3511caef466871d2", + "sha256:b5ba1f0d5f9087e03bf5958c28d421a03a4c1ad260bf81556195dffeccd979c4", + "sha256:b5dfcd22c6bab08dfeded8d5b44bdcb68c6f1ab261861e35c470b89074f78a70", + "sha256:cd01c599cf9f897b6b6c6b5d8b182557fb7d99326bcdf5d449a0fbbb4ccee4b9", + "sha256:e89880168c67cf4fde4506b80ee42f1537ad66ad366c101d388b3fd7d7ce2afd", + "sha256:ebe2bc9cb638475f5d39068d2dbe8ae1d605bb8d8d3ff281c695df1670ab3987", + "sha256:f89bfda7f0f66b789792ab64ce0978e4a991a0e4dd6197349d0767b0f1095b21", + "sha256:fc4d63da57ef0e8cd4ab45131f3fe5c286ce7dd7f032650d0fbc239c6190e167", + "sha256:fd634bc17b1e2d6ce716f0e43446d0d61cdadb1efcad5c56ca211c22b246ebc8" + ], + "markers": "implementation_name == 'cpython'", + "version": "==0.902" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" }, "outcome": { "hashes": [ @@ -316,31 +342,31 @@ }, "pydantic": { "hashes": [ - "sha256:0c40162796fc8d0aa744875b60e4dc36834db9f2a25dbf9ba9664b1915a23850", - "sha256:20d42f1be7c7acc352b3d09b0cf505a9fab9deb93125061b376fbe1f06a5459f", - "sha256:2287ebff0018eec3cc69b1d09d4b7cebf277726fa1bd96b45806283c1d808683", - "sha256:258576f2d997ee4573469633592e8b99aa13bda182fcc28e875f866016c8e07e", - "sha256:26cf3cb2e68ec6c0cfcb6293e69fb3450c5fd1ace87f46b64f678b0d29eac4c3", - "sha256:2f2736d9a996b976cfdfe52455ad27462308c9d3d0ae21a2aa8b4cd1a78f47b9", - "sha256:3114d74329873af0a0e8004627f5389f3bb27f956b965ddd3e355fe984a1789c", - "sha256:3bbd023c981cbe26e6e21c8d2ce78485f85c2e77f7bab5ec15b7d2a1f491918f", - "sha256:3bcb9d7e1f9849a6bdbd027aabb3a06414abd6068cb3b21c49427956cce5038a", - "sha256:4bbc47cf7925c86a345d03b07086696ed916c7663cb76aa409edaa54546e53e2", - "sha256:6388ef4ef1435364c8cc9a8192238aed030595e873d8462447ccef2e17387125", - "sha256:830ef1a148012b640186bf4d9789a206c56071ff38f2460a32ae67ca21880eb8", - "sha256:8fbb677e4e89c8ab3d450df7b1d9caed23f254072e8597c33279460eeae59b99", - "sha256:c17a0b35c854049e67c68b48d55e026c84f35593c66d69b278b8b49e2484346f", - "sha256:dd4888b300769ecec194ca8f2699415f5f7760365ddbe243d4fd6581485fa5f0", - "sha256:dde4ca368e82791de97c2ec019681ffb437728090c0ff0c3852708cf923e0c7d", - "sha256:e3f8790c47ac42549dc8b045a67b0ca371c7f66e73040d0197ce6172b385e520", - "sha256:e8bc082afef97c5fd3903d05c6f7bb3a6af9fc18631b4cc9fedeb4720efb0c58", - "sha256:eb8ccf12295113ce0de38f80b25f736d62f0a8d87c6b88aca645f168f9c78771", - "sha256:fb77f7a7e111db1832ae3f8f44203691e15b1fa7e5a1cb9691d4e2659aee41c4", - "sha256:fbfb608febde1afd4743c6822c19060a8dbdd3eb30f98e36061ba4973308059e", - "sha256:fff29fe54ec419338c522b908154a2efabeee4f483e48990f87e189661f31ce3" + "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd", + "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739", + "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f", + "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840", + "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23", + "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287", + "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62", + "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b", + "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb", + "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820", + "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3", + "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b", + "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e", + "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3", + "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316", + "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b", + "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4", + "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20", + "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e", + "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505", + "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1", + "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833" ], "markers": "python_full_version >= '3.6.1'", - "version": "==1.8.1" + "version": "==1.8.2" }, "pypng": { "hashes": [ @@ -366,18 +392,18 @@ }, "python-dotenv": { "hashes": [ - "sha256:471b782da0af10da1a80341e8438fca5fadeba2881c54360d5fd8d03d03a4f4a", - "sha256:49782a97c9d641e8a09ae1d9af0856cc587c8d2474919342d5104d85be9890b2" + "sha256:dd8fe852847f4fbfadabf6183ddd4c824a9651f02d51714fa075c95561959c7d", + "sha256:effaac3c1e58d89b3ccb4d04a40dc7ad6e0275fda25fd75ae9d323e2465e202d" ], - "version": "==0.17.0" + "version": "==0.18.0" }, "quart": { "hashes": [ - "sha256:429c5b4ff27e1d2f9ca0aacc38f6aba0ff49b38b815448bf24b613d3de12ea02", - "sha256:7b13786e07541cc9ce1466fdc6a6ccd5f36eb39118edd25a42d617593cd17707" + "sha256:f35134fb1d81af61624e6d89bca33cd611dcedce2dc4e291f527ab04395f4e1a", + "sha256:f80c91d1e0588662483e22dd9c368a5778886b62e128c5399d2cc1b1898482cf" ], "index": "pypi", - "version": "==0.14.1" + "version": "==0.15.1" }, "quart-compress": { "hashes": [ @@ -389,19 +415,19 @@ }, "quart-cors": { "hashes": [ - "sha256:0ea23ea8db2c21835f6698b91a09d99ab59f98f8d90a2a739475ef0409591573", - "sha256:e526e9929934ad31301853efe357a3bd2e08c3282aff37184fa8671ed854f052" + "sha256:c2be932f20413a56b176527090229afe8f725a3ee029d45ea08a174cdc319823", + "sha256:ea08d26aef918d59194fbf065cde9b6cae90dc5f21120dcd254d7d46190cd293" ], "index": "pypi", - "version": "==0.4.0" + "version": "==0.5.0" }, "quart-trio": { "hashes": [ - "sha256:1e7fce0df41afc3038bf0431b20614f90984de50341b19f9d4d3b9ba1ac7574a", - "sha256:933e3c18e232ece30ccbac7579fdc5f62f2f9c79c3273d6c341f5a1686791eb1" + "sha256:27617f0c9fa8759d3056e9ddcdc038d44093af45eb5f84f8d5714872aaaa8c7d", + "sha256:30dfab5e382f06c605d4a5960e8188e8e05d10198f02097f0a16c1dca41b3574" ], "index": "pypi", - "version": "==0.7.0" + "version": "==0.8.0" }, "represent": { "hashes": [ @@ -416,18 +442,18 @@ "idna2008" ], "hashes": [ - "sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d", - "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50" + "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835", + "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97" ], - "version": "==1.4.0" + "version": "==1.5.0" }, "secure": { "hashes": [ - "sha256:4dc8dd4b548831c3ad7f94079332c41d67c781eccc32215ff5a8a49582c1a447", - "sha256:b3bf1e39ebf40040fc3248392343a5052aa14cb45fc87ec91b0bd11f19cc46bd" + "sha256:6e30939d8f95bf3b8effb8a36ebb5ed57f265daeeae905e3aa9677ea538ab64e", + "sha256:a93b720c7614809c131ca80e477263140107c6c212829d0a6e1f7bc8d859c608" ], "index": "pypi", - "version": "==0.2.1" + "version": "==0.3.0" }, "shortuuid": { "hashes": [ @@ -439,11 +465,11 @@ }, "six": { "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.15.0" + "version": "==1.16.0" }, "sniffio": { "hashes": [ @@ -455,10 +481,10 @@ }, "sortedcontainers": { "hashes": [ - "sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f", - "sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1" + "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", + "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" ], - "version": "==2.3.0" + "version": "==2.4.0" }, "sqlalchemy": { "hashes": [ @@ -528,22 +554,30 @@ "index": "pypi", "version": "==0.16.0" }, - "typing-extensions": { + "trio-typing": { "hashes": [ - "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", - "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", - "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" + "sha256:35f1bec8df2150feab6c8b073b54135321722c9d9289bbffa78a9a091ea83b72", + "sha256:f2007df617a6c26a2294db0dd63645b5451149757e1bde4cb8dbf3e1369174fb" ], "index": "pypi", - "version": "==3.7.4.3" + "version": "==0.5.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" + ], + "index": "pypi", + "version": "==3.10.0.0" }, "werkzeug": { "hashes": [ - "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", - "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" + "sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42", + "sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==1.0.1" + "markers": "python_version >= '3.6'", + "version": "==2.0.1" }, "wsproto": { "hashes": [ @@ -572,11 +606,11 @@ }, "attrs": { "hashes": [ - "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", - "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.3.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" }, "black": { "hashes": [ @@ -587,11 +621,11 @@ }, "click": { "hashes": [ - "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", - "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", + "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==7.1.2" + "markers": "python_version >= '3.6'", + "version": "==8.0.1" }, "coverage": { "hashes": [ @@ -653,10 +687,10 @@ }, "idna": { "hashes": [ - "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16", - "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1" + "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", + "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" ], - "version": "==3.1" + "version": "==3.2" }, "iniconfig": { "hashes": [ @@ -667,31 +701,32 @@ }, "mypy": { "hashes": [ - "sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e", - "sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064", - "sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c", - "sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4", - "sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97", - "sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df", - "sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8", - "sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a", - "sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56", - "sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7", - "sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6", - "sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5", - "sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a", - "sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521", - "sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564", - "sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49", - "sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66", - "sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a", - "sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119", - "sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506", - "sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c", - "sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb" + "sha256:0190fb77e93ce971954c9e54ea61de2802065174e5e990c9d4c1d0f54fbeeca2", + "sha256:0756529da2dd4d53d26096b7969ce0a47997123261a5432b48cc6848a2cb0bd4", + "sha256:2f9fedc1f186697fda191e634ac1d02f03d4c260212ccb018fabbb6d4b03eee8", + "sha256:353aac2ce41ddeaf7599f1c73fed2b75750bef3b44b6ad12985a991bc002a0da", + "sha256:3f12705eabdd274b98f676e3e5a89f247ea86dc1af48a2d5a2b080abac4e1243", + "sha256:4efc67b9b3e2fddbe395700f91d5b8deb5980bfaaccb77b306310bd0b9e002eb", + "sha256:517e7528d1be7e187a5db7f0a3e479747307c1b897d9706b1c662014faba3116", + "sha256:68a098c104ae2b75e946b107ef69dd8398d54cb52ad57580dfb9fc78f7f997f0", + "sha256:746e0b0101b8efec34902810047f26a8c80e1efbb4fc554956d848c05ef85d76", + "sha256:8be7bbd091886bde9fcafed8dd089a766fa76eb223135fe5c9e9798f78023a20", + "sha256:9236c21194fde5df1b4d8ebc2ef2c1f2a5dc7f18bcbea54274937cae2e20a01c", + "sha256:9ef5355eaaf7a23ab157c21a44c614365238a7bdb3552ec3b80c393697d974e1", + "sha256:9f1d74eeb3f58c7bd3f3f92b8f63cb1678466a55e2c4612bf36909105d0724ab", + "sha256:a26d0e53e90815c765f91966442775cf03b8a7514a4e960de7b5320208b07269", + "sha256:ae94c31bb556ddb2310e4f913b706696ccbd43c62d3331cd3511caef466871d2", + "sha256:b5ba1f0d5f9087e03bf5958c28d421a03a4c1ad260bf81556195dffeccd979c4", + "sha256:b5dfcd22c6bab08dfeded8d5b44bdcb68c6f1ab261861e35c470b89074f78a70", + "sha256:cd01c599cf9f897b6b6c6b5d8b182557fb7d99326bcdf5d449a0fbbb4ccee4b9", + "sha256:e89880168c67cf4fde4506b80ee42f1537ad66ad366c101d388b3fd7d7ce2afd", + "sha256:ebe2bc9cb638475f5d39068d2dbe8ae1d605bb8d8d3ff281c695df1670ab3987", + "sha256:f89bfda7f0f66b789792ab64ce0978e4a991a0e4dd6197349d0767b0f1095b21", + "sha256:fc4d63da57ef0e8cd4ab45131f3fe5c286ce7dd7f032650d0fbc239c6190e167", + "sha256:fd634bc17b1e2d6ce716f0e43446d0d61cdadb1efcad5c56ca211c22b246ebc8" ], - "index": "pypi", - "version": "==0.812" + "markers": "implementation_name == 'cpython'", + "version": "==0.902" }, "mypy-extensions": { "hashes": [ @@ -749,19 +784,19 @@ }, "pytest": { "hashes": [ - "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634", - "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc" + "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b", + "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890" ], "index": "pypi", - "version": "==6.2.3" + "version": "==6.2.4" }, "pytest-cov": { "hashes": [ - "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7", - "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da" + "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a", + "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7" ], "index": "pypi", - "version": "==2.11.1" + "version": "==2.12.1" }, "pytest-trio": { "hashes": [ @@ -826,10 +861,10 @@ }, "sortedcontainers": { "hashes": [ - "sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f", - "sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1" + "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", + "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" ], - "version": "==2.3.0" + "version": "==2.4.0" }, "toml": { "hashes": [ @@ -884,12 +919,12 @@ }, "typing-extensions": { "hashes": [ - "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", - "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", - "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" ], "index": "pypi", - "version": "==3.7.4.3" + "version": "==3.10.0.0" } } } diff --git a/docs/devs/installation.md b/docs/devs/installation.md index 55636181d..013f7be90 100644 --- a/docs/devs/installation.md +++ b/docs/devs/installation.md @@ -40,6 +40,13 @@ Take a look at [Polar][polar] for an excellent way of spinning up a Lightning Ne ## Running the server LNbits uses [Quart][quart] as an application server. +Before running the server for the first time, make sure to create the data folder: + +```sh +$ mkdir data +``` + +To then run the server, use: ```sh $ pipenv run python -m lnbits diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 2f580d8ee..9fb8a3e32 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -23,11 +23,11 @@ mkdir data ./venv/bin/hypercorn -k trio --bind 0.0.0.0:5000 'lnbits.app:create_app()' ``` -No you can visit your LNbits at http://localhost:5000/. +Now you can visit your LNbits at http://localhost:5000/. Now modify the `.env` file with any settings you prefer and add a proper [funding source](./wallets.md) by modifying the value of `LNBITS_BACKEND_WALLET_CLASS` and providing the extra information and credentials related to the chosen funding source. -Then you can run restart it and it will be using the new settings. +Then you can restart it and it will be using the new settings. You might also need to install additional packages or perform additional setup steps, depending on the chosen backend. See [the short guide](./wallets.md) on each different funding source. @@ -37,7 +37,7 @@ Docker installation To install using docker you first need to build the docker image as: ``` git clone https://github.com/lnbits/lnbits.git -cd lnbits/ # ${PWD} refered as +cd lnbits/ # ${PWD} referred as docker build -t lnbits . ``` @@ -56,5 +56,5 @@ sudo chown 1000:1000 ./data/ Then the image can be run as: ``` docker run --detach --publish 5000:5000 --name lnbits --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits -`` -Finally you can access the lnbits on your machine port 5000. +``` +Finally you can access your lnbits on your machine at port 5000. diff --git a/lnbits/__main__.py b/lnbits/__main__.py index fa75231c4..90b086423 100644 --- a/lnbits/__main__.py +++ b/lnbits/__main__.py @@ -1,4 +1,4 @@ -import trio # type: ignore +import trio from .commands import migrate_databases, transpile_scss, bundle_vendored diff --git a/lnbits/app.py b/lnbits/app.py index 35852cd9b..40d50d045 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -7,7 +7,6 @@ from quart import g from quart_trio import QuartTrio from quart_cors import cors # type: ignore from quart_compress import Compress # type: ignore -from secure import SecureHeaders # type: ignore from .commands import db_migrate, handle_assets from .core import core_app @@ -27,8 +26,6 @@ from .tasks import ( ) from .settings import WALLET -secure_headers = SecureHeaders(hsts=False, xfo=False) - def create_app(config_object="lnbits.settings") -> QuartTrio: """Create application factory. @@ -46,7 +43,6 @@ def create_app(config_object="lnbits.settings") -> QuartTrio: register_blueprints(app) register_filters(app) register_commands(app) - register_request_hooks(app) register_async_tasks(app) register_exception_handlers(app) @@ -108,19 +104,11 @@ def register_assets(app: QuartTrio): def register_filters(app: QuartTrio): """Jinja filters.""" app.jinja_env.globals["SITE_TITLE"] = app.config["LNBITS_SITE_TITLE"] + app.jinja_env.globals["LNBITS_THEME_OPTIONS"] = app.config["LNBITS_THEME_OPTIONS"] app.jinja_env.globals["LNBITS_VERSION"] = app.config["LNBITS_COMMIT"] app.jinja_env.globals["EXTENSIONS"] = get_valid_extensions() -def register_request_hooks(app: QuartTrio): - """Open the core db for each request so everything happens in a big transaction""" - - @app.after_request - async def set_secure_headers(response): - secure_headers.quart(response) - return response - - def register_async_tasks(app): @app.route("/wallet/webhook", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) async def webhook_listener(): diff --git a/lnbits/commands.py b/lnbits/commands.py index 2be04d127..2e9b837fd 100644 --- a/lnbits/commands.py +++ b/lnbits/commands.py @@ -1,4 +1,4 @@ -import trio # type: ignore +import trio import warnings import click import importlib diff --git a/lnbits/core/services.py b/lnbits/core/services.py index f2821fdcb..09b9f4f71 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -1,4 +1,4 @@ -import trio # type: ignore +import trio import json import httpx from io import BytesIO @@ -229,10 +229,10 @@ async def redeem_lnurl_withdraw( pass async with httpx.AsyncClient() as client: - await client.get( - res["callback"], - params=params, - ) + try: + await client.get(res["callback"], params=params) + except Exception: + pass async def perform_lnurlauth( diff --git a/lnbits/core/static/js/wallet.js b/lnbits/core/static/js/wallet.js index 7e91278bd..d01910514 100644 --- a/lnbits/core/static/js/wallet.js +++ b/lnbits/core/static/js/wallet.js @@ -119,6 +119,8 @@ new Vue({ paymentHash: null, minMax: [0, 2100000000000000], lnurl: null, + units: ['sat'], + unit: 'sat', data: { amount: null, memo: '' @@ -233,6 +235,7 @@ new Vue({ this.receive.paymentHash = null this.receive.data.amount = null this.receive.data.memo = null + this.receive.unit = 'sat' this.receive.paymentChecker = null this.receive.minMax = [0, 2100000000000000] this.receive.lnurl = null @@ -269,11 +272,13 @@ new Vue({ }, createInvoice: function () { this.receive.status = 'loading' + LNbits.api .createInvoice( this.g.wallet, this.receive.data.amount, this.receive.data.memo, + this.receive.unit, this.receive.lnurl && this.receive.lnurl.callback ) .then(response => { @@ -619,6 +624,15 @@ new Vue({ created: function () { this.fetchBalance() this.fetchPayments() + + LNbits.api + .request('GET', '/api/v1/currencies') + .then(response => { + this.receive.units = ['sat', ...response.data] + }) + .catch(err => { + LNbits.utils.notifyApiError(err) + }) }, mounted: function () { // show disclaimer diff --git a/lnbits/core/tasks.py b/lnbits/core/tasks.py index 3a296e666..fa2df9645 100644 --- a/lnbits/core/tasks.py +++ b/lnbits/core/tasks.py @@ -1,4 +1,4 @@ -import trio # type: ignore +import trio import httpx from typing import List diff --git a/lnbits/core/templates/core/extensions.html b/lnbits/core/templates/core/extensions.html index 97fa89369..daeb660fa 100644 --- a/lnbits/core/templates/core/extensions.html +++ b/lnbits/core/templates/core/extensions.html @@ -17,14 +17,14 @@ > {% raw %}
{{ extension.name }}
- {{ extension.shortDescription }} {% endraw %} + {{ extension.shortDescription }} {% endraw %}
Open diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html index 7ca61a3a8..14842e925 100644 --- a/lnbits/core/templates/core/index.html +++ b/lnbits/core/templates/core/index.html @@ -8,7 +8,7 @@ {% if lnurl %} Add a new wallet
- +
diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index b3f7b1c13..2856f2539 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -22,7 +22,7 @@
Paste Request Create Invoice scan @@ -313,12 +313,21 @@ {{receive.lnurl.domain}} is requesting an invoice:

+ @@ -346,7 +355,7 @@
@@ -386,7 +395,7 @@

{% endraw %}
- Pay + Pay Cancel
@@ -414,7 +423,7 @@ {{ parse.lnurlauth.pubkey }}

- Login + Login Cancel @@ -476,9 +485,7 @@
- Send satoshis + Send satoshis Cancel @@ -503,7 +510,7 @@
Read - New AMilk @@ -109,7 +109,7 @@ > Create amilk - Add Bleskomat @@ -150,14 +150,14 @@ Update Bleskomat - New captcha @@ -141,7 +141,7 @@ @@ -157,7 +157,7 @@
Create captcha Copilots: + copilot_id = urlsafe_short_hash() + + await db.execute( + """ + INSERT INTO copilots ( + id, + user, + lnurl_toggle, + wallet, + title, + animation1, + animation2, + animation3, + animation1threshold, + animation2threshold, + animation3threshold, + animation1webhook, + animation2webhook, + animation3webhook, + lnurl_title, + show_message, + show_ack, + show_price, + lnurl_title, + amount_made + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + copilot_id, + user, + lnurl_toggle, + wallet, + title, + animation1, + animation2, + animation3, + animation1threshold, + animation2threshold, + animation3threshold, + animation1webhook, + animation2webhook, + animation3webhook, + lnurl_title, + show_message, + show_ack, + show_price, + lnurl_title, + 0, + ), + ) + return await get_copilot(copilot_id) + + +async def update_copilot(copilot_id: str, **kwargs) -> Optional[Copilots]: + q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + await db.execute( + f"UPDATE copilots SET {q} WHERE id = ?", (*kwargs.values(), copilot_id) + ) + row = await db.fetchone("SELECT * FROM copilots WHERE id = ?", (copilot_id,)) + return Copilots.from_row(row) if row else None + + +async def get_copilot(copilot_id: str) -> Copilots: + row = await db.fetchone("SELECT * FROM copilots WHERE id = ?", (copilot_id,)) + return Copilots.from_row(row) if row else None + + +async def get_copilots(user: str) -> List[Copilots]: + rows = await db.fetchall("SELECT * FROM copilots WHERE user = ?", (user,)) + return [Copilots.from_row(row) for row in rows] + + +async def delete_copilot(copilot_id: str) -> None: + await db.execute("DELETE FROM copilots WHERE id = ?", (copilot_id,)) diff --git a/lnbits/extensions/copilot/lnurl.py b/lnbits/extensions/copilot/lnurl.py new file mode 100644 index 000000000..0a10e29bc --- /dev/null +++ b/lnbits/extensions/copilot/lnurl.py @@ -0,0 +1,86 @@ +import json +import hashlib +import math +from quart import jsonify, url_for, request +from lnurl import LnurlPayResponse, LnurlPayActionResponse, LnurlErrorResponse # type: ignore +from lnurl.types import LnurlPayMetadata +from lnbits.core.services import create_invoice + +from . import copilot_ext +from .crud import get_copilot + + +@copilot_ext.route("/lnurl/", methods=["GET"]) +async def lnurl_response(cp_id): + cp = await get_copilot(cp_id) + if not cp: + return jsonify({"status": "ERROR", "reason": "Copilot not found."}) + + resp = LnurlPayResponse( + callback=url_for("copilot.lnurl_callback", cp_id=cp_id, _external=True), + min_sendable=10000, + max_sendable=50000000, + metadata=LnurlPayMetadata(json.dumps([["text/plain", str(cp.lnurl_title)]])), + ) + + params = resp.dict() + if cp.show_message: + params["commentAllowed"] = 300 + + return jsonify(params) + + +@copilot_ext.route("/lnurl/cb/", methods=["GET"]) +async def lnurl_callback(cp_id): + cp = await get_copilot(cp_id) + if not cp: + return jsonify({"status": "ERROR", "reason": "Copilot not found."}) + + amount_received = int(request.args.get("amount")) + + if amount_received < 10000: + return ( + jsonify( + LnurlErrorResponse( + reason=f"Amount {round(amount_received / 1000)} is smaller than minimum 10 sats." + ).dict() + ), + ) + elif amount_received / 1000 > 10000000: + return ( + jsonify( + LnurlErrorResponse( + reason=f"Amount {round(amount_received / 1000)} is greater than maximum 50000." + ).dict() + ), + ) + comment = "" + if request.args.get("comment"): + comment = request.args.get("comment") + if len(comment or "") > 300: + return jsonify( + LnurlErrorResponse( + reason=f"Got a comment with {len(comment)} characters, but can only accept 300" + ).dict() + ) + if len(comment) < 1: + comment = "none" + + payment_hash, payment_request = await create_invoice( + wallet_id=cp.wallet, + amount=int(amount_received / 1000), + memo=cp.lnurl_title, + description_hash=hashlib.sha256( + ( + LnurlPayMetadata(json.dumps([["text/plain", str(cp.lnurl_title)]])) + ).encode("utf-8") + ).digest(), + extra={"tag": "copilot", "copilot": cp.id, "comment": comment}, + ) + resp = LnurlPayActionResponse( + pr=payment_request, + success_action=None, + disposable=False, + routes=[], + ) + return jsonify(resp.dict()) diff --git a/lnbits/extensions/copilot/migrations.py b/lnbits/extensions/copilot/migrations.py new file mode 100644 index 000000000..8e4e72d2f --- /dev/null +++ b/lnbits/extensions/copilot/migrations.py @@ -0,0 +1,33 @@ +async def m001_initial(db): + """ + Initial copilot table. + """ + + await db.execute( + """ + CREATE TABLE IF NOT EXISTS copilots ( + id TEXT NOT NULL PRIMARY KEY, + user TEXT, + title TEXT, + lnurl_toggle INTEGER, + wallet TEXT, + animation1 TEXT, + animation2 TEXT, + animation3 TEXT, + animation1threshold INTEGER, + animation2threshold INTEGER, + animation3threshold INTEGER, + animation1webhook TEXT, + animation2webhook TEXT, + animation3webhook TEXT, + lnurl_title TEXT, + show_message INTEGER, + show_ack INTEGER, + show_price INTEGER, + amount_made INTEGER, + fullscreen_cam INTEGER, + iframe_url TEXT, + timestamp TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now')) + ); + """ + ) diff --git a/lnbits/extensions/copilot/models.py b/lnbits/extensions/copilot/models.py new file mode 100644 index 000000000..70d70cf5f --- /dev/null +++ b/lnbits/extensions/copilot/models.py @@ -0,0 +1,41 @@ +from sqlite3 import Row +from typing import NamedTuple +import time +from quart import url_for +from lnurl import Lnurl, encode as lnurl_encode # type: ignore +from lnurl.types import LnurlPayMetadata # type: ignore +from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore + + +class Copilots(NamedTuple): + id: str + user: str + title: str + lnurl_toggle: int + wallet: str + animation1: str + animation2: str + animation3: str + animation1threshold: int + animation2threshold: int + animation3threshold: int + animation1webhook: str + animation2webhook: str + animation3webhook: str + lnurl_title: str + show_message: int + show_ack: int + show_price: int + amount_made: int + timestamp: int + fullscreen_cam: int + iframe_url: str + + @classmethod + def from_row(cls, row: Row) -> "Copilots": + return cls(**dict(row)) + + @property + def lnurl(self) -> Lnurl: + url = url_for("copilot.lnurl_response", cp_id=self.id, _external=True) + return lnurl_encode(url) diff --git a/lnbits/extensions/copilot/static/bitcoin.gif b/lnbits/extensions/copilot/static/bitcoin.gif new file mode 100644 index 000000000..ef8c2ecd5 Binary files /dev/null and b/lnbits/extensions/copilot/static/bitcoin.gif differ diff --git a/lnbits/extensions/copilot/static/confetti.gif b/lnbits/extensions/copilot/static/confetti.gif new file mode 100644 index 000000000..a3fec9712 Binary files /dev/null and b/lnbits/extensions/copilot/static/confetti.gif differ diff --git a/lnbits/extensions/copilot/static/face.gif b/lnbits/extensions/copilot/static/face.gif new file mode 100644 index 000000000..3e70d779c Binary files /dev/null and b/lnbits/extensions/copilot/static/face.gif differ diff --git a/lnbits/extensions/copilot/static/lnurl.png b/lnbits/extensions/copilot/static/lnurl.png new file mode 100644 index 000000000..ad2c97155 Binary files /dev/null and b/lnbits/extensions/copilot/static/lnurl.png differ diff --git a/lnbits/extensions/copilot/static/martijn.gif b/lnbits/extensions/copilot/static/martijn.gif new file mode 100644 index 000000000..e410677dc Binary files /dev/null and b/lnbits/extensions/copilot/static/martijn.gif differ diff --git a/lnbits/extensions/copilot/static/rick.gif b/lnbits/extensions/copilot/static/rick.gif new file mode 100644 index 000000000..c36c7e197 Binary files /dev/null and b/lnbits/extensions/copilot/static/rick.gif differ diff --git a/lnbits/extensions/copilot/static/rocket.gif b/lnbits/extensions/copilot/static/rocket.gif new file mode 100644 index 000000000..6f19597d0 Binary files /dev/null and b/lnbits/extensions/copilot/static/rocket.gif differ diff --git a/lnbits/extensions/copilot/tasks.py b/lnbits/extensions/copilot/tasks.py new file mode 100644 index 000000000..ff291e9ac --- /dev/null +++ b/lnbits/extensions/copilot/tasks.py @@ -0,0 +1,88 @@ +import trio # type: ignore +import json +import httpx +from quart import g, jsonify, url_for, websocket +from http import HTTPStatus + +from lnbits.core import db as core_db +from lnbits.core.models import Payment +from lnbits.tasks import register_invoice_listener + +from .crud import get_copilot +from .views import updater +import shortuuid + + +async def register_listeners(): + invoice_paid_chan_send, invoice_paid_chan_recv = trio.open_memory_channel(2) + register_invoice_listener(invoice_paid_chan_send) + await wait_for_paid_invoices(invoice_paid_chan_recv) + + +async def wait_for_paid_invoices(invoice_paid_chan: trio.MemoryReceiveChannel): + async for payment in invoice_paid_chan: + await on_invoice_paid(payment) + + +async def on_invoice_paid(payment: Payment) -> None: + webhook = None + data = None + if "copilot" != payment.extra.get("tag"): + # not an copilot invoice + return + + if payment.extra.get("wh_status"): + # this webhook has already been sent + return + + copilot = await get_copilot(payment.extra.get("copilot", -1)) + + if not copilot: + return ( + jsonify({"message": "Copilot link link does not exist."}), + HTTPStatus.NOT_FOUND, + ) + if copilot.animation1threshold: + if int(payment.amount / 1000) >= copilot.animation1threshold: + data = copilot.animation1 + webhook = copilot.animation1webhook + if copilot.animation2threshold: + if int(payment.amount / 1000) >= copilot.animation2threshold: + data = copilot.animation2 + webhook = copilot.animation1webhook + if copilot.animation3threshold: + if int(payment.amount / 1000) >= copilot.animation3threshold: + data = copilot.animation3 + webhook = copilot.animation1webhook + if webhook: + async with httpx.AsyncClient() as client: + try: + r = await client.post( + webhook, + json={ + "payment_hash": payment.payment_hash, + "payment_request": payment.bolt11, + "amount": payment.amount, + "comment": payment.extra.get("comment"), + }, + timeout=40, + ) + await mark_webhook_sent(payment, r.status_code) + except (httpx.ConnectError, httpx.RequestError): + await mark_webhook_sent(payment, -1) + if payment.extra.get("comment"): + await updater(copilot.id, data, payment.extra.get("comment")) + else: + await updater(copilot.id, data, "none") + + +async def mark_webhook_sent(payment: Payment, status: int) -> None: + payment.extra["wh_status"] = status + + await core_db.execute( + """ + UPDATE apipayments SET extra = ? + WHERE hash = ? + """, + (json.dumps(payment.extra), payment.payment_hash), + ) diff --git a/lnbits/extensions/copilot/templates/copilot/_api_docs.html b/lnbits/extensions/copilot/templates/copilot/_api_docs.html new file mode 100644 index 000000000..d6289be9c --- /dev/null +++ b/lnbits/extensions/copilot/templates/copilot/_api_docs.html @@ -0,0 +1,172 @@ + + +

+ StreamerCopilot: get tips via static QR (lnurl-pay) and show an + animation
+ + Created by,
Ben Arc +

+ + + + + + POST /copilot/api/v1/copilot +
Headers
+ {"X-Api-Key": <admin_key>}
+
+ Body (application/json) +
+
+ Returns 200 OK (application/json) +
+ [<copilot_object>, ...] +
Curl example
+ curl -X POST {{ request.url_root }}api/v1/copilot -d '{"title": + <string>, "animation": <string>, + "show_message":<string>, "amount": <integer>, + "lnurl_title": <string>}' -H "Content-type: application/json" + -H "X-Api-Key: {{g.user.wallets[0].adminkey }}" + +
+
+
+ + + + PUT + /copilot/api/v1/copilot/<copilot_id> +
Headers
+ {"X-Api-Key": <admin_key>}
+
+ Body (application/json) +
+
+ Returns 200 OK (application/json) +
+ [<copilot_object>, ...] +
Curl example
+ curl -X POST {{ request.url_root + }}api/v1/copilot/<copilot_id> -d '{"title": <string>, + "animation": <string>, "show_message":<string>, + "amount": <integer>, "lnurl_title": <string>}' -H + "Content-type: application/json" -H "X-Api-Key: + {{g.user.wallets[0].adminkey }}" + +
+
+
+ + + + + GET + /copilot/api/v1/copilot/<copilot_id> +
Headers
+ {"X-Api-Key": <invoice_key>}
+
+ Body (application/json) +
+
+ Returns 200 OK (application/json) +
+ [<copilot_object>, ...] +
Curl example
+ curl -X GET {{ request.url_root }}api/v1/copilot/<copilot_id> + -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" + +
+
+
+ + + + GET /copilot/api/v1/copilots +
Headers
+ {"X-Api-Key": <invoice_key>}
+
+ Body (application/json) +
+
+ Returns 200 OK (application/json) +
+ [<copilot_object>, ...] +
Curl example
+ curl -X GET {{ request.url_root }}api/v1/copilots -H "X-Api-Key: {{ + g.user.wallets[0].inkey }}" + +
+
+
+ + + + DELETE + /copilot/api/v1/copilot/<copilot_id> +
Headers
+ {"X-Api-Key": <admin_key>}
+
Returns 204 NO CONTENT
+ +
Curl example
+ curl -X DELETE {{ request.url_root + }}api/v1/copilot/<copilot_id> -H "X-Api-Key: {{ + g.user.wallets[0].adminkey }}" + +
+
+
+ + + + GET + /api/v1/copilot/ws/<copilot_id>/<comment>/<data> +
Headers
+ {"X-Api-Key": <admin_key>}
+
Returns 200
+ +
Curl example
+ curl -X GET {{ request.url_root }}/api/v1/copilot/ws/<string, + copilot_id>/<string, comment>/<string, gif name> -H + "X-Api-Key: {{ g.user.wallets[0].adminkey }}" + +
+
+
+
+ diff --git a/lnbits/extensions/copilot/templates/copilot/compose.html b/lnbits/extensions/copilot/templates/copilot/compose.html new file mode 100644 index 000000000..33bffda32 --- /dev/null +++ b/lnbits/extensions/copilot/templates/copilot/compose.html @@ -0,0 +1,289 @@ +{% extends "public.html" %} {% block page %} + + + + +
+
+ +
+ {% raw %}{{ copilot.lnurl_title }}{% endraw %} +
+
+
+ +

+ {% raw %}{{ price }}{% endraw %} +

+

+ Powered by LNbits/StreamerCopilot +

+
+{% endblock %} {% block scripts %} + + + +{% endblock %} diff --git a/lnbits/extensions/copilot/templates/copilot/index.html b/lnbits/extensions/copilot/templates/copilot/index.html new file mode 100644 index 000000000..0e652e71a --- /dev/null +++ b/lnbits/extensions/copilot/templates/copilot/index.html @@ -0,0 +1,637 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} +
+
+ + + {% raw %} + New copilot instance + + + + + + +
+
+
Copilots
+
+ +
+ + + + Export to CSV +
+
+ + + + + {% endraw %} + +
+
+
+ +
+ + +
LNbits StreamCopilot Extension
+
+ + + {% include "copilot/_api_docs.html" %} + +
+
+ + + + +
+ +
+ +
+ + + + + + + +
+
+ +
+ +
+ + +
+
+ + +
+
+
+
+
+ + + + +
+
+ +
+ +
+ + +
+
+ + +
+
+
+
+
+ + + + +
+
+ +
+ +
+ + +
+
+ + +
+
+
+
+
+ + + +
+ +
+ +
+
+
+ +
+
+
+ Update Copilot + Create Copilot + Cancel +
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }} + + + +{% endblock %} diff --git a/lnbits/extensions/copilot/templates/copilot/panel.html b/lnbits/extensions/copilot/templates/copilot/panel.html new file mode 100644 index 000000000..904ab104b --- /dev/null +++ b/lnbits/extensions/copilot/templates/copilot/panel.html @@ -0,0 +1,157 @@ +{% extends "public.html" %} {% block page %} +
+ +
+
+
+ +
+
+
+
+ Title: {% raw %} {{ copilot.title }} {% endraw %} +
+
+ +
+
+
+ + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ +{% endblock %} {% block scripts %} + + +{% endblock %} diff --git a/lnbits/extensions/copilot/views.py b/lnbits/extensions/copilot/views.py new file mode 100644 index 000000000..ef313a61a --- /dev/null +++ b/lnbits/extensions/copilot/views.py @@ -0,0 +1,61 @@ +from quart import g, abort, render_template, jsonify, websocket +from http import HTTPStatus +import httpx +from collections import defaultdict +from lnbits.decorators import check_user_exists, validate_uuids +from . import copilot_ext +from .crud import get_copilot +from quart import g, abort, render_template, jsonify, websocket +from functools import wraps +import trio +import shortuuid +from . import copilot_ext + + +@copilot_ext.route("/") +@validate_uuids(["usr"], required=True) +@check_user_exists() +async def index(): + return await render_template("copilot/index.html", user=g.user) + + +@copilot_ext.route("/cp/") +async def compose(): + return await render_template("copilot/compose.html") + + +@copilot_ext.route("/pn/") +async def panel(): + return await render_template("copilot/panel.html") + + +##################WEBSOCKET ROUTES######################## + +# socket_relay is a list where the control panel or +# lnurl endpoints can leave a message for the compose window + +connected_websockets = defaultdict(set) + + +@copilot_ext.websocket("/ws//") +async def wss(id): + copilot = await get_copilot(id) + if not copilot: + return "", HTTPStatus.FORBIDDEN + global connected_websockets + send_channel, receive_channel = trio.open_memory_channel(0) + connected_websockets[id].add(send_channel) + try: + while True: + data = await receive_channel.receive() + await websocket.send(data) + finally: + connected_websockets[id].remove(send_channel) + + +async def updater(copilot_id, data, comment): + copilot = await get_copilot(copilot_id) + if not copilot: + return + for queue in connected_websockets[copilot_id]: + await queue.send(f"{data + '-' + comment}") diff --git a/lnbits/extensions/copilot/views_api.py b/lnbits/extensions/copilot/views_api.py new file mode 100644 index 000000000..bf3b4eb75 --- /dev/null +++ b/lnbits/extensions/copilot/views_api.py @@ -0,0 +1,109 @@ +import hashlib +from quart import g, jsonify, url_for, websocket +from http import HTTPStatus +import httpx + +from lnbits.core.crud import get_user +from lnbits.decorators import api_check_wallet_key, api_validate_post_request +from .views import updater + +from . import copilot_ext + +from lnbits.extensions.copilot import copilot_ext +from .crud import ( + create_copilot, + update_copilot, + get_copilot, + get_copilots, + delete_copilot, +) + +#######################COPILOT########################## + + +@copilot_ext.route("/api/v1/copilot", methods=["POST"]) +@copilot_ext.route("/api/v1/copilot/", methods=["PUT"]) +@api_check_wallet_key("admin") +@api_validate_post_request( + schema={ + "title": {"type": "string", "empty": False, "required": True}, + "lnurl_toggle": {"type": "integer", "empty": False}, + "wallet": {"type": "string", "empty": False, "required": False}, + "animation1": {"type": "string", "empty": True, "required": False}, + "animation2": {"type": "string", "empty": True, "required": False}, + "animation3": {"type": "string", "empty": True, "required": False}, + "animation1threshold": {"type": "integer", "empty": True, "required": False}, + "animation2threshold": {"type": "integer", "empty": True, "required": False}, + "animation3threshold": {"type": "integer", "empty": True, "required": False}, + "animation1webhook": {"type": "string", "empty": True, "required": False}, + "animation2webhook": {"type": "string", "empty": True, "required": False}, + "animation3webhook": {"type": "string", "empty": True, "required": False}, + "lnurl_title": {"type": "string", "empty": True, "required": False}, + "show_message": {"type": "integer", "empty": True, "required": False}, + "show_ack": {"type": "integer", "empty": True}, + "show_price": {"type": "string", "empty": True}, + } +) +async def api_copilot_create_or_update(copilot_id=None): + if not copilot_id: + copilot = await create_copilot(user=g.wallet.user, **g.data) + return jsonify(copilot._asdict()), HTTPStatus.CREATED + else: + copilot = await update_copilot(copilot_id=copilot_id, **g.data) + return jsonify(copilot._asdict()), HTTPStatus.OK + + +@copilot_ext.route("/api/v1/copilot", methods=["GET"]) +@api_check_wallet_key("invoice") +async def api_copilots_retrieve(): + try: + return ( + jsonify( + [{**copilot._asdict()} for copilot in await get_copilots(g.wallet.user)] + ), + HTTPStatus.OK, + ) + except: + return "" + + +@copilot_ext.route("/api/v1/copilot/", methods=["GET"]) +@api_check_wallet_key("invoice") +async def api_copilot_retrieve(copilot_id): + copilot = await get_copilot(copilot_id) + if not copilot: + return jsonify({"message": "copilot does not exist"}), HTTPStatus.NOT_FOUND + if not copilot.lnurl_toggle: + return ( + jsonify({**copilot._asdict()}), + HTTPStatus.OK, + ) + return ( + jsonify({**copilot._asdict(), **{"lnurl": copilot.lnurl}}), + HTTPStatus.OK, + ) + + +@copilot_ext.route("/api/v1/copilot/", methods=["DELETE"]) +@api_check_wallet_key("admin") +async def api_copilot_delete(copilot_id): + copilot = await get_copilot(copilot_id) + + if not copilot: + return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND + + await delete_copilot(copilot_id) + + return "", HTTPStatus.NO_CONTENT + + +@copilot_ext.route("/api/v1/copilot/ws///", methods=["GET"]) +async def api_copilot_ws_relay(copilot_id, comment, data): + copilot = await get_copilot(copilot_id) + if not copilot: + return jsonify({"message": "copilot does not exist"}), HTTPStatus.NOT_FOUND + try: + await updater(copilot_id, data, comment) + except: + return "", HTTPStatus.FORBIDDEN + return "", HTTPStatus.OK diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html index cdf9da539..c041239f6 100644 --- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html +++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html @@ -4,10 +4,10 @@
- New Product - New Indexer Frontend shop your stall will list its products in @@ -282,7 +282,7 @@ Update Product @@ -290,7 +290,7 @@ Update Indexer @@ -382,7 +382,7 @@ SubmitLink to your ticket! diff --git a/lnbits/extensions/events/templates/events/index.html b/lnbits/extensions/events/templates/events/index.html index 384cf6300..5d3cc262c 100644 --- a/lnbits/extensions/events/templates/events/index.html +++ b/lnbits/extensions/events/templates/events/index.html @@ -4,7 +4,7 @@
- New Event @@ -267,14 +267,14 @@ Update Event Create Event - Scan ticket @@ -82,7 +82,7 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/lnbits/extensions/jukebox/templates/jukebox/jukebox.html b/lnbits/extensions/jukebox/templates/jukebox/jukebox.html index 801952ec3..ff8a4488a 100644 --- a/lnbits/extensions/jukebox/templates/jukebox/jukebox.html +++ b/lnbits/extensions/jukebox/templates/jukebox/jukebox.html @@ -199,6 +199,7 @@ self.receive.paymentReq = response.data[0][1] self.receive.paymentHash = response.data[0][0] self.receive.dialogues.second = true + self.$q.notify({ message: 'Processing' }) diff --git a/lnbits/extensions/livestream/migrations.py b/lnbits/extensions/livestream/migrations.py index 9fb01d300..465d04ee1 100644 --- a/lnbits/extensions/livestream/migrations.py +++ b/lnbits/extensions/livestream/migrations.py @@ -4,7 +4,7 @@ async def m001_initial(db): """ await db.execute( """ - CREATE TABLE livestreams ( + CREATE TABLE IF NOT EXISTS livestreams ( id INTEGER PRIMARY KEY AUTOINCREMENT, wallet TEXT NOT NULL, fee_pct INTEGER NOT NULL DEFAULT 10, @@ -15,7 +15,7 @@ async def m001_initial(db): await db.execute( """ - CREATE TABLE producers ( + CREATE TABLE IF NOT EXISTS producers ( livestream INTEGER NOT NULL REFERENCES livestreams (id), id INTEGER PRIMARY KEY AUTOINCREMENT, user TEXT NOT NULL, @@ -27,7 +27,7 @@ async def m001_initial(db): await db.execute( """ - CREATE TABLE tracks ( + CREATE TABLE IF NOT EXISTS tracks ( livestream INTEGER NOT NULL REFERENCES livestreams (id), id INTEGER PRIMARY KEY AUTOINCREMENT, download_url TEXT, diff --git a/lnbits/extensions/livestream/tasks.py b/lnbits/extensions/livestream/tasks.py index c69db02ad..52f86d155 100644 --- a/lnbits/extensions/livestream/tasks.py +++ b/lnbits/extensions/livestream/tasks.py @@ -1,5 +1,5 @@ import json -import trio # type: ignore +import trio from lnbits.core.models import Payment from lnbits.core.crud import create_payment diff --git a/lnbits/extensions/livestream/templates/livestream/index.html b/lnbits/extensions/livestream/templates/livestream/index.html index e6585ac04..c6fc5b4a7 100644 --- a/lnbits/extensions/livestream/templates/livestream/index.html +++ b/lnbits/extensions/livestream/templates/livestream/index.html @@ -26,7 +26,7 @@
{% raw %} - + {{ nextCurrentTrack && nextCurrentTrack === livestream.current_track ? 'Stop' : 'Set' }} current track @@ -46,7 +46,7 @@ >
- Set percent rate
@@ -61,7 +61,7 @@
Tracks
- Add new track
@@ -296,7 +296,7 @@
diff --git a/lnbits/extensions/lndhub/config.json b/lnbits/extensions/lndhub/config.json index 2b536d7d2..6285ff80d 100644 --- a/lnbits/extensions/lndhub/config.json +++ b/lnbits/extensions/lndhub/config.json @@ -1,6 +1,6 @@ { "name": "LndHub", - "short_description": "Access lnbits from BlueWallet or Zeus.", + "short_description": "Access lnbits from BlueWallet or Zeus", "icon": "navigation", "contributors": ["fiatjaf"] } diff --git a/lnbits/extensions/lnticket/__init__.py b/lnbits/extensions/lnticket/__init__.py index 0e0aa146a..cfdadc402 100644 --- a/lnbits/extensions/lnticket/__init__.py +++ b/lnbits/extensions/lnticket/__init__.py @@ -10,3 +10,8 @@ lnticket_ext: Blueprint = Blueprint( from .views_api import * # noqa from .views import * # noqa +from .tasks import register_listeners + +from lnbits.tasks import record_async + +lnticket_ext.record(record_async(register_listeners)) diff --git a/lnbits/extensions/lnticket/tasks.py b/lnbits/extensions/lnticket/tasks.py new file mode 100644 index 000000000..5160de1dd --- /dev/null +++ b/lnbits/extensions/lnticket/tasks.py @@ -0,0 +1,37 @@ +import json +import trio # type: ignore + +from lnbits.core.models import Payment +from lnbits.core.crud import create_payment +from lnbits.core import db as core_db +from lnbits.tasks import register_invoice_listener, internal_invoice_paid +from lnbits.helpers import urlsafe_short_hash + +from .crud import get_ticket, set_ticket_paid + + +async def register_listeners(): + invoice_paid_chan_send, invoice_paid_chan_recv = trio.open_memory_channel(2) + register_invoice_listener(invoice_paid_chan_send) + await wait_for_paid_invoices(invoice_paid_chan_recv) + + +async def wait_for_paid_invoices(invoice_paid_chan: trio.MemoryReceiveChannel): + async for payment in invoice_paid_chan: + await on_invoice_paid(payment) + + +async def on_invoice_paid(payment: Payment) -> None: + if "lnticket" != payment.extra.get("tag"): + # not a lnticket invoice + return + + ticket = await get_ticket(payment.checking_id) + if not ticket: + print("this should never happen", payment) + return + + await payment.set_pending(False) + await set_ticket_paid(payment.payment_hash) + _ticket = await get_ticket(payment.checking_id) + print("ticket", _ticket) diff --git a/lnbits/extensions/lnticket/templates/lnticket/display.html b/lnbits/extensions/lnticket/templates/lnticket/display.html index 9a5accaf2..d570ad793 100644 --- a/lnbits/extensions/lnticket/templates/lnticket/display.html +++ b/lnbits/extensions/lnticket/templates/lnticket/display.html @@ -33,7 +33,7 @@
Submit - console.log('{{ form_costpword }}') + //console.log('{{ form_costpword }}') Vue.component(VueQrcode.name, VueQrcode) new Vue({ @@ -99,7 +99,11 @@ show: false, status: 'pending', paymentReq: null - } + }, + wallet: { + inkey: '' + }, + cancelListener: () => {} } }, computed: { @@ -128,12 +132,35 @@ }, closeReceiveDialog: function () { - var checker = this.receive.paymentChecker + var checker = this.startPaymentNotifier dismissMsg() - clearInterval(paymentChecker) setTimeout(function () {}, 10000) }, + startPaymentNotifier() { + this.cancelListener() + + this.cancelListener = LNbits.events.onInvoicePaid( + this.wallet, + payment => { + this.receive = { + show: false, + status: 'complete', + paymentReq: null + } + dismissMsg() + + this.formDialog.data.name = '' + this.formDialog.data.email = '' + this.formDialog.data.text = '' + this.$q.notify({ + type: 'positive', + message: 'Sent, thank you!', + icon: 'thumb_up' + }) + } + ) + }, Invoice: function () { var self = this axios @@ -158,39 +185,15 @@ status: 'pending', paymentReq: self.paymentReq } - - paymentChecker = setInterval(function () { - axios - .get('/lnticket/api/v1/tickets/' + self.paymentCheck) - .then(function (res) { - if (res.data.paid) { - clearInterval(paymentChecker) - self.receive = { - show: false, - status: 'complete', - paymentReq: null - } - dismissMsg() - - self.formDialog.data.name = '' - self.formDialog.data.email = '' - self.formDialog.data.text = '' - self.$q.notify({ - type: 'positive', - message: 'Sent, thank you!', - icon: 'thumb_up' - }) - } - }) - .catch(function (error) { - LNbits.utils.notifyApiError(error) - }) - }, 2000) }) .catch(function (error) { LNbits.utils.notifyApiError(error) }) } + }, + created() { + this.wallet.inkey = '{{form_wallet}}' + this.startPaymentNotifier() } }) diff --git a/lnbits/extensions/lnticket/templates/lnticket/index.html b/lnbits/extensions/lnticket/templates/lnticket/index.html index 7f2f8170a..1c909f092 100644 --- a/lnbits/extensions/lnticket/templates/lnticket/index.html +++ b/lnbits/extensions/lnticket/templates/lnticket/index.html @@ -4,7 +4,7 @@
- New Form @@ -90,6 +90,16 @@
Tickets
+
Export to CSVUpdate Form @@ -215,7 +225,7 @@ Create Form {% endblock %} {% block scripts %} {{ window_vars(user) }} + {% block scripts %}{% endblock %} diff --git a/lnbits/utils/exchange_rates.py b/lnbits/utils/exchange_rates.py index ef4d3306d..801994bef 100644 --- a/lnbits/utils/exchange_rates.py +++ b/lnbits/utils/exchange_rates.py @@ -1,4 +1,4 @@ -import trio # type: ignore +import trio import httpx from typing import Callable, NamedTuple diff --git a/lnbits/wallets/clightning.py b/lnbits/wallets/clightning.py index b9f24f571..bd6921938 100644 --- a/lnbits/wallets/clightning.py +++ b/lnbits/wallets/clightning.py @@ -3,7 +3,7 @@ try: except ImportError: # pragma: nocover LightningRpc = None -import trio # type: ignore +import trio import random import json diff --git a/lnbits/wallets/lnbits.py b/lnbits/wallets/lnbits.py index f55df5a71..aa392ce35 100644 --- a/lnbits/wallets/lnbits.py +++ b/lnbits/wallets/lnbits.py @@ -1,4 +1,4 @@ -import trio # type: ignore +import trio import json import httpx from os import getenv diff --git a/lnbits/wallets/lndrest.py b/lnbits/wallets/lndrest.py index b6746c6b8..c0310e772 100644 --- a/lnbits/wallets/lndrest.py +++ b/lnbits/wallets/lndrest.py @@ -1,4 +1,4 @@ -import trio # type: ignore +import trio import httpx import json import base64 diff --git a/lnbits/wallets/lnpay.py b/lnbits/wallets/lnpay.py index dc4a2e58c..bb08d35eb 100644 --- a/lnbits/wallets/lnpay.py +++ b/lnbits/wallets/lnpay.py @@ -1,5 +1,5 @@ import json -import trio # type: ignore +import trio import httpx from os import getenv from http import HTTPStatus diff --git a/lnbits/wallets/lntxbot.py b/lnbits/wallets/lntxbot.py index f8ff709c9..e5e1cf158 100644 --- a/lnbits/wallets/lntxbot.py +++ b/lnbits/wallets/lntxbot.py @@ -1,4 +1,4 @@ -import trio # type: ignore +import trio import json import httpx from os import getenv diff --git a/lnbits/wallets/opennode.py b/lnbits/wallets/opennode.py index 8354e8199..5cc046a5e 100644 --- a/lnbits/wallets/opennode.py +++ b/lnbits/wallets/opennode.py @@ -1,4 +1,4 @@ -import trio # type: ignore +import trio import hmac import httpx from http import HTTPStatus diff --git a/lnbits/wallets/spark.py b/lnbits/wallets/spark.py index 0c81733e3..770d5777d 100644 --- a/lnbits/wallets/spark.py +++ b/lnbits/wallets/spark.py @@ -1,4 +1,4 @@ -import trio # type: ignore +import trio import json import httpx import random diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 000000000..b991c7cc2 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +plugins = trio_typing.plugin diff --git a/package-lock.json b/package-lock.json index e08f5cc4c..3d0735425 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,31 @@ { + "name": "lnbits", + "lockfileVersion": 2, "requires": true, - "lockfileVersion": 1, + "packages": { + "": { + "devDependencies": { + "prettier": "2.1.1" + } + }, + "node_modules/prettier": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz", + "integrity": "sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + } + }, "dependencies": { "prettier": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", - "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz", + "integrity": "sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==", "dev": true } } diff --git a/requirements.txt b/requirements.txt index 703fbbd15..5e10e7cd4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,6 @@ quart-cors==0.4.0 quart-trio==0.7.0 represent==1.6.0.post0 rfc3986==1.4.0 -secure==0.2.1 shortuuid==1.0.1 six==1.15.0 sniffio==1.2.0