diff --git a/lnbits/extensions/tipjar/README.md b/lnbits/extensions/tipjar/README.md deleted file mode 100644 index 4965ec93..00000000 --- a/lnbits/extensions/tipjar/README.md +++ /dev/null @@ -1,15 +0,0 @@ -

Tip Jars

-

Accept tips in Bitcoin, with small messages attached!

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

How to set it up

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

And that's it already! Let the sats flow!

diff --git a/lnbits/extensions/tipjar/__init__.py b/lnbits/extensions/tipjar/__init__.py deleted file mode 100644 index b7e2b967..00000000 --- a/lnbits/extensions/tipjar/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -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 diff --git a/lnbits/extensions/tipjar/config.json b/lnbits/extensions/tipjar/config.json deleted file mode 100644 index 90f634ed..00000000 --- a/lnbits/extensions/tipjar/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Tip Jar", - "short_description": "Accept Bitcoin donations, with messages attached!", - "tile": "/tipjar/static/image/tipjar.png", - "contributors": ["Fittiboy"] -} diff --git a/lnbits/extensions/tipjar/crud.py b/lnbits/extensions/tipjar/crud.py deleted file mode 100644 index 3ea45d0d..00000000 --- a/lnbits/extensions/tipjar/crud.py +++ /dev/null @@ -1,123 +0,0 @@ -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) diff --git a/lnbits/extensions/tipjar/migrations.py b/lnbits/extensions/tipjar/migrations.py deleted file mode 100644 index d8f6da3f..00000000 --- a/lnbits/extensions/tipjar/migrations.py +++ /dev/null @@ -1,27 +0,0 @@ -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) - ); - """ - ) diff --git a/lnbits/extensions/tipjar/models.py b/lnbits/extensions/tipjar/models.py deleted file mode 100644 index 655888da..00000000 --- a/lnbits/extensions/tipjar/models.py +++ /dev/null @@ -1,56 +0,0 @@ -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)) diff --git a/lnbits/extensions/tipjar/static/image/tipjar.png b/lnbits/extensions/tipjar/static/image/tipjar.png deleted file mode 100644 index 6f0d69b7..00000000 Binary files a/lnbits/extensions/tipjar/static/image/tipjar.png and /dev/null differ diff --git a/lnbits/extensions/tipjar/templates/tipjar/_api_docs.html b/lnbits/extensions/tipjar/templates/tipjar/_api_docs.html deleted file mode 100644 index 2fd8cf08..00000000 --- a/lnbits/extensions/tipjar/templates/tipjar/_api_docs.html +++ /dev/null @@ -1,19 +0,0 @@ - - -

- Tip Jar: Receive tips with messages! -

-

- Your personal Bitcoin tip page, which supports lightning and on-chain - payments. Notifications, including a donation message, can be sent via - webhook. - - Created by, - Fitti -

