Browse Source

Updated Withdraw extension to handle unique facets

LNURLwalletsupport
benarc 5 years ago
parent
commit
294beb2fca
  1. 44
      lnbits/extensions/withdraw/crud.py
  2. 75
      lnbits/extensions/withdraw/migrations.py
  3. 11
      lnbits/extensions/withdraw/models.py
  4. 204
      lnbits/extensions/withdraw/static/js/index.js
  5. 11
      lnbits/extensions/withdraw/templates/withdraw/display.html
  6. 1
      lnbits/extensions/withdraw/templates/withdraw/index.html
  7. 101
      lnbits/extensions/withdraw/templates/withdraw/print_qr.html
  8. 14
      lnbits/extensions/withdraw/views.py
  9. 65
      lnbits/extensions/withdraw/views_api.py

44
lnbits/extensions/withdraw/crud.py

@ -1,6 +1,6 @@
from datetime import datetime from datetime import datetime
from typing import List, Optional, Union from typing import List, Optional, Union
import shortuuid # type: ignore
from lnbits.db import open_ext_db from lnbits.db import open_ext_db
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
@ -17,6 +17,7 @@ def create_withdraw_link(
wait_time: int, wait_time: int,
is_unique: bool, is_unique: bool,
) -> WithdrawLink: ) -> WithdrawLink:
with open_ext_db("withdraw") as db: with open_ext_db("withdraw") as db:
link_id = urlsafe_short_hash() link_id = urlsafe_short_hash()
db.execute( db.execute(
@ -32,9 +33,10 @@ def create_withdraw_link(
is_unique, is_unique,
unique_hash, unique_hash,
k1, k1,
open_time open_time,
usescsv
) )
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", """,
( (
link_id, link_id,
@ -48,24 +50,36 @@ def create_withdraw_link(
urlsafe_short_hash(), urlsafe_short_hash(),
urlsafe_short_hash(), urlsafe_short_hash(),
int(datetime.now().timestamp()) + wait_time, int(datetime.now().timestamp()) + wait_time,
usescsv,
), ),
) )
return get_withdraw_link(link_id, uses)
return get_withdraw_link(link_id)
def get_withdraw_link(link_id: str, num=None) -> Optional[WithdrawLink]:
def get_withdraw_link(link_id: str) -> Optional[WithdrawLink]:
with open_ext_db("withdraw") as db: with open_ext_db("withdraw") as db:
row = db.fetchone("SELECT * FROM withdraw_links WHERE id = ?", (link_id,)) row = db.fetchone("SELECT * FROM withdraw_links WHERE id = ?", (link_id,))
link = []
return WithdrawLink.from_row(row) if row else None for item in row:
link.append(item)
tohash = row["id"] + row["unique_hash"] + str(num)
link.append(shortuuid.uuid(name=tohash))
return WithdrawLink._make(link)
def get_withdraw_link_by_hash(unique_hash: str) -> Optional[WithdrawLink]: def get_withdraw_link_by_hash(unique_hash: str, num=None) -> Optional[WithdrawLink]:
with open_ext_db("withdraw") as db: with open_ext_db("withdraw") as db:
row = db.fetchone("SELECT * FROM withdraw_links WHERE unique_hash = ?", (unique_hash,)) row = db.fetchone("SELECT * FROM withdraw_links WHERE unique_hash = ?", (unique_hash,))
link = []
for item in row:
link.append(item)
if not num:
link.append("")
return WithdrawLink._make(link)
tohash = row["id"] + row["unique_hash"] + str(num)
link.append(shortuuid.uuid(name=tohash))
return WithdrawLink._make(link)
return WithdrawLink.from_row(row) if row else None
def get_withdraw_links(wallet_ids: Union[str, List[str]]) -> List[WithdrawLink]: def get_withdraw_links(wallet_ids: Union[str, List[str]]) -> List[WithdrawLink]:
@ -76,12 +90,14 @@ def get_withdraw_links(wallet_ids: Union[str, List[str]]) -> List[WithdrawLink]:
q = ",".join(["?"] * len(wallet_ids)) q = ",".join(["?"] * len(wallet_ids))
rows = db.fetchall(f"SELECT * FROM withdraw_links WHERE wallet IN ({q})", (*wallet_ids,)) rows = db.fetchall(f"SELECT * FROM withdraw_links WHERE wallet IN ({q})", (*wallet_ids,))
return [WithdrawLink.from_row(row) for row in rows] links = []
for x in rows:
links.append(WithdrawLink.from_row(row) for row in rows)
return links
def update_withdraw_link(link_id: str, **kwargs) -> Optional[WithdrawLink]: def update_withdraw_link(link_id: str, **kwargs) -> Optional[WithdrawLink]:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
with open_ext_db("withdraw") as db: with open_ext_db("withdraw") as db:
db.execute(f"UPDATE withdraw_links SET {q} WHERE id = ?", (*kwargs.values(), link_id)) db.execute(f"UPDATE withdraw_links SET {q} WHERE id = ?", (*kwargs.values(), link_id))
row = db.fetchone("SELECT * FROM withdraw_links WHERE id = ?", (link_id,)) row = db.fetchone("SELECT * FROM withdraw_links WHERE id = ?", (link_id,))
@ -92,3 +108,7 @@ def update_withdraw_link(link_id: str, **kwargs) -> Optional[WithdrawLink]:
def delete_withdraw_link(link_id: str) -> None: def delete_withdraw_link(link_id: str) -> None:
with open_ext_db("withdraw") as db: with open_ext_db("withdraw") as db:
db.execute("DELETE FROM withdraw_links WHERE id = ?", (link_id,)) db.execute("DELETE FROM withdraw_links WHERE id = ?", (link_id,))
def chunks(lst, n):
for i in range(0, len(lst), n):
yield lst[i:i + n]

