Skip to content

Commit e2d56db

Browse files
andersktimabbott
authored andcommitted
message_cache: Use the sender’s recipient_id for incoming 1:1 DMs.
For an incoming 1:1 DM, the recipient’s own recipient_id is useless to the recipient themselves. Substitute the sender’s recipient_id, so the recipient can use recipient_id as documented to uniquely represent the set of 2 users in this conversation. Signed-off-by: Anders Kaseorg <[email protected]>
1 parent 6ef0f0c commit e2d56db

14 files changed

+120
-12
lines changed

api_docs/changelog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ format used by the Zulip server that they are interacting with.
2020

2121
## Changes in Zulip 10.0
2222

23+
**Feature level 327**
24+
25+
* [`GET /messages`](/api/get-messages), [`GET
26+
/messages/{message_id}`](/api/get-message), [`GET /events`](/api/get-events):
27+
Adjusted the `recipient_id` field of an incoming 1:1 direct message to use the
28+
same value that would be used for an outgoing message in that conversation.
29+
2330
**Feature level 326**
2431

2532
* [`POST /register`](/api/register-queue): Removed `allow_owners_group`

version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
# new level means in api_docs/changelog.md, as well as "**Changes**"
3535
# entries in the endpoint's documentation in `zulip.yaml`.
3636

37-
API_FEATURE_LEVEL = 326 # Last bumped for updating fields in server_supported_permission_settings
37+
API_FEATURE_LEVEL = 327 # Last bumped to adjust recipient_id in incoming 1:1 DMs
3838

3939
# Bump the minor PROVISION_VERSION to indicate that folks should provision
4040
# only when going from an old version of the code to a newer version. Bump

zerver/lib/message.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,13 @@ def messages_for_ids(
253253
msg_dict["can_access_sender"] = msg_dict["sender_id"] not in inaccessible_sender_ids
254254
message_list.append(msg_dict)
255255

256-
MessageDict.post_process_dicts(message_list, apply_markdown, client_gravatar, realm)
256+
MessageDict.post_process_dicts(
257+
message_list,
258+
apply_markdown=apply_markdown,
259+
client_gravatar=client_gravatar,
260+
realm=realm,
261+
user_recipient_id=None if user_profile is None else user_profile.recipient_id,
262+
)
257263

258264
return message_list
259265

zerver/lib/message_cache.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,11 @@ def wide_dict(message: Message, realm_id: int | None = None) -> dict[str, Any]:
175175
@staticmethod
176176
def post_process_dicts(
177177
objs: list[dict[str, Any]],
178+
*,
178179
apply_markdown: bool,
179180
client_gravatar: bool,
180181
realm: Realm,
182+
user_recipient_id: int | None,
181183
) -> None:
182184
"""
183185
NOTE: This function mutates the objects in
@@ -199,6 +201,7 @@ def post_process_dicts(
199201
skip_copy=True,
200202
can_access_sender=can_access_sender,
201203
realm_host=realm.host,
204+
is_incoming_1_to_1=obj["recipient_id"] == user_recipient_id,
202205
)
203206

204207
@staticmethod
@@ -211,6 +214,7 @@ def finalize_payload(
211214
skip_copy: bool = False,
212215
can_access_sender: bool,
213216
realm_host: str,
217+
is_incoming_1_to_1: bool,
214218
) -> dict[str, Any]:
215219
"""
216220
By default, we make a shallow copy of the incoming dict to avoid
@@ -247,12 +251,20 @@ def finalize_payload(
247251
else:
248252
obj["content_type"] = "text/x-markdown"
249253

254+
if is_incoming_1_to_1:
255+
# For an incoming 1:1 DM, the recipient’s own recipient_id is
256+
# useless to the recipient themselves. Substitute the sender’s
257+
# recipient_id, so the recipient can use recipient_id as documented
258+
# to uniquely represent the set of 2 users in this conversation.
259+
obj["recipient_id"] = obj["sender_recipient_id"]
260+
250261
for item in obj.get("edit_history", []):
251262
if "prev_rendered_content_version" in item:
252263
del item["prev_rendered_content_version"]
253264

254265
if not keep_rendered_content:
255266
del obj["rendered_content"]
267+
del obj["sender_recipient_id"]
256268
del obj["sender_realm_id"]
257269
del obj["sender_avatar_source"]
258270
del obj["sender_delivery_email"]
@@ -472,6 +484,7 @@ def bulk_hydrate_sender_info(objs: list[dict[str, Any]]) -> None:
472484
"full_name",
473485
"delivery_email",
474486
"email",
487+
"recipient_id",
475488
"realm__string_id",
476489
"avatar_source",
477490
"avatar_version",
@@ -486,6 +499,7 @@ def bulk_hydrate_sender_info(objs: list[dict[str, Any]]) -> None:
486499
for obj in objs:
487500
sender_id = obj["sender_id"]
488501
user_row = sender_dict[sender_id]
502+
obj["sender_recipient_id"] = user_row["recipient_id"]
489503
obj["sender_full_name"] = user_row["full_name"]
490504
obj["sender_email"] = user_row["email"]
491505
obj["sender_delivery_email"] = user_row["delivery_email"]

zerver/lib/outgoing_webhook.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def make_request(self, base_url: str, event: dict[str, Any], realm: Realm) -> Re
6868
get_user_profile_by_id(event["message"]["sender_id"]), self.user_profile
6969
),
7070
realm_host=realm.host,
71+
is_incoming_1_to_1=event["message"]["recipient_id"] == self.user_profile.recipient_id,
7172
)
7273

