Merge pull request #12 from arcbtc/lnbitsevents

Lnbitsevents
This commit is contained in:
Arc
2020-02-20 11:08:58 +00:00
committed by GitHub
16 changed files with 452 additions and 95 deletions

View File

@ -1,5 +1,5 @@
{ {
"name": "LNEVENTS", "name": "LNEVENTS",
"short_description": "Make LNURL withdraw links.", "short_description": "Make LNURL withdraw links.",
"ion_icon": "calendar-outline" "ion_icon": "calendar"
} }

View File

@ -6,15 +6,16 @@ CREATE TABLE IF NOT EXISTS events (
walinvkey INTEGER, walinvkey INTEGER,
uni TEXT, uni TEXT,
tit TEXT, tit TEXT,
amt INTEGER, cldate TEXT,
sold INTEGER, notickets INTEGER,
dat TEXT, sold INTEGER DEFAULT 0,
tme TEXT, prtick INTEGER
price INTEGER
); );
CREATE TABLE IF NOT EXISTS eventssold ( CREATE TABLE IF NOT EXISTS eventssold (
key INTEGER PRIMARY KEY AUTOINCREMENT, key INTEGER PRIMARY KEY AUTOINCREMENT,
uni TEXT, uni TEXT,
email TEXT,
name TEXT,
hash TEXT hash TEXT
); );

View File

