Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ At the moment, the only service that has an open API to work with is Streamlabs,
For now, simply set the "Redirect URI" to `http://localhost`, you will change this soon.
Then, hit create:
![image](https://user-images.githubusercontent.com/28876473/127759264-ae91539a-5694-4096-a478-80eb02b7b594.png)
1. In LNbits, enable the Stream Alerts extension and optionally the SatsPayServer (to observe donations directly) and Onchain Wallet (watch-only) (to accept on-chain donations) extenions:
1. In LNbits, enable Stream Alerts and SatsPay. To accept onchain donations, select a core LNbits onchain wallet or enable Watchonly and select a Watchonly wallet. The wallet must match SatsPay's network:
![image](https://user-images.githubusercontent.com/28876473/127759486-0e3420c2-c498-4bf9-932e-0abfa17bd478.png)
1. Create a "NEW SERVICE" using the button. Fill in all the information (you get your Client ID and Secret from the Streamlabs App page):
![image](https://user-images.githubusercontent.com/28876473/127759512-8e8b4e90-2a64-422a-bf0a-5508d0630bed.png)
Expand Down
4 changes: 3 additions & 1 deletion crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,13 @@ async def post_donation(donation_id: str) -> dict:
return {"message": "StreamElements not yet supported!"}
else:
return {"message": "Unsopported servicename"}
response.raise_for_status()
result = response.json()
await db.execute(
"UPDATE streamalerts.donations SET posted = :posted WHERE id = :id",
{"id": donation_id, "posted": True},
)
return response.json()
return result


async def create_service(data: CreateService) -> Service:
Expand Down
9 changes: 9 additions & 0 deletions helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@
from .models import ChargeStatus


async def get_satspay_network() -> str:
async with httpx.AsyncClient() as client:
response = await client.get(
f"http://{settings.host}:{settings.port}/satspay/api/v1/settings/public"
)
response.raise_for_status()
return response.json()["network"]


async def create_charge(data: dict, api_key: str) -> str:
async with httpx.AsyncClient() as client:
headers = {"X-API-KEY": api_key}
Expand Down
186 changes: 154 additions & 32 deletions static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ window.app = Vue.createApp({
services: [],
donations: [],
walletLinks: [],
coreOnchainWallets: [],
loadingOnchainWallets: false,
servicesTable: {
columns: [
{
Expand All @@ -39,7 +41,7 @@ window.app = Vue.createApp({
{
name: 'onchain address',
align: 'left',
label: 'Onchain Address',
label: 'Onchain Wallet',
field: 'onchain'
},
{
Expand Down Expand Up @@ -106,21 +108,116 @@ window.app = Vue.createApp({
}
}
},
computed: {
editingService() {
return this.services.find(
service => service.id === this.serviceDialog.data.id
)
},
lightningWalletOptions() {
const walletIds = this.g.user.wallets
.filter(wallet => wallet.canReceivePayments)
.map(wallet => wallet.id)
return this.g.user.walletOptions.filter(wallet =>
walletIds.includes(wallet.value)
)
},
onchainWalletOptions() {
const core = this.coreOnchainWallets.map(wallet => ({
label: `LNbits: ${wallet.name}${wallet.status === 'ready' ? '' : ' (Not ready)'}`,
value: `core:${wallet.id}`
}))
const watchonly = this.walletLinks.map(wallet => ({
label: `Watchonly: ${wallet.title || wallet.id}`,
value: wallet.id
}))
return [...core, ...watchonly]
},
selectedCoreWallet() {
const reference = this.serviceDialog.data.onchain
if (!reference?.startsWith('core:')) return null
return (
this.coreOnchainWallets.find(w => w.id === reference.slice(5)) || null
)
},
hasCoreOnchainWallet() {
return this.g.user.wallets.some(wallet => wallet.walletType === 'onchain')
},
onchainSelectionInvalid() {
if (!this.serviceDialog.chain) return false
const reference = this.serviceDialog.data.onchain
if (reference && reference === this.editingService?.onchain) return false
return (
!this.onchainWalletOptions.some(option => option.value === reference) ||
(reference?.startsWith('core:') &&
this.selectedCoreWallet?.status !== 'ready')
)
}
},
methods: {
getWalletLinks() {
LNbits.api
.request(
async getWalletLinks() {
if (this.loadingOnchainWallets) return
this.loadingOnchainWallets = true
this.walletLinks = []
this.coreOnchainWallets = []
const inkey = this.g.user.wallets[0].inkey
try {
const {
data: {network}
} = await LNbits.api.request('GET', '/satspay/api/v1/settings/public')
const core = LNbits.api
.request('GET', '/satspay/api/v1/onchain-wallets', inkey)
.then(({data}) => {
this.coreOnchainWallets = data.filter(
wallet => wallet.network === network
)
})
.catch(LNbits.utils.notifyApiError)
if (this.g.user.extensions.includes('watchonly')) {
await Promise.all([core, this.getWatchonlyWallets(network, inkey)])
} else {
await core
}
} catch (error) {
LNbits.utils.notifyApiError(error)
} finally {
this.loadingOnchainWallets = false
}
},
async getWatchonlyWallets(network, inkey) {
try {
const {data: config} = await LNbits.api.request(
'GET',
'/watchonly/api/v1/wallet',
this.g.user.wallets[0].inkey
'/watchonly/api/v1/config',
inkey
)
.then(response => {
for (i = 0; i < response.data.length; i++) {
this.walletLinks.push(response.data[i].id)
}
if (config.network !== network) {
this.$q.notify({
type: 'warning',
message: `Watchonly must use the SatsPay network (${network}).`
})
return
})
.catch(LNbits.utils.notifyApiError)
}
const {data} = await LNbits.api.request(
'GET',
`/watchonly/api/v1/wallet?network=${encodeURIComponent(network)}`,
inkey
)
this.walletLinks = data
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
createOnchainWallet() {
this.g.newWalletType = 'onchain'
},
openOnchainWallet(walletId) {
this.$router.push({path: '/wallet', query: {wal: walletId}})
},
openServiceDialog() {
this.serviceDialog.data = {}
this.serviceDialog.chain = false
this.serviceDialog.show = true
},
getDonations() {
LNbits.api
Expand Down Expand Up @@ -173,33 +270,59 @@ window.app = Vue.createApp({
})
},
sendServiceData() {
const wallet = _.findWhere(this.g.user.wallets, {
id: this.serviceDialog.data.wallet
})
const data = this.serviceDialog.data

this.createService(wallet, data)
},

createService(wallet, data) {
LNbits.api
.request('POST', '/streamalerts/api/v1/services', wallet.adminkey, data)
if (this.onchainSelectionInvalid) return
const data = {
...this.serviceDialog.data,
onchain: this.serviceDialog.chain
? this.serviceDialog.data.onchain
: null
}
const wallet = this.g.user.wallets.find(
wallet => wallet.id === data.wallet
)
return LNbits.api
.request(
data.id ? 'PUT' : 'POST',
'/streamalerts/api/v1/services' + (data.id ? `/${data.id}` : ''),
wallet.adminkey,
data
)
.then(response => {
this.services.push(mapStreamAlerts(response.data))
const service = mapStreamAlerts(response.data)
if (data.id) {
this.services = this.services.map(row =>
row.id === data.id ? service : row
)
} else {
this.services.push(service)
}
this.serviceDialog.show = false
this.serviceDialog.data = {}
})
.catch(LNbits.utils.notifyApiError)
},
updateserviceDialog(serviceId) {
const link = _.findWhere(this.services, {id: serviceId})
this.serviceDialog.data.id = link.id
this.serviceDialog.data.wallet = link.wallet
this.serviceDialog.data.twitchuser = link.twitchuser
this.serviceDialog.data.servicename = link.servicename
this.serviceDialog.data.client_id = link.client_id
this.serviceDialog.data.client_secret = link.client_secret
const {
id,
wallet,
twitchuser,
servicename,
client_id,
client_secret,
onchain
} = this.services.find(service => service.id === serviceId)
this.serviceDialog.data = {
id,
wallet,
twitchuser,
servicename,
client_id,
client_secret,
onchain
}
this.serviceDialog.chain = Boolean(onchain)
this.serviceDialog.show = true
if (this.serviceDialog.chain) this.getWalletLinks()
},
deleteService(servicesId) {
const services = _.findWhere(this.services, {id: servicesId})
Expand Down Expand Up @@ -228,7 +351,6 @@ window.app = Vue.createApp({

created() {
if (this.g.user.wallets.length) {
this.getWalletLinks()
this.getDonations()
this.getServices()
}
Expand Down
31 changes: 17 additions & 14 deletions templates/streamalerts/display.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ <h5 class="q-my-none">Donate Bitcoin to {{ twitchuser }}</h5>
data() {
return {
paymentReq: null,
redirectUrl: null,
donationDialog: {
show: false,
data: {
Expand All @@ -74,19 +73,23 @@ <h5 class="q-my-none">Donate Bitcoin to {{ twitchuser }}</h5>
},

methods: {
submitInvoice() {
axios
.post('/streamalerts/api/v1/donations', {
service: '{{ service }}',
name: this.donationDialog.data.name,
sats: this.donationDialog.data.sats,
message: this.donationDialog.data.message
})
.then(response => {
this.redirect_url = response.data.redirect_url
console.log(this.redirect_url)
window.location.href = this.redirect_url
})
async submitInvoice() {
try {
const {data} = await LNbits.api.request(
'POST',
'/streamalerts/api/v1/donations',
null,
{
service: '{{ service }}',
name: this.donationDialog.data.name || 'Anonymous',
sats: this.donationDialog.data.sats,
message: this.donationDialog.data.message
}
)
window.location.href = data.redirect_url
} catch (error) {
LNbits.utils.notifyApiError(error)
}
}
}
})
Expand Down
Loading
Loading