mirror of
https://github.com/lnbits/lnbits.git
synced 2026-08-13 12:42:47 +02:00
tweak
This commit is contained in:
parent
a9b3d6efaa
commit
f336739f2d
10 changed files with 114 additions and 86 deletions
|
|
@ -27,7 +27,6 @@ from lnbits.core.models.notifications import (
|
|||
)
|
||||
from lnbits.core.models.users import UserNotifications
|
||||
from lnbits.core.services.nostr import (
|
||||
fetch_nip5_details,
|
||||
resolve_nostr_recipient,
|
||||
send_nostr_dm,
|
||||
send_nostr_nip17_dm,
|
||||
|
|
@ -36,7 +35,7 @@ from lnbits.core.services.nostr import (
|
|||
from lnbits.core.services.websockets import websocket_manager
|
||||
from lnbits.helpers import check_callback_url, is_valid_email_address
|
||||
from lnbits.settings import settings
|
||||
from lnbits.utils.nostr import normalize_private_key
|
||||
from lnbits.utils.nostr import is_ws_url, normalize_private_key
|
||||
|
||||
notifications_queue: asyncio.Queue[NotificationMessage] = asyncio.Queue()
|
||||
NostrDmType = Literal["nip04", "nip17", "nip17b"]
|
||||
|
|
@ -185,8 +184,9 @@ async def send_nostr_notification(
|
|||
|
||||
if "@" in identifier:
|
||||
user_pubkey, relays = await resolve_nostr_recipient(identifier)
|
||||
relays = relays or _configured_nostr_notification_relays()
|
||||
else:
|
||||
fallback_relays = await _fetch_configured_nostr_notification_relays()
|
||||
fallback_relays = _configured_nostr_notification_relays()
|
||||
user_pubkey, relays = await resolve_nostr_recipient(
|
||||
identifier,
|
||||
fallback_relays,
|
||||
|
|
@ -221,33 +221,9 @@ async def send_nostr_notification(
|
|||
raise ValueError(f"Unsupported Nostr DM type: {dm_type}")
|
||||
|
||||
|
||||
async def _fetch_configured_nostr_notification_relays() -> list[str]:
|
||||
identifiers = list(
|
||||
dict.fromkeys(
|
||||
identifier
|
||||
for identifier in settings.lnbits_nostr_notifications_identifiers
|
||||
if "@" in identifier
|
||||
)
|
||||
)
|
||||
if not identifiers:
|
||||
return []
|
||||
|
||||
results = await asyncio.gather(
|
||||
*(fetch_nip5_details(identifier) for identifier in identifiers),
|
||||
return_exceptions=True,
|
||||
)
|
||||
relays: list[str] = []
|
||||
for identifier, result in zip(identifiers, results, strict=True):
|
||||
if isinstance(result, BaseException):
|
||||
if isinstance(result, asyncio.CancelledError):
|
||||
raise result
|
||||
logger.warning(
|
||||
f"Error fetching fallback Nostr relays from {identifier}: {result}"
|
||||
)
|
||||
continue
|
||||
_, identifier_relays = result
|
||||
relays.extend(identifier_relays)
|
||||
return list(dict.fromkeys(relays))
|
||||
def _configured_nostr_notification_relays() -> list[str]:
|
||||
relays = settings.lnbits_nostr_notifications_relays
|
||||
return list(dict.fromkeys(relay for relay in relays if is_ws_url(relay)))
|
||||
|
||||
|
||||
async def send_telegram_notification(chat_id: str, message: str) -> dict:
|
||||
|
|
|
|||
|
|
@ -480,6 +480,7 @@ class NotificationsSettings(LNbitsSettings):
|
|||
lnbits_nostr_notifications_enabled: bool = Field(default=False)
|
||||
lnbits_nostr_notifications_private_key: str = Field(default="")
|
||||
lnbits_nostr_notifications_identifiers: list[str] = Field(default=[])
|
||||
lnbits_nostr_notifications_relays: list[str] = Field(default=[])
|
||||
lnbits_nostr_notifications_dm_types: list[Literal["nip17", "nip17b"]] = Field(
|
||||
default=[]
|
||||
)
|
||||
|
|
|
|||
2
lnbits/static/bundle-components.min.js
vendored
2
lnbits/static/bundle-components.min.js
vendored
File diff suppressed because one or more lines are too long
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
|
|
@ -314,6 +314,9 @@ window.localisation.en = {
|
|||
notifications_nostr_dm_types: 'Other Direct Message Types Allowed',
|
||||
notifications_nostr_dm_types_desc:
|
||||
'Allow extensions to explicitly send notifications using these protocols.',
|
||||
notifications_nostr_relays: 'Fallback Nostr Relays',
|
||||
notifications_nostr_relays_desc:
|
||||
'Relays used when no relay list is returned by NIP-05.',
|
||||
|
||||
notifications_telegram_config: 'Telegram Configuration',
|
||||
notifications_enable_telegram: 'Enable Telegram',
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ window.app.component('lnbits-admin-notifications', {
|
|||
data() {
|
||||
return {
|
||||
nostrNotificationIdentifier: '',
|
||||
nostrNotificationRelay: '',
|
||||
nostrDmTypeOptions: [
|
||||
{label: 'NIP-17', value: 'nip17'},
|
||||
{label: 'NIP-17B', value: 'nip17b'}
|
||||
|
|
@ -52,6 +53,20 @@ window.app.component('lnbits-admin-notifications', {
|
|||
m => m !== identifer
|
||||
)
|
||||
},
|
||||
addNostrNotificationRelay() {
|
||||
const relay = this.nostrNotificationRelay.trim()
|
||||
const relays = this.formData.lnbits_nostr_notifications_relays || []
|
||||
if (relay && !relays.includes(relay)) {
|
||||
this.formData.lnbits_nostr_notifications_relays = [...relays, relay]
|
||||
this.nostrNotificationRelay = ''
|
||||
}
|
||||
},
|
||||
removeNostrNotificationRelay(relay) {
|
||||
const relays = this.formData.lnbits_nostr_notifications_relays || []
|
||||
this.formData.lnbits_nostr_notifications_relays = relays.filter(
|
||||
item => item !== relay
|
||||
)
|
||||
},
|
||||
addEmailNotificationAddress() {
|
||||
const email = this.emailNotificationAddress.trim()
|
||||
const emails = this.formData.lnbits_email_notifications_to_emails
|
||||
|
|
|
|||
|
|
@ -113,6 +113,46 @@
|
|||
></q-option-group>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item tag="label" v-ripple>
|
||||
<q-item-section>
|
||||
<q-item-label
|
||||
v-text="$t('notifications_nostr_relays')"
|
||||
></q-item-label>
|
||||
<q-item-label
|
||||
caption
|
||||
v-text="$t('notifications_nostr_relays_desc')"
|
||||
></q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-input
|
||||
filled
|
||||
v-model="nostrNotificationRelay"
|
||||
placeholder="wss://relay.example.com"
|
||||
@keydown.enter="addNostrNotificationRelay"
|
||||
>
|
||||
<q-btn
|
||||
@click="addNostrNotificationRelay()"
|
||||
dense
|
||||
flat
|
||||
icon="add"
|
||||
></q-btn>
|
||||
</q-input>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<div>
|
||||
<q-chip
|
||||
v-for="relay in formData.lnbits_nostr_notifications_relays"
|
||||
:key="relay"
|
||||
removable
|
||||
@remove="removeNostrNotificationRelay(relay)"
|
||||
color="primary"
|
||||
text-color="white"
|
||||
class="ellipsis"
|
||||
:label="relay"
|
||||
><q-tooltip v-if="relay" anchor="top middle" self="bottom middle"
|
||||
><span v-text="relay"></span></q-tooltip
|
||||
></q-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-6">
|
||||
|
|
|
|||
|
|
@ -195,14 +195,16 @@ async def test_send_nostr_nip17_dm_builds_gift_wrap(
|
|||
assert result["kind"] == 1059
|
||||
assert result["tags"] == [["p", receiver.public_key().to_hex()]]
|
||||
send_mock.assert_awaited_once()
|
||||
gift = send_mock.await_args.args[0]
|
||||
send_call = send_mock.await_args
|
||||
assert send_call
|
||||
gift = send_call.args[0]
|
||||
unwrapped = await UnwrappedGift.from_gift_wrap(
|
||||
NostrSigner.keys(receiver),
|
||||
gift,
|
||||
)
|
||||
assert unwrapped.rumor().content() == "hello"
|
||||
assert unwrapped.sender().to_hex() == sender.public_key().to_hex()
|
||||
assert send_mock.await_args.args[1] == ["wss://relay"]
|
||||
assert send_call.args[1] == ["wss://relay"]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
|
|
@ -241,7 +243,9 @@ async def test_send_nostr_nip17b_dm_uses_epoch_ticket(
|
|||
group.public_key().to_hex(),
|
||||
["wss://group"],
|
||||
)
|
||||
gift = send_mock.await_args.args[0]
|
||||
send_call = send_mock.await_args
|
||||
assert send_call
|
||||
gift = send_call.args[0]
|
||||
unwrapped = await UnwrappedGift.from_gift_wrap(
|
||||
NostrSigner.keys(epoch),
|
||||
gift,
|
||||
|
|
@ -256,7 +260,7 @@ async def test_send_nostr_nip17b_dm_uses_epoch_ticket(
|
|||
["invitation_proof", "f" * 128],
|
||||
]
|
||||
assert unwrapped.sender().to_hex() == member.public_key().to_hex()
|
||||
assert send_mock.await_args.args[1] == ["wss://group"]
|
||||
assert send_call.args[1] == ["wss://group"]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ from lnbits.core.models.wallets import (
|
|||
WalletShareStatus,
|
||||
)
|
||||
from lnbits.core.services.notifications import (
|
||||
_fetch_configured_nostr_notification_relays,
|
||||
dispatch_webhook,
|
||||
enqueue_admin_notification,
|
||||
enqueue_user_notification,
|
||||
|
|
@ -249,7 +248,7 @@ async def test_send_notification_uses_available_channels_and_swallows_exceptions
|
|||
|
||||
@pytest.mark.anyio
|
||||
async def test_send_nostr_notifications_and_single_notification(
|
||||
mocker: MockerFixture,
|
||||
settings: Settings, mocker: MockerFixture
|
||||
):
|
||||
send_mock = mocker.patch(
|
||||
"lnbits.core.services.notifications.send_nostr_notification",
|
||||
|
|
@ -277,7 +276,7 @@ async def test_send_nostr_notifications_and_single_notification(
|
|||
|
||||
resolve_mock = mocker.patch(
|
||||
"lnbits.core.services.notifications.resolve_nostr_recipient",
|
||||
mocker.AsyncMock(return_value=("pubkey", ["wss://relay"])),
|
||||
mocker.AsyncMock(return_value=("pubkey", [])),
|
||||
)
|
||||
normalize_mock = mocker.patch(
|
||||
"lnbits.core.services.notifications.normalize_private_key",
|
||||
|
|
@ -288,7 +287,12 @@ async def test_send_nostr_notifications_and_single_notification(
|
|||
mocker.AsyncMock(),
|
||||
)
|
||||
|
||||
await send_nostr_notification("alice@example.com", "hello")
|
||||
original_relays = list(settings.lnbits_nostr_notifications_relays)
|
||||
try:
|
||||
settings.lnbits_nostr_notifications_relays = ["wss://fallback"]
|
||||
await send_nostr_notification("alice@example.com", "hello")
|
||||
finally:
|
||||
settings.lnbits_nostr_notifications_relays = original_relays
|
||||
|
||||
resolve_mock.assert_awaited_once_with("alice@example.com")
|
||||
normalize_mock.assert_called_once()
|
||||
|
|
@ -296,7 +300,7 @@ async def test_send_nostr_notifications_and_single_notification(
|
|||
"server-private-key",
|
||||
"pubkey",
|
||||
"hello",
|
||||
["wss://relay"],
|
||||
["wss://fallback"],
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -305,9 +309,11 @@ async def test_send_nostr_notification_selects_nip17_and_nip17b(
|
|||
settings: Settings, mocker: MockerFixture
|
||||
):
|
||||
original_dm_types = list(settings.lnbits_nostr_notifications_dm_types)
|
||||
original_relays = list(settings.lnbits_nostr_notifications_relays)
|
||||
try:
|
||||
settings.lnbits_nostr_notifications_dm_types = ["nip17", "nip17b"]
|
||||
mocker.patch(
|
||||
settings.lnbits_nostr_notifications_relays = ["wss://fallback"]
|
||||
resolve_mock = mocker.patch(
|
||||
"lnbits.core.services.notifications.resolve_nostr_recipient",
|
||||
mocker.AsyncMock(return_value=("pubkey", ["wss://recipient"])),
|
||||
)
|
||||
|
|
@ -328,6 +334,7 @@ async def test_send_nostr_notification_selects_nip17_and_nip17b(
|
|||
await send_nostr_notification("group@example.com", "hello", "nip17b")
|
||||
finally:
|
||||
settings.lnbits_nostr_notifications_dm_types = original_dm_types
|
||||
settings.lnbits_nostr_notifications_relays = original_relays
|
||||
|
||||
nip17_mock.assert_awaited_once_with(
|
||||
"server-private-key",
|
||||
|
|
@ -341,65 +348,46 @@ async def test_send_nostr_notification_selects_nip17_and_nip17b(
|
|||
"hello",
|
||||
["wss://recipient"],
|
||||
)
|
||||
assert [call.args[0] for call in resolve_mock.await_args_list] == [
|
||||
"alice@example.com",
|
||||
"group@example.com",
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_bare_nostr_pubkey_uses_configured_nip5_relays(
|
||||
async def test_bare_nostr_pubkey_uses_fallback_relays(
|
||||
settings: Settings, mocker: MockerFixture
|
||||
):
|
||||
original_identifiers = list(settings.lnbits_nostr_notifications_identifiers)
|
||||
original_relays = list(settings.lnbits_nostr_notifications_relays)
|
||||
try:
|
||||
settings.lnbits_nostr_notifications_identifiers = [
|
||||
"alice@example.com",
|
||||
"bare-pubkey",
|
||||
"bob@example.com",
|
||||
settings.lnbits_nostr_notifications_relays = [
|
||||
"invalid",
|
||||
"wss://fallback",
|
||||
"wss://fallback",
|
||||
]
|
||||
fetch_mock = mocker.patch(
|
||||
"lnbits.core.services.notifications.fetch_nip5_details",
|
||||
mocker.AsyncMock(
|
||||
side_effect=[
|
||||
("alice-pubkey", ["wss://one", "wss://shared"]),
|
||||
("bob-pubkey", ["wss://shared", "wss://two"]),
|
||||
]
|
||||
),
|
||||
resolve_mock = mocker.patch(
|
||||
"lnbits.core.services.notifications.resolve_nostr_recipient",
|
||||
mocker.AsyncMock(return_value=("recipient-pubkey", ["wss://fallback"])),
|
||||
)
|
||||
mocker.patch(
|
||||
"lnbits.core.services.notifications.normalize_private_key",
|
||||
return_value="server-private-key",
|
||||
)
|
||||
dm_mock = mocker.patch(
|
||||
"lnbits.core.services.notifications.send_nostr_dm",
|
||||
mocker.AsyncMock(),
|
||||
)
|
||||
|
||||
relays = await _fetch_configured_nostr_notification_relays()
|
||||
await send_nostr_notification("f" * 64, "hello")
|
||||
finally:
|
||||
settings.lnbits_nostr_notifications_identifiers = original_identifiers
|
||||
settings.lnbits_nostr_notifications_relays = original_relays
|
||||
|
||||
assert relays == ["wss://one", "wss://shared", "wss://two"]
|
||||
assert [call.args[0] for call in fetch_mock.await_args_list] == [
|
||||
"alice@example.com",
|
||||
"bob@example.com",
|
||||
]
|
||||
|
||||
fallback_mock = mocker.patch(
|
||||
"lnbits.core.services.notifications._fetch_configured_nostr_notification_relays",
|
||||
mocker.AsyncMock(return_value=relays),
|
||||
)
|
||||
resolve_mock = mocker.patch(
|
||||
"lnbits.core.services.notifications.resolve_nostr_recipient",
|
||||
mocker.AsyncMock(return_value=("recipient-pubkey", relays)),
|
||||
)
|
||||
mocker.patch(
|
||||
"lnbits.core.services.notifications.normalize_private_key",
|
||||
return_value="server-private-key",
|
||||
)
|
||||
dm_mock = mocker.patch(
|
||||
"lnbits.core.services.notifications.send_nostr_dm",
|
||||
mocker.AsyncMock(),
|
||||
)
|
||||
|
||||
await send_nostr_notification("f" * 64, "hello")
|
||||
|
||||
fallback_mock.assert_awaited_once_with()
|
||||
resolve_mock.assert_awaited_once_with("f" * 64, relays)
|
||||
resolve_mock.assert_awaited_once_with("f" * 64, ["wss://fallback"])
|
||||
dm_mock.assert_awaited_once_with(
|
||||
"server-private-key",
|
||||
"recipient-pubkey",
|
||||
"hello",
|
||||
relays,
|
||||
["wss://fallback"],
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -363,6 +363,7 @@ def test_asset_security_and_notification_helpers(
|
|||
assert security_settings.is_wallet_max_balance_exceeded(100) is False
|
||||
assert notification_settings.is_nostr_notifications_configured() is True
|
||||
assert notification_settings.lnbits_nostr_notifications_dm_types == []
|
||||
assert notification_settings.lnbits_nostr_notifications_relays == []
|
||||
assert notification_settings.is_telegram_notifications_configured() is True
|
||||
|
||||
legacy_super_settings = dict_to_model(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue