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!
-
-
-
-How to set it up
-
-1. Simply create a new Tip Jar with the desired details (onchain optional):
-
-1. Share the URL you get from this little button:
-
-
-
-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 %}
-
-
-
-
- {{ col.label }}
-
-
-
-
-
-
-
-
-
-
- {{ col.value }}
-
-
-
-
-
-
- {% endraw %}
-
-
-
-
-
-
-
-
-
Tips
-
-
- Export to CSV
-
-
-
- {% raw %}
-
-
-
- {{ col.label }}
-
-
-
-
-
-
- {{ col.value }}
-
-
-
-
-
-
- {% 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