1
1
from collections import UserList
2
- from .event import Event
2
+ from typing import List
3
+
4
+ from .event import Event , EventKind
5
+
6
+
3
7
4
8
class Filter :
9
+ """
10
+ NIP-01 filtering.
11
+
12
+ Explicitly supports "#e" and "#p" tag filters via `event_refs` and `pubkey_refs`.
13
+
14
+ Arbitrary NIP-12 single-letter tag filters are also supported via `add_arbitrary_tag`.
15
+ If a particular single-letter tag gains prominence, explicit support should be
16
+ added. For example:
17
+ # arbitrary tag
18
+ filter.add_arbitrary_tag('t', [hashtags])
19
+
20
+ # promoted to explicit support
21
+ Filter(hashtag_refs=[hashtags])
22
+ """
5
23
def __init__ (
6
24
self ,
7
- ids : "list[str]" = None ,
8
- kinds : "list[int]" = None ,
9
- authors : "list[str]" = None ,
10
- since : int = None ,
11
- until : int = None ,
12
- tags : "dict[str, list[str]]" = None ,
13
- limit : int = None ) -> None :
14
- self .IDs = ids
25
+ event_ids : List [str ] = None ,
26
+ kinds : List [EventKind ] = None ,
27
+ authors : List [str ] = None ,
28
+ since : int = None ,
29
+ until : int = None ,
30
+ event_refs : List [str ] = None , # the "#e" attr; list of event ids referenced in an "e" tag
31
+ pubkey_refs : List [str ] = None , # The "#p" attr; list of pubkeys referenced in a "p" tag
32
+ limit : int = None ) -> None :
33
+ self .event_ids = event_ids
15
34
self .kinds = kinds
16
35
self .authors = authors
17
36
self .since = since
18
37
self .until = until
19
- self .tags = tags
38
+ self .event_refs = event_refs
39
+ self .pubkey_refs = pubkey_refs
20
40
self .limit = limit
21
41
42
+ self .tags = {}
43
+ if self .event_refs :
44
+ self .add_arbitrary_tag ('e' , self .event_refs )
45
+ if self .pubkey_refs :
46
+ self .add_arbitrary_tag ('p' , self .pubkey_refs )
47
+
48
+
49
+ def add_arbitrary_tag (self , tag : str , values : list ):
50
+ """
51
+ Filter on any arbitrary tag with explicit handling for NIP-01 and NIP-12
52
+ single-letter tags.
53
+ """
54
+ # NIP-01 'e' and 'p' tags and any NIP-12 single-letter tags must be prefixed with "#"
55
+ tag_key = tag if len (tag ) > 1 else f"#{ tag } "
56
+ self .tags [tag_key ] = values
57
+
58
+
22
59
def matches (self , event : Event ) -> bool :
23
- if self .IDs != None and event .id not in self .IDs :
60
+ if self .event_ids is not None and event .id not in self .event_ids :
24
61
return False
25
- if self .kinds != None and event .kind not in self .kinds :
62
+ if self .kinds is not None and event .kind not in self .kinds :
26
63
return False
27
- if self .authors != None and event .public_key not in self .authors :
64
+ if self .authors is not None and event .public_key not in self .authors :
28
65
return False
29
- if self .since != None and event .created_at < self .since :
66
+ if self .since is not None and event .created_at < self .since :
30
67
return False
31
- if self .until != None and event .created_at > self .until :
68
+ if self .until is not None and event .created_at > self .until :
32
69
return False
33
- if self .tags != None and len (event .tags ) == 0 :
70
+ if ( self .event_refs is not None or self . pubkey_refs is not None ) and len (event .tags ) == 0 :
34
71
return False
35
- if self .tags != None :
36
- e_tag_identifiers = [e_tag [0 ] for e_tag in event .tags ]
72
+
73
+ if self .tags :
74
+ e_tag_identifiers = set ([e_tag [0 ] for e_tag in event .tags ])
37
75
for f_tag , f_tag_values in self .tags .items ():
38
- if f_tag [1 :] not in e_tag_identifiers :
76
+ # Omit any NIP-01 or NIP-12 "#" chars on single-letter tags
77
+ f_tag = f_tag .replace ("#" , "" )
78
+
79
+ if f_tag not in e_tag_identifiers :
80
+ # Event is missing a tag type that we're looking for
39
81
return False
82
+
83
+ # Multiple values within f_tag_values are treated as OR search; an Event
84
+ # needs to match only one.
85
+ # Note: an Event could have multiple entries of the same tag type
86
+ # (e.g. a reply to multiple people) so we have to check all of them.
87
+ match_found = False
40
88
for e_tag in event .tags :
41
- if e_tag [1 ] not in f_tag_values :
42
- return False
43
-
89
+ if e_tag [0 ] == f_tag and e_tag [1 ] in f_tag_values :
90
+ match_found = True
91
+ break
92
+ if not match_found :
93
+ return False
94
+
44
95
return True
45
96
97
+
46
98
def to_json_object (self ) -> dict :
47
99
res = {}
48
- if self .IDs != None :
49
- res ["ids" ] = self .IDs
50
- if self .kinds != None :
100
+ if self .event_ids is not None :
101
+ res ["ids" ] = self .event_ids
102
+ if self .kinds is not None :
51
103
res ["kinds" ] = self .kinds
52
- if self .authors != None :
104
+ if self .authors is not None :
53
105
res ["authors" ] = self .authors
54
- if self .since != None :
106
+ if self .since is not None :
55
107
res ["since" ] = self .since
56
- if self .until != None :
108
+ if self .until is not None :
57
109
res ["until" ] = self .until
58
- if self .tags != None :
59
- for tag , values in self .tags .items ():
60
- res [tag ] = values
61
- if self .limit != None :
110
+ if self .limit is not None :
62
111
res ["limit" ] = self .limit
112
+ if self .tags :
113
+ res .update (self .tags )
63
114
64
115
return res
65
-
116
+
117
+
118
+
66
119
class Filters (UserList ):
67
120
def __init__ (self , initlist : "list[Filter]" = []) -> None :
68
121
super ().__init__ (initlist )
@@ -75,5 +128,4 @@ def match(self, event: Event):
75
128
return False
76
129
77
130
def to_json_array (self ) -> list :
78
- return [filter .to_json_object () for filter in self .data ]
79
-
131
+ return [filter .to_json_object () for filter in self .data ]
0 commit comments