diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4508a32 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +- 10 July 2022 - receives hard coded users latest events, and latest events from feed if NIP 02 type event is seen of the given user diff --git a/bin/nostr_console.dart b/bin/nostr_console.dart new file mode 100644 index 0000000..50751d8 --- /dev/null +++ b/bin/nostr_console.dart @@ -0,0 +1,44 @@ + +import 'dart:io'; +import 'package:nostr_console/nostr_console.dart'; +import 'package:nostr_console/relays.dart'; + +var userPublickey = "3235036bd0957dfb27ccda02d452d7c763be40c91a1ac082ba6983b25238388c"; + + +Future main() async { + List events = []; + + int numEvents = 6; + getUserEvents(defaultServerUrl, userPublickey, events, numEvents); + + + print('waiting for user events to come in'); + Future.delayed(const Duration(milliseconds: 2000), () { + + + for( int i = 0; i < events.length; i++) { + var e = events[i]; + if( e.eventData.kind == 3) { + print('calling getfeed'); + getFeed(e.eventData.contactList, events, 3); + } + } + + + print('waiting for feed to come in'); + Future.delayed(const Duration(milliseconds: 4000), () { + events.sort(ascendingTime); + print('====================all events ================='); + printEvents(events); + print('number of all events: ${events.length}'); + + + exit(0); + + }); + + + + }); +} diff --git a/lib/nostr_console.dart b/lib/nostr_console.dart new file mode 100644 index 0000000..5b4ca66 --- /dev/null +++ b/lib/nostr_console.dart @@ -0,0 +1,158 @@ + +int keyLenPrinted = 6; +String defaultServerUrl = 'wss://nostr-pub.wellorder.net'; + + +class Contact { + String id; + String relay; + String name; + Contact(this.id, this.relay, this.name); + factory Contact.fromJson(dynamic json) { + return Contact(json[1] as String, json[2] as String, json[3]); + } +} + +class EventData { + String id; + String pubkey; + String content; + int createdAt; + int kind; + List contactList = []; + + EventData(this.id, this.pubkey, this.content, this.createdAt, this.kind, this.contactList); + + factory EventData.fromJson(dynamic json) { + List contactList = []; + + // NIP 02: if the event is a contact list type, then populate contactList + if(json['kind'] == 3) { + var tags = json['tags']; + //print(tags); + var numTags = tags.length; + for( int i = 0; i < numTags; i++) { + + var tag = tags[i]; + var n = tag.length; + String server = defaultServerUrl; + if( n >=3 ) { + server = tag[2].toString(); + } + Contact c = Contact(tag[1] as String, server, 3.toString()); + contactList.add(c); + } + + } + + // return + return EventData(json['id'] as String, + json['pubkey'] as String, + json['content'] as String, + json['created_at'] as int, + json['kind'] as int, + contactList); + } + @override + String toString() { + if( id == "non") { + return ''; + } + + String max3(String v) => v.length > 3? v.substring(0,3) : v.substring(0, v.length); + DateTime dTime = DateTime.fromMillisecondsSinceEpoch(createdAt *1000); + + return '-------+\nid : ${max3(id)} \nAuthor : ${max3(pubkey)}\nTime : $dTime\nKind : $kind\nMessage: $content\n'; + } +} + + +class Event { + String event; + String id; + EventData eventData; + Event(this.event, this.id, this.eventData); + factory Event.fromJson(dynamic json) { + if( json.length < 3) { + String e = ""; + e = json.length > 1? json[0]: ""; + return Event(e,"",EventData("non","","", 0, 0, [])); + } + + return Event(json[0] as String, json[1] as String, EventData.fromJson(json[2]) ); + } + + @override + String toString() { + return '$eventData'; + } +} + +int ascendingTime(Event a, Event b) { + if(a.eventData.createdAt < b.eventData.createdAt) { + return 0; + } + + return 1; +} + +void printEvents(List events) { + events.sort(ascendingTime); + for( int i = 0; i < events.length; i++) { + if( events[i].eventData.kind == 1) { + print('${events[i]}'); + } + } +} + + + +/* +NIP 02 +{ + "kind": 3, + "tags": [ + ["p", "91cf9..4e5ca", "wss://alicerelay.com/", "alice"], + ["p", "14aeb..8dad4", "wss://bobrelay.com/nostr", "bob"], + ["p", "612ae..e610f", "ws://carolrelay.com/ws", "carol"] + ], + "content": "", + ...other fields + + +Example Event returned +[ + "EVENT", + "latest", + { + "id": "5e4ca472540c567ba877ab232fa77c0801f4b2121756ed21d827e749b5074ac7", + "pubkey": "47bae3a008414e24b4d91c8c170f7fce777dedc6780a462d010761dca6482327", + "created_at": 1657316002, + "kind": 1, + "tags": [ + [ + "p", + "80482e60178c2ce996da6d67577f56a2b2c47ccb1c84c81f2b7960637cb71b78", + "wss://relay.damus.io" + ], + [ + "e", + "241f1108b3616eb4b3cfb9fdbab29d7f8d291fda9db84a79f2491271e2f6122e" + ], + [ + "e", + "b3349c4de2ff7a672c564e6fd147bc2d5dd71b525f96f35f8ede75138136c867", + "wss://nostr-pub.wellorder.net" + ] + ], + "content": "Not even surprised it's a thing https://coinmarketcap.com/currencies/lol/", + "sig": "abebbc96a8a922f06ca2773c59521d03b6a8dd5597ae5654afc2d49e03a1e9c193729ff60473f837edc526e41c94f3de8a328c20bf9cff5353cb9c409a982461" + } +] + + + factory EventData.fromStr(String str) { + var json = jsonDecode(str); + return EventData(json['id'] as String, json['pubkey'] as String, json['content'] as String); + } + */ \ No newline at end of file diff --git a/lib/relays.dart b/lib/relays.dart new file mode 100644 index 0000000..202487e --- /dev/null +++ b/lib/relays.dart @@ -0,0 +1,91 @@ + +import 'dart:io'; +import 'dart:convert'; +import 'package:nostr_console/nostr_console.dart'; + +int getLatestNum = 2; + +String getSubscriptionRequest(String publicKey, int numUserEvents) { + var strSubscription1 = '["REQ","latest",{ "authors": ["'; + var strSubscription2 ='"], "limit": $numUserEvents } ]'; + return strSubscription1 + publicKey + strSubscription2; +} + +class Relays { + Map > relays; + + Relays(this.relays); + + factory Relays.relay(String relay) { + Future fws = WebSocket.connect(relay); + print('In Relay.relay: connecting to relay $relay'); + Map> r = Map(); + r[relay] = fws; + return Relays(r); + } + + void connect(String relay, String publicKey, List events, int numUserEvents) { + Future? fws; + if(relays.containsKey(relay)) { + fws = relays[relay]; + } + else { + print('connecting to $relay'); + + try { + fws = WebSocket.connect(relay); + relays[relay] = fws; + fws.then((WebSocket ws) { + ws.listen( + (d) { + print(d); + Event e; + try { + e = Event.fromJson(jsonDecode(d)); + events.add(e); + if( e.eventData.kind == 3) { + + } + } on FormatException { + print( 'exception in fromJson for event'); + } + }, + onError: (e) { print("error"); print(e); }, + onDone: () { print('in onDone'); ws.close() ; } + );}); + + + } on WebSocketException { + print('WebSocketException exception'); + return; + } catch(e) { + print('exception generic'); + return; + } + } + + print('sending request ${getSubscriptionRequest(publicKey, numUserEvents)}'); + fws?.then((WebSocket ws) { ws.add(getSubscriptionRequest(publicKey, numUserEvents)); }); + + } + + Future? getWS(String relay) { + return relays[relay]; + } + +} + +Relays relays = Relays(Map()); + +void getFeed(List contacts, events, numEvents) { + for( int i = 0; i < contacts.length; i++) { + var contact = contacts[i]; + relays.connect(contact.relay, contact.id, events, numEvents); + } + + +} + +void getUserEvents(serverUrl, publicKey, events, numUserEvents) { + relays.connect(serverUrl, publicKey, events, numUserEvents); +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..53dac77 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,14 @@ +name: nostr_console +description: A sample command-line application. +version: 1.0.0 +# homepage: https://www.example.com + +environment: + sdk: '>=2.17.3 <3.0.0' + +# dependencies: +# path: ^1.8.0 + +dev_dependencies: + lints: ^2.0.0 + test: ^1.16.0 diff --git a/test/nostr_console_test.dart b/test/nostr_console_test.dart new file mode 100644 index 0000000..fc04471 --- /dev/null +++ b/test/nostr_console_test.dart @@ -0,0 +1,8 @@ +import 'package:nostr_console/nostr_console.dart'; +import 'package:test/test.dart'; + +void main() { + test('PrintEmptyEvent', () { + expect(EventData("non","","",1,1,[]).toString(), ""); + }); +}