mirror of
https://github.com/aljazceru/plugins.git
synced 2026-01-09 16:24:20 +01:00
donations: Imported Rene's donation plugin
This comes from a stale PR on the c-lightning issue tracker and after talking with Rene, I decided to give it an official place to live.
This commit is contained in:
22
donations/LICENSE
Normal file
22
donations/LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
Note: the modules in the ccan/ directory have their own licenses, but
|
||||
the rest of the code is covered by the following (BSD-MIT) license:
|
||||
|
||||
Copyright Rene Pickhardt 2018.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
45
donations/README.md
Normal file
45
donations/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Invoice Service (for Donations) plugin
|
||||
|
||||
This plugin enables c-lightning nodes to start one or several small webserver
|
||||
via the command line on specified port. The webserver is based on flask and
|
||||
exposes the invoice API call.
|
||||
|
||||
Therefor people can query for an invoice which they can use to pay.
|
||||
|
||||
Run the plugin with:
|
||||
|
||||
```
|
||||
lightningd --plugin=/path/to/lightning/contrib/plugins/donations/donations.py
|
||||
```
|
||||
|
||||
Once the plugin is active you can run `lightning-cli help donationserver`
|
||||
to learn about the command line API:
|
||||
|
||||
Controls a donationserver with `start`/`stop`/`restart`/`list` on `port`
|
||||
|
||||
A Simple HTTP Server is created that can serve a donation webpage and allow to issue invoices.
|
||||
The plugin takes one of the following three commands {start/stop/restart} as the first agument
|
||||
By default the plugin starts the server on port 8088. This can however be changed with the
|
||||
port argument.
|
||||
|
||||
This means after starting `lightningd` together with the plugin you can run:
|
||||
`lightning-cli donationserver start` and access the server at
|
||||
http://localhost:33506/donation (in case you run your lightning node at
|
||||
`localhost`)
|
||||
|
||||
## About the plugin
|
||||
You can see a demo of the plugin on the [authors website][rene-donations]:
|
||||
|
||||
|
||||
This plugin was created and is maintained by Rene Pickhardt. Thus Rene Pickhardt
|
||||
is the copyright owner of this plugin. It shall serve as an educational resource
|
||||
on his [Youtube channel][rene-youtube].
|
||||
|
||||
The plugin is licensed like the rest of c-lightning with BSD-MIT license
|
||||
and comes without any warrenty.
|
||||
|
||||
If you like my work feel free to support me on [patreon][rene-patreon].
|
||||
|
||||
[rene-donations]: https://ln.rene-pickhardt.de/donation
|
||||
[rene-patreon]: https://www.patreon.com/renepickhardt
|
||||
[rene-youtube]: https://www.youtube.com/user/RenePickhardt
|
||||
215
donations/donations.py
Executable file
215
donations/donations.py
Executable file
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env python3
|
||||
""" A small donation service so that users can request ln invoices
|
||||
|
||||
This plugin spins up a small flask server that provides a form to
|
||||
users who wish to donate some money to the owner of the lightning
|
||||
node. The server can run on an arbitrary port and returns an invoice.
|
||||
Also a list of previously paid invoices (only those that used this
|
||||
service) will be displayed. Displaying paid invoices could be made
|
||||
optionally in a future version.
|
||||
|
||||
Author: Rene Pickhardt (https://ln.rene-pickhardt.de)
|
||||
|
||||
you can see a demo of the plugin (and leave a tip) directly at:
|
||||
https://ln.rene-pickhardt.de/donation
|
||||
|
||||
LICENSE: MIT / APACHE
|
||||
"""
|
||||
import base64
|
||||
import json
|
||||
import multiprocessing
|
||||
import qrcode
|
||||
import sys
|
||||
|
||||
|
||||
from flask import Flask, render_template
|
||||
from flask_bootstrap import Bootstrap
|
||||
from flask_wtf import FlaskForm
|
||||
from io import BytesIO
|
||||
from lightning import LightningRpc, Plugin
|
||||
from os.path import join
|
||||
from random import random
|
||||
from time import time
|
||||
from wtforms import StringField, SubmitField, IntegerField
|
||||
from wtforms.validators import Required, NumberRange
|
||||
|
||||
|
||||
plugin = Plugin()
|
||||
|
||||
|
||||
class DonationForm(FlaskForm):
|
||||
"""Form for donations """
|
||||
amount = IntegerField("Enter how many Satoshis you want to donate!",
|
||||
validators=[Required(), NumberRange(min=1, max=16666666)])
|
||||
description = StringField("Leave a comment (displayed publically)")
|
||||
submit = SubmitField('Donate')
|
||||
|
||||
|
||||
def make_base64_qr_code(bolt11):
|
||||
qr = qrcode.QRCode(
|
||||
version=1,
|
||||
error_correction=qrcode.constants.ERROR_CORRECT_H,
|
||||
box_size=4,
|
||||
border=4,
|
||||
)
|
||||
|
||||
qr.add_data(bolt11)
|
||||
qr.make(fit=True)
|
||||
img = qr.make_image()
|
||||
|
||||
buffered = BytesIO()
|
||||
img.save(buffered, format="PNG")
|
||||
img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
||||
return img_str
|
||||
|
||||
|
||||
def ajax(label):
|
||||
global plugin
|
||||
msg = plugin.rpc.listinvoices(label)["invoices"][0]
|
||||
if msg["status"] == "paid":
|
||||
return "Your donation has been received and is well appricated."
|
||||
return "waiting"
|
||||
|
||||
|
||||
def donation_form():
|
||||
global plugin
|
||||
form = DonationForm()
|
||||
b11 = None
|
||||
qr = None
|
||||
label = None
|
||||
if form.validate_on_submit():
|
||||
amount = form.amount.data
|
||||
description = form.description.data
|
||||
label = "ln-plugin-donation-{}".format(random())
|
||||
invoice = plugin.rpc.invoice(
|
||||
int(amount)*1000, label, description)
|
||||
b11 = invoice["bolt11"]
|
||||
qr = make_base64_qr_code(b11)
|
||||
|
||||
invoices = plugin.rpc.listinvoices()["invoices"]
|
||||
donations = []
|
||||
for invoice in invoices:
|
||||
if invoice["label"].startswith("ln-plugin-donation-"):
|
||||
# FIXME: change to paid after debugging
|
||||
if invoice["status"] == "paid":
|
||||
bolt11 = plugin.rpc.decodepay(invoice["bolt11"])
|
||||
satoshis = int(bolt11["msatoshi"])//1000
|
||||
description = bolt11["description"]
|
||||
ts = bolt11["created_at"]
|
||||
donations.append((ts, satoshis, description))
|
||||
|
||||
if b11 is not None:
|
||||
return render_template("donation.html", donations=sorted(donations, reverse=True), form=form, bolt11=b11, qr=qr, label=label)
|
||||
else:
|
||||
return render_template("donation.html", donations=sorted(donations, reverse=True), form=form)
|
||||
|
||||
|
||||
def worker(port):
|
||||
app = Flask(__name__)
|
||||
# FIXME: use hexlified hsm secret or something else
|
||||
app.config['SECRET_KEY'] = 'you-will-never-guess-this'
|
||||
app.add_url_rule('/donation', 'donation',
|
||||
donation_form, methods=["GET", "POST"])
|
||||
app.add_url_rule('/is_invoice_paid/<label>', 'ajax', ajax)
|
||||
bootstrap = Bootstrap(app)
|
||||
app.run("0.0.0.0", port=port)
|
||||
return
|
||||
|
||||
|
||||
jobs = {}
|
||||
|
||||
|
||||
def start_server(port):
|
||||
if port in jobs:
|
||||
return False, "server already running"
|
||||
|
||||
p = multiprocessing.Process(
|
||||
target=worker, args=[port], name="server on port {}".format(port))
|
||||
p.daemon = True
|
||||
|
||||
jobs[port] = p
|
||||
p.start()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def stop_server(port):
|
||||
if port in jobs:
|
||||
jobs[port].terminate()
|
||||
del jobs[port]
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
@plugin.method('donationserver')
|
||||
def donationserver(request, command="start", port=8088):
|
||||
"""Starts a donationserver with {start/stop/restart} on {port}.
|
||||
|
||||
A Simple HTTP Server is created that can serve a donation webpage and
|
||||
allow to issue invoices. The plugin takes one of the following three
|
||||
commands {start/stop/restart} as the first agument By default the plugin
|
||||
starts the server on port 8088. This can however be changed with the port
|
||||
argument.
|
||||
|
||||
"""
|
||||
commands = {"start", "stop", "restart", "list"}
|
||||
|
||||
# if command unknown make start our default command
|
||||
if command not in commands:
|
||||
command = "start"
|
||||
|
||||
# if port not an integer make 8088 as default
|
||||
try:
|
||||
port = int(port)
|
||||
except:
|
||||
port = int(plugin.options['donation-web-port']['value'])
|
||||
|
||||
if command == "list":
|
||||
return "servers running on the following ports: {}".format(list(jobs.keys()))
|
||||
|
||||
if command == "start":
|
||||
if port in jobs:
|
||||
return "Server already running on port {}. Maybe restart the server?".format(port)
|
||||
suc = start_server(port)
|
||||
if suc:
|
||||
return "started server successfully on port {}".format(port)
|
||||
else:
|
||||
return "Could not start server on port {}".format(port)
|
||||
|
||||
if command == "stop":
|
||||
if stop_server(port):
|
||||
return "stopped server on port{}".format(port)
|
||||
else:
|
||||
return "could not stop the server"
|
||||
|
||||
if command == "restart":
|
||||
stop_server(port)
|
||||
suc = start_server(port)
|
||||
if suc:
|
||||
return "started server successfully on port {}".format(port)
|
||||
else:
|
||||
return "Could not start server on port {}".format(port)
|
||||
|
||||
plugin.add_option(
|
||||
'donation-autostart',
|
||||
'true',
|
||||
'Should the donation server start automatically'
|
||||
)
|
||||
|
||||
plugin.add_option(
|
||||
'donation-web-port',
|
||||
'33506',
|
||||
'Which port should the donation server listen to?'
|
||||
)
|
||||
|
||||
|
||||
@plugin.init()
|
||||
def init(options, configuration, plugin):
|
||||
port = int(options['donation-web-port'])
|
||||
|
||||
if options['donation-autostart'].lower() in ['true', '1']:
|
||||
start_server(port)
|
||||
|
||||
|
||||
plugin.run()
|
||||
4
donations/requirements.txt
Normal file
4
donations/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
qrcode==6.1
|
||||
flask-bootstrap==3.3.7.1
|
||||
flask-wtf==0.14.2
|
||||
pillow==5.4.1
|
||||
79
donations/templates/donation.html
Normal file
79
donations/templates/donation.html
Normal file
@@ -0,0 +1,79 @@
|
||||
{% extends 'bootstrap/base.html' %}
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% block title %}
|
||||
Lightning Donations
|
||||
{% endblock %}
|
||||
|
||||
{% block navbar %}
|
||||
<nav class="navbar navbar-default">
|
||||
... navigation bar here (see complete code on GitHub) ...
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h1>Leave a donation to support my work!</h1>
|
||||
{% if bolt11 %}
|
||||
<div id="target_div">
|
||||
<div>
|
||||
<input type="text" value="{{bolt11}}" id="bolt11">
|
||||
<button onclick="copyFunction()">Copy invoice</button>
|
||||
</div>
|
||||
<div>
|
||||
<img src="data:image/png;base64,{{qr}}" />
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ wtf.quick_form(form) }}
|
||||
{% endif %}
|
||||
<h2>Most recent donations & comments</h2>
|
||||
<ul>
|
||||
{% for item in donations %}
|
||||
<li>{{ item[1] }} Satoshi. Message: {{ item[2] }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p>The above texts come from a community of unknown users. If you think they violate against your copyrite please <a href="https://www.rene-pickhardt.de/imprint" rel="nofollow"> contact me</a> so that I can remove those comments. According to the German law I am not responsible for Copyright violations rising from user generated content unless you notify me and I don't react.</p>
|
||||
|
||||
<hr>
|
||||
<p>
|
||||
c-lightning invoice query service for donations and spontanious payments is brought to you by
|
||||
<a href="https://ln.rene-pickhardt.de">Rene Pickhardt</a>.</p>
|
||||
|
||||
<p>
|
||||
If you want to learn more about the Lightning network (for beginners and for developers) check out
|
||||
<a href="https://www.youtube.com/user/RenePickhardt">his youtube channel</a>.
|
||||
</p>
|
||||
<p>
|
||||
Find the source code for this plugin at: <a href="https://github.com/ElementsProject/lightning/tree/master/contrib/plugins/donations">https://github.com/ElementsProject/lightning/tree/master/contrib/plugins/donations</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
{{super()}}
|
||||
<script>
|
||||
var interval = null;
|
||||
$(document).on('ready',function(){
|
||||
interval = setInterval(updateDiv,3000);
|
||||
});
|
||||
|
||||
function updateDiv(){
|
||||
$.ajax({
|
||||
url: '/is_invoice_paid/{{label}}',
|
||||
success: function(data){
|
||||
if (data != "waiting") {
|
||||
var tc = document.getElementById("target_div");
|
||||
tc.innerHTML = data;
|
||||
clearInterval(interval);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function copyFunction() {
|
||||
document.getElementById("bolt11").select();
|
||||
document.execCommand("copy");
|
||||
alert("Copied invoice to clipboard.");
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user