14
14
# limitations under the License.
15
15
import logging
16
16
import re
17
- from typing import TYPE_CHECKING
17
+ from typing import TYPE_CHECKING , Iterable , List , Match , Optional
18
18
19
19
from synapse .api .constants import EventTypes
20
- from synapse .appservice . api import ApplicationServiceApi
21
- from synapse .types import GroupID , get_domain_from_id
20
+ from synapse .events import EventBase
21
+ from synapse .types import GroupID , JsonDict , UserID , get_domain_from_id
22
22
from synapse .util .caches .descriptors import cached
23
23
24
24
if TYPE_CHECKING :
25
+ from synapse .appservice .api import ApplicationServiceApi
25
26
from synapse .storage .databases .main import DataStore
26
27
27
28
logger = logging .getLogger (__name__ )
@@ -32,38 +33,6 @@ class ApplicationServiceState:
32
33
UP = "up"
33
34
34
35
35
- class AppServiceTransaction :
36
- """Represents an application service transaction."""
37
-
38
- def __init__ (self , service , id , events ):
39
- self .service = service
40
- self .id = id
41
- self .events = events
42
-
43
- async def send (self , as_api : ApplicationServiceApi ) -> bool :
44
- """Sends this transaction using the provided AS API interface.
45
-
46
- Args:
47
- as_api: The API to use to send.
48
- Returns:
49
- True if the transaction was sent.
50
- """
51
- return await as_api .push_bulk (
52
- service = self .service , events = self .events , txn_id = self .id
53
- )
54
-
55
- async def complete (self , store : "DataStore" ) -> None :
56
- """Completes this transaction as successful.
57
-
58
- Marks this transaction ID on the application service and removes the
59
- transaction contents from the database.
60
-
61
- Args:
62
- store: The database store to operate on.
63
- """
64
- await store .complete_appservice_txn (service = self .service , txn_id = self .id )
65
-
66
-
67
36
class ApplicationService :
68
37
"""Defines an application service. This definition is mostly what is
69
38
provided to the /register AS API.
@@ -91,6 +60,7 @@ def __init__(
91
60
protocols = None ,
92
61
rate_limited = True ,
93
62
ip_range_whitelist = None ,
63
+ supports_ephemeral = False ,
94
64
):
95
65
self .token = token
96
66
self .url = (
@@ -102,6 +72,7 @@ def __init__(
102
72
self .namespaces = self ._check_namespaces (namespaces )
103
73
self .id = id
104
74
self .ip_range_whitelist = ip_range_whitelist
75
+ self .supports_ephemeral = supports_ephemeral
105
76
106
77
if "|" in self .id :
107
78
raise Exception ("application service ID cannot contain '|' character" )
@@ -161,19 +132,21 @@ def _check_namespaces(self, namespaces):
161
132
raise ValueError ("Expected string for 'regex' in ns '%s'" % ns )
162
133
return namespaces
163
134
164
- def _matches_regex (self , test_string , namespace_key ) :
135
+ def _matches_regex (self , test_string : str , namespace_key : str ) -> Optional [ Match ] :
165
136
for regex_obj in self .namespaces [namespace_key ]:
166
137
if regex_obj ["regex" ].match (test_string ):
167
138
return regex_obj
168
139
return None
169
140
170
- def _is_exclusive (self , ns_key , test_string ) :
141
+ def _is_exclusive (self , ns_key : str , test_string : str ) -> bool :
171
142
regex_obj = self ._matches_regex (test_string , ns_key )
172
143
if regex_obj :
173
144
return regex_obj ["exclusive" ]
174
145
return False
175
146
176
- async def _matches_user (self , event , store ):
147
+ async def _matches_user (
148
+ self , event : Optional [EventBase ], store : Optional ["DataStore" ] = None
149
+ ) -> bool :
177
150
if not event :
178
151
return False
179
152
@@ -188,27 +161,38 @@ async def _matches_user(self, event, store):
188
161
if not store :
189
162
return False
190
163
191
- does_match = await self ._matches_user_in_member_list (event .room_id , store )
164
+ does_match = await self .matches_user_in_member_list (event .room_id , store )
192
165
return does_match
193
166
194
- @cached (num_args = 1 , cache_context = True )
195
- async def _matches_user_in_member_list (self , room_id , store , cache_context ):
196
- member_list = await store .get_users_in_room (
197
- room_id , on_invalidate = cache_context .invalidate
198
- )
167
+ @cached (num_args = 1 )
168
+ async def matches_user_in_member_list (
169
+ self , room_id : str , store : "DataStore"
170
+ ) -> bool :
171
+ """Check if this service is interested a room based upon it's membership
172
+
173
+ Args:
174
+ room_id: The room to check.
175
+ store: The datastore to query.
176
+
177
+ Returns:
178
+ True if this service would like to know about this room.
179
+ """
180
+ member_list = await store .get_users_in_room (room_id )
199
181
200
182
# check joined member events
201
183
for user_id in member_list :
202
184
if self .is_interested_in_user (user_id ):
203
185
return True
204
186
return False
205
187
206
- def _matches_room_id (self , event ) :
188
+ def _matches_room_id (self , event : EventBase ) -> bool :
207
189
if hasattr (event , "room_id" ):
208
190
return self .is_interested_in_room (event .room_id )
209
191
return False
210
192
211
- async def _matches_aliases (self , event , store ):
193
+ async def _matches_aliases (
194
+ self , event : EventBase , store : Optional ["DataStore" ] = None
195
+ ) -> bool :
212
196
if not store or not event :
213
197
return False
214
198
@@ -218,52 +202,82 @@ async def _matches_aliases(self, event, store):
218
202
return True
219
203
return False
220
204
221
- async def is_interested (self , event , store = None ) -> bool :
205
+ async def is_interested (
206
+ self , event : EventBase , store : Optional ["DataStore" ] = None
207
+ ) -> bool :
222
208
"""Check if this service is interested in this event.
223
209
224
210
Args:
225
- event(Event): The event to check.
226
- store(DataStore)
211
+ event: The event to check.
212
+ store: The datastore to query.
213
+
227
214
Returns:
228
215
True if this service would like to know about this event.
229
216
"""
230
217
# Do cheap checks first
231
218
if self ._matches_room_id (event ):
232
219
return True
233
220
221
+ # This will check the namespaces first before
222
+ # checking the store, so should be run before _matches_aliases
223
+ if await self ._matches_user (event , store ):
224
+ return True
225
+
226
+ # This will check the store, so should be run last
234
227
if await self ._matches_aliases (event , store ):
235
228
return True
236
229
237
- if await self ._matches_user (event , store ):
230
+ return False
231
+
232
+ @cached (num_args = 1 )
233
+ async def is_interested_in_presence (
234
+ self , user_id : UserID , store : "DataStore"
235
+ ) -> bool :
236
+ """Check if this service is interested a user's presence
237
+
238
+ Args:
239
+ user_id: The user to check.
240
+ store: The datastore to query.
241
+
242
+ Returns:
243
+ True if this service would like to know about presence for this user.
244
+ """
245
+ # Find all the rooms the sender is in
246
+ if self .is_interested_in_user (user_id .to_string ()):
238
247
return True
248
+ room_ids = await store .get_rooms_for_user (user_id .to_string ())
239
249
250
+ # Then find out if the appservice is interested in any of those rooms
251
+ for room_id in room_ids :
252
+ if await self .matches_user_in_member_list (room_id , store ):
253
+ return True
240
254
return False
241
255
242
- def is_interested_in_user (self , user_id ) :
256
+ def is_interested_in_user (self , user_id : str ) -> bool :
243
257
return (
244
- self ._matches_regex (user_id , ApplicationService .NS_USERS )
258
+ bool ( self ._matches_regex (user_id , ApplicationService .NS_USERS ) )
245
259
or user_id == self .sender
246
260
)
247
261
248
- def is_interested_in_alias (self , alias ) :
262
+ def is_interested_in_alias (self , alias : str ) -> bool :
249
263
return bool (self ._matches_regex (alias , ApplicationService .NS_ALIASES ))
250
264
251
- def is_interested_in_room (self , room_id ) :
265
+ def is_interested_in_room (self , room_id : str ) -> bool :
252
266
return bool (self ._matches_regex (room_id , ApplicationService .NS_ROOMS ))
253
267
254
- def is_exclusive_user (self , user_id ) :
268
+ def is_exclusive_user (self , user_id : str ) -> bool :
255
269
return (
256
270
self ._is_exclusive (ApplicationService .NS_USERS , user_id )
257
271
or user_id == self .sender
258
272
)
259
273
260
- def is_interested_in_protocol (self , protocol ) :
274
+ def is_interested_in_protocol (self , protocol : str ) -> bool :
261
275
return protocol in self .protocols
262
276
263
- def is_exclusive_alias (self , alias ) :
277
+ def is_exclusive_alias (self , alias : str ) -> bool :
264
278
return self ._is_exclusive (ApplicationService .NS_ALIASES , alias )
265
279
266
- def is_exclusive_room (self , room_id ) :
280
+ def is_exclusive_room (self , room_id : str ) -> bool :
267
281
return self ._is_exclusive (ApplicationService .NS_ROOMS , room_id )
268
282
269
283
def get_exclusive_user_regexes (self ):
@@ -276,22 +290,22 @@ def get_exclusive_user_regexes(self):
276
290
if regex_obj ["exclusive" ]
277
291
]
278
292
279
- def get_groups_for_user (self , user_id ) :
293
+ def get_groups_for_user (self , user_id : str ) -> Iterable [ str ] :
280
294
"""Get the groups that this user is associated with by this AS
281
295
282
296
Args:
283
- user_id (str) : The ID of the user.
297
+ user_id: The ID of the user.
284
298
285
299
Returns:
286
- iterable[str]: an iterable that yields group_id strings.
300
+ An iterable that yields group_id strings.
287
301
"""
288
302
return (
289
303
regex_obj ["group_id" ]
290
304
for regex_obj in self .namespaces [ApplicationService .NS_USERS ]
291
305
if "group_id" in regex_obj and regex_obj ["regex" ].match (user_id )
292
306
)
293
307
294
- def is_rate_limited (self ):
308
+ def is_rate_limited (self ) -> bool :
295
309
return self .rate_limited
296
310
297
311
def __str__ (self ):
@@ -300,3 +314,45 @@ def __str__(self):
300
314
dict_copy ["token" ] = "<redacted>"
301
315
dict_copy ["hs_token" ] = "<redacted>"
302
316
return "ApplicationService: %s" % (dict_copy ,)
317
+
318
+
319
+ class AppServiceTransaction :
320
+ """Represents an application service transaction."""
321
+
322
+ def __init__ (
323
+ self ,
324
+ service : ApplicationService ,
325
+ id : int ,
326
+ events : List [EventBase ],
327
+ ephemeral : List [JsonDict ],
328
+ ):
329
+ self .service = service
330
+ self .id = id
331
+ self .events = events
332
+ self .ephemeral = ephemeral
333
+
334
+ async def send (self , as_api : "ApplicationServiceApi" ) -> bool :
335
+ """Sends this transaction using the provided AS API interface.
336
+
337
+ Args:
338
+ as_api: The API to use to send.
339
+ Returns:
340
+ True if the transaction was sent.
341
+ """
342
+ return await as_api .push_bulk (
343
+ service = self .service ,
344
+ events = self .events ,
345
+ ephemeral = self .ephemeral ,
346
+ txn_id = self .id ,
347
+ )
348
+
349
+ async def complete (self , store : "DataStore" ) -> None :
350
+ """Completes this transaction as successful.
351
+
352
+ Marks this transaction ID on the application service and removes the
353
+ transaction contents from the database.
354
+
355
+ Args:
356
+ store: The database store to operate on.
357
+ """
358
+ await store .complete_appservice_txn (service = self .service , txn_id = self .id )
0 commit comments