From 0e1dbfcd2609b7b52f122600b9df62d581c96000 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 15 Feb 2023 10:44:13 +0000 Subject: [PATCH] again --- lnbits/extensions/satsdice/README.md | 5 - lnbits/extensions/satsdice/__init__.py | 26 - lnbits/extensions/satsdice/config.json | 6 - lnbits/extensions/satsdice/crud.py | 279 --------- lnbits/extensions/satsdice/lnurl.py | 156 ----- lnbits/extensions/satsdice/migrations.py | 73 --- lnbits/extensions/satsdice/models.py | 134 ----- .../satsdice/static/image/satsdice.png | Bin 22157 -> 0 bytes .../templates/satsdice/_api_docs.html | 198 ------- .../satsdice/templates/satsdice/_lnurl.html | 32 -- .../satsdice/templates/satsdice/display.html | 63 --- .../templates/satsdice/displaywin.html | 56 -- .../satsdice/templates/satsdice/error.html | 54 -- .../satsdice/templates/satsdice/index.html | 534 ------------------ lnbits/extensions/satsdice/views.py | 147 ----- lnbits/extensions/satsdice/views_api.py | 129 ----- 16 files changed, 1892 deletions(-) delete mode 100644 lnbits/extensions/satsdice/README.md delete mode 100644 lnbits/extensions/satsdice/__init__.py delete mode 100644 lnbits/extensions/satsdice/config.json delete mode 100644 lnbits/extensions/satsdice/crud.py delete mode 100644 lnbits/extensions/satsdice/lnurl.py delete mode 100644 lnbits/extensions/satsdice/migrations.py delete mode 100644 lnbits/extensions/satsdice/models.py delete mode 100644 lnbits/extensions/satsdice/static/image/satsdice.png delete mode 100644 lnbits/extensions/satsdice/templates/satsdice/_api_docs.html delete mode 100644 lnbits/extensions/satsdice/templates/satsdice/_lnurl.html delete mode 100644 lnbits/extensions/satsdice/templates/satsdice/display.html delete mode 100644 lnbits/extensions/satsdice/templates/satsdice/displaywin.html delete mode 100644 lnbits/extensions/satsdice/templates/satsdice/error.html delete mode 100644 lnbits/extensions/satsdice/templates/satsdice/index.html delete mode 100644 lnbits/extensions/satsdice/views.py delete mode 100644 lnbits/extensions/satsdice/views_api.py diff --git a/lnbits/extensions/satsdice/README.md b/lnbits/extensions/satsdice/README.md deleted file mode 100644 index c24199300..000000000 --- a/lnbits/extensions/satsdice/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# satsdice - -## Create staic LNURL powered satsdices - -Gambling is dangerous, flip responsibly diff --git a/lnbits/extensions/satsdice/__init__.py b/lnbits/extensions/satsdice/__init__.py deleted file mode 100644 index a13653bf3..000000000 --- a/lnbits/extensions/satsdice/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -from fastapi import APIRouter -from starlette.staticfiles import StaticFiles - -from lnbits.db import Database -from lnbits.helpers import template_renderer - -db = Database("ext_satsdice") - -satsdice_ext: APIRouter = APIRouter(prefix="/satsdice", tags=["satsdice"]) - -satsdice_static_files = [ - { - "path": "/satsdice/static", - "app": StaticFiles(directory="lnbits/extensions/satsdice/static"), - "name": "satsdice_static", - } -] - - -def satsdice_renderer(): - return template_renderer(["lnbits/extensions/satsdice/templates"]) - - -from .lnurl import * # noqa: F401,F403 -from .views import * # noqa: F401,F403 -from .views_api import * # noqa: F401,F403 diff --git a/lnbits/extensions/satsdice/config.json b/lnbits/extensions/satsdice/config.json deleted file mode 100644 index 3f4355fea..000000000 --- a/lnbits/extensions/satsdice/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Sats Dice", - "short_description": "LNURL Satoshi dice", - "tile": "/satsdice/static/image/satsdice.png", - "contributors": ["arcbtc"] -} diff --git a/lnbits/extensions/satsdice/crud.py b/lnbits/extensions/satsdice/crud.py deleted file mode 100644 index 6aeaf31ff..000000000 --- a/lnbits/extensions/satsdice/crud.py +++ /dev/null @@ -1,279 +0,0 @@ -from datetime import datetime -from typing import List, Optional, Union - -from lnbits.helpers import urlsafe_short_hash - -from . import db -from .models import ( - CreateSatsDiceLink, - CreateSatsDicePayment, - CreateSatsDiceWithdraw, - satsdiceLink, - satsdicePayment, - satsdiceWithdraw, -) - - -async def create_satsdice_pay(wallet_id: str, data: CreateSatsDiceLink) -> satsdiceLink: - satsdice_id = urlsafe_short_hash() - await db.execute( - """ - INSERT INTO satsdice.satsdice_pay ( - id, - wallet, - title, - base_url, - min_bet, - max_bet, - amount, - served_meta, - served_pr, - multiplier, - chance, - haircut, - open_time - ) - VALUES (?, ?, ?, ?, ?, ?, 0, 0, 0, ?, ?, ?, ?) - """, - ( - satsdice_id, - wallet_id, - data.title, - data.base_url, - data.min_bet, - data.max_bet, - data.multiplier, - data.chance, - data.haircut, - int(datetime.now().timestamp()), - ), - ) - link = await get_satsdice_pay(satsdice_id) - assert link, "Newly created link couldn't be retrieved" - return link - - -async def get_satsdice_pay(link_id: str) -> Optional[satsdiceLink]: - row = await db.fetchone( - "SELECT * FROM satsdice.satsdice_pay WHERE id = ?", (link_id,) - ) - return satsdiceLink(**row) if row else None - - -async def get_satsdice_pays(wallet_ids: Union[str, List[str]]) -> List[satsdiceLink]: - if isinstance(wallet_ids, str): - wallet_ids = [wallet_ids] - - q = ",".join(["?"] * len(wallet_ids)) - rows = await db.fetchall( - f""" - SELECT * FROM satsdice.satsdice_pay WHERE wallet IN ({q}) - ORDER BY id - """, - (*wallet_ids,), - ) - return [satsdiceLink(**row) for row in rows] - - -async def update_satsdice_pay(link_id: str, **kwargs) -> satsdiceLink: - q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - await db.execute( - f"UPDATE satsdice.satsdice_pay SET {q} WHERE id = ?", - (*kwargs.values(), link_id), - ) - row = await db.fetchone( - "SELECT * FROM satsdice.satsdice_pay WHERE id = ?", (link_id,) - ) - return satsdiceLink(**row) - - -async def increment_satsdice_pay(link_id: str, **kwargs) -> Optional[satsdiceLink]: - q = ", ".join([f"{field[0]} = {field[0]} + ?" for field in kwargs.items()]) - await db.execute( - f"UPDATE satsdice.satsdice_pay SET {q} WHERE id = ?", - (*kwargs.values(), link_id), - ) - row = await db.fetchone( - "SELECT * FROM satsdice.satsdice_pay WHERE id = ?", (link_id,) - ) - return satsdiceLink(**row) if row else None - - -async def delete_satsdice_pay(link_id: str) -> None: - await db.execute("DELETE FROM satsdice.satsdice_pay WHERE id = ?", (link_id,)) - - -##################SATSDICE PAYMENT LINKS - - -async def create_satsdice_payment(data: CreateSatsDicePayment) -> satsdicePayment: - await db.execute( - """ - INSERT INTO satsdice.satsdice_payment ( - payment_hash, - satsdice_pay, - value, - paid, - lost - ) - VALUES (?, ?, ?, ?, ?) - """, - ( - data.payment_hash, - data.satsdice_pay, - data.value, - False, - False, - ), - ) - payment = await get_satsdice_payment(data.payment_hash) - assert payment, "Newly created withdraw couldn't be retrieved" - return payment - - -async def get_satsdice_payment(payment_hash: str) -> Optional[satsdicePayment]: - row = await db.fetchone( - "SELECT * FROM satsdice.satsdice_payment WHERE payment_hash = ?", - (payment_hash,), - ) - return satsdicePayment(**row) if row else None - - -async def update_satsdice_payment(payment_hash: str, **kwargs) -> satsdicePayment: - q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - - await db.execute( - f"UPDATE satsdice.satsdice_payment SET {q} WHERE payment_hash = ?", - (bool(*kwargs.values()), payment_hash), - ) - row = await db.fetchone( - "SELECT * FROM satsdice.satsdice_payment WHERE payment_hash = ?", - (payment_hash,), - ) - return satsdicePayment(**row) - - -##################SATSDICE WITHDRAW LINKS - - -async def create_satsdice_withdraw(data: CreateSatsDiceWithdraw) -> satsdiceWithdraw: - await db.execute( - """ - INSERT INTO satsdice.satsdice_withdraw ( - id, - satsdice_pay, - value, - unique_hash, - k1, - open_time, - used - ) - VALUES (?, ?, ?, ?, ?, ?, ?) - """, - ( - data.payment_hash, - data.satsdice_pay, - data.value, - urlsafe_short_hash(), - urlsafe_short_hash(), - int(datetime.now().timestamp()), - data.used, - ), - ) - withdraw = await get_satsdice_withdraw(data.payment_hash, 0) - assert withdraw, "Newly created withdraw couldn't be retrieved" - return withdraw - - -async def get_satsdice_withdraw(withdraw_id: str, num=0) -> Optional[satsdiceWithdraw]: - row = await db.fetchone( - "SELECT * FROM satsdice.satsdice_withdraw WHERE id = ?", (withdraw_id,) - ) - if not row: - return None - - withdraw = [] - for item in row: - withdraw.append(item) - withdraw.append(num) - return satsdiceWithdraw(**row) - - -async def get_satsdice_withdraw_by_hash( - unique_hash: str, num=0 -) -> Optional[satsdiceWithdraw]: - row = await db.fetchone( - "SELECT * FROM satsdice.satsdice_withdraw WHERE unique_hash = ?", (unique_hash,) - ) - if not row: - return None - - withdraw = [] - for item in row: - withdraw.append(item) - withdraw.append(num) - return satsdiceWithdraw(**row) - - -async def get_satsdice_withdraws( - wallet_ids: Union[str, List[str]] -) -> List[satsdiceWithdraw]: - if isinstance(wallet_ids, str): - wallet_ids = [wallet_ids] - - q = ",".join(["?"] * len(wallet_ids)) - rows = await db.fetchall( - f"SELECT * FROM satsdice.satsdice_withdraw WHERE wallet IN ({q})", - (*wallet_ids,), - ) - - return [satsdiceWithdraw(**row) for row in rows] - - -async def update_satsdice_withdraw( - withdraw_id: str, **kwargs -) -> Optional[satsdiceWithdraw]: - q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - await db.execute( - f"UPDATE satsdice.satsdice_withdraw SET {q} WHERE id = ?", - (*kwargs.values(), withdraw_id), - ) - row = await db.fetchone( - "SELECT * FROM satsdice.satsdice_withdraw WHERE id = ?", (withdraw_id,) - ) - return satsdiceWithdraw(**row) if row else None - - -async def delete_satsdice_withdraw(withdraw_id: str) -> None: - await db.execute( - "DELETE FROM satsdice.satsdice_withdraw WHERE id = ?", (withdraw_id,) - ) - - -async def create_withdraw_hash_check(the_hash: str, lnurl_id: str): - await db.execute( - """ - INSERT INTO satsdice.hash_checkw ( - id, - lnurl_id - ) - VALUES (?, ?) - """, - (the_hash, lnurl_id), - ) - hashCheck = await get_withdraw_hash_checkw(the_hash, lnurl_id) - return hashCheck - - -async def get_withdraw_hash_checkw(the_hash: str, lnurl_id: str): - rowid = await db.fetchone( - "SELECT * FROM satsdice.hash_checkw WHERE id = ?", (the_hash,) - ) - rowlnurl = await db.fetchone( - "SELECT * FROM satsdice.hash_checkw WHERE lnurl_id = ?", (lnurl_id,) - ) - if not rowlnurl or not rowid: - await create_withdraw_hash_check(the_hash, lnurl_id) - return {"lnurl": True, "hash": False} - else: - return {"lnurl": True, "hash": True} diff --git a/lnbits/extensions/satsdice/lnurl.py b/lnbits/extensions/satsdice/lnurl.py deleted file mode 100644 index 2bb590162..000000000 --- a/lnbits/extensions/satsdice/lnurl.py +++ /dev/null @@ -1,156 +0,0 @@ -import json -import math -from http import HTTPStatus - -from fastapi import Request -from fastapi.param_functions import Query -from starlette.exceptions import HTTPException -from starlette.responses import HTMLResponse - -from lnbits.core.services import create_invoice, pay_invoice - -from . import satsdice_ext -from .crud import ( - create_satsdice_payment, - get_satsdice_pay, - get_satsdice_withdraw_by_hash, - update_satsdice_withdraw, -) -from .models import CreateSatsDicePayment - - -@satsdice_ext.get( - "/api/v1/lnurlp/{link_id}", - response_class=HTMLResponse, - name="satsdice.lnurlp_response", -) -async def api_lnurlp_response(req: Request, link_id: str = Query(None)): - link = await get_satsdice_pay(link_id) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="LNURL-pay not found." - ) - payResponse = { - "tag": "payRequest", - "callback": req.url_for("satsdice.api_lnurlp_callback", link_id=link.id), - "metadata": link.lnurlpay_metadata, - "minSendable": math.ceil(link.min_bet * 1) * 1000, - "maxSendable": round(link.max_bet * 1) * 1000, - } - return json.dumps(payResponse) - - -@satsdice_ext.get( - "/api/v1/lnurlp/cb/{link_id}", - response_class=HTMLResponse, - name="satsdice.api_lnurlp_callback", -) -async def api_lnurlp_callback( - req: Request, link_id: str = Query(None), amount: str = Query(None) -): - link = await get_satsdice_pay(link_id) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="LNURL-pay not found." - ) - - min, max = link.min_bet, link.max_bet - min = link.min_bet * 1000 - max = link.max_bet * 1000 - - amount_received = int(amount or 0) - if amount_received < min: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, - detail=f"Amount {amount_received} is smaller than minimum {min}.", - ) - elif amount_received > max: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, - detail=f"Amount {amount_received} is greater than maximum {max}.", - ) - - payment_hash, payment_request = await create_invoice( - wallet_id=link.wallet, - amount=int(amount_received / 1000), - memo="Satsdice bet", - unhashed_description=link.lnurlpay_metadata.encode(), - extra={"tag": "satsdice", "link": link.id, "comment": "comment"}, - ) - - success_action = link.success_action(payment_hash=payment_hash, req=req) - - data = CreateSatsDicePayment( - satsdice_pay=link.id, - value=int(amount_received / 1000), - payment_hash=payment_hash, - ) - - await create_satsdice_payment(data) - payResponse: dict = { - "pr": payment_request, - "successAction": success_action, - "routes": [], - } - return json.dumps(payResponse) - - -##############LNURLW STUFF - - -@satsdice_ext.get( - "/api/v1/lnurlw/{unique_hash}", - response_class=HTMLResponse, - name="satsdice.lnurlw_response", -) -async def api_lnurlw_response(req: Request, unique_hash: str = Query(None)): - link = await get_satsdice_withdraw_by_hash(unique_hash) - - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="LNURL-satsdice not found." - ) - if link.used: - raise HTTPException(status_code=HTTPStatus.OK, detail="satsdice is spent.") - url = req.url_for("satsdice.api_lnurlw_callback", unique_hash=link.unique_hash) - withdrawResponse = { - "tag": "withdrawRequest", - "callback": url, - "k1": link.k1, - "minWithdrawable": link.value * 1000, - "maxWithdrawable": link.value * 1000, - "defaultDescription": "Satsdice winnings!", - } - return json.dumps(withdrawResponse) - - -# CALLBACK - - -@satsdice_ext.get( - "/api/v1/lnurlw/cb/{unique_hash}", - status_code=HTTPStatus.OK, - name="satsdice.api_lnurlw_callback", -) -async def api_lnurlw_callback( - unique_hash: str = Query(None), - pr: str = Query(None), -): - - link = await get_satsdice_withdraw_by_hash(unique_hash) - if not link: - return {"status": "ERROR", "reason": "no withdraw"} - if link.used: - return {"status": "ERROR", "reason": "spent"} - paylink = await get_satsdice_pay(link.satsdice_pay) - - if paylink: - await update_satsdice_withdraw(link.id, used=1) - await pay_invoice( - wallet_id=paylink.wallet, - payment_request=pr, - max_sat=link.value, - extra={"tag": "withdraw"}, - ) - - return {"status": "OK"} diff --git a/lnbits/extensions/satsdice/migrations.py b/lnbits/extensions/satsdice/migrations.py deleted file mode 100644 index 82ab35ba7..000000000 --- a/lnbits/extensions/satsdice/migrations.py +++ /dev/null @@ -1,73 +0,0 @@ -async def m001_initial(db): - """ - Creates an improved satsdice table and migrates the existing data. - """ - await db.execute( - f""" - CREATE TABLE satsdice.satsdice_pay ( - id TEXT PRIMARY KEY, - wallet TEXT, - title TEXT, - min_bet INTEGER, - max_bet INTEGER, - amount {db.big_int} DEFAULT 0, - served_meta INTEGER NOT NULL, - served_pr INTEGER NOT NULL, - multiplier FLOAT, - haircut FLOAT, - chance FLOAT, - base_url TEXT, - open_time INTEGER - ); - """ - ) - - -async def m002_initial(db): - """ - Creates an improved satsdice table and migrates the existing data. - """ - await db.execute( - f""" - CREATE TABLE satsdice.satsdice_withdraw ( - id TEXT PRIMARY KEY, - satsdice_pay TEXT, - value {db.big_int} DEFAULT 1, - unique_hash TEXT UNIQUE, - k1 TEXT, - open_time INTEGER, - used INTEGER DEFAULT 0 - ); - """ - ) - - -async def m003_initial(db): - """ - Creates an improved satsdice table and migrates the existing data. - """ - await db.execute( - f""" - CREATE TABLE satsdice.satsdice_payment ( - payment_hash TEXT PRIMARY KEY, - satsdice_pay TEXT, - value {db.big_int}, - paid BOOL DEFAULT FALSE, - lost BOOL DEFAULT FALSE - ); - """ - ) - - -async def m004_make_hash_check(db): - """ - Creates a hash check table. - """ - await db.execute( - """ - CREATE TABLE satsdice.hash_checkw ( - id TEXT PRIMARY KEY, - lnurl_id TEXT - ); - """ - ) diff --git a/lnbits/extensions/satsdice/models.py b/lnbits/extensions/satsdice/models.py deleted file mode 100644 index 510b7bde0..000000000 --- a/lnbits/extensions/satsdice/models.py +++ /dev/null @@ -1,134 +0,0 @@ -import json -from sqlite3 import Row -from typing import Dict, Optional - -from fastapi import Request -from fastapi.param_functions import Query -from lnurl import Lnurl -from lnurl import encode as lnurl_encode -from lnurl.types import LnurlPayMetadata -from pydantic import BaseModel - - -class satsdiceLink(BaseModel): - id: str - wallet: str - title: str - min_bet: int - max_bet: int - amount: int - served_meta: int - served_pr: int - multiplier: float - haircut: float - chance: float - base_url: str - open_time: int - - def lnurl(self, req: Request) -> str: - return lnurl_encode(req.url_for("satsdice.lnurlp_response", link_id=self.id)) - - @classmethod - def from_row(cls, row: Row) -> "satsdiceLink": - data = dict(row) - return cls(**data) - - @property - def lnurlpay_metadata(self) -> LnurlPayMetadata: - return LnurlPayMetadata( - json.dumps( - [ - [ - "text/plain", - f"{self.title} (Chance: {self.chance}%, Multiplier: {self.multiplier})", - ] - ] - ) - ) - - def success_action(self, payment_hash: str, req: Request) -> Optional[Dict]: - url = req.url_for( - "satsdice.displaywin", link_id=self.id, payment_hash=payment_hash - ) - return {"tag": "url", "description": "Check the attached link", "url": url} - - -class satsdicePayment(BaseModel): - payment_hash: str - satsdice_pay: str - value: int - paid: bool - lost: bool - - -class satsdiceWithdraw(BaseModel): - id: str - satsdice_pay: str - value: int - unique_hash: str - k1: str - open_time: int - used: int - - def lnurl(self, req: Request) -> Lnurl: - return lnurl_encode( - req.url_for("satsdice.lnurlw_response", unique_hash=self.unique_hash) - ) - - @property - def is_spent(self) -> bool: - return self.used >= 1 - - def lnurl_response(self, req: Request): - url = req.url_for("satsdice.api_lnurlw_callback", unique_hash=self.unique_hash) - withdrawResponse = { - "tag": "withdrawRequest", - "callback": url, - "k1": self.k1, - "minWithdrawable": self.value * 1000, - "maxWithdrawable": self.value * 1000, - "defaultDescription": "Satsdice winnings!", - } - return withdrawResponse - - -class HashCheck(BaseModel): - id: str - lnurl_id: str - - @classmethod - def from_row(cls, row: Row): - return cls(**dict(row)) - - -class CreateSatsDiceLink(BaseModel): - wallet: str = Query(None) - title: str = Query(None) - base_url: str = Query(None) - min_bet: str = Query(None) - max_bet: str = Query(None) - multiplier: float = Query(0) - chance: float = Query(0) - haircut: int = Query(0) - - -class CreateSatsDicePayment(BaseModel): - satsdice_pay: str = Query(None) - value: int = Query(0) - payment_hash: str = Query(None) - - -class CreateSatsDiceWithdraw(BaseModel): - payment_hash: str = Query(None) - satsdice_pay: str = Query(None) - value: int = Query(0) - used: int = Query(0) - - -class CreateSatsDiceWithdraws(BaseModel): - title: str = Query(None) - min_satsdiceable: int = Query(0) - max_satsdiceable: int = Query(0) - uses: int = Query(0) - wait_time: str = Query(None) - is_unique: bool = Query(False) diff --git a/lnbits/extensions/satsdice/static/image/satsdice.png b/lnbits/extensions/satsdice/static/image/satsdice.png deleted file mode 100644 index 8c7ccaf9622237a318826924d6e790782dfd9cab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22157 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 z - - - - - GET /satsdice/api/v1/links -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- [<satsdice_link_object>, ...] -
Curl example
- curl -X GET {{ request.base_url }}satsdice/api/v1/links -H - "X-Api-Key: {{ user.wallets[0].inkey }}" - -
-
-
- - - - GET - /satsdice/api/v1/links/<satsdice_id> -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 201 CREATED (application/json) -
- {"lnurl": <string>} -
Curl example
- curl -X GET {{ request.base_url - }}satsdice/api/v1/links/<satsdice_id> -H "X-Api-Key: {{ - user.wallets[0].inkey }}" - -
-
-
- - - - POST /satsdice/api/v1/links -
Headers
- {"X-Api-Key": <admin_key>}
-
Body (application/json)
- {"title": <string>, "min_satsdiceable": <integer>, - "max_satsdiceable": <integer>, "uses": <integer>, - "wait_time": <integer>, "is_unique": <boolean>} -
- Returns 201 CREATED (application/json) -
- {"lnurl": <string>} -
Curl example
- curl -X POST {{ request.base_url }}satsdice/api/v1/links -d - '{"title": <string>, "min_satsdiceable": <integer>, - "max_satsdiceable": <integer>, "uses": <integer>, - "wait_time": <integer>, "is_unique": <boolean>}' -H - "Content-type: application/json" -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" - -
-
-
- - - - PUT - /satsdice/api/v1/links/<satsdice_id> -
Headers
- {"X-Api-Key": <admin_key>}
-
Body (application/json)
- {"title": <string>, "min_satsdiceable": <integer>, - "max_satsdiceable": <integer>, "uses": <integer>, - "wait_time": <integer>, "is_unique": <boolean>} -
- Returns 200 OK (application/json) -
- {"lnurl": <string>} -
Curl example
- curl -X PUT {{ request.base_url - }}satsdice/api/v1/links/<satsdice_id> -d '{"title": - <string>, "min_satsdiceable": <integer>, - "max_satsdiceable": <integer>, "uses": <integer>, - "wait_time": <integer>, "is_unique": <boolean>}' -H - "Content-type: application/json" -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" - -
-
-
- - - - DELETE - /satsdice/api/v1/links/<satsdice_id> -
Headers
- {"X-Api-Key": <admin_key>}
-
Returns 204 NO CONTENT
- -
Curl example
- curl -X DELETE {{ request.base_url - }}satsdice/api/v1/links/<satsdice_id> -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" - -
-
-
- - - - GET - /satsdice/api/v1/links/<the_hash>/<lnurl_id> -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 201 CREATED (application/json) -
- {"status": <bool>} -
Curl example
- curl -X GET {{ request.base_url - }}satsdice/api/v1/links/<the_hash>/<lnurl_id> -H - "X-Api-Key: {{ user.wallets[0].inkey }}" - -
-
-
- - - - GET - /satsdice/img/<lnurl_id> -
Curl example
- curl -X GET {{ request.base_url }}satsdice/img/<lnurl_id>" - -
-
-
- diff --git a/lnbits/extensions/satsdice/templates/satsdice/_lnurl.html b/lnbits/extensions/satsdice/templates/satsdice/_lnurl.html deleted file mode 100644 index 750fb5869..000000000 --- a/lnbits/extensions/satsdice/templates/satsdice/_lnurl.html +++ /dev/null @@ -1,32 +0,0 @@ - - - -

