Skip to content

Commit

Permalink
Merge pull request #57 from nostrver-se/feat/read_from_relay
Browse files Browse the repository at this point in the history
Read events from relays
  • Loading branch information
Sebastix authored Jun 20, 2024
2 parents 7bd8b05 + 703ff6a commit 859ff95
Show file tree
Hide file tree
Showing 18 changed files with 1,057 additions and 13 deletions.
120 changes: 116 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ $eventMessage = new EventMessage($note);
$message_string = $eventMessage->generate();
```

## Interacting with a relay
## Publish an event to a relay

Publish an event with a note that has been prepared for sending to a relay.

Expand All @@ -93,10 +93,115 @@ $signer->signEvent($note, $private_key);
$eventMessage = new EventMessage($note);

$relayUrl = 'wss://nostr-websocket.tld';
$relay = new Relay($relayUrl, $eventMessage);
$relay = new Relay($relayUrl);
$relay->setMessage($eventMessage);
$result = $relay->send();
```

If you would like to publish the event to multiple relays, you can use the `RelaySet` class.

```php
$relay1 = new Relay(''wss://nostr-websocket1.tld'');
$relay2 = new Relay(''wss://nostr-websocket2.tld'');
$relay3 = new Relay(''wss://nostr-websocket3.tld'');
$relay4 = new Relay(''wss://nostr-websocket4.tld'');
$relaySet = new RelaySet();
$relaySet->setRelays([$relay1, $relay2, $relay3, $relay4]);
$relaySet->setMessage($eventMessage);
$result = $relay->send();
```

## Read events from a relay

Fetch events from a relay.

```php
$subscription = new Subscription();
$subscriptionId = $subscription->setId();

$filter1 = new Filter();
$filter1->setKinds([1, 3]); // You can add multiple kind numbers
$filter1->setLimit(25); // Limit to fetch only a maximum of 25 events
$filters = [$filter1]; // You can add multiple filters.

$requestMessage = new RequestMessage($subscriptionId, $filters);

$relayUrl = 'wss://nostr-websocket.tld';
$relay = new Relay($relayUrl);
$relay->setMessage($requestMessage);

$request = new Request($relay, $requestMessage);
$response = $request->send();
```

`$response` is an multidimensional array with elements containing each a response message (JSON string) decoded to an array from the relay and sorted by the relay.
Output example:
```php
[
'wss://nostr-websocket.tld' => [
0 => [
"EVENT",
"A8kWzjCVUHSD1rmuwGqyK2PxsolZMO9YXditbg05fch6p3Q4eT7vRFLEJINBna",
[
'id' => '1e8534623845629d40f7761c0577edf10f778c490e7b95a524845d9280c7c25a',
'kind' => 1,
'pubkey' => '06639a386c9c1014217622ccbcf40908c4f1a0c33e23f8d6d68f4abf655f8f71',
'created_at' => 1718723787,
'content' => 'Losing your social graph can feel the same for some I think 😮 ',
'tags' => [
['e', 'f754a238947b7f32168f872650a8dd0b9376493e58005d7e0b8be52f6f229364', 'wss://nos.lol/', 'root'],
['e', 'fe7dd6ba22fa0aa39370aa160226b8bc2413460621c8d67ce862205ad5a02c24', 'wss://nos.lol/', 'reply'],
['p', 'fb1366abd5e4c92a8a950791bc72d51bde291a83555cb2c629a92fedd78068ac', '', 'mention']
],
'sig' => '888c9b5d9e0b69eba3510dd2b5d03eddcf0a680ab0e7673820fb36a56448ad80701042a669c7ef9918593c5a41c8b3ccc1d82ade50f32b62dd843144f32df403'
],
1 => [
"EVENT",
"A8kWzjCVUHSD1rmuwGqyK2PxsolZMO9YXditbg05fch6p3Q4eT7vRFLEJINBna",
[
...Nostr event
]
],
2 => [
...
],
3 => [
...
],
4 => [
...
]
]
]

```

## Read events from a set of relays

Read events from a set of relays with the `RelaySet` class.
It's basically the same snippet as above with the difference you create a `RelaySet` class and pass it through the `Request` object.

```php
$subscription = new Subscription();
$subscriptionId = $subscription->setId();

$filter1 = new Filter();
$filter1->setKinds([1]);
$filter1->setLimit(5);
$filters = [$filter1];
$requestMessage = new RequestMessage($subscriptionId, $filters);
$relays = [
new Relay('wss://nostr-websocket-1.tld'),
new Relay('wss://nostr-websocket-2.tld'),
new Relay('wss://nostr-websocket-3.tld'),
];
$relaySet = new RelaySet();
$relaySet->setRelays($relays);

$request = new Request($relaySet, $requestMessage);
$response = $request->send();
```

## Generating a private key and a public key

