diff --git a/client_manager.py b/client_manager.py index d149884..31325c9 100644 --- a/client_manager.py +++ b/client_manager.py @@ -81,8 +81,10 @@ class NostrClientConnection: resp_nip20: List[Any] = ["OK", e.id] try: e.check_signature() - if e.is_meta_event(): - await delete_events("111", NostrFilter(kinds=[0], authors=[e.pubkey])) + if e.is_replaceable_event(): + await delete_events( + "111", NostrFilter(kinds=[e.kind], authors=[e.pubkey]) + ) await create_event("111", e) await self.broadcast_event(self, e) if e.is_delete_event(): diff --git a/models.py b/models.py index b2c2ac3..bb1efe5 100644 --- a/models.py +++ b/models.py @@ -59,9 +59,9 @@ class NostrEvent(BaseModel): id = hashlib.sha256(data.encode()).hexdigest() return id - def is_meta_event(self) -> bool: - return self.kind == 0 - + def is_replaceable_event(self) -> bool: + return self.kind in [0, 3] + def is_delete_event(self) -> bool: return self.kind == 5 diff --git a/tests/fixture/clients.json b/tests/fixture/clients.json index 7fa2f95..865ec19 100644 --- a/tests/fixture/clients.json +++ b/tests/fixture/clients.json @@ -148,6 +148,18 @@ "2751f2ee0f894268c61300c5b1a1a434f49a33a467a6f4516f10a82a1848f093", true, "" + ], + "subscribe_to_bob_contact_list": [ + "REQ", + "contact", + { + "kinds": [ + 3 + ], + "authors": [ + "d685447c43c7c18dbbea61923cf0b63e1ab46bed69b153a48279a95c40bd414a" + ] + } ] }, "bob": { @@ -317,6 +329,56 @@ ], "limit": 400 } + ], + "contact_list_create": [ + "EVENT", + { + "id": "141ddb3008ed1cc35fa09ff88d3b82da0351c6166c566e6220293136aa902a62", + "pubkey": "d685447c43c7c18dbbea61923cf0b63e1ab46bed69b153a48279a95c40bd414a", + "created_at": 1675350109, + "kind": 3, + "tags": [ + [ + "p", + "0b29ecc73ba400e5b4bd1e4cb0d8f524e9958345749197ca21c8da38d0622816" + ] + ], + "content": "", + "sig": "740972ce0335fe6be7194c995e407e440b4194e49ee2775a19dc36eb5e9d8302ea8d0ab93cdc11eb345a9f8bae32c14bcbd4b7f3fe9b97d197b8426dba139847" + } + ], + "contact_list_create_response": [ + "OK", + "141ddb3008ed1cc35fa09ff88d3b82da0351c6166c566e6220293136aa902a62", + true, + "" + ], + "contact_list_update": [ + "EVENT", + { + "id": "1439f08983433295bc54d24b8c3cda2fa137d86636535a408d2d9a7bac5f0c40", + "pubkey": "d685447c43c7c18dbbea61923cf0b63e1ab46bed69b153a48279a95c40bd414a", + "created_at": 1675444161, + "kind": 3, + "tags": [ + [ + "p", + "0b29ecc73ba400e5b4bd1e4cb0d8f524e9958345749197ca21c8da38d0622816" + ], + [ + "p", + "8d21cd7c3f204cbb8aaf7708445b49e6cef7da23a550f9a27d21b1122c0cb4e9" + ] + ], + "content": "", + "sig": "648230464f6b79063da76c1c9d06cd290c65f95fca4bac2e055f84f003847a4b9a2e144b4d77ec9f2f5289d477353a21494548d1b1fbf8795602c8914a062d50" + } + ], + "contact_list_update_response": [ + "OK", + "1439f08983433295bc54d24b8c3cda2fa137d86636535a408d2d9a7bac5f0c40", + true, + "" ] } } \ No newline at end of file diff --git a/tests/fixture/events.json b/tests/fixture/events.json index 432f949..29720f1 100644 --- a/tests/fixture/events.json +++ b/tests/fixture/events.json @@ -48,7 +48,7 @@ } }, { - "name": "kind 3", + "name": "kind 3, contact list", "data": { "id": "d1e5db203ef5fb1699f106f132bae1a3b5c9c8acf4fbb6c4a50844a6827164f1", "pubkey": "69795541a6635015b7e18b7f3f0f663fdec952bbd92642ee879610fae2e25718", diff --git a/tests/test_clients.py b/tests/test_clients.py index 194a006..49fedf7 100644 --- a/tests/test_clients.py +++ b/tests/test_clients.py @@ -1,8 +1,8 @@ import asyncio -import pytest - -from json import dumps, loads from copy import deepcopy +from json import dumps, loads + +import pytest from fastapi import WebSocket from lnbits.extensions.nostrrelay.client_manager import ( @@ -45,6 +45,8 @@ async def test_alice_and_bob(): await bob_wires_meta_and_folows_alice(ws_bob) + await bob_wires_contact_list(ws_alice, ws_bob) + await alice_wires_post02_____bob_is_notified(ws_alice, ws_bob) await bob_likes_post01_____alice_subscribes_and_receives_notifications( @@ -130,6 +132,41 @@ async def bob_wires_meta_and_folows_alice(ws_bob: MockWebSocket): ), "Bob: Wrong End Of Streaming Event for sub0" +async def bob_wires_contact_list(ws_alice: MockWebSocket, ws_bob: MockWebSocket): + ws_alice.sent_messages.clear() + ws_bob.sent_messages.clear() + + await ws_bob.wire_mock_data(bob["contact_list_create"]) + await ws_bob.wire_mock_data(bob["contact_list_update"]) + await asyncio.sleep(0.1) + await ws_alice.wire_mock_data(alice["subscribe_to_bob_contact_list"]) + await asyncio.sleep(0.1) + + + print("### ws_alice.sent_message", ws_alice.sent_messages) + print("### ws_bob.sent_message", ws_bob.sent_messages) + + assert ( + len(ws_bob.sent_messages) == 2 + ), "Bob: Expected 1 confirmation for create contact list" + assert ws_bob.sent_messages[0] == dumps( + bob["contact_list_create_response"] + ), "Bob: Wrong confirmation for contact list create" + assert ws_bob.sent_messages[1] == dumps( + bob["contact_list_update_response"] + ), "Bob: Wrong confirmation for contact list update" + + assert ( + len(ws_alice.sent_messages) == 2 + ), "Alice: Expected 3 messages for Bob's contact list" + assert ws_alice.sent_messages[0] == dumps( + ["EVENT", "contact", bob["contact_list_update"][1]] + ), "Alice: Expected to receive the updated contact list (two items)" + assert ws_alice.sent_messages[1] == dumps( + ["EOSE", "contact"] + ), "Alice: Wrong End Of Streaming Event for contact list" + + async def alice_wires_post02_____bob_is_notified( ws_alice: MockWebSocket, ws_bob: MockWebSocket ):