|
1 | 1 | import logging |
| 2 | +from http import HTTPStatus |
2 | 3 | from typing import TYPE_CHECKING, Optional |
3 | 4 |
|
4 | | -from synapse.api.errors import AuthError, NotFoundError |
5 | | -from synapse.storage.databases.main.thread_subscriptions import ThreadSubscription |
6 | | -from synapse.types import UserID |
| 5 | +from synapse.api.constants import RelationTypes |
| 6 | +from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError |
| 7 | +from synapse.events import relation_from_event |
| 8 | +from synapse.storage.databases.main.thread_subscriptions import ( |
| 9 | + AutomaticSubscriptionConflicted, |
| 10 | + ThreadSubscription, |
| 11 | +) |
| 12 | +from synapse.types import EventOrderings, UserID |
7 | 13 |
|
8 | 14 | if TYPE_CHECKING: |
9 | 15 | from synapse.server import HomeServer |
@@ -55,42 +61,79 @@ async def subscribe_user_to_thread( |
55 | 61 | room_id: str, |
56 | 62 | thread_root_event_id: str, |
57 | 63 | *, |
58 | | - automatic: bool, |
| 64 | + automatic_event_id: Optional[str], |
59 | 65 | ) -> Optional[int]: |
60 | 66 | """Sets or updates a user's subscription settings for a specific thread root. |
61 | 67 |
|
62 | 68 | Args: |
63 | 69 | requester_user_id: The ID of the user whose settings are being updated. |
64 | 70 | thread_root_event_id: The event ID of the thread root. |
65 | | - automatic: whether the user was subscribed by an automatic decision by |
66 | | - their client. |
| 71 | + automatic_event_id: if the user was subscribed by an automatic decision by |
| 72 | + their client, the event ID that caused this. |
67 | 73 |
|
68 | 74 | Returns: |
69 | 75 | The stream ID for this update, if the update isn't no-opped. |
70 | 76 |
|
71 | 77 | Raises: |
72 | 78 | NotFoundError if the user cannot access the thread root event, or it isn't |
73 | | - known to this homeserver. |
| 79 | + known to this homeserver. Ditto for the automatic cause event if supplied. |
| 80 | +
|
| 81 | + SynapseError(400, M_NOT_IN_THREAD): if client supplied an automatic cause event |
| 82 | + but user cannot access the event. |
| 83 | +
|
| 84 | + SynapseError(409, M_SKIPPED): if client requested an automatic subscription |
| 85 | + but it was skipped because the cause event is logically later than an unsubscription. |
74 | 86 | """ |
75 | 87 | # First check that the user can access the thread root event |
76 | 88 | # and that it exists |
77 | 89 | try: |
78 | | - event = await self.event_handler.get_event( |
| 90 | + thread_root_event = await self.event_handler.get_event( |
79 | 91 | user_id, room_id, thread_root_event_id |
80 | 92 | ) |
81 | | - if event is None: |
| 93 | + if thread_root_event is None: |
82 | 94 | raise NotFoundError("No such thread root") |
83 | 95 | except AuthError: |
84 | 96 | logger.info("rejecting thread subscriptions change (thread not accessible)") |
85 | 97 | raise NotFoundError("No such thread root") |
86 | 98 |
|
87 | | - return await self.store.subscribe_user_to_thread( |
| 99 | + if automatic_event_id: |
| 100 | + autosub_cause_event = await self.event_handler.get_event( |
| 101 | + user_id, room_id, automatic_event_id |
| 102 | + ) |
| 103 | + if autosub_cause_event is None: |
| 104 | + raise NotFoundError("Automatic subscription event not found") |
| 105 | + relation = relation_from_event(autosub_cause_event) |
| 106 | + if ( |
| 107 | + relation is None |
| 108 | + or relation.rel_type != RelationTypes.THREAD |
| 109 | + or relation.parent_id != thread_root_event_id |
| 110 | + ): |
| 111 | + raise SynapseError( |
| 112 | + HTTPStatus.BAD_REQUEST, |
| 113 | + "Automatic subscription must use an event in the thread", |
| 114 | + errcode=Codes.MSC4306_NOT_IN_THREAD, |
| 115 | + ) |
| 116 | + |
| 117 | + automatic_event_orderings = EventOrderings.from_event(autosub_cause_event) |
| 118 | + else: |
| 119 | + automatic_event_orderings = None |
| 120 | + |
| 121 | + outcome = await self.store.subscribe_user_to_thread( |
88 | 122 | user_id.to_string(), |
89 | | - event.room_id, |
| 123 | + room_id, |
90 | 124 | thread_root_event_id, |
91 | | - automatic=automatic, |
| 125 | + automatic_event_orderings=automatic_event_orderings, |
92 | 126 | ) |
93 | 127 |
|
| 128 | + if isinstance(outcome, AutomaticSubscriptionConflicted): |
| 129 | + raise SynapseError( |
| 130 | + HTTPStatus.CONFLICT, |
| 131 | + "Automatic subscription obsoleted by an unsubscription request.", |
| 132 | + errcode=Codes.MSC4306_CONFLICTING_UNSUBSCRIPTION, |
| 133 | + ) |
| 134 | + |
| 135 | + return outcome |
| 136 | + |
94 | 137 | async def unsubscribe_user_from_thread( |
95 | 138 | self, user_id: UserID, room_id: str, thread_root_event_id: str |
96 | 139 | ) -> Optional[int]: |
|
0 commit comments