Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
dni committed Feb 17, 2023
0 parents commit edfadb7
Show file tree
Hide file tree
Showing 17 changed files with 918 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/release.yml
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Scrub

## Automatically forward funds (Scrub) that get paid to the wallet to an LNURLpay or Lightning Address

SCRUB is a small but handy extension that allows a user to take advantage of all the functionalities inside **LNbits** and upon a payment received to your LNbits wallet, automatically forward it to your desired wallet via LNURL or LNAddress!

<small>Only whole values, integers, are Scrubbed, amounts will be rounded down (example: 6.3 will be 6)! The decimals, if existing, will be kept in your wallet!</small>

[**Wallets supporting LNURL**](https://github.com/fiatjaf/awesome-lnurl#wallets)

## Usage

1. Create an scrub (New Scrub link)\
![create scrub](https://i.imgur.com/LUeNkzM.jpg)

- select the wallet to be _scrubbed_
- make a small description
- enter either an LNURL pay or a lightning address

Make sure your LNURL or LNaddress is correct!

2. A new scrub will show on the _Scrub links_ section\
![scrub](https://i.imgur.com/LNoFkeu.jpg)

- only one scrub can be created for each wallet!
- You can _edit_ or _delete_ the Scrub at any time\
![edit scrub](https://i.imgur.com/Qu65lGG.jpg)

3. On your wallet, you'll see a transaction of a payment received and another right after it as apayment sent, marked with **#scrubed**\
![wallet view](https://i.imgur.com/S6EWWCP.jpg)
38 changes: 38 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import asyncio

from fastapi import APIRouter
from fastapi.staticfiles import StaticFiles
from typing import List

from lnbits.db import Database
from lnbits.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart

db = Database("ext_scrub")

scheduled_tasks: List[asyncio.Task] = []

scrub_static_files = [
{
"path": "/scrub/static",
"app": StaticFiles(directory="lnbits/extensions/scrub/static"),
"name": "scrub_static",
}
]

scrub_ext: APIRouter = APIRouter(prefix="/scrub", tags=["scrub"])


def scrub_renderer():
return template_renderer(["lnbits/extensions/scrub/templates"])


from .tasks import wait_for_paid_invoices
from .views import * # noqa: F401,F403
from .views_api import * # noqa: F401,F403


def scrub_start():
loop = asyncio.get_event_loop()
task = loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
scheduled_tasks.append(task)
6 changes: 6 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "Scrub",
"short_description": "Pass payments to LNURLp/LNaddress",
"tile": "/scrub/static/image/scrub.png",
"contributors": ["arcbtc", "talvasconcelos"]
}
80 changes: 80 additions & 0 deletions crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from typing import List, Optional, Union

from lnbits.helpers import urlsafe_short_hash

from . import db
from .models import CreateScrubLink, ScrubLink


async def create_scrub_link(data: CreateScrubLink) -> ScrubLink:
scrub_id = urlsafe_short_hash()
await db.execute(
"""
INSERT INTO scrub.scrub_links (
id,
wallet,
description,
payoraddress
)
VALUES (?, ?, ?, ?)
""",
(
scrub_id,
data.wallet,
data.description,
data.payoraddress,
),
)
link = await get_scrub_link(scrub_id)
assert link, "Newly created link couldn't be retrieved"
return link


async def get_scrub_link(link_id: str) -> Optional[ScrubLink]:
row = await db.fetchone("SELECT * FROM scrub.scrub_links WHERE id = ?", (link_id,))
return ScrubLink(**row) if row else None


async def get_scrub_links(wallet_ids: Union[str, List[str]]) -> List[ScrubLink]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]

q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(
f"""
SELECT * FROM scrub.scrub_links WHERE wallet IN ({q})
ORDER BY id
""",
(*wallet_ids,),
)
return [ScrubLink(**row) for row in rows]


async def update_scrub_link(link_id: int, **kwargs) -> Optional[ScrubLink]:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute(
f"UPDATE scrub.scrub_links SET {q} WHERE id = ?",
(*kwargs.values(), link_id),
)
row = await db.fetchone("SELECT * FROM scrub.scrub_links WHERE id = ?", (link_id,))
return ScrubLink(**row) if row else None


async def delete_scrub_link(link_id: int) -> None:
await db.execute("DELETE FROM scrub.scrub_links WHERE id = ?", (link_id,))


async def get_scrub_by_wallet(wallet_id) -> Optional[ScrubLink]:
row = await db.fetchone(
"SELECT * from scrub.scrub_links WHERE wallet = ?",
(wallet_id,),
)
return ScrubLink(**row) if row else None


async def unique_scrubed_wallet(wallet_id):
(row,) = await db.fetchone(
"SELECT COUNT(wallet) FROM scrub.scrub_links WHERE wallet = ?",
(wallet_id,),
)
return row
9 changes: 9 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"repos": [
{
"id": "scrub",
"organisation": "lnbits",
"repository": "scrub"
}
]
}
14 changes: 14 additions & 0 deletions migrations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
async def m001_initial(db):
"""
Initial scrub table.
"""
await db.execute(
"""
CREATE TABLE scrub.scrub_links (
id TEXT PRIMARY KEY,
wallet TEXT NOT NULL,
description TEXT NOT NULL,
payoraddress TEXT NOT NULL
);
"""
)
28 changes: 28 additions & 0 deletions models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from sqlite3 import Row

from pydantic import BaseModel
from starlette.requests import Request

from lnbits.lnurl import encode as lnurl_encode


class CreateScrubLink(BaseModel):
wallet: str
description: str
payoraddress: str


class ScrubLink(BaseModel):
id: str
wallet: str
description: str
payoraddress: str

@classmethod
def from_row(cls, row: Row) -> "ScrubLink":
data = dict(row)
return cls(**data)

def lnurl(self, req: Request) -> str:
url = req.url_for("scrub.api_lnurl_response", link_id=self.id)
return lnurl_encode(url)
Binary file added static/image/scrub.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
143 changes: 143 additions & 0 deletions static/js/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/* globals Quasar, Vue, _, VueQrcode, windowMixin, LNbits, LOCALE */

Vue.component(VueQrcode.name, VueQrcode)

var locationPath = [
window.location.protocol,
'//',
window.location.host,
window.location.pathname
].join('')

var mapScrubLink = obj => {
obj._data = _.clone(obj)
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
obj.amount = new Intl.NumberFormat(LOCALE).format(obj.amount)
obj.print_url = [locationPath, 'print/', obj.id].join('')
obj.pay_url = [locationPath, obj.id].join('')
return obj
}

new Vue({
el: '#vue',
mixins: [windowMixin],
data() {
return {
checker: null,
payLinks: [],
payLinksTable: {
pagination: {
rowsPerPage: 10
}
},
formDialog: {
show: false,
data: {}
},
qrCodeDialog: {
show: false,
data: null
}
}
},
methods: {
getScrubLinks() {
LNbits.api
.request(
'GET',
'/scrub/api/v1/links?all_wallets=true',
this.g.user.wallets[0].inkey
)
.then(response => {
this.payLinks = response.data.map(mapScrubLink)
})
.catch(err => {
clearInterval(this.checker)
LNbits.utils.notifyApiError(err)
})
},
closeFormDialog() {
this.resetFormData()
},
openUpdateDialog(linkId) {
const link = _.findWhere(this.payLinks, {id: linkId})

this.formDialog.data = _.clone(link._data)
this.formDialog.show = true
},
sendFormData() {
const wallet = _.findWhere(this.g.user.wallets, {
id: this.formDialog.data.wallet
})
let data = Object.freeze(this.formDialog.data)
console.log(wallet, data)

if (data.id) {
this.updateScrubLink(wallet, data)
} else {
this.createScrubLink(wallet, data)
}
},
resetFormData() {
this.formDialog = {
show: false,
data: {}
}
},
updateScrubLink(wallet, data) {
LNbits.api
.request('PUT', '/scrub/api/v1/links/' + data.id, wallet.adminkey, data)
.then(response => {
this.payLinks = _.reject(this.payLinks, obj => obj.id === data.id)
this.payLinks.push(mapScrubLink(response.data))
this.formDialog.show = false
this.resetFormData()
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
},
createScrubLink(wallet, data) {
LNbits.api
.request('POST', '/scrub/api/v1/links', wallet.adminkey, data)
.then(response => {
console.log('RES', response)
this.getScrubLinks()
this.formDialog.show = false
this.resetFormData()
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
},
deleteScrubLink(linkId) {
var link = _.findWhere(this.payLinks, {id: linkId})

LNbits.utils
.confirmDialog('Are you sure you want to delete this pay link?')
.onOk(() => {
LNbits.api
.request(
'DELETE',
'/scrub/api/v1/links/' + linkId,
_.findWhere(this.g.user.wallets, {id: link.wallet}).adminkey
)
.then(response => {
this.payLinks = _.reject(this.payLinks, obj => obj.id === linkId)
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
})
}
},
created() {
if (this.g.user.wallets.length) {
var getScrubLinks = this.getScrubLinks
getScrubLinks()
}
}
})
Loading

0 comments on commit edfadb7

Please sign in to comment.