Skip to content
Draft
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: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
- [x] **NIP-04**: Encrypted Direct Message
- if `AUTH` enabled: send only to the intended target
- [x] **NIP-09**: Event Deletion
- [x] 'e' tags: Delete regular events by event ID
- [x] 'a' tags: Delete addressable events by address (kind:pubkey:d-identifier)
- [x] **NIP-11**: Relay Information Document
- > **Note**: the endpoint is NOT on the root level of the domain. It also includes a path (eg https://lnbits.link/nostrrelay/)
- [ ] **NIP-12**: Generic Tag Queries
Expand Down
65 changes: 59 additions & 6 deletions relay/client_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,65 @@ async def _send_msg(self, data: list):
await self.websocket.send_text(json.dumps(data))

async def _handle_delete_event(self, event: NostrEvent):
# NIP 09
nostr_filter = NostrFilter(authors=[event.pubkey])
nostr_filter.ids = [t[1] for t in event.tags if t[0] == "e"]
events_to_delete = await get_events(self.relay_id, nostr_filter, False)
ids = [e.id for e in events_to_delete if not e.is_delete_event]
await mark_events_deleted(self.relay_id, NostrFilter(ids=ids))
# NIP 09 - Handle both regular events (e tags) and parameterized replaceable events (a tags)

# Get event IDs from 'e' tags (for regular events)
event_ids = [t[1] for t in event.tags if t[0] == "e"]

# Get event addresses from 'a' tags (for parameterized replaceable events)
event_addresses = [t[1] for t in event.tags if t[0] == "a"]

ids_to_delete = []

# Handle regular event deletions (e tags)
if event_ids:
nostr_filter = NostrFilter(authors=[event.pubkey], ids=event_ids)
events_to_delete = await get_events(self.relay_id, nostr_filter, False)
ids_to_delete.extend(
[e.id for e in events_to_delete if not e.is_delete_event]
)

# Handle parameterized replaceable event deletions (a tags)
if event_addresses:
for addr in event_addresses:
# Parse address format: kind:pubkey:d-tag
parts = addr.split(":")
if len(parts) == 3:
kind_str, addr_pubkey, d_tag = parts
try:
kind = int(kind_str)
# Only delete if the address pubkey matches the deletion event author
if addr_pubkey == event.pubkey:
# NOTE: Use "#d" alias, not "d" directly (Pydantic Field alias)
nostr_filter = NostrFilter(
authors=[addr_pubkey],
kinds=[kind],
**{"#d": [d_tag]}, # Use alias to set d field
)
events_to_delete = await get_events(
self.relay_id, nostr_filter, False
)
ids_to_delete.extend(
[
e.id
for e in events_to_delete
if not e.is_delete_event
]
)
else:
logger.warning(
f"Deletion request pubkey mismatch: {addr_pubkey} != {event.pubkey}"
)
except ValueError:
logger.warning(f"Invalid kind in address: {addr}")
else:
logger.warning(
f"Invalid address format (expected kind:pubkey:d-tag): {addr}"
)

# Only mark events as deleted if we found specific IDs
if ids_to_delete:
await mark_events_deleted(self.relay_id, NostrFilter(ids=ids_to_delete))

async def _handle_request(
self, subscription_id: str, nostr_filter: NostrFilter
Expand Down
Loading