75
lnbits/extensions/withdraw/migrations.py

@ -5,34 +5,6 @@ from lnbits.helpers import urlsafe_short_hash
def m001_initial(db): def m001_initial(db):
"""
Initial withdraw table.
"""
db.execute(
"""
CREATE TABLE IF NOT EXISTS withdraws (
key INTEGER PRIMARY KEY AUTOINCREMENT,
usr TEXT,
wal TEXT,
walnme TEXT,
adm INTEGER,
uni TEXT,
tit TEXT,
maxamt INTEGER,
minamt INTEGER,
spent INTEGER,
inc INTEGER,
tme INTEGER,
uniq INTEGER DEFAULT 0,
withdrawals TEXT,
tmestmp INTEGER,
rand TEXT
);
"""
)
def m002_change_withdraw_table(db):
""" """
Creates an improved withdraw table and migrates the existing data. Creates an improved withdraw table and migrates the existing data.
""" """
@ -50,52 +22,13 @@ def m002_change_withdraw_table(db):
unique_hash TEXT UNIQUE, unique_hash TEXT UNIQUE,
k1 TEXT, k1 TEXT,
open_time INTEGER, open_time INTEGER,
used INTEGER DEFAULT 0 used INTEGER DEFAULT 0,
usescsv TEXT
); );
""" """)
)
db.execute("CREATE INDEX IF NOT EXISTS wallet_idx ON withdraw_links (wallet)")
db.execute("CREATE UNIQUE INDEX IF NOT EXISTS unique_hash_idx ON withdraw_links (unique_hash)")
for row in [list(row) for row in db.fetchall("SELECT * FROM withdraws")]:
db.execute(
"""
INSERT INTO withdraw_links (
id,
wallet,
title,
min_withdrawable,
max_withdrawable,
uses,
wait_time,
is_unique,
unique_hash,
k1,
open_time,
used
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
row[5], # uni
row[2], # wal
row[6], # tit
row[8], # minamt
row[7], # maxamt
row[10], # inc
row[11], # tme
row[12], # uniq
urlsafe_short_hash(),
urlsafe_short_hash(),
int(datetime.now().timestamp()) + row[11],
row[9], # spent
),
)
db.execute("DROP TABLE withdraws")
def migrate(): def migrate():
with open_ext_db("withdraw") as db: with open_ext_db("withdraw") as db:
m001_initial(db) m001_initial(db)
m002_change_withdraw_table(db)

11
lnbits/extensions/withdraw/models.py

@ -19,11 +19,14 @@ class WithdrawLink(NamedTuple):
k1: str k1: str
open_time: int open_time: int
used: int used: int
usescsv: str
multihash: str
@classmethod @classmethod
def from_row(cls, row: Row) -> "WithdrawLink": def from_row(cls, row: Row) -> "WithdrawLink":
data = dict(row) data = dict(row)
data["is_unique"] = bool(data["is_unique"]) data["is_unique"] = bool(data["is_unique"])
data["multihash"] = ""
return cls(**data) return cls(**data)
@property @property
@ -33,14 +36,20 @@ class WithdrawLink(NamedTuple):
@property @property
def lnurl(self) -> Lnurl: def lnurl(self) -> Lnurl:
scheme = "https" if FORCE_HTTPS else None scheme = "https" if FORCE_HTTPS else None
print(self.is_unique)
if self.is_unique:
url = url_for("withdraw.api_lnurl_multi_response", unique_hash=self.unique_hash, id_unique_hash=self.multihash, _external=True, _scheme=scheme)
else:
url = url_for("withdraw.api_lnurl_response", unique_hash=self.unique_hash, _external=True, _scheme=scheme) url = url_for("withdraw.api_lnurl_response", unique_hash=self.unique_hash, _external=True, _scheme=scheme)
return lnurl_encode(url) return lnurl_encode(url)
@property @property
def lnurl_response(self) -> LnurlWithdrawResponse: def lnurl_response(self) -> LnurlWithdrawResponse:
scheme = "https" if FORCE_HTTPS else None scheme = "https" if FORCE_HTTPS else None
url = url_for("withdraw.api_lnurl_callback", unique_hash=self.unique_hash, _external=True, _scheme=scheme)
url = url_for("withdraw.api_lnurl_callback", unique_hash=self.unique_hash, _external=True, _scheme=scheme)
print(url)
return LnurlWithdrawResponse( return LnurlWithdrawResponse(
callback=url, callback=url,
k1=self.k1, k1=self.k1,

204
lnbits/extensions/withdraw/static/js/index.js

@ -1,16 +1,24 @@
Vue.component(VueQrcode.name, VueQrcode); Vue.component(VueQrcode.name, VueQrcode)
var locationPath = [window.location.protocol, '//', window.location.host, window.location.pathname].join(''); var locationPath = [
window.location.protocol,
'//',
window.location.host,
window.location.pathname
].join('')
var mapWithdrawLink = function (obj) { var mapWithdrawLink = function (obj) {
obj._data = _.clone(obj); obj._data = _.clone(obj)
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm'); obj.date = Quasar.utils.date.formatDate(
obj.min_fsat = new Intl.NumberFormat(LOCALE).format(obj.min_withdrawable); new Date(obj.time * 1000),
obj.max_fsat = new Intl.NumberFormat(LOCALE).format(obj.max_withdrawable); 'YYYY-MM-DD HH:mm'
obj.uses_left = obj.uses - obj.used; )
obj.print_url = [locationPath, 'print/', obj.id].join(''); obj.min_fsat = new Intl.NumberFormat(LOCALE).format(obj.min_withdrawable)
obj.withdraw_url = [locationPath, obj.id].join(''); obj.max_fsat = new Intl.NumberFormat(LOCALE).format(obj.max_withdrawable)
return obj; obj.uses_left = obj.uses - obj.used
obj.print_url = [locationPath, 'print/', obj.id].join('')
obj.withdraw_url = [locationPath, obj.id].join('')
return obj
} }
new Vue({ new Vue({
@ -24,8 +32,18 @@ new Vue({
columns: [ columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'}, {name: 'id', align: 'left', label: 'ID', field: 'id'},
{name: 'title', align: 'left', label: 'Title', field: 'title'}, {name: 'title', align: 'left', label: 'Title', field: 'title'},
{name: 'wait_time', align: 'right', label: 'Wait', field: 'wait_time'}, {
{name: 'uses_left', align: 'right', label: 'Uses left', field: 'uses_left'}, name: 'wait_time',
align: 'right',
label: 'Wait',
field: 'wait_time'
},
{
name: 'uses_left',
align: 'right',
label: 'Uses left',
field: 'uses_left'
},
{name: 'min', align: 'right', label: 'Min (sat)', field: 'min_fsat'}, {name: 'min', align: 'right', label: 'Min (sat)', field: 'min_fsat'},
{name: 'max', align: 'right', label: 'Max (sat)', field: 'max_fsat'} {name: 'max', align: 'right', label: 'Max (sat)', field: 'max_fsat'}
], ],
@ -45,118 +63,146 @@ new Vue({
show: false, show: false,
data: null data: null
} }
}; }
}, },
computed: { computed: {
sortedWithdrawLinks: function () { sortedWithdrawLinks: function () {
return this.withdrawLinks.sort(function (a, b) { return this.withdrawLinks.sort(function (a, b) {
return b.uses_left - a.uses_left; return b.uses_left - a.uses_left
}); })
} }
}, },
methods: { methods: {
getWithdrawLinks: function () { getWithdrawLinks: function () {
var self = this; var self = this
LNbits.api.request( LNbits.api
.request(
'GET', 'GET',
'/withdraw/api/v1/links?all_wallets', '/withdraw/api/v1/links?all_wallets',
this.g.user.wallets[0].inkey this.g.user.wallets[0].inkey
).then(function (response) { )
.then(function (response) {
self.withdrawLinks = response.data.map(function (obj) { self.withdrawLinks = response.data.map(function (obj) {
return mapWithdrawLink(obj); return mapWithdrawLink(obj)
}); })
}).catch(function (error) { })
clearInterval(self.checker); .catch(function (error) {
LNbits.utils.notifyApiError(error); clearInterval(self.checker)
}); LNbits.utils.notifyApiError(error)
})
}, },
closeFormDialog: function () { closeFormDialog: function () {
this.formDialog.data = { this.formDialog.data = {
is_unique: false is_unique: false
}; }
}, },
openQrCodeDialog: function (linkId) { openQrCodeDialog: function (linkId) {
var link = _.findWhere(this.withdrawLinks, {id: linkId}); var link = _.findWhere(this.withdrawLinks, {id: linkId})
this.qrCodeDialog.data = _.clone(link); this.qrCodeDialog.data = _.clone(link)
this.qrCodeDialog.show = true; this.qrCodeDialog.show = true
}, },
openUpdateDialog: function (linkId) { openUpdateDialog: function (linkId) {
var link = _.findWhere(this.withdrawLinks, {id: linkId}); var link = _.findWhere(this.withdrawLinks, {id: linkId})
this.formDialog.data = _.clone(link._data); this.formDialog.data = _.clone(link._data)
this.formDialog.show = true; this.formDialog.show = true
}, },
sendFormData: function () { sendFormData: function () {
var wallet = _.findWhere(this.g.user.wallets, {id: this.formDialog.data.wallet}); var wallet = _.findWhere(this.g.user.wallets, {
var data = _.omit(this.formDialog.data, 'wallet'); id: this.formDialog.data.wallet
})
var data = _.omit(this.formDialog.data, 'wallet')
data.wait_time = data.wait_time * { data.wait_time =
'seconds': 1, data.wait_time *
'minutes': 60, {
'hours': 3600 seconds: 1,
}[this.formDialog.secondMultiplier]; minutes: 60,
hours: 3600
}[this.formDialog.secondMultiplier]
if (data.id) { this.updateWithdrawLink(wallet, data); } if (data.id) {
else { this.createWithdrawLink(wallet, data); } this.updateWithdrawLink(wallet, data)
} else {
this.createWithdrawLink(wallet, data)
}
}, },
updateWithdrawLink: function (wallet, data) { updateWithdrawLink: function (wallet, data) {
var self = this; var self = this
LNbits.api.request( LNbits.api
.request(
'PUT', 'PUT',
'/withdraw/api/v1/links/' + data.id, '/withdraw/api/v1/links/' + data.id,
wallet.adminkey, wallet.adminkey,
_.pick(data, 'title', 'min_withdrawable', 'max_withdrawable', 'uses', 'wait_time', 'is_unique') _.pick(
).then(function (response) { data,
self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) { return obj.id == data.id; }); 'title',
self.withdrawLinks.push(mapWithdrawLink(response.data)); 'min_withdrawable',
self.formDialog.show = false; 'max_withdrawable',
}).catch(function (error) { 'uses',
LNbits.utils.notifyApiError(error); 'wait_time',
}); 'is_unique'
)
)
.then(function (response) {
self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) {
return obj.id == data.id
})
self.withdrawLinks.push(mapWithdrawLink(response.data))
self.formDialog.show = false
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
}, },
createWithdrawLink: function (wallet, data) { createWithdrawLink: function (wallet, data) {
var self = this; var self = this
LNbits.api.request( LNbits.api
'POST', .request('POST', '/withdraw/api/v1/links', wallet.adminkey, data)
'/withdraw/api/v1/links', .then(function (response) {
wallet.adminkey, self.withdrawLinks.push(mapWithdrawLink(response.data))
data self.formDialog.show = false
).then(function (response) { })
self.withdrawLinks.push(mapWithdrawLink(response.data)); .catch(function (error) {
self.formDialog.show = false; LNbits.utils.notifyApiError(error)
}).catch(function (error) { })
LNbits.utils.notifyApiError(error);
});
}, },
deleteWithdrawLink: function (linkId) { deleteWithdrawLink: function (linkId) {
var self = this; var self = this
var link = _.findWhere(this.withdrawLinks, {id: linkId}); var link = _.findWhere(this.withdrawLinks, {id: linkId})
LNbits.utils.confirmDialog( LNbits.utils
'Are you sure you want to delete this withdraw link?' .confirmDialog('Are you sure you want to delete this withdraw link?')
).onOk(function () { .onOk(function () {
LNbits.api.request( LNbits.api
.request(
'DELETE', 'DELETE',
'/withdraw/api/v1/links/' + linkId, '/withdraw/api/v1/links/' + linkId,
_.findWhere(self.g.user.wallets, {id: link.wallet}).adminkey _.findWhere(self.g.user.wallets, {id: link.wallet}).adminkey
).then(function (response) { )
self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) { return obj.id == linkId; }); .then(function (response) {
}).catch(function (error) { self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) {
LNbits.utils.notifyApiError(error); return obj.id == linkId
}); })
}); })
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
}, },
exportCSV: function () { exportCSV: function () {
LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls); LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls)
} }
}, },
created: function () { created: function () {
if (this.g.user.wallets.length) { if (this.g.user.wallets.length) {
var getWithdrawLinks = this.getWithdrawLinks; var getWithdrawLinks = this.getWithdrawLinks
getWithdrawLinks(); getWithdrawLinks()
this.checker = setInterval(function () { getWithdrawLinks(); }, 20000); this.checker = setInterval(function () {
getWithdrawLinks()
}, 20000)
} }
} }
}); })

11
lnbits/extensions/withdraw/templates/withdraw/display.html

@ -45,13 +45,22 @@
</div> </div>
</div> </div>
{% endblock %} {% block scripts %} {% endblock %} {% block scripts %}
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script> <script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
<script> <script>
Vue.component(VueQrcode.name, VueQrcode) Vue.component(VueQrcode.name, VueQrcode)
new Vue({ new Vue({
el: '#vue', el: '#vue',
mixins: [windowMixin] mixins: [windowMixin],
data: function () {
return {
theurl: location.host,
}
},
}) })
</script> </script>
{% endblock %} {% endblock %}

1
lnbits/extensions/withdraw/templates/withdraw/index.html

@ -274,7 +274,6 @@
>Shareable link</q-btn >Shareable link</q-btn
> >
<q-btn <q-btn
v-if="!qrCodeDialog.data.is_unique"
outline outline
color="grey" color="grey"
icon="print" icon="print"

101
lnbits/extensions/withdraw/templates/withdraw/print_qr.html

@ -1,58 +1,79 @@
{% extends "print.html" %} {% block page %} <!DOCTYPE html>
{% block page %}
<div class="row justify-center"> <div class="row justify-center">
<div class="col-12 col-sm-8 col-lg-6 text-center"> <div class="col-12 col-sm-8 col-lg-6 text-center" id="vue">
{% for i in range(link.uses) %} {% for page in link %}
<div class="zimbabwe"> <page size="A4" id="pdfprint">
<div class="qr"> <table style="width: 100%;">
<qrcode value="{{ link.lnurl }}" :options="{width: 150}"></qrcode> {% for threes in page %}
<br /><br /> <tr style="height: 37.125mm;">
<strong>{{ SITE_TITLE }}</strong><br /> {% for one in threes %}
<strong>{{ link.max_withdrawable }} FREE SATS</strong><br /> <td style="width: 52.5mm;">
<small>Scan and follow link<br />or use Lightning wallet</small> <center>
</div> <h3 class="q-mb-md"></h3>
<img src="{{ url_for('static', filename='images/note.jpg') }}" /> <qrcode
</div> :value="theurl + '/lnurlwallet?lightning={{one.lnurl}}'"
:options="{width: 110}"
></qrcode>
</center>
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
</page>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% endblock %} {% block styles %} {% endblock %} {% block styles %}
<style> <style>
.zimbabwe { body {
page-break-inside: avoid; background: rgb(204, 204, 204);
height: 7cm; }
width: 16cm; page {
position: relative; background: white;
margin-bottom: 10px; display: block;
overflow: hidden; margin: 0 auto;
} margin-bottom: 0.5cm;
.zimbabwe img { box-shadow: 0 0 0.5cm rgba(0, 0, 0, 0.5);
position: absolute; }
top: 0; page[size='A4'] {
left: 0; width: 21cm;
z-index: 0; height: 29.7cm;
width: 100%; }
} @media print {
.zimbabwe .qr { body,
position: absolute; page {
top: 0; margin: 0;
bottom: 0; box-shadow: 0;
right: 0; }
z-index: 10;
background: rgb(255, 255, 255, 0.7);
padding: 10px;
text-align: center;
line-height: 1.1;
} }
</style> </style>
{% endblock %} {% block scripts %} {% endblock %} {% block scripts %}
<script src="{{ url_for('static', filename='vendor/vue@2.6.11/vue.js') }}"></script>
<script src="{{ url_for('static', filename='vendor/vue-router@3.1.6/vue-router.js') }}"></script>
<script src="{{ url_for('static', filename='vendor/vuex@3.1.3/vuex.js') }}"></script>
<script src="{{ url_for('static', filename='vendor/quasar@1.10.4/quasar.umd.js') }}"></script>
<script
type="text/javascript"
src="/static/__bundle__/base.js?a52a989e"
></script>
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script> <script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
<script> <script>
Vue.component(VueQrcode.name, VueQrcode) Vue.component(VueQrcode.name, VueQrcode)
new Vue({ new Vue({
el: '#vue', el: '#vue',
created: function () { mixins: [windowMixin],
window.print() data: function () {
return {
theurl: location.host,
printDialog: {
show: true,
data: null
}
}
} }
}) })
</script> </script>

14
lnbits/extensions/withdraw/views.py

@ -4,7 +4,7 @@ from http import HTTPStatus
from lnbits.decorators import check_user_exists, validate_uuids from lnbits.decorators import check_user_exists, validate_uuids
from lnbits.extensions.withdraw import withdraw_ext from lnbits.extensions.withdraw import withdraw_ext
from .crud import get_withdraw_link from .crud import get_withdraw_link, chunks
@withdraw_ext.route("/") @withdraw_ext.route("/")
@ -17,6 +17,7 @@ def index():
@withdraw_ext.route("/<link_id>") @withdraw_ext.route("/<link_id>")
def display(link_id): def display(link_id):
link = get_withdraw_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.") link = get_withdraw_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
link = get_withdraw_link(link_id, len(link.usescsv.split(","))) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
return render_template("withdraw/display.html", link=link) return render_template("withdraw/display.html", link=link)
@ -24,5 +25,12 @@ def display(link_id):
@withdraw_ext.route("/print/<link_id>") @withdraw_ext.route("/print/<link_id>")
def print_qr(link_id): def print_qr(link_id):
link = get_withdraw_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.") link = get_withdraw_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
if link.uses == 0:
return render_template("withdraw/print_qr.html", link=link) return render_template("withdraw/print_qr.html", link=link, unique=False)
links = []
for x in link.usescsv.split(","):
linkk = get_withdraw_link(link_id, x) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
links.append(linkk)
page_link = list(chunks(links, 4))
linked = list(chunks(page_link, 8))
return render_template("withdraw/print_qr.html", link=linked, unique=True)

65
lnbits/extensions/withdraw/views_api.py

@ -2,6 +2,7 @@ from datetime import datetime
from flask import g, jsonify, request from flask import g, jsonify, request
from http import HTTPStatus from http import HTTPStatus
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl
import shortuuid # type: ignore
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
from lnbits.core.services import pay_invoice from lnbits.core.services import pay_invoice
@ -26,10 +27,9 @@ def api_links():
if "all_wallets" in request.args: if "all_wallets" in request.args:
wallet_ids = get_user(g.wallet.user).wallet_ids wallet_ids = get_user(g.wallet.user).wallet_ids
try: try:
return ( return (
jsonify([{**link._asdict(), **{"lnurl": link.lnurl}} for link in get_withdraw_links(wallet_ids)]), jsonify([{**link._asdict(), **{"lnurl": link.lnurl}} for link in get_withdraw_links(wallet_ids)[0]]),
HTTPStatus.OK, HTTPStatus.OK,
) )
except LnurlInvalidUrl: except LnurlInvalidUrl:
@ -76,6 +76,15 @@ def api_link_create_or_update(link_id=None):
if (g.data["max_withdrawable"] * g.data["uses"] * 1000) > g.wallet.balance_msat: if (g.data["max_withdrawable"] * g.data["uses"] * 1000) > g.wallet.balance_msat:
return jsonify({"message": "Insufficient balance."}), HTTPStatus.FORBIDDEN return jsonify({"message": "Insufficient balance."}), HTTPStatus.FORBIDDEN
usescsv = ""
for i in range(g.data["uses"]):
if g.data["is_unique"]:
usescsv += "," + str(i + 1)
else:
usescsv += "," + str(1)
usescsv = usescsv[1:]
if link_id: if link_id:
link = get_withdraw_link(link_id) link = get_withdraw_link(link_id)
@ -85,9 +94,9 @@ def api_link_create_or_update(link_id=None):
if link.wallet != g.wallet.id: if link.wallet != g.wallet.id:
return jsonify({"message": "Not your withdraw link."}), HTTPStatus.FORBIDDEN return jsonify({"message": "Not your withdraw link."}), HTTPStatus.FORBIDDEN
link = update_withdraw_link(link_id, **g.data) link = update_withdraw_link(link_id, **g.data, usescsv=usescsv, used=0)
else: else:
link = create_withdraw_link(wallet_id=g.wallet.id, **g.data) link = create_withdraw_link(wallet_id=g.wallet.id, **g.data, usescsv=usescsv)
return jsonify({**link._asdict(), **{"lnurl": link.lnurl}}), HTTPStatus.OK if link_id else HTTPStatus.CREATED return jsonify({**link._asdict(), **{"lnurl": link.lnurl}}), HTTPStatus.OK if link_id else HTTPStatus.CREATED
@ -107,6 +116,7 @@ def api_link_delete(link_id):
return "", HTTPStatus.NO_CONTENT return "", HTTPStatus.NO_CONTENT
#FOR LNURLs WHICH ARE NOT UNIQUE
@withdraw_ext.route("/api/v1/lnurl/<unique_hash>", methods=["GET"]) @withdraw_ext.route("/api/v1/lnurl/<unique_hash>", methods=["GET"])
def api_lnurl_response(unique_hash): def api_lnurl_response(unique_hash):
@ -115,8 +125,49 @@ def api_lnurl_response(unique_hash):
if not link: if not link:
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
link = update_withdraw_link(link.id, k1=urlsafe_short_hash()) if link.is_unique == 1:
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
usescsv = ""
for x in range(1, link.uses - link.used):
usescsv += "," + str(1)
usescsv = usescsv[1:]
link = update_withdraw_link(link.id, used=link.used + 1, usescsv=usescsv)
return jsonify(link.lnurl_response.dict()), HTTPStatus.OK
#FOR LNURLs WHICH ARE UNIQUE
@withdraw_ext.route("/api/v1/lnurl/<unique_hash>/<id_unique_hash>", methods=["GET"])
def api_lnurl_multi_response(unique_hash, id_unique_hash):
link = get_withdraw_link_by_hash(unique_hash)
if not link:
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
useslist = link.usescsv.split(",")
usescsv = ""
hashed = []
found = False
print(link.uses - link.used)
print("link.uses - link.used")
print("svfsfv")
if link.is_unique == 0:
for x in range(link.uses - link.used):
usescsv += "," + str(1)
else:
for x in useslist:
tohash = link.id + link.unique_hash + str(x)
if id_unique_hash == shortuuid.uuid(name=tohash):
found = True
else:
usescsv += "," + x
print(x)
print("usescsv: " + usescsv)
if not found:
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
usescsv = usescsv[1:]
link = update_withdraw_link(link.id, used=link.used + 1, usescsv=usescsv)
return jsonify(link.lnurl_response.dict()), HTTPStatus.OK return jsonify(link.lnurl_response.dict()), HTTPStatus.OK
@ -143,13 +194,9 @@ def api_lnurl_callback(unique_hash):
pay_invoice(wallet_id=link.wallet, bolt11=payment_request, max_sat=link.max_withdrawable) pay_invoice(wallet_id=link.wallet, bolt11=payment_request, max_sat=link.max_withdrawable)
changes = { changes = {
"used": link.used + 1,
"open_time": link.wait_time + now, "open_time": link.wait_time + now,
} }
if link.is_unique:
changes["unique_hash"] = urlsafe_short_hash()
update_withdraw_link(link.id, **changes) update_withdraw_link(link.id, **changes)
except ValueError as e: except ValueError as e:

Loading…
Cancel
Save