7374
request_data = {

zerver/openapi/zulip.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23510,6 +23510,11 @@ components:
2351023510
A unique ID for the set of users receiving the
2351123511
message (either a channel or group of users). Useful primarily
2351223512
for hashing.
23513+
23514+
**Changes**: Before Zulip 10.0 (feature level 327), `recipient_id`
23515+
was the same across all incoming 1:1 direct messages. Now, each
23516+
incoming message uniquely shares a `recipient_id` with outgoing
23517+
messages in the same conversation.
2351323518
sender_email:
2351423519
type: string
2351523520
description: |

zerver/tests/test_event_system.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ def test_get_events(self) -> None:
375375
self.assertEqual(events[0]["local_message_id"], local_id)
376376
self.assertEqual(events[0]["message"]["display_recipient"][0]["is_mirror_dummy"], False)
377377
self.assertEqual(events[0]["message"]["display_recipient"][1]["is_mirror_dummy"], False)
378+
self.assertEqual(events[0]["message"]["recipient_id"], recipient_user_profile.recipient_id)
378379

379380
last_event_id = events[0]["id"]
380381
local_id = "10.02"
@@ -407,6 +408,7 @@ def test_get_events(self) -> None:
407408
self.assertEqual(events[0]["type"], "message")
408409
self.assertEqual(events[0]["message"]["sender_email"], email)
409410
self.assertEqual(events[0]["local_message_id"], local_id)
411+
self.assertEqual(events[0]["message"]["recipient_id"], recipient_user_profile.recipient_id)
410412

411413
# Test that the received message in the receiver's event queue
412414
# exists and does not contain a local id
@@ -426,9 +428,12 @@ def test_get_events(self) -> None:
426428
self.assertEqual(recipient_events[0]["type"], "message")
427429
self.assertEqual(recipient_events[0]["message"]["sender_email"], email)
428430
self.assertTrue("local_message_id" not in recipient_events[0])
431+
# Incoming DMs show the recipient_id that outgoing DMs would.
432+
self.assertEqual(recipient_events[0]["message"]["recipient_id"], user_profile.recipient_id)
429433
self.assertEqual(recipient_events[1]["type"], "message")
430434
self.assertEqual(recipient_events[1]["message"]["sender_email"], email)
431435
self.assertTrue("local_message_id" not in recipient_events[1])
436+
self.assertEqual(recipient_events[1]["message"]["recipient_id"], user_profile.recipient_id)
432437

433438
def test_get_events_narrow(self) -> None:
434439
user_profile = self.example_user("hamlet")
@@ -861,6 +866,7 @@ def test_get_client_info_for_all_public_streams(self) -> None:
861866
queue_timeout=0,
862867
realm_id=realm.id,
863868
user_profile_id=hamlet.id,
869+
user_recipient_id=hamlet.recipient_id,
864870
)
865871