```php
Expand Down Expand Up @@ -161,13 +266,20 @@ private key on command line.
- [x] Event validation (issue [#17](https://github.com/swentel/nostr-php/issues/17))
- [ ] Support NIP-01 basic protocol flow description
- [x] Publish events
- [ ] Request events (pr [#48](https://github.com/swentel/nostr-php/pull/48))
- [x] Request events (issue [#55](https://github.com/nostrver-se/nostr-php/pull/55) credits to [kriptonix](https://github.com/kriptonix))
- [ ] Implement all types of relay responses
- [ ] EVENT - sends events requested by the client
- [ ] OK - indicate an acceptance or denial of an EVENT message
- [ ] EOSE - end of stored events
- [ ] CLOSED - subscription is ended on the server side
- [ ] NOTICE - used to send human-readable messages (like errors) to clients
- [ ] Improve handling relay responses
- [ ] Support NIP-19 bech32-encoded identifiers
- [ ] Support NIP-42 authentication of clients to relays
- [ ] Support NIP-45 event counts
- [ ] Support NIP-50 search capability
- [ ] Support multi-threading for handling requests simultaneously
- [ ] Support multi-threading (async concurrency) for handling requests simultaneously
- [ ] Support realtime (runtime) subscriptions with the `bin/nostr-php` CLI client to listen to new events from relays

## Community

Expand Down
191 changes: 191 additions & 0 deletions src/Filter/Filter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
<?php

declare(strict_types=1);

namespace swentel\nostr\Filter;

use swentel\nostr\FilterInterface;

class Filter implements FilterInterface
{
/**
* A list of event ids
*/
public array $id;

/**
* A list of lowercase pubkeys, the pubkey of an event must be one of these
*/
public array $authors;

/**
* A list of a kind numbers
*/
public array $kinds;

/**
* A list of #e tag values (list of event ids)
*/
public array $etags;

/**
* A list of #p tag values (list of pubkeys).
*/
public array $ptags;

/**
* An integer unix timestamp in seconds, events must be newer than this to pass
*/
public int $since;

/**
* An integer unix timestamp in seconds, events must be older than this to pass
*/
public int $until;

/**
* Maximum number of events relays SHOULD return in the initial query
*/
public int $limit;

/**
* Set the authors for the Filter object.
*
* @param array $pubkey The array of authors to set.
*/
public function setAuthors(array $pubkeys): static
{
foreach($pubkeys as $key) {
if(!$this->isLowercaseHex($key)) {
throw new \RuntimeException("Author pubkeys must be an array of 64-character lowercase hex values");
}
}
$this->authors = $pubkeys;
return $this;
}

/**
* Set the kinds for the Filter object.
*
* @param array $kinds The array of kinds to set.
*/
public function setKinds(array $kinds): static
{
$this->kinds = $kinds;
return $this;
}

/**
* Set the #e tag for the Filter object.
*
* @param array $etag The array of tag to set.
*/
public function setLowercaseETags(array $etags): static
{
foreach($etags as $tag) {
if(!$this->isLowercaseHex($tag)) {
throw new \RuntimeException("#e tags must be an array of 64-character lowercase hex values");
}
}
$this->etags = $etags;
return $this;
}

/**
* Set the #p tag for the Filter object.
*
* @param array $ptag The array of tag to set.
*/
public function setLowercasePTags(array $ptags): static
{
// Check IF array contain exact 64-character lowercase hex values
foreach($ptags as $tag) {
if(!$this->isLowercaseHex($tag)) {
throw new \RuntimeException("#p tags must be an array of 64-character lowercase hex values");
}
}
$this->ptags = $ptags;
return $this;
}

/**
* Set the since for the Filter object.
*
* @param int $since The limit to set.
*/
public function setSince(int $since): static
{
$this->since = $since;
return $this;
}

/**
* Set the until for the Filter object.
*
* @param int $until The limit to set.
*/
public function setUntil(int $until): static
{
$this->until = $until;
return $this;
}

/**
* Set the limit for the Filter object.
*
* @param int $limit The limit to set.
*/
public function setLimit(int $limit): static
{
$this->limit = $limit;
return $this;
}

/**
* Check if a given string is a 64-character lowercase hexadecimal value.
*
* @param string $string The string to check.
* @return bool True if the string is a 64-character lowercase hexadecimal value, false otherwise.
*/
public function isLowercaseHex($string): bool
{
// Regular expression to match 64-character lowercase hexadecimal value
$pattern = '/^[a-f0-9]{64}$/';
// Check if the string matches the pattern
return preg_match($pattern, $string) === 1;
}

/**
* Check if a given timestamp is valid.
*
* @param mixed $timestamp The timestamp to check.
* @return bool True if the timestamp is valid, false otherwise.
*/
public function isValidTimestamp($timestamp): bool
{
// Convert the timestamp to seconds
$timestamp = (int) $timestamp;
// Check if the timestamp is valid
return ($timestamp !== 0 && $timestamp !== false && $timestamp !== -1);
}

/**
* Return an array representation of the object by iterating through its properties.
*
* @return array The array representation of the object.
*/
public function toArray(): array
{
$array = [];
foreach (get_object_vars($this) as $key => $val) {
if($key === 'etags') {
$array['#e'] = $val;
} elseif($key === 'ptags') {
$array['#p'] = $val;
} else {
$array[$key] = $val;
}
}
return $array;
}
}
Loading

0 comments on commit 859ff95

Please sign in to comment.