-
- -
diff --git a/lnbits/extensions/tipjar/templates/tipjar/display.html b/lnbits/extensions/tipjar/templates/tipjar/display.html deleted file mode 100644 index 80e5c6fe..00000000 --- a/lnbits/extensions/tipjar/templates/tipjar/display.html +++ /dev/null @@ -1,94 +0,0 @@ -{% extends "public.html" %} {% block page %} -
-
- - -
Tip {{ donatee }} some sats!
-
- - - - -
- Submit -
-
-
-
-
-
- -{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/lnbits/extensions/tipjar/templates/tipjar/index.html b/lnbits/extensions/tipjar/templates/tipjar/index.html deleted file mode 100644 index 19fca6e4..00000000 --- a/lnbits/extensions/tipjar/templates/tipjar/index.html +++ /dev/null @@ -1,443 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} -
-
- - - New TipJar - - - - - -
-
-
TipJars
-
-
- Export to CSV -
-
- - {% raw %} - - - {% endraw %} - -
-
- - - -
-
-
Tips
-
-
- Export to CSV -
-
- - {% raw %} - - - {% endraw %} - -
-
-
-
- - -
- {{SITE_TITLE}} TipJar extension -
-
- - - {% include "tipjar/_api_docs.html" %} - -
-
- - - - - - -
-
-
- -
-
- - - Watch-Only extension MUST be activated and have a wallet - - -
-
-
-
- -
- - -
- Update TipJar - - Create TipJar - Cancel -
-
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(user) }} - -{% endblock %} diff --git a/lnbits/extensions/tipjar/views.py b/lnbits/extensions/tipjar/views.py deleted file mode 100644 index ddb1b63c..00000000 --- a/lnbits/extensions/tipjar/views.py +++ /dev/null @@ -1,35 +0,0 @@ -from http import HTTPStatus - -from fastapi import Depends, Query, Request -from fastapi.templating import Jinja2Templates -from starlette.exceptions import HTTPException - -from lnbits.core.models import User -from lnbits.decorators import check_user_exists - -from . import tipjar_ext, tipjar_renderer -from .crud import get_tipjar - -templates = Jinja2Templates(directory="templates") - - -@tipjar_ext.get("/") -async def index(request: Request, user: User = Depends(check_user_exists)): - return tipjar_renderer().TemplateResponse( - "tipjar/index.html", {"request": request, "user": user.dict()} - ) - - -@tipjar_ext.get("/{tipjar_id}") -async def tip(request: Request, tipjar_id: int = Query(None)): - """Return the donation form for the Tipjar corresponding to id""" - tipjar = await get_tipjar(tipjar_id) - if not tipjar: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="TipJar does not exist." - ) - - return tipjar_renderer().TemplateResponse( - "tipjar/display.html", - {"request": request, "donatee": tipjar.name, "tipjar": tipjar.id}, - ) diff --git a/lnbits/extensions/tipjar/views_api.py b/lnbits/extensions/tipjar/views_api.py deleted file mode 100644 index 7d420fae..00000000 --- a/lnbits/extensions/tipjar/views_api.py +++ /dev/null @@ -1,220 +0,0 @@ -from http import HTTPStatus - -from fastapi import Depends, Query -from starlette.exceptions import HTTPException - -from lnbits.core.crud import get_user, get_wallet -from lnbits.decorators import WalletTypeInfo, get_key_type - -# todo: use the API, not direct import -from ..satspay.crud import create_charge # type: ignore -from ..satspay.models import CreateCharge # type: ignore -from . import tipjar_ext -from .crud import ( - create_tip, - create_tipjar, - delete_tip, - delete_tipjar, - get_tip, - get_tipjar, - get_tipjars, - get_tips, - update_tip, - update_tipjar, -) -from .models import createTip, createTipJar, createTips - - -@tipjar_ext.post("/api/v1/tipjars") -async def api_create_tipjar(data: createTipJar): - """Create a tipjar, which holds data about how/where to post tips""" - try: - tipjar = await create_tipjar(data) - except Exception as e: - raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) - - return tipjar.dict() - - -async def user_from_wallet(wallet: WalletTypeInfo = Depends(get_key_type)): - return wallet.wallet.user - - -@tipjar_ext.post("/api/v1/tips") -async def api_create_tip(data: createTips): - """Take data from tip form and return satspay charge""" - sats = int(data.sats) - message = data.message - if not message: - message = "No message" - tipjar_id = int(data.tipjar) - tipjar = await get_tipjar(tipjar_id) - if not tipjar: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Tipjar does not exist." - ) - - wallet_id = tipjar.wallet - wallet = await get_wallet(wallet_id) - if not wallet: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Tipjar wallet does not exist." - ) - - name = data.name - - # Ensure that description string can be split reliably - name = name.replace('"', "''") - if not name: - name = "Anonymous" - - description = f"{name}: {message}" - charge = await create_charge( - user=wallet.user, - data=CreateCharge( - amount=sats, - webhook=tipjar.webhook or "", - description=description, - onchainwallet=tipjar.onchain or "", - lnbitswallet=tipjar.wallet, - completelink="/tipjar/" + str(tipjar_id), - completelinktext="Thanks for the tip!", - time=1440, - custom_css="", - ), - ) - - await create_tip( - id=charge.id, - wallet=tipjar.wallet, - message=message, - name=name, - sats=int(data.sats), - tipjar=data.tipjar, - ) - - return {"redirect_url": f"/satspay/{charge.id}"} - - -@tipjar_ext.get("/api/v1/tipjars") -async def api_get_tipjars(wallet: WalletTypeInfo = Depends(get_key_type)): - """Return list of all tipjars assigned to wallet with given invoice key""" - user = await get_user(wallet.wallet.user) - if not user: - return [] - tipjars = [] - for wallet_id in user.wallet_ids: - new_tipjars = await get_tipjars(wallet_id) - tipjars += new_tipjars if new_tipjars else [] - return [tipjar.dict() for tipjar in tipjars] - - -@tipjar_ext.get("/api/v1/tips") -async def api_get_tips(wallet: WalletTypeInfo = Depends(get_key_type)): - """Return list of all tips assigned to wallet with given invoice key""" - user = await get_user(wallet.wallet.user) - if not user: - return [] - tips = [] - for wallet_id in user.wallet_ids: - new_tips = await get_tips(wallet_id) - tips += new_tips if new_tips else [] - return [tip.dict() for tip in tips] - - -@tipjar_ext.put("/api/v1/tips/{tip_id}") -async def api_update_tip( - data: createTip, - wallet: WalletTypeInfo = Depends(get_key_type), - tip_id: str = Query(None), -): - """Update a tip with the data given in the request""" - if tip_id: - tip = await get_tip(tip_id) - - if not tip: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Tip does not exist." - ) - - if tip.wallet != wallet.wallet.id: - - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not your tip." - ) - - tip = await update_tip(tip_id, **data.dict()) - else: - raise HTTPException( - status_code=HTTPStatus.BAD_REQUEST, detail="No tip ID specified" - ) - return tip.dict() - - -@tipjar_ext.put("/api/v1/tipjars/{tipjar_id}") -async def api_update_tipjar( - data: createTipJar, - wallet: WalletTypeInfo = Depends(get_key_type), - tipjar_id: int = Query(None), -): - """Update a tipjar with the data given in the request""" - if tipjar_id: - tipjar = await get_tipjar(tipjar_id) - - if not tipjar: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="TipJar does not exist." - ) - - if tipjar.wallet != wallet.wallet.id: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not your tipjar." - ) - - tipjar = await update_tipjar(str(tipjar_id), **data.dict()) - else: - raise HTTPException( - status_code=HTTPStatus.BAD_REQUEST, detail="No tipjar ID specified" - ) - return tipjar.dict() - - -@tipjar_ext.delete("/api/v1/tips/{tip_id}") -async def api_delete_tip( - wallet: WalletTypeInfo = Depends(get_key_type), tip_id: str = Query(None) -): - """Delete the tip with the given tip_id""" - tip = await get_tip(tip_id) - if not tip: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="No tip with this ID!" - ) - if tip.wallet != wallet.wallet.id: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, - detail="Not authorized to delete this tip!", - ) - await delete_tip(tip_id) - - return "", HTTPStatus.NO_CONTENT - - -@tipjar_ext.delete("/api/v1/tipjars/{tipjar_id}") -async def api_delete_tipjar( - wallet: WalletTypeInfo = Depends(get_key_type), tipjar_id: int = Query(None) -): - """Delete the tipjar with the given tipjar_id""" - tipjar = await get_tipjar(tipjar_id) - if not tipjar: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="No tipjar with this ID!" - ) - if tipjar.wallet != wallet.wallet.id: - - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, - detail="Not authorized to delete this tipjar!", - ) - await delete_tipjar(tipjar_id) - - return "", HTTPStatus.NO_CONTENT