866872
client = allocate_client_descriptor(queue_data)
@@ -915,6 +921,7 @@ def test_get_info(apply_markdown: bool, client_gravatar: bool) -> None:
915921
queue_timeout=0,
916922
realm_id=realm.id,
917923
user_profile_id=hamlet.id,
924+
user_recipient_id=hamlet.recipient_id,
918925
)
919926

920927
client = allocate_client_descriptor(queue_data)
@@ -959,9 +966,15 @@ def test_process_message_event_with_mocked_client_info(self) -> None:
959966

960967
class MockClient:
961968
def __init__(
962-
self, user_profile_id: int, apply_markdown: bool, client_gravatar: bool
969+
self,
970+
*,
971+
user_profile_id: int,
972+
user_recipient_id: int | None,
973+
apply_markdown: bool,
974+
client_gravatar: bool,
963975
) -> None:
964976
self.user_profile_id = user_profile_id
977+
self.user_recipient_id = user_recipient_id
965978
self.apply_markdown = apply_markdown
966979
self.client_gravatar = client_gravatar
967980
self.client_type_name = "whatever"
@@ -979,24 +992,28 @@ def add_event(self, event: dict[str, Any]) -> None:
979992

980993
client1 = MockClient(
981994
user_profile_id=hamlet.id,
995+
user_recipient_id=hamlet.recipient_id,
982996
apply_markdown=True,
983997
client_gravatar=False,
984998
)
985999

9861000
client2 = MockClient(
9871001
user_profile_id=hamlet.id,
1002+
user_recipient_id=hamlet.recipient_id,
9881003
apply_markdown=False,
9891004
client_gravatar=False,
9901005
)
9911006

9921007
client3 = MockClient(
9931008
user_profile_id=hamlet.id,
1009+
user_recipient_id=hamlet.recipient_id,
9941010
apply_markdown=True,
9951011
client_gravatar=True,
9961012
)
9971013

9981014
client4 = MockClient(
9991015
user_profile_id=hamlet.id,
1016+
user_recipient_id=hamlet.recipient_id,
10001017
apply_markdown=False,
10011018
client_gravatar=True,
10021019
)
@@ -1028,11 +1045,13 @@ def add_event(self, event: dict[str, Any]) -> None:
10281045
content="**hello**",
10291046
rendered_content="<b>hello</b>",
10301047
sender_id=sender.id,
1048+
recipient_id=1111,
10311049
type="stream",
10321050
client="website",
10331051
# NOTE: Some of these fields are clutter, but some
10341052
# will be useful when we let clients specify
10351053
# that they can compute their own gravatar URLs.
1054+
sender_recipient_id=sender.recipient_id,
10361055
sender_email=sender.email,
10371056
sender_delivery_email=sender.delivery_email,
10381057
sender_realm_id=sender.realm_id,
@@ -1073,6 +1092,7 @@ def add_event(self, event: dict[str, Any]) -> None:
10731092
sender_id=sender.id,
10741093
sender_email=sender.email,
10751094
id=999,
1095+
recipient_id=1111,
10761096
content="<b>hello</b>",
10771097
content_type="text/html",
10781098
client="website",
@@ -1092,6 +1112,7 @@ def add_event(self, event: dict[str, Any]) -> None:
10921112
sender_id=sender.id,
10931113
sender_email=sender.email,
10941114
id=999,
1115+
recipient_id=1111,
10951116
content="**hello**",
10961117
content_type="text/x-markdown",
10971118
client="website",
@@ -1112,6 +1133,7 @@ def add_event(self, event: dict[str, Any]) -> None:
11121133
sender_email=sender.email,
11131134
avatar_url=None,
11141135
id=999,
1136+
recipient_id=1111,
11151137
content="<b>hello</b>",
11161138
content_type="text/html",
11171139
client="website",
@@ -1132,6 +1154,7 @@ def add_event(self, event: dict[str, Any]) -> None:
11321154
sender_email=sender.email,
11331155
avatar_url=None,
11341156
id=999,
1157+
recipient_id=1111,
11351158
content="**hello**",
11361159
content_type="text/x-markdown",
11371160
client="website",
@@ -1159,6 +1182,7 @@ def test_web_reload_clients(self) -> None:
11591182
queue_timeout=0,
11601183
realm_id=realm.id,
11611184
user_profile_id=hamlet.id,
1185+
user_recipient_id=hamlet.recipient_id,
11621186
)
11631187
client = allocate_client_descriptor(queue_data)
11641188

