Merge pull request #1505 from lnbits/removesatsdiceagain

Remove satsdice...again
This commit is contained in:
Arc 2023-02-15 10:59:00 +00:00 committed by GitHub
commit ef3beaaccf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 0 additions and 1892 deletions

View file

@ -1,5 +0,0 @@
# satsdice
## Create staic LNURL powered satsdices
Gambling is dangerous, flip responsibly

View file

@ -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

View file

@ -1,6 +0,0 @@
{
"name": "Sats Dice",
"short_description": "LNURL Satoshi dice",
"tile": "/satsdice/static/image/satsdice.png",
"contributors": ["arcbtc"]
}

View file

@ -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}

View file

@ -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"}

View file

@ -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
);
"""
)

View file

@ -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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View file

@ -1,198 +0,0 @@
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/satsdice"></q-btn>
<q-expansion-item group="api" dense expand-separator label="List satsdices">
<q-card>
<q-card-section>
<code><span class="text-blue">GET</span> /satsdice/api/v1/links</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code>[&lt;satsdice_link_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.base_url }}satsdice/api/v1/links -H
"X-Api-Key: {{ user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Get a satsdice link"
>
<q-card>
<q-card-section>
<code
><span class="text-blue">GET</span>
/satsdice/api/v1/links/&lt;satsdice_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code>{"lnurl": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.base_url
}}satsdice/api/v1/links/&lt;satsdice_id&gt; -H "X-Api-Key: {{
user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Create a satsdice link"
>
<q-card>
<q-card-section>
<code><span class="text-green">POST</span> /satsdice/api/v1/links</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code
>{"title": &lt;string&gt;, "min_satsdiceable": &lt;integer&gt;,
"max_satsdiceable": &lt;integer&gt;, "uses": &lt;integer&gt;,
"wait_time": &lt;integer&gt;, "is_unique": &lt;boolean&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code>{"lnurl": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X POST {{ request.base_url }}satsdice/api/v1/links -d
'{"title": &lt;string&gt;, "min_satsdiceable": &lt;integer&gt;,
"max_satsdiceable": &lt;integer&gt;, "uses": &lt;integer&gt;,
"wait_time": &lt;integer&gt;, "is_unique": &lt;boolean&gt;}' -H
"Content-type: application/json" -H "X-Api-Key: {{
user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Update a satsdice link"
>
<q-card>
<q-card-section>
<code
><span class="text-green">PUT</span>
/satsdice/api/v1/links/&lt;satsdice_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code
>{"title": &lt;string&gt;, "min_satsdiceable": &lt;integer&gt;,
"max_satsdiceable": &lt;integer&gt;, "uses": &lt;integer&gt;,
"wait_time": &lt;integer&gt;, "is_unique": &lt;boolean&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code>{"lnurl": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X PUT {{ request.base_url
}}satsdice/api/v1/links/&lt;satsdice_id&gt; -d '{"title":
&lt;string&gt;, "min_satsdiceable": &lt;integer&gt;,
"max_satsdiceable": &lt;integer&gt;, "uses": &lt;integer&gt;,
"wait_time": &lt;integer&gt;, "is_unique": &lt;boolean&gt;}' -H
"Content-type: application/json" -H "X-Api-Key: {{
user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Delete a satsdice link"
>
<q-card>
<q-card-section>
<code
><span class="text-pink">DELETE</span>
/satsdice/api/v1/links/&lt;satsdice_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Returns 204 NO CONTENT</h5>
<code></code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X DELETE {{ request.base_url
}}satsdice/api/v1/links/&lt;satsdice_id&gt; -H "X-Api-Key: {{
user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Get hash check (for captchas to prevent milking)"
>
<q-card>
<q-card-section>
<code
><span class="text-blue">GET</span>
/satsdice/api/v1/links/&lt;the_hash&gt;/&lt;lnurl_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code>{"status": &lt;bool&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.base_url
}}satsdice/api/v1/links/&lt;the_hash&gt;/&lt;lnurl_id&gt; -H
"X-Api-Key: {{ user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Get image to embed"
class="q-pb-md"
>
<q-card>
<q-card-section>
<code
><span class="text-blue">GET</span>
/satsdice/img/&lt;lnurl_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.base_url }}satsdice/img/&lt;lnurl_id&gt;"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
</q-expansion-item>

View file

@ -1,32 +0,0 @@
<q-expansion-item group="extras" icon="info" label="Powered by LNURL">
<q-card>
<q-card-section>
<p>
<b>WARNING: LNURL must be used over https or TOR</b><br />
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.
</p>
<p>
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.
</p>
<small
>Check
<a
class="text-secondary"
href="https://github.com/fiatjaf/awesome-lnurl"
target="_blank"
>Awesome LNURL</a
>
for further information.</small
>
</q-card-section>
</q-card>
</q-expansion-item>

View file

@ -1,63 +0,0 @@
{% extends "public.html" %} {% block page %}
<div class="row q-col-gutter-md justify-center">
<div class="col-12 col-sm-6 col-md-5 col-lg-4">
<q-card class="q-pa-lg">
<q-card-section class="q-pa-none">
<div class="text-center">
<a class="text-secondary" href="lightning:{{ lnurl }}">
<q-responsive :ratio="1" class="q-mx-md">
<qrcode
:value="'lightning:{{ lnurl }}'"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive>
</a>
</div>
<div class="row q-mt-lg">
<q-btn outline color="grey" @click="copyText('{{ lnurl }}')"
>Copy Satsdice LNURL</q-btn
>
</div>
</q-card-section>
</q-card>
</div>
<div class="col-12 col-sm-6 col-md-5 col-lg-4 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-mb-sm q-mt-none">
Chance of winning: {% raw %}{{ chance }}{% endraw %}, Amount
multiplier: {{ multiplier }}
</h6>
<p class="q-my-none">
Use a LNURL compatible bitcoin wallet to play the satsdice.
</p>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list> {% include "satsdice/_lnurl.html" %} </q-list>
</q-card-section>
</q-card>
</div>
</div>
{% endblock %} {% block scripts %}
<script>
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
mixins: [windowMixin],
data: function () {
return {
here: location.protocol + '//' + location.host,
chance: parseFloat('{{chance}}') + '%'
}
},
filters: {
percent(val) {
return (chance / 100) * 100 + '%'
}
}
})
</script>
{% endblock %}

View file

@ -1,56 +0,0 @@
{% extends "public.html" %} {% block page %}
<div class="row q-col-gutter-md justify-center">
<div class="col-12 col-sm-6 col-md-5 col-lg-4">
<q-card class="q-pa-lg">
<q-card-section class="q-pa-none">
<div class="text-center">
<a class="text-secondary" href="lightning:{{ lnurl }}">
<q-responsive :ratio="1" class="q-mx-md">
<qrcode
:value="'lightning:{{ lnurl }}'"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive>
</a>
</div>
<div class="row q-mt-lg">
<q-btn outline color="grey" @click="copyText('{{ lnurl }}')"
>Copy winnings LNURL</q-btn
>
</div>
</q-card-section>
</q-card>
</div>
<div class="col-12 col-sm-6 col-md-5 col-lg-4 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-mb-sm q-mt-none">
Congrats! You have won {{ value }}sats (you must claim the sats now)
</h6>
<p class="q-my-none">
Use a LNURL compatible bitcoin wallet to play the satsdice.
</p>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list> {% include "satsdice/_lnurl.html" %} </q-list>
</q-card-section>
</q-card>
</div>
</div>
{% endblock %} {% block scripts %}
<script>
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
mixins: [windowMixin],
data: function () {
return {
here: location.protocol + '//' + location.host
}
}
})
</script>
{% endblock %}

View file

@ -1,54 +0,0 @@
{% extends "public.html" %} {% from "macros.jinja" import window_vars with
context %}{% block page %}
<div class="row q-col-gutter-md justify-center">
<div class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
<q-card class="q-pa-lg">
<q-card-section class="q-pa-none">
<center>
{% if lost %}
<h5 class="q-my-none">
You lost.
<a class="text-secondary" href="/satsdice/{{ link }}"
>Play again?</a
>
</h5>
{% endif %} {% if paid %}
<h5 class="q-my-none">
Winnings spent.
<a class="text-secondary" href="/satsdice/{{ link }}"
>Play again?</a
>
</h5>
{% endif %}
<br />
<q-icon
name="sentiment_dissatisfied"
class="text-grey"
style="font-size: 20rem"
></q-icon>
<br />
</center>
</q-card-section>
</q-card>
</div>
</div>
{% endblock %} {% block scripts %}{{ window_vars(user) }}
<script>
new Vue({
el: '#vue',
mixins: [windowMixin],
data: function () {
return {
satsdice_lost: '{{lost}}',
satsdice_paid: '{{paid}}'
}
},
methods: {
getUrl() {}
},
created() {
console.log('play_location')
}
})
</script>
{% endblock %}

View file

@ -1,534 +0,0 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
<div class="row q-col-gutter-md">
<div class="col-12 col-md-7 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn unelevated color="primary" @click="formDialog.show = true"
>New satsdice</q-btn
>
</q-card-section>
</q-card>
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">satsdices</h5>
</div>
</div>
<q-table
dense
flat
:data="payLinks"
row-key="id"
:pagination.sync="payLinksTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th style="width: 10%"></q-th>
<q-th auto-width style="text-align: left">Title</q-th>
<q-th auto-width style="text-align: left">Min bet</q-th>
<q-th auto-width style="text-align: left">Max bet</q-th>
<q-th auto-width style="text-align: left">Multiplier</q-th>
<q-th auto-width style="text-align: left">Haircut</q-th>
<q-th auto-width style="text-align: left">Chance</q-th>
<q-th auto-width style="text-align: left"></q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td style="width: 10%">
<q-btn
unelevated
dense
size="xs"
icon="visibility"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openQrCodeDialog(props.row.id)"
></q-btn>
</q-td>
<q-td auto-width>{{ props.row.title }}</q-td>
<q-td auto-width>{{ props.row.min_bet }}</q-td>
<q-td auto-width>{{ props.row.max_bet }}</q-td>
<q-td auto-width>*{{ props.row.multiplier }}</q-td>
<q-td auto-width>{{ props.row.haircut }}</q-td>
<q-td auto-width>{{ props.row.chance }}%</q-td>
<q-td auto-width>
<q-btn
flat
dense
size="xs"
@click="openUpdateDialog(props.row.id)"
icon="edit"
color="light-blue"
></q-btn>
<q-btn
flat
dense
size="xs"
@click="deletePayLink(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
</q-card-section>
</q-card>
</div>
<div class="col-12 col-md-5 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">
{{SITE_TITLE}} Sats Dice extension
</h6>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list>
{% include "satsdice/_api_docs.html" %}
<q-separator></q-separator>
{% include "satsdice/_lnurl.html" %}
</q-list>
</q-card-section>
</q-card>
</div>
<q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendFormData" class="q-gutter-md">
<q-select
filled
dense
emit-value
v-model="formDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
>
</q-select>
{% raw %}
<q-input
filled
dense
v-model.trim="formDialog.data.title"
type="text"
label="Title *"
></q-input>
<div class="row">
<div class="col">
<q-input
class="q-pr-xs"
filled
dense
v-model.trim="formDialog.data.min_bet"
type="number"
label="Min bet size (sats)"
></q-input>
</div>
<div class="col">
<q-input
class="q-pl-xs"
filled
dense
v-model.trim="formDialog.data.max_bet"
type="number"
label="Max bet size (sats)"
></q-input>
</div>
</div>
<q-input
filled
dense
v-model.trim="formDialog.data.haircut"
type="number"
label="Haircut (chance of winning % to remove)"
></q-input>
<center>
<q-badge color="secondary" class="q-mb-lg">
Multipler: x{{ multiValue }}, Chance of winning: {{ chanceValueCalc
| percent }}
</q-badge>
<q-slider
style="width: 95%"
class="q-pt-lg"
v-model="multiValue"
:min="1.5"
:max="20"
:step="2"
label
label-always
color="primary"
markers
snap
></q-slider>
</center>
<div class="row q-mt-lg">
<q-btn
v-if="formDialog.data.id"
unelevated
color="primary"
type="submit"
>Update flip link</q-btn
>
<q-btn
v-else
unelevated
color="primary"
:disable="
formDialog.data.wallet == null ||
formDialog.data.title == null || formDialog.data.min < 10 || formDialog.data.max < formDialog.data.min"
type="submit"
>Create satsdice</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
<q-dialog v-model="qrCodeDialog.show" position="top">
<q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card">
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode
:value="'lightning:' + qrCodeDialog.data.lnurl"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive>
<p style="word-break: break-all">
<strong>ID:</strong> {{ qrCodeDialog.data.id }}<br />
<strong>Amount:</strong> {{ qrCodeDialog.data.amount }}<br />
<span v-if="qrCodeDialog.data.currency"
><strong>{{ qrCodeDialog.data.currency }} price:</strong> {{
fiatRates[qrCodeDialog.data.currency] ?
fiatRates[qrCodeDialog.data.currency] + ' sat' : 'Loading...' }}<br
/></span>
<strong>Accepts comments:</strong> {{ qrCodeDialog.data.comments }}<br />
<strong>Dispatches webhook to:</strong> {{ qrCodeDialog.data.webhook
}}<br />
<strong>On success:</strong> {{ qrCodeDialog.data.success }}<br />
</p>
{% endraw %}
<div class="row q-mt-lg q-gutter-sm">
<q-btn
outline
color="grey"
@click="copyText(qrCodeDialog.data.lnurl, 'Satsdice copied to clipboard!')"
class="q-ml-sm"
>Copy Satsdice LNURL</q-btn
>
<q-btn
outline
color="grey"
icon="link"
@click="copyText(qrCodeDialog.data.pay_url, 'Link copied to clipboard!')"
><q-tooltip>Copy shareable link</q-tooltip></q-btn
>
<q-btn
outline
color="grey"
icon="launch"
type="a"
:href="qrCodeDialog.data.pay_url"
target="_blank"
><q-tooltip>Launch shareable link</q-tooltip></q-btn
>
<q-btn
outline
color="grey"
icon="print"
type="a"
:href="qrCodeDialog.data.print_url"
target="_blank"
><q-tooltip>Print Satsdice</q-tooltip></q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
</div>
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script>
/* globals Quasar, Vue, _, VueQrcode, windowMixin, LNbits, LOCALE */
Vue.component(VueQrcode.name, VueQrcode)
var locationPath = [
window.location.protocol,
'//',
window.location.host,
window.location.pathname
].join('')
var mapPayLink = obj => {
obj._data = _.clone(obj)
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
obj.amount = new Intl.NumberFormat(LOCALE).format(obj.amount)
obj.print_url = [locationPath, 'print/', obj.id].join('')
obj.pay_url = [locationPath, obj.id].join('')
console.log(obj)
return obj
}
new Vue({
el: '#vue',
mixins: [windowMixin],
data() {
return {
chanceValue: 0,
multiValue: 1.5,
currencies: [],
fiatRates: {},
checker: null,
payLinks: [],
payLinksTable: {
pagination: {
rowsPerPage: 10
}
},
formDialog: {
show: false,
fixedAmount: true,
data: {
haircut: 0,
min_bet: 10,
max_bet: 1000,
currency: 'satoshis',
comment_chars: 0
}
},
qrCodeDialog: {
show: false,
data: null
}
}
},
filters: {
percent(val) {
return val + '%'
}
},
computed: {
chanceValueCalc() {
this.chanceValue = (
(1 / this.multiValue) * 100 -
this.formDialog.data.haircut -
(1 / this.multiValue) * 10
).toFixed(2)
return this.chanceValue
}
},
methods: {
chanceValueTableCalc(multiplier, haircut) {
return ((1 / multiplier) * 100 - haircut).toFixed(2)
},
getPayLinks() {
LNbits.api
.request(
'GET',
'/satsdice/api/v1/links?all_wallets=true',
this.g.user.wallets[0].inkey
)
.then(response => {
console.log(response.data)
this.payLinks = response.data.map(mapPayLink)
})
.catch(err => {
clearInterval(this.checker)
LNbits.utils.notifyApiError(err)
})
},
closeFormDialog() {
this.resetFormData()
},
openQrCodeDialog(linkId) {
var link = _.findWhere(this.payLinks, {id: linkId})
console.log(link)
if (link.currency) this.updateFiatRate(link.currency)
this.qrCodeDialog.data = {
id: link.id,
amount:
(link.min === link.max ? link.min : `${link.min} - ${link.max}`) +
' ' +
(link.currency || 'sat'),
currency: link.currency,
comments: link.comment_chars
? `${link.comment_chars} characters`
: 'no',
webhook: link.webhook_url || 'nowhere',
success:
link.success_text || link.success_url
? 'Display message "' +
link.success_text +
'"' +
(link.success_url ? ' and URL "' + link.success_url + '"' : '')
: 'do nothing',
lnurl: link.lnurl,
pay_url: link.pay_url,
print_url: link.print_url
}
this.qrCodeDialog.show = true
},
openUpdateDialog(linkId) {
const link = _.findWhere(this.payLinks, {id: linkId})
if (link.currency) this.updateFiatRate(link.currency)
this.formDialog.data = _.clone(link._data)
this.formDialog.show = true
this.formDialog.fixedAmount =
this.formDialog.data.min === this.formDialog.data.max
},
sendFormData() {
const wallet = _.findWhere(this.g.user.wallets, {
id: this.formDialog.data.wallet
})
var data = _.omit(this.formDialog.data, 'wallet')
data.min_bet = parseInt(data.min_bet)
data.max_bet = parseInt(data.max_bet)
data.multiplier = parseFloat(this.multiValue)
data.haircut = parseFloat(data.haircut)
data.chance = parseFloat(this.chanceValue)
data.base_url = window.location.origin
if (data.currency === 'satoshis') data.currency = null
if (isNaN(parseInt(data.comment_chars))) data.comment_chars = 0
if (data.id) {
this.updatePayLink(wallet, data)
} else {
this.createPayLink(wallet, data)
}
},
resetFormData() {
this.formDialog = {
show: false,
fixedAmount: true,
data: {
haircut: 0,
min_bet: 10,
max_bet: 1000,
currency: 'satoshis',
comment_chars: 0
}
}
},
updatePayLink(wallet, data) {
let values = _.omit(
_.pick(
data,
'chance',
'base_url',
'multiplier',
'haircut',
'title',
'min_bet',
'max_bet',
'webhook_url',
'success_text',
'success_url',
'comment_chars',
'currency'
),
(value, key) =>
(key === 'webhook_url' ||
key === 'success_text' ||
key === 'success_url') &&
(value === null || value === '')
)
LNbits.api
.request(
'PUT',
'/satsdice/api/v1/links/' + data.id,
wallet.adminkey,
values
)
.then(response => {
this.payLinks = _.reject(this.payLinks, obj => obj.id === data.id)
this.payLinks.push(mapPayLink(response.data))
this.formDialog.show = false
this.resetFormData()
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
},
createPayLink(wallet, data) {
LNbits.api
.request('POST', '/satsdice/api/v1/links', wallet.adminkey, data)
.then(response => {
this.payLinks.push(mapPayLink(response.data))
this.formDialog.show = false
this.resetFormData()
this.getPayLinks()
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
},
deletePayLink(linkId) {
var link = _.findWhere(this.payLinks, {id: linkId})
LNbits.utils
.confirmDialog('Are you sure you want to delete this pay link?')
.onOk(() => {
LNbits.api
.request(
'DELETE',
'/satsdice/api/v1/links/' + linkId,
_.findWhere(this.g.user.wallets, {id: link.wallet}).adminkey
)
.then(response => {
this.payLinks = _.reject(
this.payLinks,
obj => obj.id === linkId
)
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
})
},
updateFiatRate(currency) {
LNbits.api
.request('GET', '/satsdice/api/v1/rate/' + currency, null)
.then(response => {
let rates = _.clone(this.fiatRates)
rates[currency] = response.data.rate
this.fiatRates = rates
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
}
},
created() {
if (this.g.user.wallets.length) {
var getPayLinks = this.getPayLinks
getPayLinks()
// this.checker = setInterval(() => {
// getPayLinks()
// }, 20000)
}
}
})
</script>
{% endblock %}

View file

@ -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",
},
)

View file

@ -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