- WARNING: LNURL must be used over https or TOR
- LNURL is a range of lightning-network standards that allow us to use - lightning-network differently. An LNURL satsdice is the permission for - someone to pull a certain amount of funds from a lightning wallet. In - this extension time is also added - an amount can be satsdice over a - period of time. A typical use case for an LNURL satsdice is a faucet, - although it is a very powerful technology, with much further reaching - implications. For example, an LNURL satsdice could be minted to pay for - a subscription service. -

-

- Exploring LNURL and finding use cases, is really helping inform - lightning protocol development, rather than the protocol dictating how - lightning-network should be engaged with. -

- Check - Awesome LNURL - for further information. -
-
-
diff --git a/lnbits/extensions/satsdice/templates/satsdice/display.html b/lnbits/extensions/satsdice/templates/satsdice/display.html deleted file mode 100644 index 56e7ebe6e..000000000 --- a/lnbits/extensions/satsdice/templates/satsdice/display.html +++ /dev/null @@ -1,63 +0,0 @@ -{% extends "public.html" %} {% block page %} -
-
- - - -
- Copy Satsdice LNURL -
-
-
-
-
- - -
- Chance of winning: {% raw %}{{ chance }}{% endraw %}, Amount - multiplier: {{ multiplier }} -
-

- Use a LNURL compatible bitcoin wallet to play the satsdice. -