zerver/tests/test_message_dict.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def reload_message(msg_id: int) -> Message:
7171
return msg
7272

7373
def get_send_message_payload(
74-
msg_id: int, apply_markdown: bool, client_gravatar: bool
74+
msg_id: int, *, apply_markdown: bool, client_gravatar: bool
7575
) -> dict[str, Any]:
7676
msg = reload_message(msg_id)
7777
wide_dict = MessageDict.wide_dict(msg)
@@ -82,11 +82,12 @@ def get_send_message_payload(
8282
client_gravatar=client_gravatar,
8383
can_access_sender=True,
8484
realm_host=get_realm("zulip").host,
85+
is_incoming_1_to_1=False,
8586
)
8687
return narrow_dict
8788

8889
def get_fetch_payload(
89-
msg_id: int, apply_markdown: bool, client_gravatar: bool
90+
msg_id: int, *, apply_markdown: bool, client_gravatar: bool
9091
) -> dict[str, Any]:
9192
msg = reload_message(msg_id)
9293
unhydrated_dict = MessageDict.messages_to_encoded_cache_helper([msg])[0]
@@ -97,6 +98,7 @@ def get_fetch_payload(
9798
apply_markdown=apply_markdown,
9899
client_gravatar=client_gravatar,
99100
realm=get_realm("zulip"),
101+
user_recipient_id=None,
100102
)
101103
final_dict = unhydrated_dict
102104
return final_dict
@@ -175,7 +177,11 @@ def test_bulk_message_fetching(self) -> None:
175177
with self.assert_database_query_count(7):
176178
objs = MessageDict.ids_to_dict(ids)
177179
MessageDict.post_process_dicts(
178-
objs, apply_markdown=False, client_gravatar=False, realm=realm
180+
objs,
181+
apply_markdown=False,
182+
client_gravatar=False,
183+
realm=realm,
184+
user_recipient_id=None,
179185
)
180186

181187
self.assert_length(objs, num_ids)

zerver/tests/test_message_edit.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,18 +171,31 @@ def test_save_message(self) -> None:
171171
self.assertEqual(Message.objects.get(id=msg_id).topic_name(), "edited")
172172

173173
def test_fetch_message_from_id(self) -> None:
174-
self.login("hamlet")
174+
hamlet = self.example_user("hamlet")
175+
cordelia = self.example_user("cordelia")
176+
self.login_user(hamlet)
177+
175178
msg_id = self.send_personal_message(
176-
from_user=self.example_user("hamlet"),
177-
to_user=self.example_user("cordelia"),
178-
content="Personal message",
179+
from_user=hamlet, to_user=cordelia, content="Outgoing direct message"
179180
)
180181
result = self.client_get("/json/messages/" + str(msg_id))
181182
response_dict = self.assert_json_success(result)
182-
self.assertEqual(response_dict["raw_content"], "Personal message")
183+
self.assertEqual(response_dict["raw_content"], "Outgoing direct message")
183184
self.assertEqual(response_dict["message"]["id"], msg_id)
185+
self.assertEqual(response_dict["message"]["recipient_id"], cordelia.recipient_id)
184186
self.assertEqual(response_dict["message"]["flags"], ["read"])
185187

