Skip to content

Commit a3a32a1

Browse files
rafaelmf3Rafael Marinhogithub-actions[bot]
authored
[CHA-1230] support delivery receipts (#205)
* [CHA-1230] support delivery receipts * fix * lint * lint * clean up * clean up * Update stream_chat/tests/test_client.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update stream_chat/tests/test_client.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update stream_chat/tests/test_client.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update stream_chat/tests/test_client.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update stream_chat/tests/test_client.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update stream_chat/tests/test_client.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update stream_chat/tests/test_client.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update stream_chat/tests/test_client.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update stream_chat/tests/test_client.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update stream_chat/tests/test_client.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update stream_chat/tests/test_client.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update stream_chat/tests/test_client.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update stream_chat/tests/test_client.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix lint * lint fix * fix unit test * fix unit tests * lint fix * fix unit tesrts * lint fix * lint * revert --------- Co-authored-by: Rafael Marinho <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent bd46569 commit a3a32a1

File tree

5 files changed

+213
-0
lines changed

5 files changed

+213
-0
lines changed

stream_chat/async_chat/client.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,48 @@ async def update_user_location(
986986
params = {"user_id": user_id, **options}
987987
return await self.put("users/live_locations", data=data, params=params)
988988

989+
async def mark_delivered(self, data: Dict[str, Any]) -> Optional[StreamResponse]:
990+
"""
991+
Send the mark delivered event for this user, only works if the `delivery_receipts` setting is enabled
992+
993+
:param data: MarkDeliveredOptions containing latest_delivered_messages and other optional fields
994+
:return: The server response or None if delivery receipts are disabled
995+
"""
996+
# Validate required fields
997+
if not data.get("latest_delivered_messages"):
998+
raise ValueError("latest_delivered_messages must not be empty")
999+
1000+
# Ensure either user or user_id is provided
1001+
if not data.get("user") and not data.get("user_id"):
1002+
raise ValueError("either user or user_id must be provided")
1003+
1004+
return await self.post("channels/delivered", data=data)
1005+
1006+
async def mark_delivered_simple(
1007+
self, user_id: str, message_id: str, channel_cid: str
1008+
) -> Optional[StreamResponse]:
1009+
"""
1010+
Convenience method to mark a message as delivered for a specific user.
1011+
1012+
:param user_id: The user ID
1013+
:param message_id: The message ID
1014+
:param channel_cid: The channel CID (channel_type:channel_id)
1015+
:return: The server response or None if delivery receipts are disabled
1016+
"""
1017+
if not user_id:
1018+
raise ValueError("user ID must not be empty")
1019+
if not message_id:
1020+
raise ValueError("message ID must not be empty")
1021+
if not channel_cid:
1022+
raise ValueError("channel CID must not be empty")
1023+
1024+
data = {
1025+
"latest_delivered_messages": [{"cid": channel_cid, "id": message_id}],
1026+
"user_id": user_id,
1027+
}
1028+
1029+
return await self.mark_delivered(data)
1030+
9891031
async def close(self) -> None:
9901032
await self.session.close()
9911033

stream_chat/base/client.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,6 +1537,32 @@ def update_user_location(
15371537
"""
15381538
pass
15391539

1540+
@abc.abstractmethod
1541+
def mark_delivered(
1542+
self, data: Dict[str, Any]
1543+
) -> Union[StreamResponse, Awaitable[StreamResponse], None]:
1544+
"""
1545+
Send the mark delivered event for this user, only works if the `delivery_receipts` setting is enabled
1546+
1547+
:param data: MarkDeliveredOptions containing latest_delivered_messages and other optional fields
1548+
:return: The server response or None if delivery receipts are disabled
1549+
"""
1550+
pass
1551+
1552+
@abc.abstractmethod
1553+
def mark_delivered_simple(
1554+
self, user_id: str, message_id: str, channel_cid: str
1555+
) -> Union[StreamResponse, Awaitable[StreamResponse], None]:
1556+
"""
1557+
Convenience method to mark a message as delivered for a specific user.
1558+
1559+
:param user_id: The user ID
1560+
:param message_id: The message ID
1561+
:param channel_cid: The channel CID (channel_type:channel_id)
1562+
:return: The server response or None if delivery receipts are disabled
1563+
"""
1564+
pass
1565+
15401566
#####################
15411567
# Private methods #
15421568
#####################

stream_chat/client.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,3 +939,49 @@ def update_user_location(
939939
data.update(cast(dict, options))
940940
params = {"user_id": user_id, **options}
941941
return self.put("users/live_locations", data=data, params=params)
942+
943+
def mark_delivered(self, data: Dict[str, Any]) -> Optional[StreamResponse]:
944+
"""
945+
Send the mark delivered event for this user, only works if the `delivery_receipts` setting is enabled
946+
947+
:param data: MarkDeliveredOptions containing latest_delivered_messages and other optional fields
948+
:return: The server response or None if delivery receipts are disabled
949+
"""
950+
# Validate required fields
951+
if not data.get("latest_delivered_messages"):
952+
raise ValueError("latest_delivered_messages must not be empty")
953+
954+
# Ensure either user or user_id is provided
955+
if not data.get("user") and not data.get("user_id"):
956+
raise ValueError("either user or user_id must be provided")
957+
958+
# Extract user_id from data
959+
user_id = data.get("user_id") or data.get("user", {}).get("id")
960+
if not user_id:
961+
raise ValueError("user_id must be provided")
962+
963+
params = {"user_id": user_id}
964+
return self.post("channels/delivered", data=data, params=params)
965+
966+
def mark_delivered_simple(
967+
self, user_id: str, message_id: str, channel_cid: str
968+
) -> Optional[StreamResponse]:
969+
"""
970+
Convenience method to mark a message as delivered for a specific user.
971+
972+
:param user_id: The user ID
973+
:param message_id: The message ID
974+
:param channel_cid: The channel CID (channel_type:channel_id)
975+
:return: The server response or None if delivery receipts are disabled
976+
"""
977+
if not user_id:
978+
raise ValueError("user ID must not be empty")
979+
if not message_id:
980+
raise ValueError("message ID must not be empty")
981+
if not channel_cid:
982+
raise ValueError("channel CID must not be empty")
983+
data = {
984+
"latest_delivered_messages": [{"cid": channel_cid, "id": message_id}],
985+
"user_id": user_id,
986+
}
987+
return self.mark_delivered(data=data)

stream_chat/tests/test_client.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,3 +1043,44 @@ def test_query_message_history(
10431043

10441044
assert len(response_next["message_history"]) == 1
10451045
assert response_next["message_history"][0]["text"] == "helloworld-2"
1046+
1047+
def test_mark_delivered(
1048+
self, client: StreamChat, channel: Channel, random_user: Dict
1049+
):
1050+
delivery_data = {
1051+
"latest_delivered_messages": [
1052+
{"cid": channel.cid, "id": "test-message-id"}
1053+
],
1054+
"user_id": random_user["id"],
1055+
}
1056+
response = client.mark_delivered(delivery_data)
1057+
assert response is not None
1058+
delivery_data_multiple = {
1059+
"latest_delivered_messages": [
1060+
{"cid": channel.cid, "id": "test-message-id-1"},
1061+
{"cid": channel.cid, "id": "test-message-id-2"},
1062+
],
1063+
"user_id": random_user["id"],
1064+
}
1065+
response = client.mark_delivered(delivery_data_multiple)
1066+
assert response is not None
1067+
1068+
def test_mark_delivered_simple(
1069+
self, client: StreamChat, channel: Channel, random_user: Dict
1070+
):
1071+
response = client.mark_delivered_simple(
1072+
user_id=random_user["id"],
1073+
message_id="test-message-id",
1074+
channel_cid=channel.cid,
1075+
)
1076+
assert response is not None
1077+
1078+
def test_mark_delivered_validation(self, client: StreamChat, random_user: Dict):
1079+
with pytest.raises(
1080+
ValueError, match="latest_delivered_messages must not be empty"
1081+
):
1082+
client.mark_delivered({"user_id": random_user["id"]})
1083+
with pytest.raises(ValueError, match="either user or user_id must be provided"):
1084+
client.mark_delivered(
1085+
{"latest_delivered_messages": [{"cid": "test:channel", "id": "test"}]}
1086+
)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import sys
2+
from typing import Dict, List, Optional
3+
4+
if sys.version_info >= (3, 8):
5+
from typing import TypedDict
6+
else:
7+
from typing_extensions import TypedDict
8+
9+
10+
class DeliveredMessageConfirmation(TypedDict):
11+
"""
12+
Confirmation of a delivered message.
13+
14+
Parameters:
15+
cid: Channel CID (channel_type:channel_id)
16+
id: Message ID
17+
"""
18+
19+
cid: str
20+
id: str
21+
22+
23+
class MarkDeliveredOptions(TypedDict, total=False):
24+
"""
25+
Options for marking messages as delivered.
26+
27+
Parameters:
28+
latest_delivered_messages: List of delivered message confirmations
29+
user: Optional user object
30+
user_id: Optional user ID
31+
"""
32+
33+
latest_delivered_messages: List[DeliveredMessageConfirmation]
34+
user: Optional[Dict] # UserResponse equivalent
35+
user_id: Optional[str]
36+
37+
38+
class ChannelReadStatus(TypedDict, total=False):
39+
"""
40+
Channel read status information.
41+
42+
Parameters:
43+
last_read: Last read timestamp
44+
unread_messages: Number of unread messages
45+
user: User information
46+
first_unread_message_id: ID of first unread message
47+
last_read_message_id: ID of last read message
48+
last_delivered_at: Last delivered timestamp
49+
last_delivered_message_id: ID of last delivered message
50+
"""
51+
52+
last_read: str # ISO format string for timestamp
53+
unread_messages: int
54+
user: Dict # UserResponse equivalent
55+
first_unread_message_id: Optional[str]
56+
last_read_message_id: Optional[str]
57+
last_delivered_at: Optional[str] # ISO format string for timestamp
58+
last_delivered_message_id: Optional[str]

0 commit comments

Comments
 (0)