diff --git a/README.md b/README.md index 469765ac..4c80bfb1 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,41 @@ call.get_or_create( ) ``` +#### Ringing Individual Members + +In some cases, you may want to ring individual members instead of the whole call, or you want to ring a member into an existing call. You can do this by using the `ring` method: + +```python +import uuid +from getstream import Stream + +# Initialize client +client = Stream(api_key="your_api_key", api_secret="your_api_secret") + +# Create a call +call = client.video.call("default", str(uuid.uuid4())) + +# Create or get a call with members +call.get_or_create( + data=CallRequest( + created_by_id="myself", + members=[ + MemberRequest(user_id= "myself"), + MemberRequest(user_id="my-friend") + ], + ) +) + +# Ring an existing member +call.ring(member_ids=["my-friend"]) + +# Add a new member to the call and ring them +call.update_call_members( + update_members=[{"user_id": "my-other-friend"}] +) +call.ring(member_ids=["my-other-friend"]) +``` + ### App configuration ```python diff --git a/generate.sh b/generate.sh index 1ab0eaec..369e066c 100755 --- a/generate.sh +++ b/generate.sh @@ -10,7 +10,7 @@ fi if ! uv -V &> /dev/null then - echo "cannot find poetry in path, did you setup this repo correctly?"; + echo "cannot find uv in path, did you setup this repo correctly?"; exit 1; fi diff --git a/getstream/base.py b/getstream/base.py index 295fc14e..c2d3e9b1 100644 --- a/getstream/base.py +++ b/getstream/base.py @@ -188,6 +188,7 @@ def __init__(self, response: httpx.Response) -> None: def __str__(self) -> str: if self.api_error: - return f'Stream error code {self.api_error.code}: {self.api_error.message}"' + message = self.api_error.message or f"Error code {self.api_error.code}" + return f'Stream error code {self.api_error.code}: {message}"' else: return f"Stream error HTTP code: {self.status_code}" diff --git a/getstream/chat/rest_client.py b/getstream/chat/rest_client.py index 45bc07c9..db83b21a 100644 --- a/getstream/chat/rest_client.py +++ b/getstream/chat/rest_client.py @@ -641,6 +641,7 @@ def create_channel_type( read_events: Optional[bool] = None, replies: Optional[bool] = None, search: Optional[bool] = None, + shared_locations: Optional[bool] = None, skip_last_msg_update_for_system_msgs: Optional[bool] = None, typing_events: Optional[bool] = None, uploads: Optional[bool] = None, @@ -671,6 +672,7 @@ def create_channel_type( read_events=read_events, replies=replies, search=search, + shared_locations=shared_locations, skip_last_msg_update_for_system_msgs=skip_last_msg_update_for_system_msgs, typing_events=typing_events, uploads=uploads, @@ -728,6 +730,7 @@ def update_channel_type( reminders: Optional[bool] = None, replies: Optional[bool] = None, search: Optional[bool] = None, + shared_locations: Optional[bool] = None, skip_last_msg_update_for_system_msgs: Optional[bool] = None, typing_events: Optional[bool] = None, uploads: Optional[bool] = None, @@ -763,6 +766,7 @@ def update_channel_type( reminders=reminders, replies=replies, search=search, + shared_locations=shared_locations, skip_last_msg_update_for_system_msgs=skip_last_msg_update_for_system_msgs, typing_events=typing_events, uploads=uploads, diff --git a/getstream/common/rest_client.py b/getstream/common/rest_client.py index 45928998..6ad2134b 100644 --- a/getstream/common/rest_client.py +++ b/getstream/common/rest_client.py @@ -583,6 +583,42 @@ def delete_users( return self.post("/api/v2/users/delete", DeleteUsersResponse, json=json) + def get_user_live_locations( + self, user_id: Optional[str] = None + ) -> StreamResponse[SharedLocationsResponse]: + query_params = build_query_param(user_id=user_id) + + return self.get( + "/api/v2/users/live_locations", + SharedLocationsResponse, + query_params=query_params, + ) + + def update_live_location( + self, + created_by_device_id: str, + message_id: str, + end_at: Optional[datetime] = None, + latitude: Optional[float] = None, + longitude: Optional[float] = None, + user_id: Optional[str] = None, + ) -> StreamResponse[SharedLocationResponse]: + query_params = build_query_param(user_id=user_id) + json = build_body_dict( + created_by_device_id=created_by_device_id, + message_id=message_id, + end_at=end_at, + latitude=latitude, + longitude=longitude, + ) + + return self.put( + "/api/v2/users/live_locations", + SharedLocationResponse, + query_params=query_params, + json=json, + ) + def reactivate_users( self, user_ids: List[str], diff --git a/getstream/models/__init__.py b/getstream/models/__init__.py index 5032c546..71071f68 100644 --- a/getstream/models/__init__.py +++ b/getstream/models/__init__.py @@ -45,10 +45,14 @@ class AIVideoConfig(DataClassJsonMixin): class APIError(DataClassJsonMixin): code: int = dc_field(metadata=dc_config(field_name="code")) duration: str = dc_field(metadata=dc_config(field_name="duration")) - message: str = dc_field(metadata=dc_config(field_name="message")) more_info: str = dc_field(metadata=dc_config(field_name="more_info")) status_code: int = dc_field(metadata=dc_config(field_name="StatusCode")) - details: "List[int]" = dc_field(metadata=dc_config(field_name="details")) + details: "Optional[List[int]]" = dc_field( + default=None, metadata=dc_config(field_name="details") + ) + message: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="message") + ) unrecoverable: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="unrecoverable") ) @@ -201,7 +205,7 @@ class ActionLogResponse(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) - review_queue_item: "Optional[ReviewQueueItem]" = dc_field( + review_queue_item: "Optional[ReviewQueueItemResponse]" = dc_field( default=None, metadata=dc_config(field_name="review_queue_item") ) target_user: "Optional[UserResponse]" = dc_field( @@ -622,12 +626,6 @@ class Attachment(DataClassJsonMixin): image_url: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="image_url") ) - latitude: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="latitude") - ) - longitude: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="longitude") - ) og_scrape_url: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="og_scrape_url") ) @@ -640,9 +638,6 @@ class Attachment(DataClassJsonMixin): pretext: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="pretext") ) - stopped_sharing: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="stopped_sharing") - ) text: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="text")) thumb_url: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="thumb_url") @@ -3182,6 +3177,9 @@ class Channel(DataClassJsonMixin): default=None, metadata=dc_config(field_name="member_count") ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) + active_live_locations: "Optional[List[SharedLocation]]" = dc_field( + default=None, metadata=dc_config(field_name="active_live_locations") + ) invites: "Optional[List[ChannelMember]]" = dc_field( default=None, metadata=dc_config(field_name="invites") ) @@ -3234,6 +3232,7 @@ class ChannelConfig(DataClassJsonMixin): reminders: bool = dc_field(metadata=dc_config(field_name="reminders")) replies: bool = dc_field(metadata=dc_config(field_name="replies")) search: bool = dc_field(metadata=dc_config(field_name="search")) + shared_locations: bool = dc_field(metadata=dc_config(field_name="shared_locations")) skip_last_msg_update_for_system_msgs: bool = dc_field( metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs") ) @@ -3307,6 +3306,7 @@ class ChannelConfigWithInfo(DataClassJsonMixin): reminders: bool = dc_field(metadata=dc_config(field_name="reminders")) replies: bool = dc_field(metadata=dc_config(field_name="replies")) search: bool = dc_field(metadata=dc_config(field_name="search")) + shared_locations: bool = dc_field(metadata=dc_config(field_name="shared_locations")) skip_last_msg_update_for_system_msgs: bool = dc_field( metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs") ) @@ -3807,6 +3807,7 @@ class ChannelOwnCapability: ) SEND_TYPING_EVENTS: Final[ChannelOwnCapabilityType] = "send-typing-events" SET_CHANNEL_COOLDOWN: Final[ChannelOwnCapabilityType] = "set-channel-cooldown" + SHARE_LOCATION: Final[ChannelOwnCapabilityType] = "share-location" SKIP_SLOW_MODE: Final[ChannelOwnCapabilityType] = "skip-slow-mode" SLOW_MODE: Final[ChannelOwnCapabilityType] = "slow-mode" TYPING_EVENTS: Final[ChannelOwnCapabilityType] = "typing-events" @@ -3970,6 +3971,9 @@ class ChannelStateResponse(DataClassJsonMixin): watcher_count: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="watcher_count") ) + active_live_locations: "Optional[List[SharedLocationResponseData]]" = dc_field( + default=None, metadata=dc_config(field_name="active_live_locations") + ) pending_messages: "Optional[List[PendingMessageResponse]]" = dc_field( default=None, metadata=dc_config(field_name="pending_messages") ) @@ -4020,6 +4024,9 @@ class ChannelStateResponseFields(DataClassJsonMixin): watcher_count: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="watcher_count") ) + active_live_locations: "Optional[List[SharedLocationResponseData]]" = dc_field( + default=None, metadata=dc_config(field_name="active_live_locations") + ) pending_messages: "Optional[List[PendingMessageResponse]]" = dc_field( default=None, metadata=dc_config(field_name="pending_messages") ) @@ -4099,6 +4106,7 @@ class ChannelTypeConfig(DataClassJsonMixin): reminders: bool = dc_field(metadata=dc_config(field_name="reminders")) replies: bool = dc_field(metadata=dc_config(field_name="replies")) search: bool = dc_field(metadata=dc_config(field_name="search")) + shared_locations: bool = dc_field(metadata=dc_config(field_name="shared_locations")) skip_last_msg_update_for_system_msgs: bool = dc_field( metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs") ) @@ -4340,7 +4348,7 @@ class CheckResponse(DataClassJsonMixin): task_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="task_id") ) - item: "Optional[ReviewQueueItem]" = dc_field( + item: "Optional[ReviewQueueItemResponse]" = dc_field( default=None, metadata=dc_config(field_name="item") ) @@ -4507,6 +4515,9 @@ class ConfigOverrides(DataClassJsonMixin): replies: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="replies") ) + shared_locations: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="shared_locations") + ) typing_events: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="typing_events") ) @@ -4706,6 +4717,9 @@ class CreateChannelTypeRequest(DataClassJsonMixin): search: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="search") ) + shared_locations: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="shared_locations") + ) skip_last_msg_update_for_system_msgs: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs"), @@ -4769,6 +4783,7 @@ class CreateChannelTypeResponse(DataClassJsonMixin): reminders: bool = dc_field(metadata=dc_config(field_name="reminders")) replies: bool = dc_field(metadata=dc_config(field_name="replies")) search: bool = dc_field(metadata=dc_config(field_name="search")) + shared_locations: bool = dc_field(metadata=dc_config(field_name="shared_locations")) skip_last_msg_update_for_system_msgs: bool = dc_field( metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs") ) @@ -6581,6 +6596,7 @@ class GetChannelTypeResponse(DataClassJsonMixin): reminders: bool = dc_field(metadata=dc_config(field_name="reminders")) replies: bool = dc_field(metadata=dc_config(field_name="replies")) search: bool = dc_field(metadata=dc_config(field_name="search")) + shared_locations: bool = dc_field(metadata=dc_config(field_name="shared_locations")) skip_last_msg_update_for_system_msgs: bool = dc_field( metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs") ) @@ -6737,12 +6753,6 @@ class GetOGResponse(DataClassJsonMixin): image_url: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="image_url") ) - latitude: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="latitude") - ) - longitude: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="longitude") - ) og_scrape_url: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="og_scrape_url") ) @@ -6755,9 +6765,6 @@ class GetOGResponse(DataClassJsonMixin): pretext: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="pretext") ) - stopped_sharing: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="stopped_sharing") - ) text: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="text")) thumb_url: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="thumb_url") @@ -7219,12 +7226,18 @@ class LimitInfo(DataClassJsonMixin): @dataclass class LimitsSettings(DataClassJsonMixin): + max_participants_exclude_roles: List[str] = dc_field( + metadata=dc_config(field_name="max_participants_exclude_roles") + ) max_duration_seconds: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="max_duration_seconds") ) max_participants: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="max_participants") ) + max_participants_exclude_owner: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="max_participants_exclude_owner") + ) @dataclass @@ -7235,16 +7248,28 @@ class LimitsSettingsRequest(DataClassJsonMixin): max_participants: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="max_participants") ) + max_participants_exclude_owner: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="max_participants_exclude_owner") + ) + max_participants_exclude_roles: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="max_participants_exclude_roles") + ) @dataclass class LimitsSettingsResponse(DataClassJsonMixin): + max_participants_exclude_roles: List[str] = dc_field( + metadata=dc_config(field_name="max_participants_exclude_roles") + ) max_duration_seconds: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="max_duration_seconds") ) max_participants: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="max_participants") ) + max_participants_exclude_owner: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="max_participants_exclude_owner") + ) @dataclass @@ -7660,6 +7685,9 @@ class Message(DataClassJsonMixin): reminder: "Optional[MessageReminder]" = dc_field( default=None, metadata=dc_config(field_name="reminder") ) + shared_location: "Optional[SharedLocation]" = dc_field( + default=None, metadata=dc_config(field_name="shared_location") + ) user: "Optional[User]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -8061,6 +8089,9 @@ class MessageRequest(DataClassJsonMixin): custom: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="custom") ) + shared_location: "Optional[SharedLocation]" = dc_field( + default=None, metadata=dc_config(field_name="shared_location") + ) user: "Optional[UserRequest]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -8201,6 +8232,9 @@ class MessageResponse(DataClassJsonMixin): reminder: "Optional[ReminderResponseData]" = dc_field( default=None, metadata=dc_config(field_name="reminder") ) + shared_location: "Optional[SharedLocationResponseData]" = dc_field( + default=None, metadata=dc_config(field_name="shared_location") + ) @dataclass @@ -8437,6 +8471,9 @@ class MessageWithChannelResponse(DataClassJsonMixin): reminder: "Optional[ReminderResponseData]" = dc_field( default=None, metadata=dc_config(field_name="reminder") ) + shared_location: "Optional[SharedLocationResponseData]" = dc_field( + default=None, metadata=dc_config(field_name="shared_location") + ) @dataclass @@ -8542,7 +8579,7 @@ class ModerationFlagResponse(DataClassJsonMixin): moderation_payload: "Optional[ModerationPayload]" = dc_field( default=None, metadata=dc_config(field_name="moderation_payload") ) - review_queue_item: "Optional[ReviewQueueItem]" = dc_field( + review_queue_item: "Optional[ReviewQueueItemResponse]" = dc_field( default=None, metadata=dc_config(field_name="review_queue_item") ) user: "Optional[UserResponse]" = dc_field( @@ -8912,6 +8949,9 @@ class OwnUser(DataClassJsonMixin): devices: "List[Device]" = dc_field(metadata=dc_config(field_name="devices")) mutes: "List[UserMute]" = dc_field(metadata=dc_config(field_name="mutes")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + total_unread_count_by_team: "Dict[str, int]" = dc_field( + metadata=dc_config(field_name="total_unread_count_by_team") + ) deactivated_at: Optional[datetime] = dc_field( default=None, metadata=dc_config( @@ -9063,6 +9103,9 @@ class OwnUserResponse(DataClassJsonMixin): teams_role: "Optional[Dict[str, str]]" = dc_field( default=None, metadata=dc_config(field_name="teams_role") ) + total_unread_count_by_team: "Optional[Dict[str, int]]" = dc_field( + default=None, metadata=dc_config(field_name="total_unread_count_by_team") + ) @dataclass @@ -11137,6 +11180,7 @@ class ReviewQueueItem(DataClassJsonMixin): flags: "List[Flag]" = dc_field(metadata=dc_config(field_name="flags")) languages: List[str] = dc_field(metadata=dc_config(field_name="languages")) teams: List[str] = dc_field(metadata=dc_config(field_name="teams")) + completed_at: "NullTime" = dc_field(metadata=dc_config(field_name="completed_at")) reviewed_at: "NullTime" = dc_field(metadata=dc_config(field_name="reviewed_at")) activity: "Optional[EnrichedActivity]" = dc_field( default=None, metadata=dc_config(field_name="activity") @@ -11740,6 +11784,9 @@ class SearchResultMessage(DataClassJsonMixin): reminder: "Optional[ReminderResponseData]" = dc_field( default=None, metadata=dc_config(field_name="reminder") ) + shared_location: "Optional[SharedLocationResponseData]" = dc_field( + default=None, metadata=dc_config(field_name="shared_location") + ) @dataclass @@ -11959,6 +12006,148 @@ class ShadowBlockActionRequest(DataClassJsonMixin): pass +@dataclass +class SharedLocation(DataClassJsonMixin): + channel_cid: str = dc_field(metadata=dc_config(field_name="channel_cid")) + created_at: datetime = dc_field( + metadata=dc_config( + field_name="created_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ) + ) + created_by_device_id: str = dc_field( + metadata=dc_config(field_name="created_by_device_id") + ) + message_id: str = dc_field(metadata=dc_config(field_name="message_id")) + updated_at: datetime = dc_field( + metadata=dc_config( + field_name="updated_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ) + ) + user_id: str = dc_field(metadata=dc_config(field_name="user_id")) + end_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="end_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + latitude: Optional[float] = dc_field( + default=None, metadata=dc_config(field_name="latitude") + ) + longitude: Optional[float] = dc_field( + default=None, metadata=dc_config(field_name="longitude") + ) + channel: "Optional[Channel]" = dc_field( + default=None, metadata=dc_config(field_name="channel") + ) + message: "Optional[Message]" = dc_field( + default=None, metadata=dc_config(field_name="message") + ) + + +@dataclass +class SharedLocationResponse(DataClassJsonMixin): + channel_cid: str = dc_field(metadata=dc_config(field_name="channel_cid")) + created_at: datetime = dc_field( + metadata=dc_config( + field_name="created_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ) + ) + created_by_device_id: str = dc_field( + metadata=dc_config(field_name="created_by_device_id") + ) + duration: str = dc_field(metadata=dc_config(field_name="duration")) + latitude: float = dc_field(metadata=dc_config(field_name="latitude")) + longitude: float = dc_field(metadata=dc_config(field_name="longitude")) + message_id: str = dc_field(metadata=dc_config(field_name="message_id")) + updated_at: datetime = dc_field( + metadata=dc_config( + field_name="updated_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ) + ) + user_id: str = dc_field(metadata=dc_config(field_name="user_id")) + end_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="end_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + channel: "Optional[ChannelResponse]" = dc_field( + default=None, metadata=dc_config(field_name="channel") + ) + message: "Optional[MessageResponse]" = dc_field( + default=None, metadata=dc_config(field_name="message") + ) + + +@dataclass +class SharedLocationResponseData(DataClassJsonMixin): + channel_cid: str = dc_field(metadata=dc_config(field_name="channel_cid")) + created_at: datetime = dc_field( + metadata=dc_config( + field_name="created_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ) + ) + created_by_device_id: str = dc_field( + metadata=dc_config(field_name="created_by_device_id") + ) + latitude: float = dc_field(metadata=dc_config(field_name="latitude")) + longitude: float = dc_field(metadata=dc_config(field_name="longitude")) + message_id: str = dc_field(metadata=dc_config(field_name="message_id")) + updated_at: datetime = dc_field( + metadata=dc_config( + field_name="updated_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ) + ) + user_id: str = dc_field(metadata=dc_config(field_name="user_id")) + end_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="end_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + channel: "Optional[ChannelResponse]" = dc_field( + default=None, metadata=dc_config(field_name="channel") + ) + message: "Optional[MessageResponse]" = dc_field( + default=None, metadata=dc_config(field_name="message") + ) + + +@dataclass +class SharedLocationsResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + active_live_locations: "List[SharedLocationResponseData]" = dc_field( + metadata=dc_config(field_name="active_live_locations") + ) + + @dataclass class ShowChannelRequest(DataClassJsonMixin): user_id: Optional[str] = dc_field( @@ -12253,7 +12442,7 @@ class SubmitActionRequest(DataClassJsonMixin): @dataclass class SubmitActionResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) - item: "Optional[ReviewQueueItem]" = dc_field( + item: "Optional[ReviewQueueItemResponse]" = dc_field( default=None, metadata=dc_config(field_name="item") ) @@ -12792,6 +12981,9 @@ class UnreadCountsResponse(DataClassJsonMixin): threads: "List[UnreadCountsThread]" = dc_field( metadata=dc_config(field_name="threads") ) + total_unread_count_by_team: "Dict[str, int]" = dc_field( + metadata=dc_config(field_name="total_unread_count_by_team") + ) @dataclass @@ -13213,6 +13405,9 @@ class UpdateChannelTypeRequest(DataClassJsonMixin): search: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="search") ) + shared_locations: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="shared_locations") + ) skip_last_msg_update_for_system_msgs: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs"), @@ -13282,6 +13477,7 @@ class UpdateChannelTypeResponse(DataClassJsonMixin): reminders: bool = dc_field(metadata=dc_config(field_name="reminders")) replies: bool = dc_field(metadata=dc_config(field_name="replies")) search: bool = dc_field(metadata=dc_config(field_name="search")) + shared_locations: bool = dc_field(metadata=dc_config(field_name="shared_locations")) skip_last_msg_update_for_system_msgs: bool = dc_field( metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs") ) @@ -13367,6 +13563,29 @@ class UpdateExternalStorageResponse(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) +@dataclass +class UpdateLiveLocationRequest(DataClassJsonMixin): + created_by_device_id: str = dc_field( + metadata=dc_config(field_name="created_by_device_id") + ) + message_id: str = dc_field(metadata=dc_config(field_name="message_id")) + end_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="end_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + latitude: Optional[float] = dc_field( + default=None, metadata=dc_config(field_name="latitude") + ) + longitude: Optional[float] = dc_field( + default=None, metadata=dc_config(field_name="longitude") + ) + + @dataclass class UpdateMemberPartialRequest(DataClassJsonMixin): unset: Optional[List[str]] = dc_field( @@ -14747,6 +14966,9 @@ class WrappedUnreadCountsResponse(DataClassJsonMixin): threads: "List[UnreadCountsThread]" = dc_field( metadata=dc_config(field_name="threads") ) + total_unread_count_by_team: "Dict[str, int]" = dc_field( + metadata=dc_config(field_name="total_unread_count_by_team") + ) @dataclass diff --git a/getstream/video/call.py b/getstream/video/call.py index 33bc2fb5..23b14d1a 100644 --- a/getstream/video/call.py +++ b/getstream/video/call.py @@ -40,12 +40,27 @@ def connect_openai( # Wrap the connection manager to check for errors in the first message return ConnectionManagerWrapper(connection_manager, self.call_type, self.id) + def ring( + self, target_member_ids: Optional[List[str]] = None + ) -> StreamResponse[GetCallResponse]: + """ + Ring method as a shorthand for call.get({ ring: true }). + + Args: + target_member_ids: Optional list of member IDs to ring + + Returns: + StreamResponse[GetCallResponse] from the get_call operation with ring=True + """ + return self.get(ring=True, target_member_ids=target_member_ids) + def get( self, members_limit: Optional[int] = None, ring: Optional[bool] = None, notify: Optional[bool] = None, video: Optional[bool] = None, + target_member_ids: Optional[List[str]] = None, ) -> StreamResponse[GetCallResponse]: response = self.client.get_call( type=self.call_type, @@ -54,6 +69,7 @@ def get( ring=ring, notify=notify, video=video, + target_member_ids=target_member_ids, ) self._sync_from_response(response.data) return response diff --git a/getstream/video/rest_client.py b/getstream/video/rest_client.py index 47684459..7c791cd6 100644 --- a/getstream/video/rest_client.py +++ b/getstream/video/rest_client.py @@ -96,9 +96,14 @@ def get_call( ring: Optional[bool] = None, notify: Optional[bool] = None, video: Optional[bool] = None, + target_member_ids: Optional[List[str]] = None, ) -> StreamResponse[GetCallResponse]: query_params = build_query_param( - members_limit=members_limit, ring=ring, notify=notify, video=video + members_limit=members_limit, + ring=ring, + notify=notify, + video=video, + target_member_ids=target_member_ids, ) path_params = { "type": type, diff --git a/tests/test_video_examples.py b/tests/test_video_examples.py index 2bf10142..41d03d37 100644 --- a/tests/test_video_examples.py +++ b/tests/test_video_examples.py @@ -7,6 +7,7 @@ from getstream.models import ( CallRequest, CallSettingsRequest, + MemberRequest, ScreensharingSettingsRequest, OwnCapability, LimitsSettingsRequest, @@ -467,3 +468,34 @@ async def test_event_representation(): for key, value in event_dict.items(): assert f"{key}=" in event_str assert str(repr(value)) in event_str + + +def test_ring_individual_members(client: Stream, get_user): + """Test ringing individual members in a call.""" + call_id = str(uuid.uuid4()) + call = client.video.call("default", call_id) + + # Create users + myself = get_user(name="myself") + my_friend = get_user(name="my-friend") + my_other_friend = get_user(name="my-other-friend") + + # Create call with two members + call.get_or_create( + data=CallRequest( + created_by_id=myself.id, + members=[ + MemberRequest(user_id=myself.id), + MemberRequest(user_id=my_friend.id), + ], + ) + ) + + # Ring existing member + response = call.ring(target_member_ids=[my_friend.id]) + assert response.status_code() == 200 + + # Add and ring a new member + call.update_call_members(update_members=[{"user_id": my_other_friend.id}]) + response = call.ring(target_member_ids=[my_other_friend.id]) + assert response.status_code() == 200