188+
msg_id = self.send_personal_message(
189+
from_user=cordelia, to_user=hamlet, content="Incoming direct message"
190+
)
191+
result = self.client_get("/json/messages/" + str(msg_id))
192+
response_dict = self.assert_json_success(result)
193+
self.assertEqual(response_dict["raw_content"], "Incoming direct message")
194+
self.assertEqual(response_dict["message"]["id"], msg_id)
195+
# Incoming DMs show the recipient_id that outgoing DMs would.
196+
self.assertEqual(response_dict["message"]["recipient_id"], cordelia.recipient_id)
197+
self.assertEqual(response_dict["message"]["flags"], [])
198+
186199
# Send message to web-public stream where hamlet is not subscribed.
187200
# This will test case of user having no `UserMessage` but having access
188201
# to message.

zerver/tests/test_message_fetch.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4089,6 +4089,7 @@ def test_message_without_rendered_content(self) -> None:
40894089
client_gravatar=False,
40904090
can_access_sender=True,
40914091
realm_host=get_realm("zulip").host,
4092+
is_incoming_1_to_1=False,
40924093
)
40934094
self.assertEqual(final_dict["content"], "<p>test content</p>")
40944095

@@ -4884,6 +4885,24 @@ def test_get_messages_with_search_using_email(self) -> None:
48844885
'@<span class="highlight">Othello</span>, the Moor of Venice</span>?</p>',
48854886
)
48864887

4888+
def test_dm_recipient_id(self) -> None:
4889+
hamlet = self.example_user("hamlet")
4890+
othello = self.example_user("othello")
4891+
self.login_user(hamlet)
4892+
4893+
outgoing_message_id = self.send_personal_message(hamlet, othello)
4894+
incoming_message_id = self.send_personal_message(othello, hamlet)
4895+
4896+
result = self.get_and_check_messages(dict(anchor="newest", num_before=2))
4897+
self.assert_length(result["messages"], 2)
4898+
self.assertEqual(result["messages"][0]["id"], outgoing_message_id)
4899+
self.assertEqual(result["messages"][0]["sender_id"], hamlet.id)
4900+
self.assertEqual(result["messages"][0]["recipient_id"], othello.recipient_id)
4901+
self.assertEqual(result["messages"][1]["id"], incoming_message_id)
4902+
self.assertEqual(result["messages"][1]["sender_id"], othello.id)
4903+
# Incoming DMs show the recipient_id that outgoing DMs would.
4904+
self.assertEqual(result["messages"][1]["recipient_id"], othello.recipient_id)
4905+
48874906

48884907
class MessageHasKeywordsTest(ZulipTestCase):
48894908
"""Test for keywords like has_link, has_image, has_attachment."""

zerver/tests/test_message_send.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1750,6 +1750,7 @@ def test_stream_message_dict(self) -> None:
17501750
apply_markdown=True,
17511751
client_gravatar=False,
17521752
realm=user_profile.realm,
1753+
user_recipient_id=None,
17531754
)
17541755
self.assertEqual(dct["display_recipient"], "Denmark")
17551756

zerver/tests/test_outgoing_webhook_system.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ def mock_event(self, bot_user: UserProfile) -> dict[str, Any]:
5353
"message": {
5454
"display_recipient": "Verona",
5555
"stream_id": 999,
56+
"recipient_id": 1111,
5657
"sender_id": bot_user.id,
58+
"sender_recipient_id": bot_user.recipient_id,
5759
"sender_email": bot_user.email,
5860
"sender_realm_id": bot_user.realm.id,
5961
"sender_realm_str": bot_user.realm.string_id,

0 commit comments

Comments
 (0)