1414# limitations under the License.
1515import logging
1616import re
17- from typing import TYPE_CHECKING
17+ from typing import TYPE_CHECKING , Iterable , List , Match , Optional
1818
1919from 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
2222from synapse .util .caches .descriptors import cached
2323
2424if TYPE_CHECKING :
25+ from synapse .appservice .api import ApplicationServiceApi
2526 from synapse .storage .databases .main import DataStore
2627
2728logger = logging .getLogger (__name__ )
@@ -32,38 +33,6 @@ class ApplicationServiceState:
3233 UP = "up"
3334
3435
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-
6736class ApplicationService :
6837 """Defines an application service. This definition is mostly what is
6938 provided to the /register AS API.
@@ -91,6 +60,7 @@ def __init__(
9160 protocols = None ,
9261 rate_limited = True ,
9362 ip_range_whitelist = None ,
63+ supports_ephemeral = False ,
9464 ):
9565 self .token = token
9666 self .url = (
@@ -102,6 +72,7 @@ def __init__(
10272 self .namespaces = self ._check_namespaces (namespaces )
10373 self .id = id
10474 self .ip_range_whitelist = ip_range_whitelist
75+ self .supports_ephemeral = supports_ephemeral
10576
10677 if "|" in self .id :
10778 raise Exception ("application service ID cannot contain '|' character" )
@@ -161,19 +132,21 @@ def _check_namespaces(self, namespaces):
161132 raise ValueError ("Expected string for 'regex' in ns '%s'" % ns )
162133 return namespaces
163134
164- def _matches_regex (self , test_string , namespace_key ) :
135+ def _matches_regex (self , test_string : str , namespace_key : str ) -> Optional [ Match ] :
165136 for regex_obj in self .namespaces [namespace_key ]:
166137 if regex_obj ["regex" ].match (test_string ):
167138 return regex_obj
168139 return None
169140
170- def _is_exclusive (self , ns_key , test_string ) :
141+ def _is_exclusive (self , ns_key : str , test_string : str ) -> bool :
171142 regex_obj = self ._matches_regex (test_string , ns_key )
172143 if regex_obj :
173144 return regex_obj ["exclusive" ]
174145 return False
175146
176- async def _matches_user (self , event , store ):
147+ async def _matches_user (
148+ self , event : Optional [EventBase ], store : Optional ["DataStore" ] = None
149+ ) -> bool :
177150 if not event :
178151 return False
179152
@@ -188,27 +161,38 @@ async def _matches_user(self, event, store):
188161 if not store :
189162 return False
190163
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 )
192165 return does_match
193166
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 )
199181
200182 # check joined member events
201183 for user_id in member_list :
202184 if self .is_interested_in_user (user_id ):
203185 return True
204186 return False
205187
206- def _matches_room_id (self , event ) :
188+ def _matches_room_id (self , event : EventBase ) -> bool :
207189 if hasattr (event , "room_id" ):
208190 return self .is_interested_in_room (event .room_id )
209191 return False
210192
211- async def _matches_aliases (self , event , store ):
193+ async def _matches_aliases (
194+ self , event : EventBase , store : Optional ["DataStore" ] = None
195+ ) -> bool :
212196 if not store or not event :
213197 return False
214198
@@ -218,52 +202,82 @@ async def _matches_aliases(self, event, store):
218202 return True
219203 return False
220204
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 :
222208 """Check if this service is interested in this event.
223209
224210 Args:
225- event(Event): The event to check.
226- store(DataStore)
211+ event: The event to check.
212+ store: The datastore to query.
213+
227214 Returns:
228215 True if this service would like to know about this event.
229216 """
230217 # Do cheap checks first
231218 if self ._matches_room_id (event ):
232219 return True
233220
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
234227 if await self ._matches_aliases (event , store ):
235228 return True
236229
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 ()):
238247 return True
248+ room_ids = await store .get_rooms_for_user (user_id .to_string ())
239249
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
240254 return False
241255
242- def is_interested_in_user (self , user_id ) :
256+ def is_interested_in_user (self , user_id : str ) -> bool :
243257 return (
244- self ._matches_regex (user_id , ApplicationService .NS_USERS )
258+ bool ( self ._matches_regex (user_id , ApplicationService .NS_USERS ) )
245259 or user_id == self .sender
246260 )
247261
248- def is_interested_in_alias (self , alias ) :
262+ def is_interested_in_alias (self , alias : str ) -> bool :
249263 return bool (self ._matches_regex (alias , ApplicationService .NS_ALIASES ))
250264
251- def is_interested_in_room (self , room_id ) :
265+ def is_interested_in_room (self , room_id : str ) -> bool :
252266 return bool (self ._matches_regex (room_id , ApplicationService .NS_ROOMS ))
253267
254- def is_exclusive_user (self , user_id ) :
268+ def is_exclusive_user (self , user_id : str ) -> bool :
255269 return (
256270 self ._is_exclusive (ApplicationService .NS_USERS , user_id )
257271 or user_id == self .sender
258272 )
259273
260- def is_interested_in_protocol (self , protocol ) :
274+ def is_interested_in_protocol (self , protocol : str ) -> bool :
261275 return protocol in self .protocols
262276
263- def is_exclusive_alias (self , alias ) :
277+ def is_exclusive_alias (self , alias : str ) -> bool :
264278 return self ._is_exclusive (ApplicationService .NS_ALIASES , alias )
265279
266- def is_exclusive_room (self , room_id ) :
280+ def is_exclusive_room (self , room_id : str ) -> bool :
267281 return self ._is_exclusive (ApplicationService .NS_ROOMS , room_id )
268282
269283 def get_exclusive_user_regexes (self ):
@@ -276,22 +290,22 @@ def get_exclusive_user_regexes(self):
276290 if regex_obj ["exclusive" ]
277291 ]
278292
279- def get_groups_for_user (self , user_id ) :
293+ def get_groups_for_user (self , user_id : str ) -> Iterable [ str ] :
280294 """Get the groups that this user is associated with by this AS
281295
282296 Args:
283- user_id (str) : The ID of the user.
297+ user_id: The ID of the user.
284298
285299 Returns:
286- iterable[str]: an iterable that yields group_id strings.
300+ An iterable that yields group_id strings.
287301 """
288302 return (
289303 regex_obj ["group_id" ]
290304 for regex_obj in self .namespaces [ApplicationService .NS_USERS ]
291305 if "group_id" in regex_obj and regex_obj ["regex" ].match (user_id )
292306 )
293307
294- def is_rate_limited (self ):
308+ def is_rate_limited (self ) -> bool :
295309 return self .rate_limited
296310
297311 def __str__ (self ):
@@ -300,3 +314,45 @@ def __str__(self):
300314 dict_copy ["token" ] = "<redacted>"
301315 dict_copy ["hs_token" ] = "<redacted>"
302316 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