-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
1,082 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
name: release github version | ||
on: | ||
push: | ||
tags: | ||
- "[0-9]+.[0-9]+" | ||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Create GitHub Release | ||
id: create_release | ||
uses: actions/create-release@v1 | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
with: | ||
tag_name: ${{ github.ref }} | ||
release_name: ${{ github.ref }} | ||
draft: false | ||
prerelease: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<h1>Tip Jars</h1> | ||
<h2>Accept tips in Bitcoin, with small messages attached!</h2> | ||
The TipJar extension allows you to integrate Bitcoin Lightning (and on-chain) tips into your website or social media! | ||
|
||
![image](https://user-images.githubusercontent.com/28876473/134997129-c2f3f13c-a65d-42ed-a9c4-8a1da569d74f.png) | ||
|
||
<h2>How to set it up</h2> | ||
|
||
1. Simply create a new Tip Jar with the desired details (onchain optional): | ||
![image](https://user-images.githubusercontent.com/28876473/134996842-ec2f2783-2eef-4671-8eaf-023713865512.png) | ||
1. Share the URL you get from this little button: | ||
![image](https://user-images.githubusercontent.com/28876473/134996973-f8ed4632-ea2f-4b62-83f1-1e4c6b6c91fa.png) | ||
|
||
|
||
<h3>And that's it already! Let the sats flow!</h3> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
from fastapi import APIRouter | ||
from fastapi.staticfiles import StaticFiles | ||
|
||
from lnbits.db import Database | ||
from lnbits.helpers import template_renderer | ||
|
||
db = Database("ext_tipjar") | ||
|
||
tipjar_ext: APIRouter = APIRouter(prefix="/tipjar", tags=["tipjar"]) | ||
|
||
tipjar_static_files = [ | ||
{ | ||
"path": "/tipjar/static", | ||
"app": StaticFiles(directory="lnbits/extensions/tipjar/static"), | ||
"name": "tipjar_static", | ||
} | ||
] | ||
|
||
|
||
def tipjar_renderer(): | ||
return template_renderer(["lnbits/extensions/tipjar/templates"]) | ||
|
||
|
||
from .views import * # noqa: F401,F403 | ||
from .views_api import * # noqa: F401,F403 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"name": "Tip Jar", | ||
"short_description": "Accept Bitcoin donations, with messages attached!", | ||
"tile": "/tipjar/static/image/tipjar.png", | ||
"contributors": ["Fittiboy"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
from typing import Optional | ||
|
||
from lnbits.db import SQLITE | ||
|
||
# todo: use the API, not direct import | ||
from ..satspay.crud import delete_charge # type: ignore | ||
from . import db | ||
from .models import Tip, TipJar, createTipJar | ||
|
||
|
||
async def create_tip( | ||
id: str, wallet: str, message: str, name: str, sats: int, tipjar: str | ||
) -> Tip: | ||
"""Create a new Tip""" | ||
await db.execute( | ||
""" | ||
INSERT INTO tipjar.Tips ( | ||
id, | ||
wallet, | ||
name, | ||
message, | ||
sats, | ||
tipjar | ||
) | ||
VALUES (?, ?, ?, ?, ?, ?) | ||
""", | ||
(id, wallet, name, message, sats, tipjar), | ||
) | ||
|
||
tip = await get_tip(id) | ||
assert tip, "Newly created tip couldn't be retrieved" | ||
return tip | ||
|
||
|
||
async def create_tipjar(data: createTipJar) -> TipJar: | ||
"""Create a new TipJar""" | ||
|
||
returning = "" if db.type == SQLITE else "RETURNING ID" | ||
method = db.execute if db.type == SQLITE else db.fetchone | ||
|
||
result = await (method)( | ||
f""" | ||
INSERT INTO tipjar.TipJars ( | ||
name, | ||
wallet, | ||
webhook, | ||
onchain | ||
) | ||
VALUES (?, ?, ?, ?) | ||
{returning} | ||
""", | ||
(data.name, data.wallet, data.webhook, data.onchain), | ||
) | ||
if db.type == SQLITE: | ||
tipjar_id = result._result_proxy.lastrowid | ||
else: | ||
tipjar_id = result[0] | ||
|
||
tipjar = await get_tipjar(tipjar_id) | ||
assert tipjar | ||
return tipjar | ||
|
||
|
||
async def get_tipjar(tipjar_id: int) -> Optional[TipJar]: | ||
"""Return a tipjar by ID""" | ||
row = await db.fetchone("SELECT * FROM tipjar.TipJars WHERE id = ?", (tipjar_id,)) | ||
return TipJar(**row) if row else None | ||
|
||
|
||
async def get_tipjars(wallet_id: str) -> Optional[list]: | ||
"""Return all TipJars belonging assigned to the wallet_id""" | ||
rows = await db.fetchall( | ||
"SELECT * FROM tipjar.TipJars WHERE wallet = ?", (wallet_id,) | ||
) | ||
return [TipJar(**row) for row in rows] if rows else None | ||
|
||
|
||
async def delete_tipjar(tipjar_id: int) -> None: | ||
"""Delete a TipJar and all corresponding Tips""" | ||
rows = await db.fetchall("SELECT * FROM tipjar.Tips WHERE tipjar = ?", (tipjar_id,)) | ||
for row in rows: | ||
await delete_tip(row["id"]) | ||
await db.execute("DELETE FROM tipjar.TipJars WHERE id = ?", (tipjar_id,)) | ||
|
||
|
||
async def get_tip(tip_id: str) -> Optional[Tip]: | ||
"""Return a Tip""" | ||
row = await db.fetchone("SELECT * FROM tipjar.Tips WHERE id = ?", (tip_id,)) | ||
return Tip(**row) if row else None | ||
|
||
|
||
async def get_tips(wallet_id: str) -> Optional[list]: | ||
"""Return all Tips assigned to wallet_id""" | ||
rows = await db.fetchall("SELECT * FROM tipjar.Tips WHERE wallet = ?", (wallet_id,)) | ||
return [Tip(**row) for row in rows] if rows else None | ||
|
||
|
||
async def delete_tip(tip_id: str) -> None: | ||
"""Delete a Tip and its corresponding statspay charge""" | ||
await db.execute("DELETE FROM tipjar.Tips WHERE id = ?", (tip_id,)) | ||
await delete_charge(tip_id) | ||
|
||
|
||
async def update_tip(tip_id: str, **kwargs) -> Tip: | ||
"""Update a Tip""" | ||
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) | ||
await db.execute( | ||
f"UPDATE tipjar.Tips SET {q} WHERE id = ?", (*kwargs.values(), tip_id) | ||
) | ||
row = await db.fetchone("SELECT * FROM tipjar.Tips WHERE id = ?", (tip_id,)) | ||
assert row, "Newly updated tip couldn't be retrieved" | ||
return Tip(**row) | ||
|
||
|
||
async def update_tipjar(tipjar_id: str, **kwargs) -> TipJar: | ||
"""Update a tipjar""" | ||
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) | ||
await db.execute( | ||
f"UPDATE tipjar.TipJars SET {q} WHERE id = ?", (*kwargs.values(), tipjar_id) | ||
) | ||
row = await db.fetchone("SELECT * FROM tipjar.TipJars WHERE id = ?", (tipjar_id,)) | ||
assert row, "Newly updated tipjar couldn't be retrieved" | ||
return TipJar(**row) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
async def m001_initial(db): | ||
|
||
await db.execute( | ||
f""" | ||
CREATE TABLE IF NOT EXISTS tipjar.TipJars ( | ||
id {db.serial_primary_key}, | ||
name TEXT NOT NULL, | ||
wallet TEXT NOT NULL, | ||
onchain TEXT, | ||
webhook TEXT | ||
); | ||
""" | ||
) | ||
|
||
await db.execute( | ||
f""" | ||
CREATE TABLE IF NOT EXISTS tipjar.Tips ( | ||
id TEXT PRIMARY KEY, | ||
wallet TEXT NOT NULL, | ||
name TEXT NOT NULL, | ||
message TEXT NOT NULL, | ||
sats {db.big_int} NOT NULL, | ||
tipjar {db.big_int} NOT NULL, | ||
FOREIGN KEY(tipjar) REFERENCES {db.references_schema}TipJars(id) | ||
); | ||
""" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
from sqlite3 import Row | ||
from typing import Optional | ||
|
||
from pydantic import BaseModel | ||
|
||
|
||
class createTip(BaseModel): | ||
id: str | ||
wallet: str | ||
sats: int | ||
tipjar: int | ||
name: str = "Anonymous" | ||
message: str = "" | ||
|
||
|
||
class Tip(BaseModel): | ||
"""A Tip represents a single donation""" | ||
|
||
id: str # This ID always corresponds to a satspay charge ID | ||
wallet: str | ||
name: str # Name of the donor | ||
message: str # Donation message | ||
sats: int | ||
tipjar: int # The ID of the corresponding tip jar | ||
|
||
@classmethod | ||
def from_row(cls, row: Row) -> "Tip": | ||
return cls(**dict(row)) | ||
|
||
|
||
class createTipJar(BaseModel): | ||
name: str | ||
wallet: str | ||
webhook: Optional[str] | ||
onchain: Optional[str] | ||
|
||
|
||
class createTips(BaseModel): | ||
name: str | ||
sats: str | ||
tipjar: str | ||
message: str | ||
|
||
|
||
class TipJar(BaseModel): | ||
"""A TipJar represents a user's tip jar""" | ||
|
||
id: int | ||
name: str # The name of the donatee | ||
wallet: str # Lightning wallet | ||
onchain: Optional[str] # Watchonly wallet | ||
webhook: Optional[str] # URL to POST tips to | ||
|
||
@classmethod | ||
def from_row(cls, row: Row) -> "TipJar": | ||
return cls(**dict(row)) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<q-card> | ||
<q-card-section> | ||
<h4 class="text-subtitle1 q-my-none"> | ||
Tip Jar: Receive tips with messages! | ||
</h4> | ||
<p> | ||
Your personal Bitcoin tip page, which supports lightning and on-chain | ||
payments. Notifications, including a donation message, can be sent via | ||
webhook. | ||
<small> | ||
Created by, | ||
<a class="text-secondary" href="https://github.com/Fittiboy" | ||
>Fitti</a | ||
></small | ||
> | ||
</p> | ||
</q-card-section> | ||
<q-btn flat label="Swagger API" type="a" href="../docs#/tipjar"></q-btn> | ||
</q-card> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
{% extends "public.html" %} {% 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"> | ||
<h5 class="q-my-none">Tip {{ donatee }} some sats!</h5> | ||
<br /> | ||
<q-form @submit="Invoice()" class="q-gutter-md"> | ||
<q-input | ||
filled | ||
dense | ||
v-model.trim="tipDialog.data.name" | ||
maxlength="25" | ||
type="name" | ||
label="Your Name (or contact info, leave blank for anonymous tip)" | ||
></q-input> | ||
<q-input | ||
filled | ||
dense | ||
v-model.number="tipDialog.data.sats" | ||
type="number" | ||
min="1" | ||
max="2100000000000000" | ||
suffix="sats" | ||
:rules="[val => val > 0 || 'Choose a positive number of sats!']" | ||
label="Amount of sats" | ||
></q-input> | ||
<q-input | ||
filled | ||
dense | ||
v-model.trim="tipDialog.data.message" | ||
maxlength="144" | ||
type="textarea" | ||
label="Tip Message (you can leave this blank too)" | ||
></q-input> | ||
<div class="row q-mt-lg"> | ||
<q-btn | ||
unelevated | ||
color="primary" | ||
:disable="tipDialog.data.sats < 1 || !tipDialog.data.sats" | ||
type="submit" | ||
>Submit</q-btn | ||
> | ||
</div> | ||
</q-form> | ||
</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 { | ||
paymentReq: null, | ||
redirectUrl: null, | ||
tipDialog: { | ||
show: false, | ||
data: { | ||
name: '', | ||
sats: '', | ||
message: '' | ||
} | ||
} | ||
} | ||
}, | ||
|
||
methods: { | ||
Invoice: function () { | ||
var self = this | ||
console.log('{{ tipjar }}') | ||
axios | ||
.post('/tipjar/api/v1/tips', { | ||
tipjar: '{{ tipjar }}', | ||
name: self.tipDialog.data.name, | ||
sats: self.tipDialog.data.sats, | ||
message: self.tipDialog.data.message | ||
}) | ||
.then(function (response) { | ||
console.log(response.data) | ||
self.redirect_url = response.data.redirect_url | ||
console.log(self.redirect_url) | ||
window.location.href = self.redirect_url | ||
}) | ||
} | ||
} | ||
}) | ||
</script> | ||
{% endblock %} |
Oops, something went wrong.