├── .github └── workflows │ ├── check.yml │ └── static.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── Makefile ├── README.md ├── build.sh ├── check.py ├── extensions.json ├── index.html ├── integration ├── 001_extensions-enable-all.jmx ├── 002_lnurl.jmx ├── 003_tpos.jmx ├── 004_tpos-pay-to-enable.jmx ├── 005_lnurlw-race.jmx ├── 006_watchonly-satspay-tipjar.jmx ├── 007_lndhub.jmx ├── 008_lnaddress-conflict.jmx ├── 009_example_upgrade.jmx ├── 010_nostrnip5.jmx ├── 011_auction_house.jmx ├── fragments │ ├── init-account.jmx │ └── init-server.jmx └── scripts │ ├── check_admin_extension_status_code.js │ ├── check_price_in_sats_vs_millisats.js │ ├── extract_extension_data.js │ ├── extract_extension_release_by_version.js │ ├── extract_extensions_ids.js │ ├── extract_latest_extension_release.js │ ├── lndhub_check_balance.js │ ├── lndhub_check_balance_after_payments.js │ ├── lndhub_check_payments.js │ ├── lndhub_check_payments_after_payment.js │ ├── lndhub_check_transactions.js │ ├── lndhub_check_transactions_after_payment.js │ ├── lnurl_check_balance.js │ ├── lnurl_check_balance_after_withdraw.js │ ├── lnurl_check_created_pay_link.js │ ├── lnurl_check_created_withdraw_link.js │ ├── lnurl_check_receive_wallet_balance.js │ ├── lnurl_check_webhook_response.js │ ├── lnurl_extract_pay_link_id.js │ ├── lnurlw_check_executed_withdraws.js │ ├── nip5_check_referer_balance.js │ ├── satspay_check_charge_details.js │ ├── satspay_check_charge_webhook_call.js │ ├── satspay_check_charges_count.js │ ├── satspay_check_payments_status.js │ ├── satspay_check_wallet_balance.js │ ├── tipjar_check_tips_for_user.js │ ├── tipjar_check_user_balance.js │ ├── tipjar_extract_charge_url_for_tip.js │ ├── tops_check_updated.js │ ├── tpos_check_admin_balance.js │ ├── tpos_check_atm_created.js │ ├── tpos_check_created.js │ ├── tpos_check_tip_wallet_created.js │ ├── tpos_check_user_balance.js │ └── tpos_extract_tip_wallet_data.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── logo.svg ├── pyproject.toml ├── src ├── index.jsx └── index.scss ├── test.sh ├── toc.md ├── update_version.py ├── util.sh └── vite.config.js /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: LNbits Extension CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | jobs: 9 | validate-json: 10 | runs-on: ubuntu-24.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: json-syntax-check 14 | uses: limitusus/json-syntax-check@v1 15 | with: 16 | pattern: "\\.json$" 17 | 18 | check-json: 19 | runs-on: ubuntu-24.04 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: actions/setup-python@v4 23 | with: 24 | python-version: "3.10" 25 | - name: Install dependencies 26 | run: pip install Pillow requests 27 | - name: Run check.py 28 | run: python check.py 29 | 30 | jmeter: 31 | needs: [validate-json, check-json] 32 | runs-on: ubuntu-24.04 33 | steps: 34 | 35 | - uses: actions/checkout@v4 36 | 37 | - name: Set up Python 3.10 38 | uses: actions/setup-python@v5 39 | with: 40 | python-version: "3.10" 41 | cache: "pip" 42 | 43 | - name: Set up Poetry 1.5.1 44 | uses: abatilo/actions-poetry@v2 45 | with: 46 | poetry-version: "1.5.1" 47 | 48 | - name: create logs and reports dir 49 | run: | 50 | mkdir logs 51 | mkdir reports 52 | 53 | - name: setup java version 54 | run: | 55 | update-java-alternatives --list 56 | sudo update-java-alternatives --set /usr/lib/jvm/temurin-8-jdk-amd64 57 | java -version 58 | 59 | - name: install jmeter 60 | env: 61 | JAVA_HOME: /usr/lib/jvm/temurin-8-jdk-amd64 62 | run: | 63 | make install-jmeter 64 | 65 | - name: start mirror server 66 | env: 67 | JAVA_HOME: /usr/lib/jvm/temurin-8-jdk-amd64 68 | run: | 69 | make start-mirror-server 70 | 71 | - name: Setup LNbits and run 72 | env: 73 | LNBITS_ADMIN_UI: true 74 | DEBUG: true 75 | LNBITS_EXTENSIONS_DEFAULT_INSTALL: "watchonly, satspay, tipjar, tpos, lnurlp, withdraw" 76 | LNBITS_EXTENSIONS_MANIFESTS: "https://raw.githubusercontent.com/lnbits/lnbits-extensions/${{ github.head_ref || github.ref_name }}/extensions.json" 77 | LNBITS_BACKEND_WALLET_CLASS: FakeWallet 78 | run: | 79 | git clone https://github.com/lnbits/lnbits.git 80 | cd lnbits 81 | git checkout dev 82 | poetry env use python3.10 83 | poetry install 84 | poetry run lnbits & 85 | sleep 10 86 | 87 | - name: run jmx scripts 88 | env: 89 | EXTENSIONS_MANIFEST_PATH: "/lnbits/lnbits-extensions/${{ github.head_ref || github.ref_name }}/extensions.json" 90 | JAVA_HOME: /usr/lib/jvm/temurin-8-jdk-amd64 91 | run: | 92 | make test 93 | 94 | - name: log results 95 | if: ${{ always() }} 96 | run: | 97 | # catch up time for lnbits 98 | sleep 1 99 | cat lnbits/data/logs/debug.log 100 | 101 | - uses: actions/upload-artifact@v4 102 | if: ${{ always() }} 103 | with: 104 | name: jmeter-test-results 105 | path: | 106 | reports/ 107 | logs/ 108 | lnbits/data/logs/ 109 | -------------------------------------------------------------------------------- /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v3 34 | - name: Use Node.js 35 | uses: actions/setup-node@v3 36 | with: 37 | node-version: "18.x" 38 | - name: Install dependencies 39 | run: npm i 40 | - name: install build deps 41 | run: sudo apt-get install -y jq curl 42 | - name: Prepare extension configs 43 | run: bash build.sh 44 | - name: Build 45 | run: npm run build 46 | - name: Setup Pages 47 | uses: actions/configure-pages@v3 48 | - name: Upload artifact 49 | uses: actions/upload-pages-artifact@v3 50 | with: 51 | # Upload entire repository 52 | path: "dist" 53 | - name: Deploy to GitHub Pages 54 | id: deployment 55 | uses: actions/deploy-pages@v4 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | .mypy_cache 3 | extensions 4 | node_modules 5 | dist 6 | apache-jmeter* 7 | logs 8 | reports 9 | jmeter.log 10 | upgrades 11 | metadata.json 12 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | extensions 2 | .mypy_cache 3 | package-lock.json 4 | dist 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "bracketSameLine": true 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Vlad Stan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install-jmeter: 2 | wget --quiet https://downloads.apache.org//jmeter/binaries/apache-jmeter-5.6.3.zip 3 | unzip -qq apache-jmeter-5.6.3.zip 4 | ./apache-jmeter-5.6.3/bin/jmeter -v 5 | 6 | start-mirror-server: 7 | echo "Fix bad Jmeter lib names." 8 | cp ./apache-jmeter-5.6.3/lib/slf4j-api-1.7.36.jar ./apache-jmeter-5.6.3/lib/slf4j-api-1.7.25.jar 9 | cp ./apache-jmeter-5.6.3/lib/log4j-slf4j-impl-2.22.1.jar ./apache-jmeter-5.6.3/lib/log4j-slf4j-impl-2.11.0.jar 10 | cp ./apache-jmeter-5.6.3/lib/log4j-api-2.22.1.jar ./apache-jmeter-5.6.3/lib/log4j-api-2.11.1.jar 11 | cp ./apache-jmeter-5.6.3/lib/log4j-core-2.22.1.jar ./apache-jmeter-5.6.3/lib/log4j-core-2.11.1.jar 12 | cp ./apache-jmeter-5.6.3/lib/log4j-1.2-api-2.22.1.jar ./apache-jmeter-5.6.3/lib/log4j-1.2-api-2.11.1.jar 13 | echo "Starting the mirror server on port 8500." 14 | 15 | ./apache-jmeter-5.6.3/bin/mirror-server --port 8500 & 16 | 17 | test: 18 | sh ./test.sh 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LNbits Vetted Extensions 2 | 3 | Official registry for vetted LNbits extensions 4 | 5 | To submit an extension to this registry add your manifest into the [`extensions.json`](extensions.json) file in this repository. 6 | ### Important 7 | 8 | Only submit fully working extensions, the review process is not intended to improve the extension code. 9 | 10 | Do not add dependencies, LNbits has plenty of dependencies you can use. 11 | 12 | The easier an extension is to review, the quicker the review process will be. 13 | 14 | ### Manifest format 15 | 16 | The file MUST use the `extensions` format: 17 | 18 | ```json 19 | { 20 | "id": "gerty", 21 | "repo": "https://github.com/lnbits/gerty", 22 | "name": "Gerty", 23 | "version": "0.1.2", 24 | "short_description": "Your bitcoin assistant", 25 | "icon": "https://raw.githubusercontent.com/lnbits/gerty/main/static/gerty.png", 26 | "archive": "https://github.com/lnbits/gerty/archive/refs/tags/0.1.2.zip", 27 | "hash": "baff0b6162ffb65cc0b4c721a4aa40a7d3d48acd55a3e344cba3eb1d35cf2074" 28 | }, 29 | ``` 30 | 31 | For an exensions local [`manifest.json`](https://github.com/lnbits/gerty/blob/main/manifest.json) use the `repos` format: 32 | 33 | ```json 34 | { 35 | "repos": [ 36 | { 37 | "id": "gerty", 38 | "organisation": "lnbits", 39 | "repository": "gerty" 40 | } 41 | ] 42 | } 43 | ``` 44 | 45 | ### Paid extensions 46 | It is possible for developers to require a payment for their extensions. In order to do so an extension release must have this field: 47 | ```json 48 | "pay_link": "# payment URL" 49 | ``` 50 | 51 | **Example**: 52 | ```json 53 | { 54 | "id": "testext", 55 | "repo": "https://github.com/lnbits/testext", 56 | "name": "Test Extension", 57 | "version": "0.5", 58 | "short_description": "Private Test Extension", 59 | "icon": "https://raw.githubusercontent.com/lnbits/example/main/static/bitcoin-extension.png", 60 | "archive": "https://demo.lnbits.com/paywall/download/m2FVCFktJzMcGKXTaHbyhi", 61 | "pay_link": "https://demo.lnbits.com/paywall/api/v1/paywalls/invoice/m2FVCFktJzMcGKXTaHbyhi", 62 | "hash": "455527407fcfdc5e8aba93f16802d1083d36dcdfdde829f919cee07420791d61" 63 | } 64 | ``` 65 | 66 | The [Paywall LNbits Extension](https://github.com/lnbits/paywall/blob/main/README.md#file-paywall) can be used to serve the extension `zip` file. 67 | 68 | If you do not want to use the [Paywall LNbits Extension](https://github.com/lnbits/paywall/) to server your extension, but instead you want to use your own paywall, then the `pay_link` endpoint must follow these specifications: 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 92 | 93 | 94 | 95 | 102 | 112 | 113 | 114 | 115 | 123 | 130 | 131 | 132 |
HTTP RequestHTTP ResponseDescription
78 | 79 | ```HTTP 80 | GET pay_link 81 | ``` 82 | 84 | 85 | ```json 86 | { 87 | "amount": 5 88 | } 89 | ```` 90 | 91 | Get the amount in `sats` required by this extension release.
96 | 97 | ```HTTP 98 | GET pay_link?amount=5 99 | ``` 100 | 101 | 103 | 104 | ```json 105 | { 106 | "payment_hash": "04c33f37d01aff...fd7c407a", 107 | "payment_request": "lnbc50n1pju...n7h8gucqn2cgau" 108 | } 109 | ``` 110 | 111 | Request an invoice for the specified amount (or higher).
116 | 117 | ```HTTP 118 | WS pay_link/{payment_hash} 119 | 120 | ``` 121 | 122 | 124 | 125 | ```json 126 | {"paid": true|false} 127 | ``` 128 | 129 | Open a websocket to be notified when the invoice has been paid.
133 | 134 | In order to download the file one must add the `payment_hash` and an `version` (optional) query parameters to the `archive` URL. Eg: 135 | 136 | ```HTTP 137 | GET https://demo.lnbits.com/paywall/download/m2FVCFktJzMcGKXTaHbyhi?payment_hash=3bf...7ec&version=v0.1 138 | ``` 139 | 140 | 141 | ### Getting sha256 checksum for a release 142 | 143 | ```console 144 | $ wget -O - https://github.com/lnbits/withdraw/archive/refs/tags/0.1.1.zip 2> /dev/null | sha256sum | cut -d" " -f 1 145 | baff0b6162ffb65cc0b4c721a4aa40a7d3d48acd55a3e344cba3eb1d35cf2074 146 | ``` 147 | 148 | ### Lighter ZIP archive 149 | 150 | - documentation, tests and other type of files should not be included in the zip archive generated when a GitHub release is created 151 | - keep the `README.md` and `LICENSE` files in the zip as these are required! 152 | - in order to exclude these files one must: 153 | - create a `.gitattributes` file (on the top level of the repo) 154 | - add a line for the ignored files/dirs: `tests/ export-ignore` 155 | 156 | ### Checking the changes before sending a pull request 157 | 158 | - after editing the `manifest.json` file in this repo you should run `python3 check.py` as a sanity check 159 | - you can run `python3 check.py foo bar` only to run sanity checks on extensions named `foo` and `bar` 160 | 161 | ### util for cloning and pulling all extensions 162 | 163 | cloning all extensions into `extensions` dir. requires `jq` to be installed. 164 | 165 | ```sh 166 | sh util.sh clone 167 | ``` 168 | 169 | pulling all extensions from `extensions` dir 170 | 171 | ```sh 172 | sh util.sh pull 173 | ``` 174 | 175 | get LNbits env variables for all extensions 176 | 177 | ```sh 178 | sh util.sh env 179 | ``` 180 | 181 | update a extension in extensions.json with id and version 182 | 183 | ```sh 184 | sh util.sh update_extension example v0.4.2 185 | ``` 186 | 187 | ### Example video on how to release a extension into this repo 188 | 189 | this uses a github workflow like this: https://github.com/lnbits/example/blob/main/.github/workflows/release.yml 190 | 191 | https://github.com/lnbits/lnbits-extensions/assets/1743657/0d0a6626-655b-4528-9547-9fdc348cf9a6 192 | 193 | 194 | # Integration Tests 195 | ## setup 196 | ```sh 197 | make install-jmeter 198 | ``` 199 | ## configure 200 | make sure LNbits is running and start the mirror server 201 | ```sh 202 | make start-mirror-server 203 | ``` 204 | ## run 205 | ```sh 206 | make test 207 | ``` 208 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CONFS=$(cat extensions.json \ 4 | | jq -r '[.extensions[].repo] | unique | .[]' \ 5 | | sed -e 's/https:\/\/github.com\//https:\/\/raw.githubusercontent.com\//' -e 's/$/\/main\/config.json/') 6 | 7 | for conf in $CONFS; do 8 | name=$(echo $conf | cut -d'/' -f5) 9 | echo "Downloading $conf" 10 | curl -s $conf | jq --arg name $name '. + {id: $name}' >> metadata.tmp.json 11 | done 12 | 13 | cat metadata.tmp.json | jq -s . > metadata.json 14 | rm metadata.tmp.json 15 | -------------------------------------------------------------------------------- /check.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | from hashlib import sha256 4 | from io import BytesIO 5 | from os.path import basename 6 | from zipfile import ZipFile 7 | 8 | import requests 9 | from PIL import Image 10 | 11 | EXTENSIONS_FILE = "extensions.json" 12 | CHECK_ZIP_CONTENTS = True 13 | UNPACK_ZIP_CONTENTS = False 14 | 15 | MANDATORY_FILES = [ 16 | "LICENSE", 17 | "README.md", 18 | "__init__.py", 19 | "config.json", 20 | "manifest.json", 21 | ] 22 | 23 | try: 24 | # use requests_cache if installed 25 | # useful for subsequent runs of the check 26 | # so the files are not downloaded again and again 27 | import requests_cache 28 | 29 | requests_cache.install_cache("lnbits-extensions-download-cache") 30 | except ImportError: 31 | pass 32 | 33 | 34 | def get_remote_file(url): 35 | r = requests.get(url) 36 | return r.content 37 | 38 | 39 | def get_remote_zip(url): 40 | d = get_remote_file(url) 41 | z = ZipFile(BytesIO(d)) 42 | return sha256(d).hexdigest(), z 43 | 44 | 45 | class Extension: 46 | def __init__(self, ext): 47 | self.id = ext["id"] 48 | self.repo = ext["repo"] 49 | self.name = ext["name"] 50 | self.version = ext["version"] 51 | self.short_description = ext["short_description"] 52 | self.icon = ext["icon"] 53 | self.archive = ext["archive"] 54 | self.hash = ext["hash"] 55 | self.min_lnbits_version = ext.get("min_lnbits_version") 56 | self.max_lnbits_version = ext.get("max_lnbits_version") 57 | 58 | def validate(self) -> tuple[bool, str]: 59 | print(f"Checking '{self.name}' extension ({self.id} {self.version})") 60 | 61 | # sanity checks 62 | if not self.name[0].isupper(): 63 | return False, "name does not start with uppercase letter" 64 | if not self.short_description[0].isupper(): 65 | return False, "short_description does not start with uppercase letter" 66 | if self.short_description.endswith("."): 67 | return False, "short_description should not end with '.'" 68 | if not self.id == self.id.lower(): 69 | return False, "id has mixed casing" 70 | if not self.archive.startswith(self.repo): 71 | return False, "archive URL does not start with repo URL" 72 | if not self.archive.endswith(f"{self.version}.zip"): 73 | bn = basename(self.archive) 74 | return False, f"archive name '{bn}' doesn't end with {self.version}.zip" 75 | 76 | # print archive info from json 77 | archive_hash, archive_zip = get_remote_zip(self.archive) 78 | print(f"- url : {self.archive}") 79 | print(f"- hash : {self.hash} (expected)") 80 | print(f"- hash : {archive_hash} (real)") 81 | if self.hash != archive_hash: 82 | return False, f"hash mismatch {self.hash} != {archive_hash}" 83 | 84 | # check downloaded zip 85 | bad_file = archive_zip.testzip() 86 | if bad_file is not None: 87 | return False, f"archive check for file {bad_file} failed" 88 | 89 | if UNPACK_ZIP_CONTENTS: 90 | archive_zip.extractall("extensions") 91 | 92 | if CHECK_ZIP_CONTENTS: 93 | filelist = archive_zip.namelist() 94 | prefix = f"{basename(self.repo)}-{self.version}" 95 | 96 | mandatory_files = MANDATORY_FILES.copy() 97 | # these are known to not include the LICENSE file in their latest release 98 | # this is fixed in master, but we are waiting for the upcoming release 99 | if self.id in [ 100 | "bleskomat", 101 | "deezy", 102 | "discordbot", 103 | "livestream", 104 | "market", 105 | "paywall", 106 | "smtp", 107 | "streamalerts", 108 | "subdomains", 109 | ]: 110 | mandatory_files.remove("LICENSE") 111 | 112 | # these are known to not include the manifest.json file in their latest release 113 | # this is fixed in master, but we are waiting for the upcoming release 114 | if self.id in [ 115 | "bleskomat", 116 | "smtp", 117 | "subdomains", 118 | ]: 119 | mandatory_files.remove("manifest.json") 120 | 121 | for f in mandatory_files: 122 | fn = f"{prefix}/{f}" 123 | if fn not in filelist: 124 | return False, f"file {fn} not contained in the archive" 125 | 126 | # check config 127 | config = json.load(archive_zip.open(f"{prefix}/config.json")) 128 | 129 | # don't check name for now, they mismatch quite often :( 130 | """ 131 | name = config.get("name") 132 | if name != self.name: 133 | return False, f"name mismatch: {name} != {self.name}" 134 | """ 135 | 136 | # don't check short_description for now, they mismatch quite often :( 137 | """ 138 | short_description = config.get("short_description") 139 | if short_description != self.short_description: 140 | return False, f"short_description mismatch: {short_description} != {self.short_description}" 141 | """ 142 | 143 | min_lnbits_version = config.get("min_lnbits_version") 144 | print(f"- min_lnbits_version : {min_lnbits_version}") 145 | if min_lnbits_version != self.min_lnbits_version: 146 | return ( 147 | False, 148 | f"min_lnbits_version mismatch: {min_lnbits_version} != {self.min_lnbits_version}", 149 | ) 150 | 151 | # check max_lnbits_version only if it's present in the config.json 152 | max_lnbits_version = config.get("max_lnbits_version") 153 | if max_lnbits_version: 154 | print(f"- max_lnbits_version : {max_lnbits_version}") 155 | if max_lnbits_version != self.max_lnbits_version: 156 | return ( 157 | False, 158 | f"max_lnbits_version mismatch: {max_lnbits_version} != {self.max_lnbits_version}", 159 | ) 160 | 161 | # check icon 162 | try: 163 | icon = get_remote_file(self.icon) 164 | img = Image.open(BytesIO(icon)) 165 | print(f"- icon : OK {img.size[0]}x{img.size[1]} @ {img.mode} ({self.icon})") 166 | except Exception: 167 | print(f"- icon : broken ({self.icon})") 168 | return False, f"broken icon ({self.icon})" 169 | 170 | return True, "" 171 | 172 | 173 | def main(args): 174 | # load stuff from json 175 | try: 176 | print(f"Loading {EXTENSIONS_FILE}") 177 | j = json.load(open(EXTENSIONS_FILE, "rt")) 178 | extensions = j["extensions"] 179 | featured = j["featured"] 180 | print("OK") 181 | except Exception as ex: 182 | print(f"FAILED: {ex}") 183 | return False 184 | print() 185 | 186 | # check whether extensions listed in featured exist 187 | try: 188 | print("Checking whether featured extensions exist") 189 | extensions_ids = [e["id"] for e in extensions] 190 | for f in featured: 191 | assert f in extensions_ids 192 | print("OK") 193 | except Exception as ex: 194 | print(f"FAILED: {ex}") 195 | return False 196 | print() 197 | 198 | failed = {} 199 | for ext in extensions: 200 | # skip extension if args are provided and extension mentioned 201 | if args and ext["id"] not in args: 202 | continue 203 | e = Extension(ext) 204 | ok, err = e.validate() 205 | if ok: 206 | print("OK") 207 | else: 208 | print(f"FAILED: {err}") 209 | failed[f"{ext['id']} {ext['version']}"] = err 210 | print() 211 | 212 | print("Failed extensions:") 213 | if not failed: 214 | print("NONE, ALL GOOD!") 215 | else: 216 | for k, v in failed.items(): 217 | print(f"- {k}: {v}") 218 | 219 | return not failed 220 | 221 | 222 | if __name__ == "__main__": 223 | if not main(sys.argv[1:]): 224 | sys.exit(1) 225 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 12 | 13 | LNbits Extension Repository 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /integration/009_example_upgrade.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | host 18 | ${__property(host,,127.0.0.1)} 19 | = 20 | 21 | 22 | scheme 23 | ${__property(scheme,,http)} 24 | = 25 | 26 | 27 | port 28 | ${__property(port,,5000)} 29 | = 30 | 31 | 32 | thinkTime 33 | 3000 34 | = 35 | 36 | 37 | config_path 38 | ${__BeanShell(import org.apache.jmeter.services.FileServer; FileServer.getFileServer().getBaseDir();)} 39 | = 40 | 41 | 42 | extension 43 | example 44 | = 45 | 46 | 47 | ext_version_1 48 | 1.0.1 49 | = 50 | 51 | 52 | ext_version_2 53 | 1.0.6 54 | = 55 | 56 | 57 | test_version 58 | 0 59 | = 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | ${host} 69 | ${port} 70 | ${scheme} 71 | 72 | 73 | 6 74 | Java 75 | 20000 76 | 30000 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | stoptest 85 | 86 | false 87 | 1 88 | 89 | 1 90 | 1 91 | 1370726934000 92 | 1370726934000 93 | false 94 | 95 | 96 | false 97 | 98 | 99 | 100 | 101 | false 102 | false 103 | 104 | 105 | 106 | false 107 | true 108 | 109 | 110 | 111 | fragments/init-server.jmx 112 | 113 | 114 | 115 | 116 | false 117 | true 118 | 119 | 120 | 121 | fragments/init-account.jmx 122 | 123 | 124 | 125 | 126 | true 127 | 128 | 129 | 130 | false 131 | {"username":"admin","password":"secret1234"} 132 | = 133 | 134 | 135 | 136 | ${host} 137 | ${port} 138 | ${scheme} 139 | UTF-8 140 | /api/v1/auth 141 | POST 142 | true 143 | false 144 | true 145 | false 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | Referer 155 | ${scheme}://${host}:${port}/ 156 | 157 | 158 | Accept-Language 159 | en-US,en;q=0.5 160 | 161 | 162 | Origin 163 | ${scheme}://${host}:${port} 164 | 165 | 166 | Content-Type 167 | application/json 168 | 169 | 170 | Accept-Encoding 171 | gzip, deflate 172 | 173 | 174 | User-Agent 175 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0 176 | 177 | 178 | Accept 179 | application/json, text/plain, */* 180 | 181 | 182 | 183 | 184 | 185 | 186 | 200 187 | 188 | 189 | Assertion.response_code 190 | false 191 | 8 192 | 193 | 194 | 195 | 196 | false 197 | true 198 | 199 | 200 | 201 | true 202 | 203 | 204 | vars.put("test_version", "1") 205 | vars.put("ext_version", vars.get("ext_version_1")); 206 | 207 | javascript 208 | 209 | 210 | 211 | 212 | Test Plan 213 | Test Plan 214 | Reusable Fragments 215 | [${extension}] Install and Check version ${test_version} 216 | 217 | 218 | 219 | 220 | 221 | false 222 | true 223 | 224 | 225 | 226 | true 227 | 228 | 229 | vars.put("test_version", "2") 230 | vars.put("ext_version", vars.get("ext_version_2")); 231 | 232 | javascript 233 | 234 | 235 | 236 | 237 | Test Plan 238 | Test Plan 239 | Reusable Fragments 240 | [${extension}] Install and Check version ${test_version} 241 | 242 | 243 | 244 | 245 | 246 | false 247 | true 248 | 249 | 250 | 251 | true 252 | 253 | 254 | vars.put("test_version", "1") 255 | vars.put("ext_version", vars.get("ext_version_1")); 256 | 257 | javascript 258 | 259 | 260 | 261 | 262 | Test Plan 263 | Test Plan 264 | Reusable Fragments 265 | [${extension}] Install and Check version ${test_version} 266 | 267 | 268 | 269 | 270 | 271 | false 272 | true 273 | 274 | 275 | 276 | true 277 | 278 | 279 | vars.put("test_version", "2") 280 | vars.put("ext_version", vars.get("ext_version_2")); 281 | 282 | javascript 283 | 284 | 285 | 286 | 287 | Test Plan 288 | Test Plan 289 | Reusable Fragments 290 | [${extension}] Install and Check version ${test_version} 291 | 292 | 293 | 294 | 295 | 296 | false 297 | true 298 | 299 | 300 | 301 | true 302 | 303 | 304 | vars.put("test_version", "1") 305 | vars.put("ext_version", vars.get("ext_version_1")); 306 | 307 | javascript 308 | 309 | 310 | 311 | 312 | Test Plan 313 | Test Plan 314 | Reusable Fragments 315 | [${extension}] Install and Check version ${test_version} 316 | 317 | 318 | 319 | 320 | 321 | false 322 | true 323 | false 324 | 325 | 326 | 327 | false 328 | 329 | saveConfig 330 | 331 | 332 | true 333 | true 334 | true 335 | 336 | true 337 | true 338 | true 339 | true 340 | false 341 | true 342 | true 343 | false 344 | false 345 | false 346 | true 347 | false 348 | false 349 | false 350 | true 351 | 0 352 | true 353 | true 354 | true 355 | true 356 | true 357 | true 358 | 359 | 360 | 361 | 362 | 363 | 364 | false 365 | 366 | saveConfig 367 | 368 | 369 | true 370 | true 371 | true 372 | 373 | true 374 | true 375 | true 376 | true 377 | false 378 | true 379 | true 380 | false 381 | false 382 | false 383 | true 384 | false 385 | false 386 | false 387 | true 388 | 0 389 | true 390 | true 391 | true 392 | true 393 | true 394 | true 395 | 396 | 397 | 398 | 399 | 400 | 401 | false 402 | 403 | saveConfig 404 | 405 | 406 | true 407 | true 408 | true 409 | 410 | true 411 | true 412 | true 413 | true 414 | false 415 | true 416 | true 417 | false 418 | false 419 | false 420 | true 421 | false 422 | false 423 | false 424 | true 425 | 0 426 | true 427 | true 428 | true 429 | true 430 | true 431 | true 432 | 433 | 434 | 435 | 436 | 437 | 438 | false 439 | 440 | saveConfig 441 | 442 | 443 | true 444 | true 445 | true 446 | 447 | true 448 | true 449 | true 450 | true 451 | false 452 | true 453 | true 454 | false 455 | false 456 | false 457 | true 458 | false 459 | false 460 | false 461 | true 462 | 0 463 | true 464 | true 465 | true 466 | true 467 | true 468 | true 469 | 470 | 471 | 472 | 100 473 | true 474 | (wallet){1} 475 | 476 | 477 | 478 | false 479 | 480 | saveConfig 481 | 482 | 483 | true 484 | true 485 | true 486 | 487 | true 488 | true 489 | true 490 | true 491 | false 492 | true 493 | true 494 | false 495 | false 496 | false 497 | true 498 | false 499 | false 500 | false 501 | true 502 | 0 503 | true 504 | true 505 | true 506 | true 507 | true 508 | true 509 | 510 | 511 | 512 | 513 | 514 | 515 | false 516 | 517 | saveConfig 518 | 519 | 520 | true 521 | true 522 | true 523 | 524 | true 525 | true 526 | true 527 | true 528 | false 529 | true 530 | true 531 | false 532 | false 533 | false 534 | true 535 | false 536 | false 537 | false 538 | true 539 | 0 540 | true 541 | true 542 | true 543 | true 544 | true 545 | true 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 8888 554 | 555 | windowsupdate\.microsoft\.com.* 556 | (?i).*\.(bmp|css|js|gif|ico|jpe?g|png|swf|eot|otf|ttf|mp4|woff|woff2) 557 | .*msg\.yahoo\.com.* 558 | www\.download\.windowsupdate\.com.* 559 | toolbarqueries\.google\..* 560 | http?://self-repair\.mozilla\.org.* 561 | tiles.*\.mozilla\.com.* 562 | .*detectportal\.firefox\.com.* 563 | us\.update\.toolbar\.yahoo\.com.* 564 | .*\.google\.com.*/safebrowsing/.* 565 | api\.bing\.com.* 566 | toolbar\.google\.com.* 567 | .*yimg\.com.* 568 | toolbar\.msn\.com.* 569 | (?i).*\.(bmp|css|js|gif|ico|jpe?g|png|swf|eot|otf|ttf|mp4|woff|woff2)[\?;].* 570 | toolbar\.avg\.com/.* 571 | www\.google-analytics\.com.* 572 | pgq\.yahoo\.com.* 573 | safebrowsing.*\.google\.com.* 574 | sqm\.microsoft\.com.* 575 | g\.msn.* 576 | clients.*\.google.* 577 | .*toolbar\.yahoo\.com.* 578 | geo\.yahoo\.com.* 579 | 580 | 581 | true 582 | 4 583 | false 584 | 585 | false 586 | true 587 | true 588 | false 589 | true 590 | 591 | 592 | false 593 | 594 | 0 595 | 596 | true 597 | UTF-8 598 | 599 | 600 | 601 | 8081 602 | 0 603 | 25 604 | 605 | 606 | 607 | continue 608 | 609 | false 610 | 1 611 | 612 | 1 613 | 1 614 | false 615 | 616 | 617 | true 618 | 619 | 620 | 621 | 622 | 623 | false 624 | true 625 | 626 | 627 | 628 | false 629 | true 630 | by admin 631 | 632 | 633 | 634 | 635 | 636 | 637 | ${host} 638 | ${port} 639 | ${scheme} 640 | utf-8 641 | /api/v1/extension/${extension}/releases 642 | GET 643 | true 644 | false 645 | true 646 | false 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | Sec-Fetch-Mode 656 | same-origin 657 | 658 | 659 | Sec-Fetch-Site 660 | same-origin 661 | 662 | 663 | Accept-Language 664 | en-US,en;q=0. 665 | 666 | 667 | Upgrade-Insecure-Requests 668 | 1 669 | 670 | 671 | Accept-Encoding 672 | gzip, deflate, br 673 | 674 | 675 | User-Agent 676 | Mozilla.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 677 | 678 | 679 | Accept 680 | text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 681 | 682 | 683 | Sec-Fetch-Dest 684 | empty 685 | 686 | 687 | X-Api-Key 688 | ${adminWalletKey} 689 | 690 | 691 | 692 | 693 | 694 | 695 | 200 696 | 697 | 698 | Assertion.response_code 699 | false 700 | 8 701 | 702 | 703 | 704 | javascript 705 | 706 | ${config_path}/scripts/extract_extension_release_by_version.js 707 | true 708 | 709 | 710 | 711 | 712 | 713 | true 714 | 715 | 716 | 717 | false 718 | { 719 | "ext_id": "${ext_id}", 720 | "archive": "${archive}", 721 | "source_repo": "${source_repo}", 722 | "version": "${version}" 723 | } 724 | = 725 | 726 | 727 | 728 | ${host} 729 | ${port} 730 | ${scheme} 731 | UTF-8 732 | /api/v1/extension 733 | POST 734 | true 735 | false 736 | true 737 | false 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | Sec-Fetch-Mode 747 | cors 748 | 749 | 750 | Sec-Fetch-Site 751 | same-origin 752 | 753 | 754 | Accept-Language 755 | en-US,en;q=0.${lnurlpCount} 756 | 757 | 758 | Origin 759 | ${scheme}://${host}:${port} 760 | 761 | 762 | Accept 763 | application/json, text/plain, */* 764 | 765 | 766 | X-Api-Key 767 | ${adminkey} 768 | 769 | 770 | Content-Type 771 | application/json 772 | 773 | 774 | Accept-Encoding 775 | gzip, deflate, br 776 | 777 | 778 | User-Agent 779 | Mozilla/${lnurlpCount}.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0 780 | 781 | 782 | Sec-Fetch-Dest 783 | empty 784 | 785 | 786 | 787 | 788 | 789 | 790 | 200 791 | 792 | 793 | Assertion.response_code 794 | false 795 | 8 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | false 805 | enable 806 | ${extension} 807 | = 808 | true 809 | 810 | 811 | 812 | ${host} 813 | ${port} 814 | ${scheme} 815 | utf-8 816 | /api/v1/extension/${extension}/enable 817 | PUT 818 | true 819 | false 820 | true 821 | false 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | Sec-Fetch-Mode 831 | same-origin 832 | 833 | 834 | Sec-Fetch-Site 835 | same-origin 836 | 837 | 838 | Accept-Language 839 | en-US,en;q=0.${paidChargeCount} 840 | 841 | 842 | Upgrade-Insecure-Requests 843 | 1 844 | 845 | 846 | Accept-Encoding 847 | gzip, deflate, br 848 | 849 | 850 | User-Agent 851 | Mozilla/${paidChargeCount}.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 852 | 853 | 854 | Accept 855 | text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 856 | 857 | 858 | Sec-Fetch-Dest 859 | empty 860 | 861 | 862 | 863 | 864 | 865 | 866 | 200 867 | 868 | 869 | Assertion.response_code 870 | false 871 | 40 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | ${host} 880 | ${port} 881 | ${scheme} 882 | UTF-8 883 | /${extension} 884 | GET 885 | true 886 | false 887 | true 888 | false 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | Sec-Fetch-Mode 898 | same-origin 899 | 900 | 901 | Referer 902 | ${scheme}://${host}:${port}/tpos/ 903 | 904 | 905 | Sec-Fetch-Site 906 | same-origin 907 | 908 | 909 | Accept-Language 910 | en-US,en;q=0.${lnurlpCount} 911 | 912 | 913 | Upgrade-Insecure-Requests 914 | 1 915 | 916 | 917 | Accept-Encoding 918 | gzip, deflate, br 919 | 920 | 921 | User-Agent 922 | Mozilla/${lnurlpCount}.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0 923 | 924 | 925 | Accept 926 | text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 927 | 928 | 929 | Sec-Fetch-Dest 930 | empty 931 | 932 | 933 | 934 | 935 | 936 | 937 | 200 938 | 939 | 940 | Assertion.response_code 941 | false 942 | 40 943 | 944 | 945 | 946 | 947 | Do not remove. Test install extension version: ${test_version} 948 | 949 | 950 | Assertion.response_data 951 | false 952 | 2 953 | 954 | 955 | 956 | 957 | 958 | 959 | 960 | ${host} 961 | ${port} 962 | ${scheme} 963 | UTF-8 964 | /${extension}/api/v1/test/00000000 965 | GET 966 | true 967 | false 968 | true 969 | false 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | Sec-Fetch-Mode 979 | same-origin 980 | 981 | 982 | Referer 983 | ${scheme}://${host}:${port}/tpos/ 984 | 985 | 986 | Sec-Fetch-Site 987 | same-origin 988 | 989 | 990 | Accept-Language 991 | en-US,en;q=0.${lnurlpCount} 992 | 993 | 994 | Upgrade-Insecure-Requests 995 | 1 996 | 997 | 998 | Accept-Encoding 999 | gzip, deflate, br 1000 | 1001 | 1002 | User-Agent 1003 | Mozilla/${lnurlpCount}.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0 1004 | 1005 | 1006 | Accept 1007 | text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 1008 | 1009 | 1010 | Sec-Fetch-Dest 1011 | empty 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 200 1019 | 1020 | 1021 | Assertion.response_code 1022 | false 1023 | 40 1024 | 1025 | 1026 | 1027 | id 1028 | "${test_version}" 1029 | true 1030 | false 1031 | false 1032 | false 1033 | 1034 | 1035 | 1036 | wallet 1037 | 00000000 1038 | true 1039 | false 1040 | false 1041 | true 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 1050 | -------------------------------------------------------------------------------- /integration/fragments/init-account.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ${host} 13 | ${port} 14 | ${scheme} 15 | UTF-8 16 | / 17 | GET 18 | true 19 | false 20 | true 21 | false 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Accept-Language 31 | en-US,en;q=0.5 32 | 33 | 34 | Upgrade-Insecure-Requests 35 | 1 36 | 37 | 38 | Accept-Encoding 39 | gzip, deflate 40 | 41 | 42 | User-Agent 43 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:105.0) Gecko/20100101 Firefox/105.0 44 | 45 | 46 | Accept 47 | text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 48 | 49 | 50 | 51 | 52 | 53 | 54 | 200 55 | 56 | 57 | Assertion.response_code 58 | false 59 | 8 60 | 61 | 62 | 63 | 64 | true 65 | 66 | 67 | 68 | false 69 | {"name":"a1"} 70 | = 71 | 72 | 73 | 74 | ${host} 75 | ${port} 76 | ${scheme} 77 | UTF-8 78 | /api/v1/account 79 | POST 80 | true 81 | false 82 | true 83 | false 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | Referer 93 | ${scheme}://${host}:${port}/ 94 | 95 | 96 | Accept-Language 97 | en-US,en;q=0.${paidChargeCount} 98 | 99 | 100 | Origin 101 | ${scheme}://${host}:${port} 102 | 103 | 104 | Content-Type 105 | application/json 106 | 107 | 108 | Accept-Encoding 109 | gzip, deflate 110 | 111 | 112 | User-Agent 113 | Mozilla/${paidChargeCount}.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0 114 | 115 | 116 | Accept 117 | application/json, text/plain, */* 118 | 119 | 120 | 121 | 122 | 123 | 124 | 200 125 | 126 | 127 | Assertion.response_code 128 | false 129 | 8 130 | 131 | 132 | 133 | userId 134 | user 135 | 136 | 137 | 138 | 139 | walletId 140 | id 141 | 142 | 143 | 144 | 145 | inkey 146 | inkey 147 | 148 | no-inkey 149 | 150 | 151 | 152 | adminkey 153 | adminkey 154 | 155 | no-adminkey 156 | 157 | 158 | 159 | 160 | true 161 | 162 | 163 | 164 | false 165 | {"usr":"${userId}"} 166 | = 167 | 168 | 169 | 170 | ${host} 171 | ${port} 172 | ${scheme} 173 | UTF-8 174 | /api/v1/auth/usr 175 | POST 176 | true 177 | false 178 | true 179 | false 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | Referer 189 | ${scheme}://${host}:${port}/ 190 | 191 | 192 | Accept-Language 193 | en-US,en;q=0.${paidChargeCount} 194 | 195 | 196 | Origin 197 | ${scheme}://${host}:${port} 198 | 199 | 200 | Content-Type 201 | application/json 202 | 203 | 204 | Accept-Encoding 205 | gzip, deflate 206 | 207 | 208 | User-Agent 209 | Mozilla/${paidChargeCount}.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0 210 | 211 | 212 | Accept 213 | application/json, text/plain, */* 214 | 215 | 216 | 217 | 218 | 219 | 220 | 200 221 | 222 | 223 | Assertion.response_code 224 | false 225 | 8 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | false 234 | wal 235 | ${walletId} 236 | = 237 | true 238 | 239 | 240 | 241 | ${host} 242 | ${port} 243 | ${scheme} 244 | UTF-8 245 | /wallet 246 | GET 247 | true 248 | false 249 | true 250 | false 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | Referer 260 | ${scheme}://${host}:${port}/ 261 | 262 | 263 | Accept-Language 264 | en-US,en;q=0.5 265 | 266 | 267 | Upgrade-Insecure-Requests 268 | 1 269 | 270 | 271 | Accept-Encoding 272 | gzip, deflate 273 | 274 | 275 | User-Agent 276 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:105.0) Gecko/20100101 Firefox/105.0 277 | 278 | 279 | Accept 280 | text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 281 | 282 | 283 | 284 | 285 | 286 | 287 | 200 288 | 289 | 290 | Assertion.response_code 291 | false 292 | 8 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | ${host} 301 | ${port} 302 | ${scheme} 303 | UTF-8 304 | /api/v1/currencies 305 | GET 306 | true 307 | false 308 | true 309 | false 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | Referer 319 | ${scheme}://${host}:${port}/wallet?wal=${walletId} 320 | 321 | 322 | Accept-Language 323 | en-US,en;q=0.5 324 | 325 | 326 | X-Api-Key 327 | undefined 328 | 329 | 330 | Accept-Encoding 331 | gzip, deflate 332 | 333 | 334 | User-Agent 335 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:105.0) Gecko/20100101 Firefox/105.0 336 | 337 | 338 | Accept 339 | application/json, text/plain, */* 340 | 341 | 342 | 343 | 344 | 345 | 346 | 200 347 | 348 | 349 | Assertion.response_code 350 | false 351 | 8 352 | 353 | 354 | 355 | 356 | ["AED","AFN","ALL","AMD","ANG","AOA","ARS","AUD","AWG","AZN","BAM","BBD","BDT","BGN","BHD","BIF","BMD","BND","BOB","BRL","BSD","BTN","BWP","BYN","BYR","BZD","CAD","CDF","CHF","CLF","CLP","CNH","CNY","COP","CRC","CUC","CVE","CZK","DJF","DKK","DOP","DZD","EGP","ERN","ETB","EUR","FJD","FKP","GBP","GEL","GGP","GHS","GIP","GMD","GNF","GTQ","GYD","HKD","HNL","HRK","HTG","HUF","IDR","ILS","IMP","INR","IQD","IRT","ISK","JEP","JMD","JOD","JPY","KES","KGS","KHR","KMF","KRW","KWD","KYD","KZT","LAK","LBP","LKR","LRD","LSL","LYD","MAD","MDL","MGA","MKD","MMK","MNT","MOP","MRO","MUR","MVR","MWK","MXN","MYR","MZN","NAD","NGN","NIO","NOK","NPR","NZD","OMR","PAB","PEN","PGK","PHP","PKR","PLN","PYG","QAR","RON","RSD","RUB","RWF","SAR","SBD","SCR","SEK","SGD","SHP","SLL","SOS","SRD","SSP","STD","SVC","SZL","THB","TJS","TMT","TND","TOP","TRY","TTD","TWD","TZS","UAH","UGX","USD","UYU","UZS","VEF","VES","VND","VUV","WST","XAF","XAG","XAU","XCD","XDR","XOF","XPD","XPF","XPT","YER","ZAR","ZMW","ZWL"] 357 | 358 | 359 | Assertion.response_data 360 | false 361 | 8 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | ${host} 370 | ${port} 371 | ${scheme} 372 | UTF-8 373 | /api/v1/wallet 374 | GET 375 | true 376 | false 377 | true 378 | false 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | Referer 388 | ${scheme}://${host}:${port}/wallet?wal=${walletId} 389 | 390 | 391 | Accept-Language 392 | en-US,en;q=0.5 393 | 394 | 395 | X-Api-Key 396 | ${inkey} 397 | 398 | 399 | Accept-Encoding 400 | gzip, deflate 401 | 402 | 403 | User-Agent 404 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:105.0) Gecko/20100101 Firefox/105.0 405 | 406 | 407 | Accept 408 | application/json, text/plain, */* 409 | 410 | 411 | 412 | 413 | 414 | 415 | 200 416 | 417 | 418 | Assertion.response_code 419 | false 420 | 8 421 | 422 | 423 | 424 | balance 425 | 0 426 | true 427 | false 428 | false 429 | false 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | ${host} 438 | ${port} 439 | ${scheme} 440 | UTF-8 441 | /api/v1/payments 442 | GET 443 | true 444 | false 445 | true 446 | false 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | Referer 456 | ${scheme}://${host}:${port}/wallet?wal=${walletId} 457 | 458 | 459 | Accept-Language 460 | en-US,en;q=0.5 461 | 462 | 463 | X-Api-Key 464 | ${inkey} 465 | 466 | 467 | Accept-Encoding 468 | gzip, deflate 469 | 470 | 471 | User-Agent 472 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:105.0) Gecko/20100101 Firefox/105.0 473 | 474 | 475 | Accept 476 | application/json, text/plain, */* 477 | 478 | 479 | 480 | 481 | 482 | 483 | [] 484 | 485 | 486 | Assertion.response_data 487 | false 488 | 8 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | -------------------------------------------------------------------------------- /integration/fragments/init-server.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | true 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ${host} 24 | ${port} 25 | ${scheme} 26 | UTF-8 27 | / 28 | GET 29 | false 30 | false 31 | true 32 | false 33 | 34 | 35 | 36 | Detected the start of a redirect chain 37 | 38 | 39 | 40 | 41 | 42 | Accept-Language 43 | en-US,en;q=0.5 44 | 45 | 46 | Upgrade-Insecure-Requests 47 | 1 48 | 49 | 50 | Accept-Encoding 51 | gzip, deflate 52 | 53 | 54 | User-Agent 55 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0 56 | 57 | 58 | Accept 59 | text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 60 | 61 | 62 | 63 | 64 | 65 | 66 | 200 67 | 307 68 | 69 | 70 | Assertion.response_code 71 | false 72 | 40 73 | 74 | 75 | 76 | true 77 | is_first_install 78 | location: (.*) 79 | $1$ 80 | not-first-install 81 | 0 82 | all 83 | 84 | 85 | 86 | 87 | ${__groovy(vars.get('is_first_install').contains("first_install"),)} 88 | false 89 | true 90 | 91 | 92 | 93 | 94 | 95 | 96 | ${host} 97 | ${port} 98 | ${scheme} 99 | UTF-8 100 | /first_install 101 | GET 102 | true 103 | false 104 | true 105 | false 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | Accept-Language 115 | en-US,en;q=0.5 116 | 117 | 118 | Upgrade-Insecure-Requests 119 | 1 120 | 121 | 122 | Accept-Encoding 123 | gzip, deflate 124 | 125 | 126 | User-Agent 127 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0 128 | 129 | 130 | Accept 131 | text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 132 | 133 | 134 | 135 | 136 | 137 | 138 | 200 139 | 140 | 141 | Assertion.response_code 142 | false 143 | 8 144 | 145 | 146 | 147 | 148 | true 149 | 150 | 151 | 152 | false 153 | {"username":"admin","password":"secret1234","password_repeat":"secret1234"} 154 | = 155 | 156 | 157 | 158 | ${host} 159 | ${port} 160 | ${scheme} 161 | UTF-8 162 | /api/v1/auth/first_install 163 | PUT 164 | true 165 | false 166 | true 167 | false 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | Referer 177 | ${scheme}://${host}:${port}/first_install 178 | 179 | 180 | Accept-Language 181 | en-US,en;q=0.5 182 | 183 | 184 | Origin 185 | ${scheme}://${host}:${port} 186 | 187 | 188 | Content-Type 189 | application/json 190 | 191 | 192 | Accept-Encoding 193 | gzip, deflate 194 | 195 | 196 | User-Agent 197 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0 198 | 199 | 200 | Accept 201 | application/json, text/plain, */* 202 | 203 | 204 | 205 | 206 | 207 | 208 | 200 209 | 210 | 211 | Assertion.response_code 212 | false 213 | 8 214 | 215 | 216 | 217 | 218 | 219 | ${__groovy(!vars.get('is_first_install').contains("first_install"),)} 220 | false 221 | true 222 | 223 | 224 | 225 | true 226 | 227 | 228 | 229 | false 230 | {"username":"admin","password":"secret1234"} 231 | = 232 | 233 | 234 | 235 | ${host} 236 | ${port} 237 | ${scheme} 238 | UTF-8 239 | /api/v1/auth 240 | POST 241 | true 242 | false 243 | true 244 | false 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | Referer 254 | ${scheme}://${host}:${port}/ 255 | 256 | 257 | Accept-Language 258 | en-US,en;q=0.5 259 | 260 | 261 | Origin 262 | ${scheme}://${host}:${port} 263 | 264 | 265 | Content-Type 266 | application/json 267 | 268 | 269 | Accept-Encoding 270 | gzip, deflate 271 | 272 | 273 | User-Agent 274 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0 275 | 276 | 277 | Accept 278 | application/json, text/plain, */* 279 | 280 | 281 | 282 | 283 | 284 | 285 | 200 286 | 287 | 288 | Assertion.response_code 289 | false 290 | 8 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | ${host} 300 | ${port} 301 | ${scheme} 302 | UTF-8 303 | /api/v1/wallets 304 | GET 305 | true 306 | false 307 | true 308 | false 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | Accept-Language 318 | en-US,en;q=0.5 319 | 320 | 321 | Upgrade-Insecure-Requests 322 | 1 323 | 324 | 325 | Accept-Encoding 326 | gzip, deflate 327 | 328 | 329 | User-Agent 330 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0 331 | 332 | 333 | Accept 334 | text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 335 | 336 | 337 | 338 | 339 | 340 | 341 | 200 342 | 343 | 344 | Assertion.response_code 345 | false 346 | 8 347 | 348 | 349 | 350 | adminWalletId 351 | $[0].id 352 | 353 | no-admin-wallet-id 354 | 355 | 356 | 357 | adminWalletKey 358 | $[0].adminkey 359 | 360 | no-admin-wallet-key 361 | 362 | 363 | 364 | 365 | true 366 | 367 | 368 | 369 | false 370 | { 371 | "lnbits_callback_url_rules": [] 372 | } 373 | = 374 | 375 | 376 | 377 | ${host} 378 | ${port} 379 | ${scheme} 380 | UTF-8 381 | /admin/api/v1/settings 382 | PUT 383 | true 384 | false 385 | true 386 | false 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | Referer 396 | ${scheme}://${host}:${port}/wallet?&wal=c1daa33fc4014ef69cd1505f14f322c2 397 | 398 | 399 | Accept-Language 400 | en-US,en;q=0.5 401 | 402 | 403 | Origin 404 | ${scheme}://${host}:${port} 405 | 406 | 407 | Content-Type 408 | application/json 409 | 410 | 411 | Accept-Encoding 412 | gzip, deflate 413 | 414 | 415 | User-Agent 416 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0 417 | 418 | 419 | Accept 420 | application/json, text/plain, */* 421 | 422 | 423 | 424 | 425 | 426 | 427 | 200 428 | 429 | 430 | Assertion.response_code 431 | false 432 | 8 433 | 434 | 435 | 436 | 437 | true 438 | 439 | 440 | 441 | false 442 | {"amount":"1000000","id":"${adminWalletId}"} 443 | = 444 | 445 | 446 | 447 | ${host} 448 | ${port} 449 | ${scheme} 450 | UTF-8 451 | /users/api/v1/balance 452 | PUT 453 | true 454 | false 455 | true 456 | false 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | Referer 466 | ${scheme}://${host}:${port}/wallet?&wal=c1daa33fc4014ef69cd1505f14f322c2 467 | 468 | 469 | Accept-Language 470 | en-US,en;q=0.5 471 | 472 | 473 | Origin 474 | ${scheme}://${host}:${port} 475 | 476 | 477 | Content-Type 478 | application/json 479 | 480 | 481 | Accept-Encoding 482 | gzip, deflate 483 | 484 | 485 | User-Agent 486 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0 487 | 488 | 489 | Accept 490 | application/json, text/plain, */* 491 | 492 | 493 | 494 | 495 | 496 | 497 | 200 498 | 499 | 500 | Assertion.response_code 501 | false 502 | 8 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | ${host} 511 | ${port} 512 | ${scheme} 513 | UTF-8 514 | /api/v1/auth/logout 515 | POST 516 | true 517 | false 518 | true 519 | false 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | Referer 529 | ${scheme}://${host}:${port}/ 530 | 531 | 532 | Accept-Language 533 | en-US,en;q=0.5 534 | 535 | 536 | Origin 537 | ${scheme}://${host}:${port} 538 | 539 | 540 | Content-Type 541 | application/json 542 | 543 | 544 | Accept-Encoding 545 | gzip, deflate 546 | 547 | 548 | User-Agent 549 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0 550 | 551 | 552 | Accept 553 | application/json, text/plain, */* 554 | 555 | 556 | 557 | 558 | 559 | 560 | 200 561 | 562 | 563 | Assertion.response_code 564 | false 565 | 8 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | -------------------------------------------------------------------------------- /integration/scripts/check_admin_extension_status_code.js: -------------------------------------------------------------------------------- 1 | var responseCode = +prev.getResponseCode() 2 | var isAdminExtension = vars.get("isAdminExtension") 3 | 4 | 5 | if (isAdminExtension === 'true') { 6 | if (responseCode !== 200 && responseCode !== 403) { 7 | AssertionResult.setFailureMessage( 8 | "Expected response code 200 for 401 for admin extension but got: " + 9 | responseCode 10 | ); 11 | AssertionResult.setFailure(true); 12 | } 13 | } else if (responseCode !== 200) { 14 | AssertionResult.setFailureMessage( 15 | "Expected response code 200 but got: " + responseCode 16 | ); 17 | AssertionResult.setFailure(true); 18 | } 19 | -------------------------------------------------------------------------------- /integration/scripts/check_price_in_sats_vs_millisats.js: -------------------------------------------------------------------------------- 1 | var priceInSats = +vars.get("priceInSats"); 2 | var priceInMillisats = +vars.get("priceInMillisats"); 3 | 4 | if (priceInSats * 1000 !== priceInMillisats) { 5 | var message = 6 | "Expected price: " + priceInSats + ", but got: " + priceInMillisats; 7 | AssertionResult.setFailureMessage(message); 8 | AssertionResult.setFailure(true); 9 | } 10 | -------------------------------------------------------------------------------- /integration/scripts/extract_extension_data.js: -------------------------------------------------------------------------------- 1 | var extensions = vars.get("extensions") 2 | var adminExtensions = vars.get("adminExtensions") 3 | var extensionIndex = vars.get("extensionIndex") 4 | var extesion = extensions.split(",")[extensionIndex - 1] 5 | var isAdminExtension = adminExtensions.split(",").indexOf(extesion) !== -1 6 | 7 | vars.put("extension", extesion) 8 | vars.put("isAdminExtension", isAdminExtension) 9 | 10 | var responseCode = prev.getResponseCode() 11 | vars.put("responseCode", responseCode) -------------------------------------------------------------------------------- /integration/scripts/extract_extension_release_by_version.js: -------------------------------------------------------------------------------- 1 | var releases = JSON.parse(prev.getResponseDataAsString()); 2 | 3 | var ext_id = vars.get("extension"); 4 | var ext_version = vars.get("ext_version"); 5 | 6 | vars.put("ext_id", ext_id); 7 | 8 | for (var i = 0; i < releases.length; i++) { 9 | var release = releases[i]; 10 | if (release.version === ext_version) { 11 | vars.put("archive", release.archive); 12 | vars.put("source_repo", release.source_repo); 13 | vars.put("version", release.version); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /integration/scripts/extract_extensions_ids.js: -------------------------------------------------------------------------------- 1 | var resp = JSON.parse(prev.getResponseDataAsString()); 2 | var excludedExtensions = (vars.get("excludedExtensions") || "").split(","); 3 | 4 | var extensions = {}; 5 | 6 | for (var i = 0; i < resp.extensions.length; i++) { 7 | var ext = resp.extensions[i]; 8 | if (excludedExtensions.indexOf(ext.id) === -1) { 9 | extensions[ext.id] = true; 10 | } 11 | } 12 | 13 | 14 | 15 | extensionList = Object.keys(extensions); 16 | 17 | vars.put("extensions", extensionList); 18 | vars.put("extensionsLength", extensionList.length); 19 | -------------------------------------------------------------------------------- /integration/scripts/extract_latest_extension_release.js: -------------------------------------------------------------------------------- 1 | var resp = JSON.parse(prev.getResponseDataAsString()) 2 | var latestRelease = resp[resp.length - 1] 3 | 4 | var ext_id = vars.get("extension") 5 | 6 | vars.put("ext_id", ext_id) 7 | vars.put("archive", latestRelease.archive) 8 | vars.put("source_repo", latestRelease.source_repo) 9 | vars.put("version", latestRelease.version) 10 | -------------------------------------------------------------------------------- /integration/scripts/lndhub_check_balance.js: -------------------------------------------------------------------------------- 1 | var resp = JSON.parse(prev.getResponseDataAsString()) 2 | 3 | 4 | var count = +vars.get("defaultPaymentCount") 5 | var paymentDelta = 20 6 | 7 | if (resp.BTC.AvailableBalance !== paymentDelta * count) { 8 | AssertionResult.setFailureMessage("Expected balance "+paymentDelta * count+" but got: " + resp.BTC.AvailableBalance); 9 | AssertionResult.setFailure(true) 10 | } -------------------------------------------------------------------------------- /integration/scripts/lndhub_check_balance_after_payments.js: -------------------------------------------------------------------------------- 1 | var resp = JSON.parse(prev.getResponseDataAsString()) 2 | 3 | 4 | var count = +vars.get("defaultPaymentCount") 5 | var paymentDelta = 20 6 | var mobilePaymentDelta = 20 7 | 8 | if (resp.BTC.AvailableBalance !== paymentDelta * count + mobilePaymentDelta) { 9 | AssertionResult.setFailureMessage("Expected balance "+paymentDelta * count+" but got: " + resp.BTC.AvailableBalance); 10 | AssertionResult.setFailure(true) 11 | } -------------------------------------------------------------------------------- /integration/scripts/lndhub_check_payments.js: -------------------------------------------------------------------------------- 1 | var resp = JSON.parse(prev.getResponseDataAsString()) 2 | 3 | 4 | var count = +vars.get("defaultPaymentCount") 5 | 6 | if (resp.length !== count) { 7 | AssertionResult.setFailureMessage("Expected "+ count+" transactions but got: " + resp.length); 8 | AssertionResult.setFailure(true) 9 | } else { 10 | for (var i=0;i 100) { 7 | var message = 8 | "Referer balance and referer bonus exceeds tolerated coversion error."; 9 | message += " Balance: " + balanceAlan + " bonus: " + refererBonus; 10 | AssertionResult.setFailureMessage(message); 11 | 12 | AssertionResult.setFailure(true); 13 | } 14 | -------------------------------------------------------------------------------- /integration/scripts/satspay_check_charge_details.js: -------------------------------------------------------------------------------- 1 | var resp = JSON.parse(prev.getResponseDataAsString()) 2 | 3 | var tipCounter = +vars.get("tipCounter") 4 | var name = "Hal" 5 | var description = "Let's go ..." + tipCounter + "!" 6 | 7 | if (resp.description !== description) { 8 | AssertionResult.setFailureMessage("Expected name to be 'Hal', but was: " + resp.name); 9 | AssertionResult.setFailure(true) 10 | } 11 | 12 | if (resp.description !== description) { 13 | AssertionResult.setFailureMessage("Expected description to be 'Let's go!', but was: " + resp.description); 14 | AssertionResult.setFailure(true) 15 | } 16 | -------------------------------------------------------------------------------- /integration/scripts/satspay_check_charge_webhook_call.js: -------------------------------------------------------------------------------- 1 | var payment = JSON.parse(prev.getResponseDataAsString()) 2 | 3 | if (!payment.paid || !payment.lnbitswallet || !payment.webhook){ 4 | AssertionResult.setFailureMessage("Charge not correctly updated after paiment: "+ payment.id); 5 | AssertionResult.setFailure(true) 6 | } 7 | 8 | var extra = JSON.parse(payment.extra) 9 | if (!extra.webhook_response) { 10 | AssertionResult.setFailureMessage("Webhook response missing for payment: "+ payment.id); 11 | AssertionResult.setFailure(true) 12 | } 13 | 14 | var separatorIndex = extra.webhook_response.indexOf("\r\n\r\n") 15 | if (separatorIndex == -1) { 16 | AssertionResult.setFailureMessage("Webhook response has wrong format"+ extra.webhook_response); 17 | AssertionResult.setFailure(true) 18 | } 19 | var headers = extra.webhook_response.substring(0, separatorIndex) 20 | var bodyStr = extra.webhook_response.substring(separatorIndex) 21 | 22 | var body = JSON.parse(bodyStr) 23 | 24 | 25 | if (vars.get("lightningChargeId") !== body.id) { 26 | AssertionResult.setFailureMessage("Wrong webhook charge id. Expected: "+ vars.get("lightningChargeId") + ", but got "+ body.id); 27 | AssertionResult.setFailure(true) 28 | } 29 | 30 | if (vars.get("paymentRequest") !== body.payment_request) { 31 | AssertionResult.setFailureMessage("Wrong webhook charge payment request. Expected: "+ vars.get("paymentRequest") + ", but got "+ body.payment_request); 32 | AssertionResult.setFailure(true) 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /integration/scripts/satspay_check_charges_count.js: -------------------------------------------------------------------------------- 1 | var resp = JSON.parse(prev.getResponseDataAsString()) 2 | 3 | var paidChargeCount = +vars.get('paidChargeCount') 4 | 5 | var totalChargeCount = paidChargeCount + 2; // one onchain and one to expire 6 | if (resp.length !== totalChargeCount) { 7 | AssertionResult.setFailureMessage("Expected charges count to be "+totalChargeCount+", got " + resp.length); 8 | AssertionResult.setFailure(true) 9 | } 10 | -------------------------------------------------------------------------------- /integration/scripts/satspay_check_payments_status.js: -------------------------------------------------------------------------------- 1 | var resp = JSON.parse(prev.getResponseDataAsString()) 2 | 3 | var paidChargeCount = +vars.get('paidChargeCount') 4 | // one payment is the expired one 5 | var totalPaymentsCount = paidChargeCount + 1 6 | 7 | if (resp.length != totalPaymentsCount) { 8 | AssertionResult.setFailureMessage("Expected total "+totalPaymentsCount+" paymet, but got: "+ resp.length); 9 | AssertionResult.setFailure(true) 10 | } 11 | 12 | var pendingCount = 0 13 | 14 | for (var i=0; i", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/lnbits/lnbits-extensions/issues" 31 | }, 32 | "homepage": "https://extensions.lnbits.com/", 33 | "devDependencies": { 34 | "prettier": "^3.3.2", 35 | "sass": "^1.77.6", 36 | "vite": "^5.3.1", 37 | "vite-plugin-solid": "^2.10.2" 38 | }, 39 | "dependencies": { 40 | "@solidjs/router": "^0.13.6", 41 | "showdown": "^2.1.0", 42 | "solid-icons": "^1.1.0", 43 | "solid-js": "^1.8.17" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/lnbits-extensions/d788af945bc03655304d3cc2994e735b41ef55e4/public/favicon.ico -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | Group 6 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "lnbits-extensions" 3 | version = "1.0.0" 4 | description = "" 5 | authors = ["Alan Bits "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.9" 10 | 11 | 12 | [build-system] 13 | requires = ["poetry-core"] 14 | build-backend = "poetry.core.masonry.api" 15 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import { createSignal, For, onMount } from "solid-js"; 2 | import { render } from "solid-js/web"; 3 | import "./index.scss"; 4 | import data from "../extensions.json"; 5 | import metadata from "../metadata.json"; 6 | import { Router, Route, useParams, A, useNavigate } from "@solidjs/router"; 7 | import { FaSolidUsers, FaBrandsGithub, FaSolidDownload } from "solid-icons/fa"; 8 | import { BiSolidBolt } from "solid-icons/bi"; 9 | import { Converter } from "showdown"; 10 | import { 11 | RiArrowsArrowLeftSLine, 12 | RiArrowsArrowRightSLine, 13 | } from "solid-icons/ri"; 14 | 15 | const extensions = [ 16 | ...new Map(data.extensions.map((ext) => [ext.id, ext])).values(), 17 | ]; 18 | extensions.forEach((ext) => { 19 | const meta = metadata.find((m) => m.id === ext.id); 20 | if (meta) { 21 | Object.assign(ext, meta); 22 | } 23 | if (!ext.contributors) ext.contributors = []; 24 | }); 25 | 26 | const Gallery = ({ images }) => { 27 | const [activeIndex, setActiveIndex] = createSignal(0); 28 | const [fullScreen, setFullScreen] = createSignal(false); 29 | const next = () => setActiveIndex((i) => (i + 1) % images.length); 30 | const prev = () => 31 | setActiveIndex((i) => (i - 1 + images.length) % images.length); 32 | 33 | let touchstartX, touchendX; 34 | const touchStart = (e) => (touchstartX = e.changedTouches[0].screenX); 35 | const touchEnd = (e) => { 36 | touchendX = e.changedTouches[0].screenX; 37 | if (touchendX < touchstartX) prev(); 38 | if (touchendX > touchstartX) next(); 39 | // if (touchendX === touchstartX) clickAction(); 40 | }; 41 | let mouseStartX, mouseEndX; 42 | const mouseStart = (e) => (mouseStartX = e.screenX); 43 | const mouseEnd = (e) => { 44 | mouseEndX = e.screenX; 45 | if (mouseEndX < mouseStartX) prev(); 46 | if (mouseEndX > mouseStartX) next(); 47 | // if (mouseEndX === mouseStartX) clickAction(); 48 | }; 49 | 50 | return ( 51 |