@ -53,8 +53,8 @@
<!-- Content Header (Page header) --> <!-- Content Header (Page header) -->
<section class="content-header"> <section class="content-header">
<h1> <h1>
Withdraw link maker Events
<small>powered by LNURL</small> <small>bitcoin tickets</small>
</h1> </h1>
<ol class="breadcrumb"> <ol class="breadcrumb">
@ -70,7 +70,14 @@
</ol> </ol>
<br /><br /> <br /><br />
</section> </section>
<style>
#ui-datepicker-div{
background-color: #1f2234;
padding: 0 10px 10px 10px;
}
</style>
<!-- Main content --> <!-- Main content -->
<section class="content"> <section class="content">
<!-- Small boxes (Stat box) --> <!-- Small boxes (Stat box) -->
@ -80,7 +87,7 @@
<!-- general form elements --> <!-- general form elements -->
<div class="box box-primary"> <div class="box box-primary">
<div class="box-header"> <div class="box-header">
<h3 class="box-title"> Make a wave</h3> <h3 class="box-title"> Make a eve</h3>
</div><!-- /.box-header --> </div><!-- /.box-header -->
<!-- form start --> <!-- form start -->
<form role="form"> <form role="form">
@ -104,17 +111,24 @@
<div class="form-group"> <div class="form-group">
<label for="nooftickets">No. of tickets</label> <label for="nooftickets">No. of tickets</label>
<input id="notickets" type="number" class="form-control" placeholder="1" max="86400"></input> <input id="notickets" type="number" class="form-control" placeholder="10" max="86400"></input>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="nooftickets">Date</label> <label>Close date:</label>
<input type="text" class="form-control" data-inputmask="'alias': 'dd/mm/yyyy'" data-mask/>
</div><!-- /.input group --> <div class="input-group date">
<div class="input-group-addon">
<i class="fa fa-calendar"></i>
</div>
<input type="text" class="form-control pull-right" id="datepicker">
</div>
<!-- /.input group -->
</div>
<div class="form-group"> <div class="form-group">
<label for="prpertick">Price per ticket</label> <label for="prpertick">Price per ticket</label>
<input id="prtickets" type="number" class="form-control" placeholder="1"></input> <input id="prtickets" type="number" class="form-control" placeholder="10"></input>
</div> </div>
@ -122,7 +136,7 @@
<div class="box-footer"> <div class="box-footer">
<button onclick="postwave()" type="button" class="btn btn-info">Create wave</button><p style="color:red;" id="error"></p> <button onclick="postev()" type="button" class="btn btn-info">Create Wave</button><p style="color:red;" id="error"></p>
</div> </div>
</form> </form>
</div><!-- /.box --> </div><!-- /.box -->
@ -136,7 +150,7 @@
<div class="col-md-6"> <div class="col-md-6">
<div class="box"> <div class="box">
<div class="box-header"> <div class="box-header">
<h3 class="box-title">Ticket waves<b id="withdraws"></b></h3> <h3 class="box-title">Ticket Waves<b id="withdraws"></b></h3>
</div> </div>
<!-- /.box-header --> <!-- /.box-header -->
<div class="box-body no-padding"> <div class="box-body no-padding">
@ -145,7 +159,7 @@
<th>Title</th> <th>Title</th>
<th style="width:15%">Amt</th> <th style="width:15%">Amt</th>
<th style="width:15%">Sold</th> <th style="width:15%">Sold</th>
<th style="width:15%">Date</th> <th style="width:15%">Closing</th>
<th style="width:15%">Price</th> <th style="width:15%">Price</th>
<th style="width:15%">Wallet</th> <th style="width:15%">Wallet</th>
<th style="width:10%">Edit</th> <th style="width:10%">Edit</th>
@ -168,36 +182,46 @@
</section> </section>
<script> <script>
//Date picker
$('#datepicker').datepicker({
autoclose: true
})
window.user = {{ user | megajson | safe }} window.user = {{ user | megajson | safe }}
window.user_wallets = {{ user_wallets | megajson | safe }} window.user_wallets = {{ user_wallets | megajson | safe }}
window.user_ext = {{ user_ext | megajson | safe }} window.user_ext = {{ user_ext | megajson | safe }}
window.user_wav = {{ user_fau | megajson | safe }} window.user_ev = {{ user_ev | megajson | safe }}
const user_wav = window.user_wav const user_ev = window.user_ev
console.log(user_wav) console.log(user_ev)
function drawChart(user_wav) { function drawChart(user_ev) {
var transactionsHTML = '' var transactionsHTML = ''
for (var i = 0; i < user_wav.length; i++) { for (var i = 0; i < user_ev.length; i++) {
var wv = user_wav[i] var ev = user_ev[i]
transactionsHTML = transactionsHTML =
"<tr><td style='width: 50%'>" + "<tr><td style='width: 50%'>" +
wv.tit + ev.tit +
'</td><td>' + '</td><td>' +
wv.nosold + ev.notickets +
'</td><td>' + '</td><td>' +
wv.noavail + ev.sold +
'</td><td>' + '</td><td>' +
wv.prpertick + ev.cldate +
'</td><td>' + '</td><td>' +
"<a href='{{ url_for('wallet') }}?usr="+ user +"'>" + wv.uni.substring(0, 4) + "...</a>" + ev.prtick +
'</td><td>' +
"<a href='{{ url_for('wallet') }}?usr="+ ev.usr +"'>" + ev.wal.substring(0, 4) + "...</a>" +
'</td><td>' + '</td><td>' +
"<i onclick='editlink("+ i +")'' class='fa fa-edit'></i>" + "<i onclick='editlink("+ i +")'' class='fa fa-edit'></i>" +
'</td><td>' + '</td><td>' +
"<b><a style='color:red;' href='" + "{{ url_for('withdraw.index') }}?del=" + wv.uni + "&usr=" + user +"'>" + "<i class='fa fa-trash'></i>" + "</a></b>" + "<b><a style='color:red;' href='" + "{{ url_for('withdraw.index') }}?del=" + ev.uni + "&usr=" + ev.usr +"'>" + "<i class='fa fa-trash'></i>" + "</a></b>" +
'</td></tr>' + '</td></tr>' +
transactionsHTML transactionsHTML
document.getElementById('ticketwaves').innerHTML = transactionsHTML document.getElementById('ticketwaves').innerHTML = transactionsHTML
@ -205,12 +229,17 @@ function drawChart(user_wav) {
} }
} }
function postwav(){ if (user_ev.length) {
drawChart(user_ev)
}
function postev(){
wal = document.getElementById('wal').value wal = document.getElementById('wal').value
tit = document.getElementById('tit').value tit = document.getElementById('tit').value
nooftickets = document.getElementById('nooftickets').value cldate = document.getElementById('datepicker').value
prtick = document.getElementById('prtick').value notickets = document.getElementById('notickets').value
prtickets = document.getElementById('prtickets').value
if (tit == "") { if (tit == "") {
document.getElementById("error").innerHTML = "Only use letters in title" document.getElementById("error").innerHTML = "Only use letters in title"
@ -220,36 +249,40 @@ function postwav(){
document.getElementById("error").innerHTML = "No wallet selected" document.getElementById("error").innerHTML = "No wallet selected"
return amt return amt
} }
if (cldate == "") {
document.getElementById("error").innerHTML = "No date selected"
return amt
}
if (isNaN(notickets) || notickets < 1) { if (isNaN(notickets) || notickets < 1) {
document.getElementById("error").innerHTML = "Must be more than 1" document.getElementById("error").innerHTML = "Must be more than 1"
return amt return amt
} }
if (isNaN(prpertickets) || prpertickets < 10) { if (isNaN(prtickets) || prtickets < 10) {
document.getElementById("error").innerHTML = "Must be higher 10" document.getElementById("error").innerHTML = "Must be higher 10"
return amt return amt
} }
postAjax( postAjax(
"{{ url_for('wave.create') }}", "{{ url_for('events.create') }}",
JSON.stringify({"tit": tit, "nooftickets": nooftickets, "nooftickets": nooftickets, "prtick": prtick}), JSON.stringify({"tit": tit, "usr": user, "wal": wal, "notickets": notickets,"cldate": cldate, "prtickets": prtickets}),
"filla", "filla",
function(data) { location.replace("{{ url_for('wav.index') }}?usr=" + user) function(data) { location.replace("{{ url_for('events.index') }}?usr=" + user)
}) })
} }
function editlink(wavnum){ function editlink(evnum){
wavdetails = user_wav[wavnum] evdetails = user_ev[evnum]
console.log(wavdetails) console.log(evdetails)
wallpick = "" wallpick = ""
checkbox = "" checkbox = ""
if (wavdetails.uniq == 1){ if (evdetails.uniq == 1){
checkbox = "checked"} checkbox = "checked"}
document.getElementById('editlink').innerHTML = "<div class='row'>"+ document.getElementById('editlink').innerHTML = "<div class='row'>"+
@ -257,7 +290,7 @@ document.getElementById('editlink').innerHTML = "<div class='row'>"+
" <!-- general form elements -->"+ " <!-- general form elements -->"+
"<div class='box box-primary'>"+ "<div class='box box-primary'>"+
"<div class='box-header'>"+ "<div class='box-header'>"+
"<h3 class='box-title'> Edit: <i id='unid'>" + wavdetails.tit + "-" + wavdetails.uni + "</i> </h3>"+ "<h3 class='box-title'> Edit: <i id='unid'>" + evdetails.tit + "-" + evdetails.uni + "</i> </h3>"+
"<div class='box-tools pull-right'>" + "<div class='box-tools pull-right'>" +
"<button class='btn btn-box-tool' data-widget='remove'><i class='fa fa-times'></i></button>" + "<button class='btn btn-box-tool' data-widget='remove'><i class='fa fa-times'></i></button>" +
"</div>" + "</div>" +
@ -269,7 +302,7 @@ document.getElementById('editlink').innerHTML = "<div class='row'>"+
"<div class='form-group'>"+ "<div class='form-group'>"+
"<label for='exampleInputEmail1'>Link title</label>"+ "<label for='exampleInputEmail1'>Link title</label>"+
"<input id='edittit' type='text' class='form-control' value='"+ "<input id='edittit' type='text' class='form-control' value='"+
wavdetails.tit + evdetails.tit +
"'></input> </div>"+ "'></input> </div>"+
" </div>"+ " </div>"+
" <div class='col-sm-4 col-md-4'>"+ " <div class='col-sm-4 col-md-4'>"+
@ -277,7 +310,7 @@ document.getElementById('editlink').innerHTML = "<div class='row'>"+
" <div class='form-group'>"+ " <div class='form-group'>"+
" <label>Select a wallet</label>"+ " <label>Select a wallet</label>"+
"<select id='editwal' class='form-control'>"+ "<select id='editwal' class='form-control'>"+
" <option>" + wavdetails.walnme + "-" + wavdetails.wal + "</option>"+ " <option>" + evdetails.walnme + "-" + evdetails.wal + "</option>"+
" {% for w in user_wallets %}"+ " {% for w in user_wallets %}"+
" <option>{{w.name}}-{{w.id}}</option>"+ " <option>{{w.name}}-{{w.id}}</option>"+
@ -289,58 +322,48 @@ document.getElementById('editlink').innerHTML = "<div class='row'>"+
" <div class='col-sm-3 col-md-4'>"+ " <div class='col-sm-3 col-md-4'>"+
"<div class='form-group'>"+ "<div class='form-group'>"+
" <label for='exampleInputPassword1'>No of tickets:</label>"+ " <label for='exampleInputPassword1'>No of tickets:</label>"+
" <input id='edittme' type='number' class='form-control' placeholder='0' max='86400' value='"+ " <input id='editnooftickets' type='number' class='form-control' placeholder='0' max='86400' value='"+
wavdetails.notickets + evdetails.notickets +
"'></input>"+ "'></input>"+
"</div> </div>"+ "</div> </div>"+
" <div class='col-sm-3 col-md-4'>"+ " <div class='col-sm-3 col-md-4'>"+
"<div class='form-group'>"+ "<div class='form-group'>"+
"<label for='exampleInputEmail1'>Price per ticket:</label>"+ "<label for='exampleInputEmail1'>Price per ticket:</label>"+
" <input id='editmaxamt' type='number' class='form-control' placeholder='1' value='"+ " <input id='editprtick' type='number' class='form-control' placeholder='1' value='"+
wavdetails.prperticket + evdetails.prtick +
"'></input>"+ "'></input>"+
" </div></div>"+ " </div></div>"+
" <div class='col-sm-3 col-md-4'>"+ " <div class='col-sm-3 col-md-4'>"+
" <div class='form-group'>"+ " <div class='input-group date'>"+
" <label for='exampleInputEmail1'>Close date:</label>"+ " <label for='exampleInputEmail1'>Close date:</label>"+
" <input id='editminamt' type='number' class='form-control' placeholder='1' value='"+ " <input id='datepicker2' type='text' class='form-control' placeholder='1' value='"+
wavdetails.minamt + evdetails.cldate +
"'></input>"+ "'></input>"+
" </div></div>"+ " </div></div>"+
" <div class='col-sm-3 col-md-4'>"+
"<div class='form-group'>"+
" <label for='exampleInputPassword1'>Amount of uses:</label>"+
" <input id='editamt' type='number' class='form-control' placeholder='1' value='"+
wavdetails.inc +
"'></input>"+
" </div> </div>"+
" <div class='col-sm-3 col-md-4'>"+ " <div class='col-sm-3 col-md-4'>"+
" <div class='checkbox'>"+ "</div><!-- /.box-body -->"+
"<label data-toggle='tooltip' title='Some tooltip text!'><input id='edituniq' type='checkbox' "+
checkbox +
">"+
"Unique links</label>"+
"</div></div><!-- /.box-body -->"+
" </div><br/>"+ " </div><br/>"+
" <div class='box-footer'>"+ " <div class='box-footer'>"+
" <button onclick='editlinkcont()' type='button' class='btn btn-info'>Edit link(s)</button><p style='color:red;' id='error2'></p>"+ " <button onclick='editlinkcont()' type='button' style='margin: 24px;' class='btn btn-info'>Edit link(s)</button><p style='color:red;' id='error2'></p>"+
" </div></form></div><!-- /.box --></div></div>" " </div></form></div><!-- /.box --></div></div>"
} }
//Date picker
$('#datepicker2').datepicker({
autoclose: true
})
function editlinkcont(){ function editlinkcont(){
unid = document.getElementById('unid').innerHTML unid = document.getElementById('unid').innerHTML
wal = document.getElementById('editwal').value wal = document.getElementById('editwal').value
tit = document.getElementById('edittit').value tit = document.getElementById('edittit').value
amt = document.getElementById('editamt').value nooftickets = document.getElementById('editnooftickets').value
maxamt = document.getElementById('editmaxamt').value prtick = document.getElementById('editprtick').value
minamt = document.getElementById('editminamt').value cldate = document.getElementById('datepicker2').value
tme = document.getElementById('edittme').value uni = unid.split("-")[1]
uniq = document.getElementById('edituniq').checked
if (tit == "") { if (tit == "") {
document.getElementById("error2").innerHTML = "Only use letters in title" document.getElementById("error2").innerHTML = "Only use letters in title"
@ -351,38 +374,29 @@ function editlinkcont(){
return amt return amt
} }
if (isNaN(maxamt) || maxamt < 10 || maxamt > 1000000) { if (isNaN(nooftickets) || nooftickets < 1 || nooftickets > 1000000) {
document.getElementById("error2").innerHTML = "Max 10 - 1000000 and must be higher than min" document.getElementById("error2").innerHTML = "No. of tickets must be between 1 - 1000000"
return amt return amt
} }
if (isNaN(minamt) || minamt < 1 || minamt > 1000000 || minamt > maxamt) { if (isNaN(prtick) || prtick < 10 ) {
document.getElementById("error2").innerHTML = "Min 1 - 1000000 and must be lower than max" document.getElementById("error2").innerHTML = "Ticket pricket must be higher than 10"
return amt
}
if (isNaN(amt) || amt < 1 || amt > 1000) {
document.getElementById("error2").innerHTML = "Amount of uses must be between 1 - 1000"
return amt
}
if (isNaN(tme) || tme < 1 || tme > 86400) {
document.getElementById("error2").innerHTML = "Max waiting time 1 day (86400 secs)"
return amt return amt
} }
postAjax( postAjax(
"{{ url_for('withdraw.create') }}", "{{ url_for('events.create') }}",
JSON.stringify({"id": unid, "tit": tit, "amt": amt, "maxamt": maxamt, "minamt": minamt, "tme": tme, "wal": wal, "usr": user, "uniq": uniq}), JSON.stringify({"tit": tit, "usr": user, "wal": wal, "notickets": nooftickets,"cldate": cldate, "prtickets": prtick, "id": unid}),
"filla", "filla",
function(data) { location.replace("{{ url_for('withdraw.index') }}?usr=" + user) function(data) { location.replace("{{ url_for('events.index') }}?usr=" + user)
}) })
} }
</script> </script>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -10,8 +10,98 @@ from lnbits.extensions.events import events_ext
@events_ext.route("/") @events_ext.route("/")
def index(): def index():
"""Main events link page.""" """Main events link page."""
usr = request.args.get("usr")
if usr:
if not len(usr) > 20:
return redirect(url_for("home"))
# Get all the data
with open_db() as db:
user_wallets = db.fetchall("SELECT * FROM wallets WHERE user = ?", (usr,))
user_ext = db.fetchall("SELECT * FROM extensions WHERE user = ?", (usr,))
user_ext = [v[0] for v in user_ext]
with open_ext_db("events") as events_ext_db:
user_ev = events_ext_db.fetchall("SELECT * FROM events WHERE usr = ?", (usr,))
# If del is selected by user from events page, the event link is to be deleted
evdel = request.args.get("del")
if evdel:
events_ext_db.execute("DELETE FROM events WHERE uni = ?", (evdel,))
user_ev = events_ext_db.fetchall("SELECT * FROM events WHERE usr = ?", (usr,))
print(user_ext)
return render_template( return render_template(
"events/index.html" "events/index.html", user_wallets=user_wallets, user=usr, user_ext=user_ext, user_ev=user_ev
) )
@events_ext.route("/create", methods=["GET", "POST"])
def create():
"""."""
data = request.json
tit = data["tit"]
wal = data["wal"]
cldate = data["cldate"]
notickets = data["notickets"]
prtick = data["prtickets"]
usr = data["usr"]
wall = wal.split("-")
# Form validation
if (
not tit.replace(" ", "").isalnum()
or wal == ""
or int(notickets) < 0
or int(prtick) < 0
):
return jsonify({"ERROR": "FORM ERROR"}), 401
# If id that means its a link being edited, delete the record first
if "id" in data:
unid = data["id"].split("-")
uni = unid[1]
with open_ext_db("events") as events_ext_db:
events_ext_db.execute("DELETE FROM events WHERE uni = ?", (unid[1],))
else:
uni = uuid.uuid4().hex
with open_db() as dbb:
user_wallets = dbb.fetchall("SELECT * FROM wallets WHERE user = ? AND id = ?", (usr, wall[1],))
if not user_wallets:
return jsonify({"ERROR": "NO WALLET USER"}), 401
with open_db() as db:
user_ext = db.fetchall("SELECT * FROM extensions WHERE user = ?", (usr,))
user_ext = [v[0] for v in user_ext]
# Add to DB
with open_ext_db("events") as events_ext_db:
events_ext_db.execute(
"""
INSERT OR IGNORE INTO events
(usr, wal, walnme, walinvkey, uni, tit, cldate, notickets, prtick)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
usr,
wall[1],
user_wallets[0][1],
user_wallets[0][4],
uni,
tit,
cldate,
notickets,
prtick,
),
)
user_ev = events_ext_db.fetchall("SELECT * FROM events WHERE usr = ?", (usr,))
if not user_ev:
return jsonify({"ERROR": "NO WALLET USER"}), 401
return render_template(
"events/index.html", user_wallets=user_wallets, user=usr, user_ext=user_ext, user_ev=user_ev
)

View File

@ -5,5 +5,7 @@ This is an example extension to help you organise and build you own.
Try to include an image Try to include an image
<img src="https://i.imgur.com/9i4xcQB.png"> <img src="https://i.imgur.com/9i4xcQB.png">
<h2>If your extension has API endpoints, include any useful ones here</h2>
<h2>If your extension has API endpoints, include useful ones here</h2>
<code>curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "Grpc-Metadata-macaroon: YOUR_WALLET-ADMIN/INVOICE-KEY"</code> <code>curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "Grpc-Metadata-macaroon: YOUR_WALLET-ADMIN/INVOICE-KEY"</code>

View File

@ -0,0 +1,8 @@
from flask import Blueprint
example_ext = Blueprint("example", __name__, static_folder="static", template_folder="templates")
from .views_api import * # noqa
from .views import * # noqa

View File

@ -0,0 +1,5 @@
{
"name": "SHORT-NAME-FOR-EXTENSIONS-PAGE",
"short_description": "BLah blah blah.",
"ion_icon": "calendar"
}

View File

@ -0,0 +1,7 @@
/* create your extensions table and the variables needed here */
CREATE TABLE IF NOT EXISTS example (
key INTEGER PRIMARY KEY AUTOINCREMENT,
usr TEXT,
wal TEXT,
walnme TEXT
);

View File

@ -0,0 +1,102 @@
<!-- @format -->
{% extends "base.html" %} {% block messages %}
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-bell-o"></i>
<span class="label label-danger">!</span>
</a>
<ul class="dropdown-menu">
<li class="header"><b>Instant wallet, bookmark to save</b></li>
<li></li>
</ul>
{% endblock %} {% block menuitems %}
<li class="treeview">
<a href="#">
<i class="fa fa-bitcoin"></i> <span>Wallets</span>
<i class="fa fa-angle-left pull-right"></i>
</a>
<ul class="treeview-menu">
{% for w in user_wallets %}
<li>
<a href="{{ url_for('wallet') }}?wal={{ w.id }}&usr={{ w.user }}"
><i class="fa fa-bolt"></i> {{ w.name }}</a
>
</li>
{% endfor %}
<li><a onclick="sidebarmake()">Add a wallet +</a></li>
<div id="sidebarmake"></div>
</ul>
</li>
<li class="active treeview">
<a href="#">
<i class="fa fa-th"></i> <span>Extensions</span>
<i class="fa fa-angle-left pull-right"></i>
</a>
<ul class="treeview-menu">
{% for extension in EXTENSIONS %}
{% if extension.code in user_ext %}
<li>
<a href="{{ url_for(extension.code + '.index') }}?usr={{ user }}"><i class="fa fa-plus"></i> {{ extension.name }}</a>
</li>
{% endif %}
{% endfor %}
<li>
<a href="{{ url_for('extensions') }}?usr={{ user }}">Manager </a></li>
</ul>
</li>
{% endblock %} {% block body %}
<!-- Right side column. Contains the navbar and content of the page -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
Withdraw link maker
<small>powered by LNURL</small>
</h1>
<ol class="breadcrumb">
<li>
<a href="{{ url_for('wallet') }}?usr={{ user }}"><i class="fa fa-dashboard"></i> Home</a>
</li>
<li>
<a href="{{ url_for('extensions') }}?usr={{ user }}"><li class="fa fa-dashboard">Extensions</li></a>
</li>
<li>
<i class="active" class="fa fa-dashboard">example</i>
</li>
</ol>
<br /><br />
<!-- DOWNLOAD AND SEARCH ADMINLITE2 FOR HTML-->
</section>
<!-- Main content -->
<section class="content">
<!-- Small boxes (Stat box) -->
<div class="row">
<div class="col-md-6">
<!-- general form elements -->
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title"> EXAMPLE BOX HEADING</h3>
</div><!-- /.box-header -->
<div class="box-body">
<p>*Some content in here</p>
</div>
</div><!-- /.box -->
</div>
</div>
</section>
<script>
//ADD YOUR JAVASCIPT HERE//
</script>
</div>
{% endblock %}

View File

@ -0,0 +1,14 @@
#add your dependencies here
from flask import jsonify, render_template, request, redirect, url_for
from lnbits.db import open_db, open_ext_db
from lnbits.extensions.example import example_ext
#add your endpoints here
@example_ext.route("/")
def index():
"""Try to add descriptions for others."""
return render_template(
"example/index.html"
)

View File

@ -0,0 +1,18 @@
#views_api.py is for you API endpoints that could be hit by another service
#add your dependencies here
import json
import requests
from flask import jsonify, render_template, request, redirect, url_for
from lnbits.db import open_db, open_ext_db
from lnbits.extensions.example import example_ext
#add your endpoints here
@example_ext.route("/api/v1/example", methods=["GET","POST"])
def api_example():
"""Try to add descriptions for others."""
#YOUR-CODE
return jsonify({"status": "TRUE"}), 200

View File

@ -6,3 +6,9 @@ https://github.com/btcontract/lnurl-rfc/blob/master/spec.md#3-lnurl-withdraw
With this extension to can create/edit LNURL withdraws, set a min/max amount, set time (useful for subscription services) With this extension to can create/edit LNURL withdraws, set a min/max amount, set time (useful for subscription services)
![lnurl](https://i.imgur.com/qHi7ExL.png) ![lnurl](https://i.imgur.com/qHi7ExL.png)
## API endpoint - /withdraw/api/v1/lnurlmaker
Easily fetch one-off LNURLw
curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/withdraw/api/v1/lnurlmaker -d '{"amount":"100","memo":"ATM"}' -H "Grpc-Metadata-macaroon: YOUR-WALLET-ADMIN-KEY"

View File

@ -6,7 +6,8 @@ from flask import jsonify, request, url_for
from lnurl import LnurlWithdrawResponse, encode as lnurl_encode from lnurl import LnurlWithdrawResponse, encode as lnurl_encode
from datetime import datetime from datetime import datetime
from lnbits.db import open_ext_db
from lnbits.db import open_ext_db, open_db
from lnbits.extensions.withdraw import withdraw_ext from lnbits.extensions.withdraw import withdraw_ext
@ -117,3 +118,64 @@ def api_lnurlwithdraw(rand):
user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE withdrawals = ?", (k1,)) user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE withdrawals = ?", (k1,))
return jsonify({"status": "OK"}), 200 return jsonify({"status": "OK"}), 200
@withdraw_ext.route("/api/v1/lnurlmaker", methods=["GET","POST"])
def api_lnurlmaker():
if request.headers["Content-Type"] != "application/json":
return jsonify({"ERROR": "MUST BE JSON"}), 400
with open_db() as db:
wallet = db.fetchall(
"SELECT * FROM wallets WHERE adminkey = ?",
(request.headers["Grpc-Metadata-macaroon"],),
)
if not wallet:
return jsonify({"ERROR": "NO KEY"}), 200
balance = db.fetchone("SELECT balance/1000 FROM balances WHERE wallet = ?", (wallet[0][0],))[0]
print(balance)
postedjson = request.json
print(postedjson["amount"])
if balance < int(postedjson["amount"]):
return jsonify({"ERROR": "NOT ENOUGH FUNDS"}), 200
uni = uuid.uuid4().hex
rand = uuid.uuid4().hex[0:5]
with open_ext_db("withdraw") as withdraw_ext_db:
withdraw_ext_db.execute(
"""
INSERT OR IGNORE INTO withdraws
(usr, wal, walnme, adm, uni, tit, maxamt, minamt, spent, inc, tme, uniq, withdrawals, tmestmp, rand)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
wallet[0][2],
wallet[0][0],
wallet[0][1],
wallet[0][3],
uni,
postedjson["memo"],
postedjson["amount"],
postedjson["amount"],
0,
1,
1,
0,
0,
1,
rand,
),
)
user_fau = withdraw_ext_db.fetchone("SELECT * FROM withdraws WHERE uni = ?", (uni,))
if not user_fau:
return jsonify({"ERROR": "WITHDRAW NOT MADE"}), 401
url = url_for("withdraw.api_lnurlfetch", _external=True, urlstr=request.host, parstr=uni, rand=rand)
return jsonify({"status": "TRUE", "lnurl": lnurl_encode(url.replace("http://", "https://"))}), 200

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,6 +9,13 @@
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
name="viewport" name="viewport"
/> />
<!-- Date picker -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='bootstrap/css/bootstrap-datepicker.min.css') }}"
/>
<!-- Bootstrap 3.3.2 --> <!-- Bootstrap 3.3.2 -->
<link <link
rel="stylesheet" rel="stylesheet"
@ -115,6 +122,12 @@
<script> <script>
$.widget.bridge('uibutton', $.ui.button) $.widget.bridge('uibutton', $.ui.button)
</script> </script>
<!-- Datepicker 3.3.2 JS -->
<script
src="{{ url_for('static', filename='bootstrap/js/bootstrap-datepicker.min.js') }}"
type="text/javascript"
></script>
<!-- Bootstrap 3.3.2 JS --> <!-- Bootstrap 3.3.2 JS -->
<script <script
src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}" src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"