Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enhancement] NIP-56 support #57

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ nostr.egg-info/
dist/
nostr/_version.py
.DS_Store
.python-version
42 changes: 40 additions & 2 deletions nostr/event.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import time
import json
from dataclasses import dataclass, field
from enum import IntEnum
from enum import Enum, IntEnum
from typing import List
from secp256k1 import PrivateKey, PublicKey
from secp256k1 import PublicKey
from hashlib import sha256

from .exceptions import EventValidationException
from .message_type import ClientMessageType


Expand All @@ -17,6 +18,7 @@ class EventKind(IntEnum):
CONTACTS = 3
ENCRYPTED_DIRECT_MESSAGE = 4
DELETE = 5
REPORT = 1984



Expand Down Expand Up @@ -121,3 +123,39 @@ def id(self) -> str:
if self.content is None:
raise Exception("EncryptedDirectMessage `id` is undefined until its message is encrypted and stored in the `content` field")
return super().id


class ReportType(Enum):
NUDITY = 'nudity'
PROFANITY = 'profanity'
ILLEGAL = 'illegal'
SPAM = 'spam'
IMPERSONATION = 'impersonation'

@dataclass
class ReportEvent(Event):
"""
NIP-56 reporting event
"""
reported_pubkey: str = None
note_id: str = None
report_type: ReportType = None
victim_pubkey: str = None

def __post_init__(self):
if self.reported_pubkey is None:
raise EventValidationException("Reports require the pubkey of the user being reported")
if self.report_type is None or not isinstance(self.report_type, ReportType):
raise EventValidationException("Reports require a valid report type")
self.kind = EventKind.REPORT
super().__post_init__()
self.tags = self.tags + self._build_tags()

def _build_tags(self) -> List[List[str]]:
report_tags = []
if self.note_id:
report_tags.append(["e", self.note_id, self.report_type])
report_tags.append(["p", self.reported_pubkey, self.report_type])
if self.victim_pubkey:
report_tags.append(["p", self.victim_pubkey])
return report_tags
4 changes: 4 additions & 0 deletions nostr/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class EventValidationException(Exception):
"""
Raised when a specific event does not meet NIP requirements
"""
8 changes: 6 additions & 2 deletions nostr/relay.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,15 @@ class RelayProxyConnectionConfig:
class Relay:
url: str
message_pool: MessagePool
policy: RelayPolicy = RelayPolicy()
proxy_config: RelayProxyConnectionConfig = RelayProxyConnectionConfig()
policy: RelayPolicy = None
proxy_config: RelayProxyConnectionConfig = None
ssl_options: Optional[dict] = None

def __post_init__(self):
if self.policy is None:
self.policy = RelayPolicy()
if self.proxy_config is None:
self.proxy_config = RelayProxyConnectionConfig()
self.subscriptions: dict[str, Subscription] = {}
self.lock: Lock = Lock()
self.ws: WebSocketApp = WebSocketApp(
Expand Down
4 changes: 3 additions & 1 deletion nostr/relay_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ def __post_init__(self):
def add_relay(
self,
url: str,
policy: RelayPolicy = RelayPolicy(),
policy: RelayPolicy = None,
ssl_options: dict = None,
proxy_config: RelayProxyConnectionConfig = None):

if RelayPolicy is None:
policy = RelayPolicy()
relay = Relay(url, self.message_pool, policy, ssl_options, proxy_config)

with self.lock:
Expand Down
31 changes: 29 additions & 2 deletions test/test_event.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from collections import namedtuple
import pytest
import time
from nostr.event import Event, EncryptedDirectMessage
from nostr.event import Event, EncryptedDirectMessage, ReportEvent, ReportType
from nostr.exceptions import EventValidationException
from nostr.key import PrivateKey


Expand Down Expand Up @@ -78,7 +80,6 @@ def test_recipient_p_tag(self):
""" Should generate recipient 'p' tag """
dm = EncryptedDirectMessage(cleartext_content="Secret message!", recipient_pubkey=self.recipient_pubkey)
assert ['p', self.recipient_pubkey] in dm.tags


def test_unencrypted_dm_has_undefined_id(self):
""" Should raise Exception if `id` is requested before DM is encrypted """
Expand All @@ -91,3 +92,29 @@ def test_unencrypted_dm_has_undefined_id(self):
# But once we encrypt it, we can request its id
self.sender_pk.encrypt_dm(dm)
assert dm.id is not None

class TestReportEvent:
def test_report_type(self):
""" Should not let users instantiate a new ReportEvent without valid data"""
pub_key = PrivateKey().public_key.hex()
reported_pubkey = PrivateKey().public_key.hex()
with pytest.raises(EventValidationException) as invalid_type_exception:
ReportEvent(pub_key, "this was a bad note!", report_type="invalidtype", reported_pubkey=reported_pubkey)
with pytest.raises(EventValidationException) as no_reported_pubkey:
ReportEvent(pub_key, report_type=ReportType.NUDITY)
assert "valid report type" in str(invalid_type_exception)
assert "user being reported" in str(no_reported_pubkey)

def test_report_tags(self):
""" Should generate report-specific tags """
report = ReportEvent(
public_key="pubkey",
reported_pubkey=PrivateKey().public_key.hex(),
note_id="fakenoteid",
report_type=ReportType.ILLEGAL,
victim_pubkey="thevictim"
)
assert len(report.tags) == 3
tag_types = [tag[0] for tag in report.tags]
print(tag_types)
assert tag_types == ["e", "p", "p"]