-
- - - {% include "satsdice/_lnurl.html" %} - -
-
-
-{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/lnbits/extensions/satsdice/templates/satsdice/displaywin.html b/lnbits/extensions/satsdice/templates/satsdice/displaywin.html deleted file mode 100644 index f3ce36d56..000000000 --- a/lnbits/extensions/satsdice/templates/satsdice/displaywin.html +++ /dev/null @@ -1,56 +0,0 @@ -{% extends "public.html" %} {% block page %} -
-
- - - -
- Copy winnings LNURL -
-
-
-
-
- - -
- Congrats! You have won {{ value }}sats (you must claim the sats now) -
-

- Use a LNURL compatible bitcoin wallet to play the satsdice. -

-
- - - {% include "satsdice/_lnurl.html" %} - -
-
-
-{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/lnbits/extensions/satsdice/templates/satsdice/error.html b/lnbits/extensions/satsdice/templates/satsdice/error.html deleted file mode 100644 index 63f726d56..000000000 --- a/lnbits/extensions/satsdice/templates/satsdice/error.html +++ /dev/null @@ -1,54 +0,0 @@ -{% extends "public.html" %} {% from "macros.jinja" import window_vars with -context %}{% block page %} -
-
- - -
- {% if lost %} -
- You lost. - Play again? -
- {% endif %} {% if paid %} -
- Winnings spent. - Play again? -
- {% endif %} -
- -
-
-
-
-
-
-{% endblock %} {% block scripts %}{{ window_vars(user) }} - -{% endblock %} diff --git a/lnbits/extensions/satsdice/templates/satsdice/index.html b/lnbits/extensions/satsdice/templates/satsdice/index.html deleted file mode 100644 index 277442734..000000000 --- a/lnbits/extensions/satsdice/templates/satsdice/index.html +++ /dev/null @@ -1,534 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} -
-
- - - New satsdice - - - - - -
-
-
satsdices
-
-
- - {% raw %} - - - {% endraw %} - -
-
-
- -
- - -
- {{SITE_TITLE}} Sats Dice extension -
-
- - - - {% include "satsdice/_api_docs.html" %} - - {% include "satsdice/_lnurl.html" %} - - -
-
- - - - - - - {% raw %} - - -
-
- -
-
- -
-
- - -
- - Multipler: x{{ multiValue }}, Chance of winning: {{ chanceValueCalc - | percent }} - - - -
- -
- Update flip link - Create satsdice - Cancel -
-
-
-
- - - - - - -

- ID: {{ qrCodeDialog.data.id }}
- Amount: {{ qrCodeDialog.data.amount }}
- {{ qrCodeDialog.data.currency }} price: {{ - fiatRates[qrCodeDialog.data.currency] ? - fiatRates[qrCodeDialog.data.currency] + ' sat' : 'Loading...' }}
- Accepts comments: {{ qrCodeDialog.data.comments }}
- Dispatches webhook to: {{ qrCodeDialog.data.webhook - }}
- On success: {{ qrCodeDialog.data.success }}
-

- {% endraw %} -
- Copy Satsdice LNURL - Copy shareable link - - Launch shareable link - Print Satsdice - Close -
-
-
-
- -{% endblock %} {% block scripts %} {{ window_vars(user) }} - - -{% endblock %} diff --git a/lnbits/extensions/satsdice/views.py b/lnbits/extensions/satsdice/views.py deleted file mode 100644 index e51c1e103..000000000 --- a/lnbits/extensions/satsdice/views.py +++ /dev/null @@ -1,147 +0,0 @@ -import random -from http import HTTPStatus -from io import BytesIO - -import pyqrcode -from fastapi import Depends, Query, Request -from fastapi.templating import Jinja2Templates -from starlette.exceptions import HTTPException -from starlette.responses import HTMLResponse - -from lnbits.core.models import User -from lnbits.core.views.api import api_payment -from lnbits.decorators import check_user_exists - -from . import satsdice_ext, satsdice_renderer -from .crud import ( - create_satsdice_withdraw, - get_satsdice_pay, - get_satsdice_payment, - get_satsdice_withdraw, - update_satsdice_payment, -) -from .models import CreateSatsDiceWithdraw - -templates = Jinja2Templates(directory="templates") - - -@satsdice_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): - return satsdice_renderer().TemplateResponse( - "satsdice/index.html", {"request": request, "user": user.dict()} - ) - - -@satsdice_ext.get("/{link_id}", response_class=HTMLResponse) -async def display(request: Request, link_id: str = Query(None)): - link = await get_satsdice_pay(link_id) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="satsdice link does not exist." - ) - - return satsdice_renderer().TemplateResponse( - "satsdice/display.html", - { - "request": request, - "chance": link.chance, - "multiplier": link.multiplier, - "lnurl": link.lnurl(request), - "unique": True, - }, - ) - - -@satsdice_ext.get( - "/win/{link_id}/{payment_hash}", - name="satsdice.displaywin", - response_class=HTMLResponse, -) -async def displaywin( - request: Request, link_id: str = Query(None), payment_hash: str = Query(None) -): - satsdicelink = await get_satsdice_pay(link_id) - if not satsdicelink: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="satsdice link does not exist." - ) - withdrawLink = await get_satsdice_withdraw(payment_hash) - payment = await get_satsdice_payment(payment_hash) - if not payment or payment.lost: - return satsdice_renderer().TemplateResponse( - "satsdice/error.html", - {"request": request, "link": satsdicelink.id, "paid": False, "lost": True}, - ) - if withdrawLink: - return satsdice_renderer().TemplateResponse( - "satsdice/displaywin.html", - { - "request": request, - "value": withdrawLink.value, - "chance": satsdicelink.chance, - "multiplier": satsdicelink.multiplier, - "lnurl": withdrawLink.lnurl(request), - "paid": False, - "lost": False, - }, - ) - rand = random.randint(0, 100) - chance = satsdicelink.chance - status = await api_payment(payment_hash) - if not rand < chance or not status["paid"]: - await update_satsdice_payment(payment_hash, lost=1) - return satsdice_renderer().TemplateResponse( - "satsdice/error.html", - {"request": request, "link": satsdicelink.id, "paid": False, "lost": True}, - ) - await update_satsdice_payment(payment_hash, paid=1) - paylink = await get_satsdice_payment(payment_hash) - if not paylink: - return satsdice_renderer().TemplateResponse( - "satsdice/error.html", - {"request": request, "link": satsdicelink.id, "paid": False, "lost": True}, - ) - - data = CreateSatsDiceWithdraw( - satsdice_pay=satsdicelink.id, - value=int(paylink.value * satsdicelink.multiplier), - payment_hash=payment_hash, - used=0, - ) - - withdrawLink = await create_satsdice_withdraw(data) - return satsdice_renderer().TemplateResponse( - "satsdice/displaywin.html", - { - "request": request, - "value": withdrawLink.value, - "chance": satsdicelink.chance, - "multiplier": satsdicelink.multiplier, - "lnurl": withdrawLink.lnurl(request), - "paid": False, - "lost": False, - }, - ) - - -@satsdice_ext.get("/img/{link_id}", response_class=HTMLResponse) -async def img(link_id): - link = await get_satsdice_pay(link_id) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="satsdice link does not exist." - ) - - qr = pyqrcode.create(link.lnurl) - stream = BytesIO() - qr.svg(stream, scale=3) - return ( - stream.getvalue(), - 200, - { - "Content-Type": "image/svg+xml", - "Cache-Control": "no-cache, no-store, must-revalidate", - "Pragma": "no-cache", - "Expires": "0", - }, - ) diff --git a/lnbits/extensions/satsdice/views_api.py b/lnbits/extensions/satsdice/views_api.py deleted file mode 100644 index 57ab26b84..000000000 --- a/lnbits/extensions/satsdice/views_api.py +++ /dev/null @@ -1,129 +0,0 @@ -from http import HTTPStatus - -from fastapi import Depends, Query, Request -from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl -from starlette.exceptions import HTTPException - -from lnbits.core.crud import get_user -from lnbits.decorators import WalletTypeInfo, get_key_type - -from . import satsdice_ext -from .crud import ( - create_satsdice_pay, - delete_satsdice_pay, - get_satsdice_pay, - get_satsdice_pays, - get_withdraw_hash_checkw, - update_satsdice_pay, -) -from .models import CreateSatsDiceLink - -################LNURL pay - - -@satsdice_ext.get("/api/v1/links") -async def api_links( - request: Request, - wallet: WalletTypeInfo = Depends(get_key_type), - all_wallets: bool = Query(False), -): - wallet_ids = [wallet.wallet.id] - - if all_wallets: - user = await get_user(wallet.wallet.user) - if user: - wallet_ids = user.wallet_ids - - try: - links = await get_satsdice_pays(wallet_ids) - - return [{**link.dict(), **{"lnurl": link.lnurl(request)}} for link in links] - except LnurlInvalidUrl: - raise HTTPException( - status_code=HTTPStatus.UPGRADE_REQUIRED, - detail="LNURLs need to be delivered over a publically accessible `https` domain or Tor.", - ) - - -@satsdice_ext.get("/api/v1/links/{link_id}") -async def api_link_retrieve( - link_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type) -): - link = await get_satsdice_pay(link_id) - - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist." - ) - - if link.wallet != wallet.wallet.id: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not your pay link." - ) - - return {**link.dict(), **{"lnurl": link.lnurl}} - - -@satsdice_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED) -@satsdice_ext.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) -async def api_link_create_or_update( - data: CreateSatsDiceLink, - wallet: WalletTypeInfo = Depends(get_key_type), - link_id: str = Query(None), -): - if data.min_bet > data.max_bet: - raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Bad request") - if link_id: - link = await get_satsdice_pay(link_id) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Satsdice does not exist" - ) - - if link.wallet != wallet.wallet.id: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, - detail="Come on, seriously, this isn't your satsdice!", - ) - - data.wallet = wallet.wallet.id - link = await update_satsdice_pay(link_id, **data.dict()) - else: - link = await create_satsdice_pay(wallet_id=wallet.wallet.id, data=data) - - return {**link.dict(), **{"lnurl": link.lnurl}} - - -@satsdice_ext.delete("/api/v1/links/{link_id}") -async def api_link_delete( - wallet: WalletTypeInfo = Depends(get_key_type), - link_id: str = Query(None), -): - link = await get_satsdice_pay(link_id) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist." - ) - - if link.wallet != wallet.wallet.id: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not your pay link." - ) - - await delete_satsdice_pay(link_id) - - return "", HTTPStatus.NO_CONTENT - - -##########LNURL withdraw - - -@satsdice_ext.get( - "/api/v1/withdraws/{the_hash}/{lnurl_id}", dependencies=[Depends(get_key_type)] -) -async def api_withdraw_hash_retrieve( - lnurl_id: str = Query(None), - the_hash: str = Query(None), -): - hashCheck = await get_withdraw_hash_checkw(the_hash, lnurl_id) - return hashCheck