Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions stream_chat/async_chat/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,48 @@ async def update_user_location(
params = {"user_id": user_id, **options}
return await self.put("users/live_locations", data=data, params=params)

async def mark_delivered(self, data: Dict[str, Any]) -> Optional[StreamResponse]:
"""
Send the mark delivered event for this user, only works if the `delivery_receipts` setting is enabled

:param data: MarkDeliveredOptions containing latest_delivered_messages and other optional fields
:return: The server response or None if delivery receipts are disabled
"""
# Validate required fields
if not data.get("latest_delivered_messages"):
raise ValueError("latest_delivered_messages must not be empty")

# Ensure either user or user_id is provided
if not data.get("user") and not data.get("user_id"):
raise ValueError("either user or user_id must be provided")

return await self.post("channels/delivered", data=data)

async def mark_delivered_simple(
self, user_id: str, message_id: str, channel_cid: str
) -> Optional[StreamResponse]:
"""
Convenience method to mark a message as delivered for a specific user.

:param user_id: The user ID
:param message_id: The message ID
:param channel_cid: The channel CID (channel_type:channel_id)
:return: The server response or None if delivery receipts are disabled
"""
if not user_id:
raise ValueError("user ID must not be empty")
if not message_id:
raise ValueError("message ID must not be empty")
if not channel_cid:
raise ValueError("channel CID must not be empty")

data = {
"latest_delivered_messages": [{"cid": channel_cid, "id": message_id}],
"user_id": user_id,
}

return await self.mark_delivered(data)

async def close(self) -> None:
await self.session.close()

Expand Down
26 changes: 26 additions & 0 deletions stream_chat/base/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1537,6 +1537,32 @@ def update_user_location(
"""
pass

@abc.abstractmethod
def mark_delivered(
self, data: Dict[str, Any]
) -> Union[StreamResponse, Awaitable[StreamResponse], None]:
"""
Send the mark delivered event for this user, only works if the `delivery_receipts` setting is enabled

:param data: MarkDeliveredOptions containing latest_delivered_messages and other optional fields
:return: The server response or None if delivery receipts are disabled
"""
pass

@abc.abstractmethod
def mark_delivered_simple(
self, user_id: str, message_id: str, channel_cid: str
) -> Union[StreamResponse, Awaitable[StreamResponse], None]:
"""
Convenience method to mark a message as delivered for a specific user.

:param user_id: The user ID
:param message_id: The message ID
:param channel_cid: The channel CID (channel_type:channel_id)
:return: The server response or None if delivery receipts are disabled
"""
pass

#####################
# Private methods #
#####################
Expand Down
46 changes: 46 additions & 0 deletions stream_chat/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -939,3 +939,49 @@ def update_user_location(
data.update(cast(dict, options))
params = {"user_id": user_id, **options}
return self.put("users/live_locations", data=data, params=params)

def mark_delivered(self, data: Dict[str, Any]) -> Optional[StreamResponse]:
"""
Send the mark delivered event for this user, only works if the `delivery_receipts` setting is enabled

:param data: MarkDeliveredOptions containing latest_delivered_messages and other optional fields
:return: The server response or None if delivery receipts are disabled
"""
# Validate required fields
if not data.get("latest_delivered_messages"):
raise ValueError("latest_delivered_messages must not be empty")

# Ensure either user or user_id is provided
if not data.get("user") and not data.get("user_id"):
raise ValueError("either user or user_id must be provided")

# Extract user_id from data
user_id = data.get("user_id") or data.get("user", {}).get("id")
if not user_id:
raise ValueError("user_id must be provided")

params = {"user_id": user_id}
return self.post("channels/delivered", data=data, params=params)

def mark_delivered_simple(
self, user_id: str, message_id: str, channel_cid: str
) -> Optional[StreamResponse]:
"""
Convenience method to mark a message as delivered for a specific user.

:param user_id: The user ID
:param message_id: The message ID
:param channel_cid: The channel CID (channel_type:channel_id)
:return: The server response or None if delivery receipts are disabled
"""
if not user_id:
raise ValueError("user ID must not be empty")
if not message_id:
raise ValueError("message ID must not be empty")
if not channel_cid:
raise ValueError("channel CID must not be empty")
data = {
"latest_delivered_messages": [{"cid": channel_cid, "id": message_id}],
"user_id": user_id,
}
return self.mark_delivered(data=data)
41 changes: 41 additions & 0 deletions stream_chat/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1043,3 +1043,44 @@ def test_query_message_history(

assert len(response_next["message_history"]) == 1
assert response_next["message_history"][0]["text"] == "helloworld-2"

def test_mark_delivered(
self, client: StreamChat, channel: Channel, random_user: Dict
):
delivery_data = {
"latest_delivered_messages": [
{"cid": channel.cid, "id": "test-message-id"}
],
"user_id": random_user["id"],
}
response = client.mark_delivered(delivery_data)
assert response is not None
delivery_data_multiple = {
"latest_delivered_messages": [
{"cid": channel.cid, "id": "test-message-id-1"},
{"cid": channel.cid, "id": "test-message-id-2"},
],
"user_id": random_user["id"],
}
response = client.mark_delivered(delivery_data_multiple)
assert response is not None

def test_mark_delivered_simple(
self, client: StreamChat, channel: Channel, random_user: Dict
):
response = client.mark_delivered_simple(
user_id=random_user["id"],
message_id="test-message-id",
channel_cid=channel.cid,
)
assert response is not None

def test_mark_delivered_validation(self, client: StreamChat, random_user: Dict):
with pytest.raises(
ValueError, match="latest_delivered_messages must not be empty"
):
client.mark_delivered({"user_id": random_user["id"]})
with pytest.raises(ValueError, match="either user or user_id must be provided"):
client.mark_delivered(
{"latest_delivered_messages": [{"cid": "test:channel", "id": "test"}]}
)
58 changes: 58 additions & 0 deletions stream_chat/types/delivery_receipts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import sys
from typing import Dict, List, Optional

if sys.version_info >= (3, 8):
from typing import TypedDict
else:
from typing_extensions import TypedDict


class DeliveredMessageConfirmation(TypedDict):
"""
Confirmation of a delivered message.

Parameters:
cid: Channel CID (channel_type:channel_id)
id: Message ID
"""

cid: str
id: str


class MarkDeliveredOptions(TypedDict, total=False):
"""
Options for marking messages as delivered.

Parameters:
latest_delivered_messages: List of delivered message confirmations
user: Optional user object
user_id: Optional user ID
"""

latest_delivered_messages: List[DeliveredMessageConfirmation]
user: Optional[Dict] # UserResponse equivalent
user_id: Optional[str]


class ChannelReadStatus(TypedDict, total=False):
"""
Channel read status information.

Parameters:
last_read: Last read timestamp
unread_messages: Number of unread messages
user: User information
first_unread_message_id: ID of first unread message
last_read_message_id: ID of last read message
last_delivered_at: Last delivered timestamp
last_delivered_message_id: ID of last delivered message
"""

last_read: str # ISO format string for timestamp
unread_messages: int
user: Dict # UserResponse equivalent
first_unread_message_id: Optional[str]
last_read_message_id: Optional[str]
last_delivered_at: Optional[str] # ISO format string for timestamp
last_delivered_message_id: Optional[str]