Feat: email notifications (#3007)
* Feat: email notifications / email db backup Because not using a simple smtp connection is silly * Added form * broken, and annoying bug Why wont the input display?!?!? * make * Encourage protonmail use, because its great. * feat: small UI polishing * chore: make bundle --------- Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
This commit is contained in:
parent
fe9b62e8a8
commit
991e0db50b
6 changed files with 242 additions and 3 deletions
|
|
@ -1,5 +1,8 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
import smtplib
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from email.mime.text import MIMEText
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
|
|
@ -62,6 +65,12 @@ async def send_notification(
|
||||||
logger.debug(f"Sent nostr notification: {message_type}")
|
logger.debug(f"Sent nostr notification: {message_type}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending nostr notification {message_type}: {e}")
|
logger.error(f"Error sending nostr notification {message_type}: {e}")
|
||||||
|
try:
|
||||||
|
if settings.lnbits_email_notifications_enabled:
|
||||||
|
await send_email_notification(message)
|
||||||
|
logger.debug(f"Sent email notification: {message_type}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error sending email notification {message_type}: {e}")
|
||||||
|
|
||||||
|
|
||||||
async def send_nostr_notification(message: str) -> dict:
|
async def send_nostr_notification(message: str) -> dict:
|
||||||
|
|
@ -102,6 +111,43 @@ async def send_telegram_message(token: str, chat_id: str, message: str) -> dict:
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
async def send_email_notification(message: str) -> dict:
|
||||||
|
await send_email(
|
||||||
|
settings.lnbits_email_notifications_server,
|
||||||
|
settings.lnbits_email_notifications_port,
|
||||||
|
settings.lnbits_email_notifications_password,
|
||||||
|
settings.lnbits_email_notifications_email,
|
||||||
|
settings.lnbits_email_notifications_to_emails,
|
||||||
|
"LNbits Notification",
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
async def send_email(
|
||||||
|
server: str,
|
||||||
|
port: int,
|
||||||
|
password: str,
|
||||||
|
from_email: str,
|
||||||
|
to_emails: list,
|
||||||
|
subject: str,
|
||||||
|
message: str,
|
||||||
|
):
|
||||||
|
msg = MIMEMultipart()
|
||||||
|
msg["From"] = from_email
|
||||||
|
msg["To"] = ", ".join(to_emails)
|
||||||
|
msg["Subject"] = subject
|
||||||
|
msg.attach(MIMEText(message, "plain"))
|
||||||
|
try:
|
||||||
|
with smtplib.SMTP(server, port) as smtp_server:
|
||||||
|
smtp_server.starttls()
|
||||||
|
smtp_server.login(from_email, password)
|
||||||
|
smtp_server.sendmail(from_email, to_emails, msg.as_string())
|
||||||
|
logger.debug(f"Emails sent successfully to: {', '.join(to_emails)}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Failed to send email: {e}")
|
||||||
|
|
||||||
|
|
||||||
def is_message_type_enabled(message_type: NotificationType) -> bool:
|
def is_message_type_enabled(message_type: NotificationType) -> bool:
|
||||||
if message_type == NotificationType.balance_update:
|
if message_type == NotificationType.balance_update:
|
||||||
return settings.lnbits_notification_credit_debit
|
return settings.lnbits_notification_credit_debit
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,14 @@
|
||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
<h6 class="q-my-none q-mb-sm">
|
<h6 class="q-my-none q-mb-sm">
|
||||||
<span v-text="$t('notifications_configure')"></span>
|
<span v-text="$t('notifications_configure')"></span>
|
||||||
|
<q-btn
|
||||||
|
round
|
||||||
|
flat
|
||||||
|
@click="hideInputsToggle()"
|
||||||
|
:icon="hideInputToggle ? 'visibility_off' : 'visibility'"
|
||||||
|
></q-btn>
|
||||||
</h6>
|
</h6>
|
||||||
|
|
||||||
<q-separator class="q-mt-md q-mb-sm"></q-separator>
|
<q-separator class="q-mt-md q-mb-sm"></q-separator>
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-sm-12 col-md-6">
|
<div class="col-sm-12 col-md-6">
|
||||||
|
|
@ -39,12 +46,13 @@
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-input
|
<q-input
|
||||||
type="password"
|
:type="hideInputToggle ? 'password' : 'text'"
|
||||||
filled
|
filled
|
||||||
v-model="formData.lnbits_nostr_notifications_private_key"
|
v-model="formData.lnbits_nostr_notifications_private_key"
|
||||||
/>
|
/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
|
||||||
<q-item tag="label" v-ripple>
|
<q-item tag="label" v-ripple>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label
|
<q-item-label
|
||||||
|
|
@ -117,7 +125,7 @@
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-input
|
<q-input
|
||||||
type="password"
|
:type="hideInputToggle ? 'password' : 'text'"
|
||||||
filled
|
filled
|
||||||
v-model="formData.lnbits_telegram_notifications_access_token"
|
v-model="formData.lnbits_telegram_notifications_access_token"
|
||||||
/>
|
/>
|
||||||
|
|
@ -139,6 +147,150 @@
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<q-separator></q-separator>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<strong v-text="$t('notifications_email_config')"></strong>
|
||||||
|
<q-item tag="label" v-ripple>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label
|
||||||
|
v-text="$t('notifications_enable_email')"
|
||||||
|
></q-item-label>
|
||||||
|
<q-item-label
|
||||||
|
caption
|
||||||
|
v-text="$t('notifications_enable_email_desc')"
|
||||||
|
></q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-toggle
|
||||||
|
size="md"
|
||||||
|
v-model="formData.lnbits_email_notifications_enabled"
|
||||||
|
checked-icon="check"
|
||||||
|
color="green"
|
||||||
|
unchecked-icon="clear"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<div v-if="formData.lnbits_email_notifications_enabled" class="row">
|
||||||
|
<div class="col">
|
||||||
|
<q-item tag="label" v-ripple>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label
|
||||||
|
v-text="$t('notifications_send_email')"
|
||||||
|
></q-item-label>
|
||||||
|
<q-item-label
|
||||||
|
caption
|
||||||
|
v-text="$t('notifications_send_email_desc')"
|
||||||
|
></q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-input
|
||||||
|
:type="hideInputToggle ? 'password' : 'text'"
|
||||||
|
filled
|
||||||
|
v-model="formData.lnbits_email_notifications_email"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item tag="label" v-ripple>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label
|
||||||
|
v-text="$t('notifications_send_email_password')"
|
||||||
|
></q-item-label>
|
||||||
|
<q-item-label
|
||||||
|
caption
|
||||||
|
v-text="$t('notifications_send_email_password_desc')"
|
||||||
|
></q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-input
|
||||||
|
:type="hideInputToggle ? 'password' : 'text'"
|
||||||
|
filled
|
||||||
|
v-model="formData.lnbits_email_notifications_password"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item tag="label" v-ripple>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label
|
||||||
|
v-text="$t('notifications_send_to_emails')"
|
||||||
|
></q-item-label>
|
||||||
|
<q-item-label
|
||||||
|
caption
|
||||||
|
v-text="$t('notifications_send_to_emails_desc')"
|
||||||
|
></q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-model="emailNotificationAddress"
|
||||||
|
@keydown.enter="addEmailNotificationAddress"
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
@click="addEmailNotificationAddress()"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
icon="add"
|
||||||
|
></q-btn>
|
||||||
|
</q-input>
|
||||||
|
<div>
|
||||||
|
<q-chip
|
||||||
|
v-for="to_email in formData.lnbits_email_notifications_to_emails"
|
||||||
|
:key="to_email"
|
||||||
|
removable
|
||||||
|
@remove="removeEmailNotificationAddress(to_email)"
|
||||||
|
color="primary"
|
||||||
|
text-color="white"
|
||||||
|
><span class="ellipsis" v-text="to_email"></span
|
||||||
|
></q-chip>
|
||||||
|
</div>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<q-item tag="label" v-ripple>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label
|
||||||
|
v-text="$t('notifications_send_email_server_port')"
|
||||||
|
></q-item-label>
|
||||||
|
<q-item-label
|
||||||
|
caption
|
||||||
|
v-text="$t('notifications_send_email_server_port_desc')"
|
||||||
|
></q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-input
|
||||||
|
:type="hideInputToggle ? 'password' : 'text'"
|
||||||
|
filled
|
||||||
|
v-model="formData.lnbits_email_notifications_port"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item tag="label" v-ripple>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label
|
||||||
|
v-text="$t('notifications_send_email_server')"
|
||||||
|
></q-item-label>
|
||||||
|
<q-item-label
|
||||||
|
caption
|
||||||
|
v-text="$t('notifications_send_email_server_desc')"
|
||||||
|
></q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-input
|
||||||
|
:type="hideInputToggle ? 'password' : 'text'"
|
||||||
|
filled
|
||||||
|
v-model="formData.lnbits_email_notifications_server"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<q-separator> </q-separator>
|
<q-separator> </q-separator>
|
||||||
<h6 class="q-mb-sm">
|
<h6 class="q-mb-sm">
|
||||||
|
|
|
||||||
|
|
@ -394,6 +394,12 @@ class NotificationsSettings(LNbitsSettings):
|
||||||
lnbits_telegram_notifications_enabled: bool = Field(default=False)
|
lnbits_telegram_notifications_enabled: bool = Field(default=False)
|
||||||
lnbits_telegram_notifications_access_token: str = Field(default="")
|
lnbits_telegram_notifications_access_token: str = Field(default="")
|
||||||
lnbits_telegram_notifications_chat_id: str = Field(default="")
|
lnbits_telegram_notifications_chat_id: str = Field(default="")
|
||||||
|
lnbits_email_notifications_enabled: bool = Field(default=False)
|
||||||
|
lnbits_email_notifications_email: str = Field(default="")
|
||||||
|
lnbits_email_notifications_password: str = Field(default="")
|
||||||
|
lnbits_email_notifications_server: str = Field(default="smtp.protonmail.ch")
|
||||||
|
lnbits_email_notifications_port: int = Field(default=587)
|
||||||
|
lnbits_email_notifications_to_emails: list[str] = Field(default=[])
|
||||||
|
|
||||||
lnbits_notification_settings_update: bool = Field(default=True)
|
lnbits_notification_settings_update: bool = Field(default=True)
|
||||||
lnbits_notification_credit_debit: bool = Field(default=True)
|
lnbits_notification_credit_debit: bool = Field(default=True)
|
||||||
|
|
|
||||||
2
lnbits/static/bundle.min.js
vendored
2
lnbits/static/bundle.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -196,6 +196,22 @@ window.localisation.en = {
|
||||||
notifications_chat_id: 'Chat ID',
|
notifications_chat_id: 'Chat ID',
|
||||||
notifications_chat_id_desc: 'Chat ID to send the notifications to',
|
notifications_chat_id_desc: 'Chat ID to send the notifications to',
|
||||||
|
|
||||||
|
notifications_email_config: 'Email Configuration',
|
||||||
|
notifications_enable_email: 'Enable Email',
|
||||||
|
notifications_enable_email_desc: 'Send notfications over Email',
|
||||||
|
notifications_send_email: 'Send email',
|
||||||
|
notifications_send_email_desc: 'Email you will send from',
|
||||||
|
notifications_send_email_password: 'Send email password',
|
||||||
|
notifications_send_email_password_desc:
|
||||||
|
'Password for the email you will send from',
|
||||||
|
notifications_send_email_server_port: 'Send email SMTP port',
|
||||||
|
notifications_send_email_server_port_desc: 'Port for the SMTP server',
|
||||||
|
notifications_send_email_server: 'Send email SMTP server',
|
||||||
|
notifications_send_email_server_desc:
|
||||||
|
'SMTP server for the email you will send from',
|
||||||
|
notifications_send_to_emails: 'Emails to send to',
|
||||||
|
notifications_send_to_emails_desc: 'Emails notifications will be sent to',
|
||||||
|
|
||||||
notification_settings_update: 'Settings updated',
|
notification_settings_update: 'Settings updated',
|
||||||
notification_settings_update_desc:
|
notification_settings_update_desc:
|
||||||
'Notify when server settings have been updated',
|
'Notify when server settings have been updated',
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,10 @@ window.AdminPageLogic = {
|
||||||
chartReady: false,
|
chartReady: false,
|
||||||
formAddAdmin: '',
|
formAddAdmin: '',
|
||||||
formAddUser: '',
|
formAddUser: '',
|
||||||
|
hideInputToggle: true,
|
||||||
formAddExtensionsManifest: '',
|
formAddExtensionsManifest: '',
|
||||||
nostrNotificationIdentifier: '',
|
nostrNotificationIdentifier: '',
|
||||||
|
emailNotificationAddress: '',
|
||||||
formAllowedIPs: '',
|
formAllowedIPs: '',
|
||||||
formCallbackUrlRule: '',
|
formCallbackUrlRule: '',
|
||||||
formBlockedIPs: '',
|
formBlockedIPs: '',
|
||||||
|
|
@ -270,6 +272,23 @@ window.AdminPageLogic = {
|
||||||
m => m !== identifer
|
m => m !== identifer
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
addEmailNotificationAddress() {
|
||||||
|
const email = this.emailNotificationAddress.trim()
|
||||||
|
const emails = this.formData.lnbits_email_notifications_to_emails
|
||||||
|
if (email && email.length && !emails.includes(email)) {
|
||||||
|
this.formData.lnbits_email_notifications_to_emails = [...emails, email]
|
||||||
|
this.emailNotificationAddress = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeEmailNotificationAddress(email) {
|
||||||
|
const emails = this.formData.lnbits_email_notifications_to_emails
|
||||||
|
this.formData.lnbits_email_notifications_to_emails = emails.filter(
|
||||||
|
m => m !== email
|
||||||
|
)
|
||||||
|
},
|
||||||
|
hideInputsToggle() {
|
||||||
|
this.hideInputToggle = !this.hideInputToggle
|
||||||
|
},
|
||||||
async toggleServerLog() {
|
async toggleServerLog() {
|
||||||
this.serverlogEnabled = !this.serverlogEnabled
|
this.serverlogEnabled = !this.serverlogEnabled
|
||||||
if (this.serverlogEnabled) {
|
if (this.serverlogEnabled) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue