diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ef33d736..72aa497a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,7 +36,8 @@ jobs: - name: Extract package version id: get_version run: | - VERSION=$(uvx -q hatch version) + FILE=$(ls dist/getstream-*.tar.gz | head -1) + VERSION=$(basename "$FILE" .tar.gz | sed 's/^getstream-//') echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Publish to PyPI (Trusted Publishing) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index a0793ba6..a3c5966e 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -69,10 +69,6 @@ jobs: fail-fast: false matrix: python-version: ["3.10", "3.11", "3.12", "3.13"] - env: - STREAM_BASE_URL: ${{ vars.STREAM_BASE_URL }} - STREAM_API_KEY: ${{ vars.STREAM_API_KEY }} - STREAM_API_SECRET: ${{ secrets.STREAM_API_SECRET }} timeout-minutes: 30 steps: - name: Checkout @@ -81,11 +77,32 @@ jobs: uses: ./.github/actions/python-uv-setup with: python-version: ${{ matrix.python-version }} - - name: Debug environment variables + - name: Run non-video tests + env: + STREAM_API_KEY: ${{ vars.STREAM_CHAT_API_KEY }} + STREAM_API_SECRET: ${{ secrets.STREAM_CHAT_API_SECRET }} + STREAM_BASE_URL: ${{ vars.STREAM_CHAT_BASE_URL }} run: | - echo "STREAM_API_KEY is set: ${{ env.STREAM_API_KEY != '' }}" - echo "STREAM_API_SECRET is set: ${{ env.STREAM_API_SECRET != '' }}" - echo "STREAM_BASE_URL is set: ${{ env.STREAM_BASE_URL != '' }}" - - name: Run tests - run: uv run pytest -m "${{ inputs.marker }}" tests/ getstream/ + uv run pytest -m "${{ inputs.marker }}" tests/ getstream/ \ + --ignore=tests/rtc \ + --ignore=tests/test_video_examples.py \ + --ignore=tests/test_video_integration.py \ + --ignore=tests/test_video_openai.py \ + --ignore=tests/test_signaling.py \ + --ignore=tests/test_audio_stream_track.py \ + --ignore=getstream/video + - name: Run video tests + env: + STREAM_API_KEY: ${{ vars.STREAM_API_KEY }} + STREAM_API_SECRET: ${{ secrets.STREAM_API_SECRET }} + STREAM_BASE_URL: ${{ vars.STREAM_BASE_URL }} + run: | + uv run pytest -m "${{ inputs.marker }}" \ + tests/rtc \ + tests/test_video_examples.py \ + tests/test_video_integration.py \ + tests/test_video_openai.py \ + tests/test_signaling.py \ + tests/test_audio_stream_track.py \ + getstream/video diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..fd590ed1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,34 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [3.0.0b1] - 2026-02-27 + +### Breaking Changes + +- Type names across all products now follow the OpenAPI spec naming convention: response types are suffixed with `Response`, input types with `Request`. See [MIGRATION_v2_to_v3.md](./MIGRATION_v2_to_v3.md) for the complete rename mapping. +- `Event` (WebSocket envelope type) renamed to `WSEvent`. Base event type renamed from `BaseEvent` to `Event` (with field `type` instead of `T`). +- Event composition changed from monolithic `*Preset` embeds to modular `Has*` types. +- `Pager` renamed to `PagerResponse` and migrated from offset-based to cursor-based pagination (`next`/`prev` tokens). +- Types that were previously `dict` or `TypedDict` (e.g., `User`, `Channel`, `Message`) are now full dataclasses with typed fields. + +### Added + +- Full product coverage: Chat, Video, Moderation, and Feeds APIs are all supported in a single SDK. +- **Feeds**: activities, feeds, feed groups, follows, comments, reactions, collections, bookmarks, membership levels, feed views, and more. +- **Video**: calls, recordings, transcription, closed captions, SFU, call statistics, user feedback analytics, and more. +- **Moderation**: flags, review queue, moderation rules, config, appeals, moderation logs, and more. +- Push notification types, preferences, and templates. +- Webhook support: `WHEvent` envelope class for receiving webhook payloads, utility functions for decoding and verifying webhook signatures, and a full set of individual typed event dataclasses for every event across all products (Chat, Video, Moderation, Feeds) usable as discriminated event types. +- Cursor-based pagination across all list endpoints. + +## [2.7.1] - 2026-02-18 + +## [2.7.0] - 2026-02-03 + +## [2.6.0] - 2025-12-11 + +## [2.5.22] - 2025-10-15 diff --git a/MIGRATION_v2_to_v3.md b/MIGRATION_v2_to_v3.md new file mode 100644 index 00000000..5f7487a3 --- /dev/null +++ b/MIGRATION_v2_to_v3.md @@ -0,0 +1,210 @@ +# Migration Guide: v2 → v3 + +This guide covers all breaking changes when upgrading from `getstream` (stream-py) v2 to v3. + +## Overview + +v3 is a full OpenAPI-aligned release. The primary change is a **systematic type renaming**: types that appear in API responses now have a `Response` suffix, and input types have a `Request` suffix. There are no removed features — all functionality from v2 is available in v3. Additionally, v3 adds complete coverage of the **Feeds**, **Video**, and **Moderation** product APIs. + +Types that were previously plain `dict` or `TypedDict` (e.g., `User`, `Channel`, `Message`) are now full dataclasses with typed fields and IDE autocompletion support. + +## Installation + +```bash +pip install getstream==3.* +``` + +Or to install the pre-release: + +```bash +pip install --pre getstream +``` + +## Naming Conventions + +- **Classes**: `PascalCase` (e.g., `UserResponse`, `MessageRequest`) +- **Fields/attributes**: `snake_case` (e.g., `user.created_at`, `message.reply_count`) + +The general renaming rules: + +- Classes returned in API responses: `Foo` → `FooResponse` +- Classes used as API inputs: `Foo` → `FooRequest` +- Some moderation action payloads: `FooRequest` → `FooRequestPayload` + +## Breaking Changes + +### Common / Shared Types + +| v2 | v3 | Notes | +| --- | --- | --- | +| `ApplicationConfig` | `AppResponseFields` | App configuration in responses | +| `ChannelPushPreferences` | `ChannelPushPreferencesResponse` | Per-channel push settings | +| `Device` | `DeviceResponse` | Device data (push, voip) | +| `Event` | `WSEvent` | WebSocket event envelope | +| `FeedsPreferences` | `FeedsPreferencesResponse` | Feeds push preferences | +| `ImportV2Task` | `ImportV2TaskItem` | V2 import task | +| `OwnUser` | `OwnUserResponse` | Authenticated user data (was `dict`) | +| `Pager` | `PagerResponse` | Now cursor-based (`next`/`prev`) | +| `PushPreferences` | `PushPreferencesResponse` | Push preferences | +| `PushTemplate` | `PushTemplateResponse` | Push template | +| `PrivacySettings` | `PrivacySettingsResponse` | Typing indicators, read receipts | +| `RateLimitInfo` | `LimitInfoResponse` | Rate limit info | +| `SortParam` | `SortParamRequest` | Sort parameter for queries | +| `User` | `UserResponse` | Full user in responses (was `dict`) | +| `UserBlock` | `BlockedUserResponse` | Blocked user details | +| `UserCustomEvent` | `CustomEvent` | Custom user event | +| `UserMute` | `UserMuteResponse` | User mute details | + +### Event System + +| Before (v2) | After (v3) | Notes | +| --- | --- | --- | +| `BaseEvent` (field `T`) | `Event` (field `type`) | Base event type | +| `Event` (WS envelope) | `WSEvent` | WebSocket event wrapper | +| `*Preset` embeds | `Has*` composition types | e.g., `HasChannel`, `HasMessage` | +| — | `WHEvent` | New webhook envelope type | + +### Chat Types + +| v2 | v3 | Notes | +| --- | --- | --- | +| `Campaign` | `CampaignResponse` | | +| `CampaignStats` | `CampaignStatsResponse` | | +| `Channel` | `ChannelResponse` | Was `dict`, now dataclass | +| `ChannelConfigFields` | `ChannelConfigWithInfo` | Channel config + commands/grants | +| `ChannelMember` | `ChannelMemberResponse` | | +| `ChannelTypeConfigWithInfo` | `ChannelTypeConfig` | | +| `ConfigOverrides` | `ConfigOverridesRequest` | | +| `DraftMessage` / `DraftMessagePayload` | `DraftResponse` | Two types merged into one | +| `Message` | `MessageResponse` | Was `dict`, now dataclass | +| `MessageReminder` | `ReminderResponseData` | | +| `PendingMessage` | `PendingMessageResponse` | | +| `Poll` | `PollResponse` | | +| `PollOption` | `PollOptionResponse` | | +| `PollVote` | `PollVoteResponse` | | +| `Reaction` | `ReactionResponse` | | +| `ReadState` | `ReadStateResponse` | | +| `Thread` | `ThreadResponse` | | + +### Video Types + +| v2 | v3 | Notes | +| --- | --- | --- | +| `AudioSettings` | `AudioSettingsResponse` | | +| `BackstageSettings` | `BackstageSettingsResponse` | | +| `BroadcastSettings` | `BroadcastSettingsResponse` | | +| `Call` | `CallResponse` | Was `dict`, now dataclass | +| `CallEgress` | `EgressResponse` | | +| `CallMember` | `MemberResponse` | Note: not `CallMemberResponse` | +| `CallParticipant` | `CallParticipantResponse` | | +| `CallParticipantFeedback` | *(removed)* | Use `CollectUserFeedbackRequest` | +| `CallSession` | `CallSessionResponse` | | +| `CallSettings` | `CallSettingsResponse` | | +| `CallType` | `CallTypeResponse` | | +| `EventNotificationSettings` | `EventNotificationSettingsResponse` | | +| `FrameRecordSettings` | `FrameRecordingSettingsResponse` | `Recording` inserted in name | +| `GeofenceSettings` | `GeofenceSettingsResponse` | | +| `HLSSettings` | `HLSSettingsResponse` | | +| `IndividualRecordSettings` | `IndividualRecordingSettingsResponse` | `Recording` inserted in name | +| `IngressSettings` | `IngressSettingsResponse` | | +| `IngressSource` | `IngressSourceResponse` | | +| `IngressAudioEncodingOptions` | `IngressAudioEncodingResponse` | Shortened name | +| `IngressVideoEncodingOptions` | `IngressVideoEncodingResponse` | Shortened name | +| `IngressVideoLayer` | `IngressVideoLayerResponse` | | +| `LimitsSettings` | `LimitsSettingsResponse` | | +| `NotificationSettings` | `NotificationSettingsResponse` | | +| `RawRecordSettings` | `RawRecordingSettingsResponse` | `Recording` inserted in name | +| `RecordSettings` | `RecordSettingsResponse` | | +| `RingSettings` | `RingSettingsResponse` | | +| `RTMPSettings` | `RTMPSettingsResponse` | | +| `ScreensharingSettings` | `ScreensharingSettingsResponse` | | +| `SessionSettings` | `SessionSettingsResponse` | | +| `SIPCallConfigs` | `SIPCallConfigsResponse` | | +| `SIPCallerConfigs` | `SIPCallerConfigsResponse` | | +| `SIPDirectRoutingRuleCallConfigs` | `SIPDirectRoutingRuleCallConfigsResponse` | | +| `SIPInboundRoutingRules` | `SIPInboundRoutingRuleResponse` | Plural → singular | +| `SIPPinProtectionConfigs` | `SIPPinProtectionConfigsResponse` | | +| `SIPTrunk` | `SIPTrunkResponse` | | +| `ThumbnailsSettings` | `ThumbnailsSettingsResponse` | | +| `TranscriptionSettings` | `TranscriptionSettingsResponse` | | +| `VideoSettings` | `VideoSettingsResponse` | | + +### Moderation Types + +| v2 | v3 | Notes | +| --- | --- | --- | +| `ActionLog` | `ActionLogResponse` | | +| `Appeal` | `AppealItemResponse` | | +| `AutomodDetails` | `AutomodDetailsResponse` | | +| `Ban` | `BanInfoResponse` | | +| `BanOptions` | *(removed)* | Merged into `BanActionRequestPayload` | +| `BanActionRequest` | `BanActionRequestPayload` | | +| `BlockActionRequest` | `BlockActionRequestPayload` | | +| `BlockedMessage` | *(removed)* | Internal only | +| `CustomActionRequest` | `CustomActionRequestPayload` | | +| `DeleteMessageRequest` | `DeleteMessageRequestPayload` | | +| `DeleteUserRequest` | `DeleteUserRequestPayload` | | +| `EntityCreator` | `EntityCreatorResponse` | | +| `Evaluation` | `EvaluationResponse` | | +| `FeedsModerationTemplate` | `QueryFeedModerationTemplate` | No `Response` suffix | +| `FeedsModerationTemplateConfig` | `FeedsModerationTemplateConfigPayload` | | +| `Flag` | *(removed)* | Use `ModerationFlagResponse` | +| `Flag2` | `ModerationFlagResponse` | Was minimal type defs, now full dataclass | +| `FlagDetails` | `FlagDetailsResponse` | | +| `FlagFeedback` | `FlagFeedbackResponse` | | +| `FlagMessageDetails` | `FlagMessageDetailsResponse` | | +| `FlagReport` | *(removed)* | Internal only | +| `FutureChannelBan` | `FutureChannelBanResponse` | | +| `MarkReviewedRequest` | `MarkReviewedRequestPayload` | | +| `Match` | `MatchResponse` | | +| `ModerationActionConfig` | `ModerationActionConfigResponse` | | +| `ModerationBulkSubmitActionRequest` | `BulkSubmitActionRequest` | `Moderation` prefix dropped | +| `ModerationConfig` | `ConfigResponse` | | +| `ModerationFlags` | *(removed)* | Use `List[ModerationFlagResponse]` | +| `ModerationLog` | *(removed)* | Use `ActionLogResponse` | +| `ModerationLogResponse` | *(removed)* | Use `QueryModerationLogsResponse` | +| `ModerationUsageStats` | `ModerationUsageStatsResponse` | | +| `RestoreActionRequest` | `RestoreActionRequestPayload` | | +| `ReviewQueueItem` | `ReviewQueueItemResponse` | | +| `Rule` | `RuleResponse` | | +| `ShadowBlockActionRequest` | `ShadowBlockActionRequestPayload` | | +| `Task` | `TaskResponse` | | +| `Trigger` | `TriggerResponse` | | +| `UnbanActionRequest` | `UnbanActionRequestPayload` | | +| `UnblockActionRequest` | `UnblockActionRequestPayload` | | +| `VideoEndCallRequest` | `VideoEndCallRequestPayload` | | +| `VideoKickUserRequest` | `VideoKickUserRequestPayload` | | + +### Feeds Types + +| v2 | v3 | Notes | +| --- | --- | --- | +| `Activity` | `ActivityResponse` | Was `dict`, now dataclass | +| `ActivityFeedback` | `ActivityFeedbackRequest` | Request-only (no `Response` suffix) | +| `ActivityMark` | `MarkActivityRequest` | | +| `ActivityPin` | `ActivityPinResponse` | | +| `AggregatedActivity` | `AggregatedActivityResponse` | | +| `Bookmark` | `BookmarkResponse` | | +| `BookmarkFolder` | `BookmarkFolderResponse` | | +| `Collection` | `CollectionResponse` | | +| `Comment` | `CommentResponse` | | +| `CommentMedia` | *(removed)* | Embedded inline in `CommentResponse` | +| `CommentMention` | *(removed)* | Embedded inline in `CommentResponse` | +| `DenormalizedFeedsReaction` | *(removed)* | Internal only | +| `Feed` | `FeedResponse` | | +| `FeedGroup` | `FeedGroupResponse` | | +| `FeedMember` | `FeedMemberResponse` | | +| `FeedsReaction` | `FeedsReactionResponse` | | +| `FeedsReactionGroup` | `FeedsReactionGroupResponse` | | +| `FeedSuggestion` | `FeedSuggestionResponse` | | +| `FeedView` | `FeedViewResponse` | | +| `FeedVisibilityInfo` | `FeedVisibilityResponse` | | +| `Follow` | `FollowResponse` | | +| `MembershipLevel` | `MembershipLevelResponse` | | +| `ThreadedComment` | `ThreadedCommentResponse` | | + +## Getting Help + +- [Stream documentation](https://getstream.io/docs/) +- [GitHub Issues](https://github.com/GetStream/stream-py/issues) +- [Stream support](https://getstream.io/contact/support/) diff --git a/getstream/chat/async_rest_client.py b/getstream/chat/async_rest_client.py index 3b5f9cdc..bba9154a 100644 --- a/getstream/chat/async_rest_client.py +++ b/getstream/chat/async_rest_client.py @@ -3,7 +3,7 @@ from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.utils import build_query_param, build_body_dict +from getstream.utils import build_query_param class ChatRestClient(AsyncBaseClient): @@ -41,14 +41,14 @@ async def query_campaigns( sort: Optional[List[SortParamRequest]] = None, filter: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryCampaignsResponse]: - json = build_body_dict( + json = QueryCampaignsRequest( limit=limit, next=next, prev=prev, user_limit=user_limit, sort=sort, filter=filter, - ) + ).to_dict() return await self.post( "/api/v2/chat/campaigns/query", QueryCampaignsResponse, json=json ) @@ -61,7 +61,7 @@ async def get_campaign( next: Optional[str] = None, limit: Optional[int] = None, ) -> StreamResponse[GetCampaignResponse]: - query_params = build_query_param(prev=prev, next=next, limit=limit) + query_params = build_query_param(**{"prev": prev, "next": next, "limit": limit}) path_params = { "id": id, } @@ -82,7 +82,9 @@ async def start_campaign( path_params = { "id": id, } - json = build_body_dict(scheduled_for=scheduled_for, stop_at=stop_at) + json = StartCampaignRequest( + scheduled_for=scheduled_for, stop_at=stop_at + ).to_dict() return await self.post( "/api/v2/chat/campaigns/{id}/start", StartCampaignResponse, @@ -90,15 +92,15 @@ async def start_campaign( json=json, ) - @telemetry.operation_name("getstream.api.chat.schedule_campaign") - async def schedule_campaign( + @telemetry.operation_name("getstream.api.chat.stop_campaign") + async def stop_campaign( self, id: str, ) -> StreamResponse[CampaignResponse]: path_params = { "id": id, } - json = build_body_dict() + json = StopCampaignRequest().to_dict() return await self.post( "/api/v2/chat/campaigns/{id}/stop", CampaignResponse, @@ -113,23 +115,29 @@ async def query_channels( member_limit: Optional[int] = None, message_limit: Optional[int] = None, offset: Optional[int] = None, + predefined_filter: Optional[str] = None, state: Optional[bool] = None, user_id: Optional[str] = None, sort: Optional[List[SortParamRequest]] = None, filter_conditions: Optional[Dict[str, object]] = None, + filter_values: Optional[Dict[str, object]] = None, + sort_values: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryChannelsResponse]: - json = build_body_dict( + json = QueryChannelsRequest( limit=limit, member_limit=member_limit, message_limit=message_limit, offset=offset, + predefined_filter=predefined_filter, state=state, user_id=user_id, sort=sort, filter_conditions=filter_conditions, + filter_values=filter_values, + sort_values=sort_values, user=user, - ) + ).to_dict() return await self.post( "/api/v2/chat/channels", QueryChannelsResponse, json=json ) @@ -138,7 +146,7 @@ async def query_channels( async def delete_channels( self, cids: List[str], hard_delete: Optional[bool] = None ) -> StreamResponse[DeleteChannelsResponse]: - json = build_body_dict(cids=cids, hard_delete=hard_delete) + json = DeleteChannelsRequest(cids=cids, hard_delete=hard_delete).to_dict() return await self.post( "/api/v2/chat/channels/delete", DeleteChannelsResponse, json=json ) @@ -149,8 +157,10 @@ async def mark_delivered( user_id: Optional[str] = None, latest_delivered_messages: Optional[List[DeliveredMessagePayload]] = None, ) -> StreamResponse[MarkDeliveredResponse]: - query_params = build_query_param(user_id=user_id) - json = build_body_dict(latest_delivered_messages=latest_delivered_messages) + query_params = build_query_param(**{"user_id": user_id}) + json = MarkDeliveredRequest( + latest_delivered_messages=latest_delivered_messages + ).to_dict() return await self.post( "/api/v2/chat/channels/delivered", MarkDeliveredResponse, @@ -165,9 +175,9 @@ async def mark_channels_read( read_by_channel: Optional[Dict[str, str]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[MarkReadResponse]: - json = build_body_dict( + json = MarkChannelsReadRequest( user_id=user_id, read_by_channel=read_by_channel, user=user - ) + ).to_dict() return await self.post( "/api/v2/chat/channels/read", MarkReadResponse, json=json ) @@ -187,7 +197,7 @@ async def get_or_create_distinct_channel( path_params = { "type": type, } - json = build_body_dict( + json = ChannelGetOrCreateRequest( hide_for_creator=hide_for_creator, state=state, thread_unread_counts=thread_unread_counts, @@ -195,7 +205,7 @@ async def get_or_create_distinct_channel( members=members, messages=messages, watchers=watchers, - ) + ).to_dict() return await self.post( "/api/v2/chat/channels/{type}/query", ChannelStateResponse, @@ -207,7 +217,7 @@ async def get_or_create_distinct_channel( async def delete_channel( self, type: str, id: str, hard_delete: Optional[bool] = None ) -> StreamResponse[DeleteChannelResponse]: - query_params = build_query_param(hard_delete=hard_delete) + query_params = build_query_param(**{"hard_delete": hard_delete}) path_params = { "type": type, "id": id, @@ -233,7 +243,9 @@ async def update_channel_partial( "type": type, "id": id, } - json = build_body_dict(user_id=user_id, unset=unset, set=set, user=user) + json = UpdateChannelPartialRequest( + user_id=user_id, unset=unset, set=set, user=user + ).to_dict() return await self.patch( "/api/v2/chat/channels/{type}/{id}", UpdateChannelPartialResponse, @@ -269,7 +281,7 @@ async def update_channel( "type": type, "id": id, } - json = build_body_dict( + json = UpdateChannelRequest( accept_invite=accept_invite, cooldown=cooldown, hide_history=hide_history, @@ -288,7 +300,7 @@ async def update_channel( data=data, message=message, user=user, - ) + ).to_dict() return await self.post( "/api/v2/chat/channels/{type}/{id}", UpdateChannelResponse, @@ -304,7 +316,7 @@ async def delete_draft( parent_id: Optional[str] = None, user_id: Optional[str] = None, ) -> StreamResponse[Response]: - query_params = build_query_param(parent_id=parent_id, user_id=user_id) + query_params = build_query_param(**{"parent_id": parent_id, "user_id": user_id}) path_params = { "type": type, "id": id, @@ -324,7 +336,7 @@ async def get_draft( parent_id: Optional[str] = None, user_id: Optional[str] = None, ) -> StreamResponse[GetDraftResponse]: - query_params = build_query_param(parent_id=parent_id, user_id=user_id) + query_params = build_query_param(**{"parent_id": parent_id, "user_id": user_id}) path_params = { "type": type, "id": id, @@ -344,7 +356,7 @@ async def send_event( "type": type, "id": id, } - json = build_body_dict(event=event) + json = SendEventRequest(event=event).to_dict() return await self.post( "/api/v2/chat/channels/{type}/{id}/event", EventResponse, @@ -356,7 +368,7 @@ async def send_event( async def delete_channel_file( self, type: str, id: str, url: Optional[str] = None ) -> StreamResponse[Response]: - query_params = build_query_param(url=url) + query_params = build_query_param(**{"url": url}) path_params = { "type": type, "id": id, @@ -380,7 +392,7 @@ async def upload_channel_file( "type": type, "id": id, } - json = build_body_dict(file=file, user=user) + json = UploadChannelFileRequest(file=file, user=user).to_dict() return await self.post( "/api/v2/chat/channels/{type}/{id}/file", UploadChannelFileResponse, @@ -401,7 +413,9 @@ async def hide_channel( "type": type, "id": id, } - json = build_body_dict(clear_history=clear_history, user_id=user_id, user=user) + json = HideChannelRequest( + clear_history=clear_history, user_id=user_id, user=user + ).to_dict() return await self.post( "/api/v2/chat/channels/{type}/{id}/hide", HideChannelResponse, @@ -413,7 +427,7 @@ async def hide_channel( async def delete_channel_image( self, type: str, id: str, url: Optional[str] = None ) -> StreamResponse[Response]: - query_params = build_query_param(url=url) + query_params = build_query_param(**{"url": url}) path_params = { "type": type, "id": id, @@ -438,7 +452,9 @@ async def upload_channel_image( "type": type, "id": id, } - json = build_body_dict(file=file, upload_sizes=upload_sizes, user=user) + json = UploadChannelRequest( + file=file, upload_sizes=upload_sizes, user=user + ).to_dict() return await self.post( "/api/v2/chat/channels/{type}/{id}/image", UploadChannelResponse, @@ -455,12 +471,12 @@ async def update_member_partial( unset: Optional[List[str]] = None, set: Optional[Dict[str, object]] = None, ) -> StreamResponse[UpdateMemberPartialResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "type": type, "id": id, } - json = build_body_dict(unset=unset, set=set) + json = UpdateMemberPartialRequest(unset=unset, set=set).to_dict() return await self.patch( "/api/v2/chat/channels/{type}/{id}/member", UpdateMemberPartialResponse, @@ -486,7 +502,7 @@ async def send_message( "type": type, "id": id, } - json = build_body_dict( + json = SendMessageRequest( message=message, force_moderation=force_moderation, keep_channel_hidden=keep_channel_hidden, @@ -494,7 +510,7 @@ async def send_message( skip_enrich_url=skip_enrich_url, skip_push=skip_push, pending_message_metadata=pending_message_metadata, - ) + ).to_dict() return await self.post( "/api/v2/chat/channels/{type}/{id}/message", SendMessageResponse, @@ -506,7 +522,7 @@ async def send_message( async def get_many_messages( self, type: str, id: str, ids: List[str] ) -> StreamResponse[GetManyMessagesResponse]: - query_params = build_query_param(ids=ids) + query_params = build_query_param(**{"ids": ids}) path_params = { "type": type, "id": id, @@ -535,7 +551,7 @@ async def get_or_create_channel( "type": type, "id": id, } - json = build_body_dict( + json = ChannelGetOrCreateRequest( hide_for_creator=hide_for_creator, state=state, thread_unread_counts=thread_unread_counts, @@ -543,7 +559,7 @@ async def get_or_create_channel( members=members, messages=messages, watchers=watchers, - ) + ).to_dict() return await self.post( "/api/v2/chat/channels/{type}/{id}/query", ChannelStateResponse, @@ -565,9 +581,9 @@ async def mark_read( "type": type, "id": id, } - json = build_body_dict( + json = MarkReadRequest( message_id=message_id, thread_id=thread_id, user_id=user_id, user=user - ) + ).to_dict() return await self.post( "/api/v2/chat/channels/{type}/{id}/read", MarkReadResponse, @@ -587,7 +603,7 @@ async def show_channel( "type": type, "id": id, } - json = build_body_dict(user_id=user_id, user=user) + json = ShowChannelRequest(user_id=user_id, user=user).to_dict() return await self.post( "/api/v2/chat/channels/{type}/{id}/show", ShowChannelResponse, @@ -612,7 +628,7 @@ async def truncate_channel( "type": type, "id": id, } - json = build_body_dict( + json = TruncateChannelRequest( hard_delete=hard_delete, skip_push=skip_push, truncated_at=truncated_at, @@ -620,7 +636,7 @@ async def truncate_channel( member_ids=member_ids, message=message, user=user, - ) + ).to_dict() return await self.post( "/api/v2/chat/channels/{type}/{id}/truncate", TruncateChannelResponse, @@ -643,13 +659,13 @@ async def mark_unread( "type": type, "id": id, } - json = build_body_dict( + json = MarkUnreadRequest( message_id=message_id, message_timestamp=message_timestamp, thread_id=thread_id, user_id=user_id, user=user, - ) + ).to_dict() return await self.post( "/api/v2/chat/channels/{type}/{id}/unread", Response, @@ -680,6 +696,7 @@ async def create_channel_type( partition_size: Optional[int] = None, partition_ttl: Optional[str] = None, polls: Optional[bool] = None, + push_level: Optional[str] = None, push_notifications: Optional[bool] = None, reactions: Optional[bool] = None, read_events: Optional[bool] = None, @@ -696,7 +713,7 @@ async def create_channel_type( permissions: Optional[List[PolicyRequest]] = None, grants: Optional[Dict[str, List[str]]] = None, ) -> StreamResponse[CreateChannelTypeResponse]: - json = build_body_dict( + json = CreateChannelTypeRequest( automod=automod, automod_behavior=automod_behavior, max_message_length=max_message_length, @@ -713,6 +730,7 @@ async def create_channel_type( partition_size=partition_size, partition_ttl=partition_ttl, polls=polls, + push_level=push_level, push_notifications=push_notifications, reactions=reactions, read_events=read_events, @@ -728,7 +746,7 @@ async def create_channel_type( commands=commands, permissions=permissions, grants=grants, - ) + ).to_dict() return await self.post( "/api/v2/chat/channeltypes", CreateChannelTypeResponse, json=json ) @@ -773,6 +791,7 @@ async def update_channel_type( partition_size: Optional[int] = None, partition_ttl: Optional[str] = None, polls: Optional[bool] = None, + push_level: Optional[str] = None, push_notifications: Optional[bool] = None, quotes: Optional[bool] = None, reactions: Optional[bool] = None, @@ -796,7 +815,7 @@ async def update_channel_type( path_params = { "name": name, } - json = build_body_dict( + json = UpdateChannelTypeRequest( automod=automod, automod_behavior=automod_behavior, max_message_length=max_message_length, @@ -811,6 +830,7 @@ async def update_channel_type( partition_size=partition_size, partition_ttl=partition_ttl, polls=polls, + push_level=push_level, push_notifications=push_notifications, quotes=quotes, reactions=reactions, @@ -830,7 +850,7 @@ async def update_channel_type( permissions=permissions, automod_thresholds=automod_thresholds, grants=grants, - ) + ).to_dict() return await self.put( "/api/v2/chat/channeltypes/{name}", UpdateChannelTypeResponse, @@ -850,7 +870,9 @@ async def create_command( args: Optional[str] = None, set: Optional[str] = None, ) -> StreamResponse[CreateCommandResponse]: - json = build_body_dict(description=description, name=name, args=args, set=set) + json = CreateCommandRequest( + description=description, name=name, args=args, set=set + ).to_dict() return await self.post( "/api/v2/chat/commands", CreateCommandResponse, json=json ) @@ -886,7 +908,9 @@ async def update_command( path_params = { "name": name, } - json = build_body_dict(description=description, args=args, set=set) + json = UpdateCommandRequest( + description=description, args=args, set=set + ).to_dict() return await self.put( "/api/v2/chat/commands/{name}", UpdateCommandResponse, @@ -905,7 +929,7 @@ async def query_drafts( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryDraftsResponse]: - json = build_body_dict( + json = QueryDraftsRequest( limit=limit, next=next, prev=prev, @@ -913,7 +937,7 @@ async def query_drafts( sort=sort, filter=filter, user=user, - ) + ).to_dict() return await self.post( "/api/v2/chat/drafts/query", QueryDraftsResponse, json=json ) @@ -928,14 +952,14 @@ async def export_channels( include_truncated_messages: Optional[bool] = None, version: Optional[str] = None, ) -> StreamResponse[ExportChannelsResponse]: - json = build_body_dict( + json = ExportChannelsRequest( channels=channels, clear_deleted_message_text=clear_deleted_message_text, export_users=export_users, include_soft_deleted_channels=include_soft_deleted_channels, include_truncated_messages=include_truncated_messages, version=version, - ) + ).to_dict() return await self.post( "/api/v2/chat/export_channels", ExportChannelsResponse, json=json ) @@ -944,7 +968,7 @@ async def export_channels( async def query_members( self, payload: Optional[QueryMembersPayload] = None ) -> StreamResponse[MembersResponse]: - query_params = build_query_param(payload=payload) + query_params = build_query_param(**{"payload": payload}) return await self.get( "/api/v2/chat/members", MembersResponse, query_params=query_params ) @@ -958,9 +982,9 @@ async def query_message_history( prev: Optional[str] = None, sort: Optional[List[SortParamRequest]] = None, ) -> StreamResponse[QueryMessageHistoryResponse]: - json = build_body_dict( + json = QueryMessageHistoryRequest( filter=filter, limit=limit, next=next, prev=prev, sort=sort - ) + ).to_dict() return await self.post( "/api/v2/chat/messages/history", QueryMessageHistoryResponse, json=json ) @@ -974,7 +998,7 @@ async def delete_message( delete_for_me: Optional[bool] = None, ) -> StreamResponse[DeleteMessageResponse]: query_params = build_query_param( - hard=hard, deleted_by=deleted_by, delete_for_me=delete_for_me + **{"hard": hard, "deleted_by": deleted_by, "delete_for_me": delete_for_me} ) path_params = { "id": id, @@ -990,7 +1014,9 @@ async def delete_message( async def get_message( self, id: str, show_deleted_message: Optional[bool] = None ) -> StreamResponse[GetMessageResponse]: - query_params = build_query_param(show_deleted_message=show_deleted_message) + query_params = build_query_param( + **{"show_deleted_message": show_deleted_message} + ) path_params = { "id": id, } @@ -1012,9 +1038,9 @@ async def update_message( path_params = { "id": id, } - json = build_body_dict( + json = UpdateMessageRequest( message=message, skip_enrich_url=skip_enrich_url, skip_push=skip_push - ) + ).to_dict() return await self.post( "/api/v2/chat/messages/{id}", UpdateMessageResponse, @@ -1027,6 +1053,7 @@ async def update_message_partial( self, id: str, skip_enrich_url: Optional[bool] = None, + skip_push: Optional[bool] = None, user_id: Optional[str] = None, unset: Optional[List[str]] = None, set: Optional[Dict[str, object]] = None, @@ -1035,13 +1062,14 @@ async def update_message_partial( path_params = { "id": id, } - json = build_body_dict( + json = UpdateMessagePartialRequest( skip_enrich_url=skip_enrich_url, + skip_push=skip_push, user_id=user_id, unset=unset, set=set, user=user, - ) + ).to_dict() return await self.put( "/api/v2/chat/messages/{id}", UpdateMessagePartialResponse, @@ -1056,14 +1084,16 @@ async def run_message_action( form_data: Dict[str, str], user_id: Optional[str] = None, user: Optional[UserRequest] = None, - ) -> StreamResponse[MessageResponse]: + ) -> StreamResponse[MessageActionResponse]: path_params = { "id": id, } - json = build_body_dict(form_data=form_data, user_id=user_id, user=user) + json = MessageActionRequest( + form_data=form_data, user_id=user_id, user=user + ).to_dict() return await self.post( "/api/v2/chat/messages/{id}/action", - MessageResponse, + MessageActionResponse, path_params=path_params, json=json, ) @@ -1072,14 +1102,14 @@ async def run_message_action( async def commit_message( self, id: str, - ) -> StreamResponse[MessageResponse]: + ) -> StreamResponse[MessageActionResponse]: path_params = { "id": id, } - json = build_body_dict() + json = CommitMessageRequest().to_dict() return await self.post( "/api/v2/chat/messages/{id}/commit", - MessageResponse, + MessageActionResponse, path_params=path_params, json=json, ) @@ -1089,6 +1119,7 @@ async def ephemeral_message_update( self, id: str, skip_enrich_url: Optional[bool] = None, + skip_push: Optional[bool] = None, user_id: Optional[str] = None, unset: Optional[List[str]] = None, set: Optional[Dict[str, object]] = None, @@ -1097,13 +1128,14 @@ async def ephemeral_message_update( path_params = { "id": id, } - json = build_body_dict( + json = UpdateMessagePartialRequest( skip_enrich_url=skip_enrich_url, + skip_push=skip_push, user_id=user_id, unset=unset, set=set, user=user, - ) + ).to_dict() return await self.patch( "/api/v2/chat/messages/{id}/ephemeral", UpdateMessagePartialResponse, @@ -1122,9 +1154,9 @@ async def send_reaction( path_params = { "id": id, } - json = build_body_dict( + json = SendReactionRequest( reaction=reaction, enforce_unique=enforce_unique, skip_push=skip_push - ) + ).to_dict() return await self.post( "/api/v2/chat/messages/{id}/reaction", SendReactionResponse, @@ -1136,7 +1168,7 @@ async def send_reaction( async def delete_reaction( self, id: str, type: str, user_id: Optional[str] = None ) -> StreamResponse[DeleteReactionResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "id": id, "type": type, @@ -1152,7 +1184,7 @@ async def delete_reaction( async def get_reactions( self, id: str, limit: Optional[int] = None, offset: Optional[int] = None ) -> StreamResponse[GetReactionsResponse]: - query_params = build_query_param(limit=limit, offset=offset) + query_params = build_query_param(**{"limit": limit, "offset": offset}) path_params = { "id": id, } @@ -1178,7 +1210,7 @@ async def query_reactions( path_params = { "id": id, } - json = build_body_dict( + json = QueryReactionsRequest( limit=limit, next=next, prev=prev, @@ -1186,7 +1218,7 @@ async def query_reactions( sort=sort, filter=filter, user=user, - ) + ).to_dict() return await self.post( "/api/v2/chat/messages/{id}/reactions", QueryReactionsResponse, @@ -1197,35 +1229,29 @@ async def query_reactions( @telemetry.operation_name("getstream.api.chat.translate_message") async def translate_message( self, id: str, language: str - ) -> StreamResponse[MessageResponse]: + ) -> StreamResponse[MessageActionResponse]: path_params = { "id": id, } - json = build_body_dict(language=language) + json = TranslateMessageRequest(language=language).to_dict() return await self.post( "/api/v2/chat/messages/{id}/translate", - MessageResponse, + MessageActionResponse, path_params=path_params, json=json, ) @telemetry.operation_name("getstream.api.chat.undelete_message") async def undelete_message( - self, - id: str, - message: MessageRequest, - skip_enrich_url: Optional[bool] = None, - skip_push: Optional[bool] = None, - ) -> StreamResponse[UpdateMessageResponse]: + self, id: str, undeleted_by: str + ) -> StreamResponse[UndeleteMessageResponse]: path_params = { "id": id, } - json = build_body_dict( - message=message, skip_enrich_url=skip_enrich_url, skip_push=skip_push - ) + json = UndeleteMessageRequest(undeleted_by=undeleted_by).to_dict() return await self.post( "/api/v2/chat/messages/{id}/undelete", - UpdateMessageResponse, + UndeleteMessageResponse, path_params=path_params, json=json, ) @@ -1243,7 +1269,7 @@ async def cast_poll_vote( "message_id": message_id, "poll_id": poll_id, } - json = build_body_dict(user_id=user_id, user=user, vote=vote) + json = CastPollVoteRequest(user_id=user_id, user=user, vote=vote).to_dict() return await self.post( "/api/v2/chat/messages/{message_id}/polls/{poll_id}/vote", PollVoteResponse, @@ -1255,7 +1281,7 @@ async def cast_poll_vote( async def delete_poll_vote( self, message_id: str, poll_id: str, vote_id: str, user_id: Optional[str] = None ) -> StreamResponse[PollVoteResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "message_id": message_id, "poll_id": poll_id, @@ -1272,7 +1298,7 @@ async def delete_poll_vote( async def delete_reminder( self, message_id: str, user_id: Optional[str] = None ) -> StreamResponse[DeleteReminderResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "message_id": message_id, } @@ -1294,7 +1320,9 @@ async def update_reminder( path_params = { "message_id": message_id, } - json = build_body_dict(remind_at=remind_at, user_id=user_id, user=user) + json = UpdateReminderRequest( + remind_at=remind_at, user_id=user_id, user=user + ).to_dict() return await self.patch( "/api/v2/chat/messages/{message_id}/reminders", UpdateReminderResponse, @@ -1313,7 +1341,9 @@ async def create_reminder( path_params = { "message_id": message_id, } - json = build_body_dict(remind_at=remind_at, user_id=user_id, user=user) + json = CreateReminderRequest( + remind_at=remind_at, user_id=user_id, user=user + ).to_dict() return await self.post( "/api/v2/chat/messages/{message_id}/reminders", ReminderResponseData, @@ -1334,13 +1364,15 @@ async def get_replies( sort: Optional[List[SortParamRequest]] = None, ) -> StreamResponse[GetRepliesResponse]: query_params = build_query_param( - limit=limit, - id_gte=id_gte, - id_gt=id_gt, - id_lte=id_lte, - id_lt=id_lt, - id_around=id_around, - sort=sort, + **{ + "limit": limit, + "id_gte": id_gte, + "id_gt": id_gt, + "id_lte": id_lte, + "id_lt": id_lt, + "id_around": id_around, + "sort": sort, + } ) path_params = { "parent_id": parent_id, @@ -1356,7 +1388,7 @@ async def get_replies( async def query_message_flags( self, payload: Optional[QueryMessageFlagsPayload] = None ) -> StreamResponse[QueryMessageFlagsResponse]: - query_params = build_query_param(payload=payload) + query_params = build_query_param(**{"payload": payload}) return await self.get( "/api/v2/chat/moderation/flags/message", QueryMessageFlagsResponse, @@ -1371,9 +1403,9 @@ async def mute_channel( channel_cids: Optional[List[str]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[MuteChannelResponse]: - json = build_body_dict( + json = MuteChannelRequest( expiration=expiration, user_id=user_id, channel_cids=channel_cids, user=user - ) + ).to_dict() return await self.post( "/api/v2/chat/moderation/mute/channel", MuteChannelResponse, json=json ) @@ -1386,9 +1418,9 @@ async def unmute_channel( channel_cids: Optional[List[str]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[UnmuteResponse]: - json = build_body_dict( + json = UnmuteChannelRequest( expiration=expiration, user_id=user_id, channel_cids=channel_cids, user=user - ) + ).to_dict() return await self.post( "/api/v2/chat/moderation/unmute/channel", UnmuteResponse, json=json ) @@ -1397,13 +1429,24 @@ async def unmute_channel( async def query_banned_users( self, payload: Optional[QueryBannedUsersPayload] = None ) -> StreamResponse[QueryBannedUsersResponse]: - query_params = build_query_param(payload=payload) + query_params = build_query_param(**{"payload": payload}) return await self.get( "/api/v2/chat/query_banned_users", QueryBannedUsersResponse, query_params=query_params, ) + @telemetry.operation_name("getstream.api.chat.query_future_channel_bans") + async def query_future_channel_bans( + self, payload: Optional[QueryFutureChannelBansPayload] = None + ) -> StreamResponse[QueryFutureChannelBansResponse]: + query_params = build_query_param(**{"payload": payload}) + return await self.get( + "/api/v2/chat/query_future_channel_bans", + QueryFutureChannelBansResponse, + query_params=query_params, + ) + @telemetry.operation_name("getstream.api.chat.query_reminders") async def query_reminders( self, @@ -1415,7 +1458,7 @@ async def query_reminders( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryRemindersResponse]: - json = build_body_dict( + json = QueryRemindersRequest( limit=limit, next=next, prev=prev, @@ -1423,7 +1466,7 @@ async def query_reminders( sort=sort, filter=filter, user=user, - ) + ).to_dict() return await self.post( "/api/v2/chat/reminders/query", QueryRemindersResponse, json=json ) @@ -1432,7 +1475,7 @@ async def query_reminders( async def search( self, payload: Optional[SearchPayload] = None ) -> StreamResponse[SearchResponse]: - query_params = build_query_param(payload=payload) + query_params = build_query_param(**{"payload": payload}) return await self.get( "/api/v2/chat/search", SearchResponse, query_params=query_params ) @@ -1446,9 +1489,9 @@ async def query_segments( prev: Optional[str] = None, sort: Optional[List[SortParamRequest]] = None, ) -> StreamResponse[QuerySegmentsResponse]: - json = build_body_dict( + json = QuerySegmentsRequest( filter=filter, limit=limit, next=next, prev=prev, sort=sort - ) + ).to_dict() return await self.post( "/api/v2/chat/segments/query", QuerySegmentsResponse, json=json ) @@ -1478,7 +1521,7 @@ async def delete_segment_targets( path_params = { "id": id, } - json = build_body_dict(target_ids=target_ids) + json = DeleteSegmentTargetsRequest(target_ids=target_ids).to_dict() return await self.post( "/api/v2/chat/segments/{id}/deletetargets", Response, @@ -1513,9 +1556,9 @@ async def query_segment_targets( path_params = { "id": id, } - json = build_body_dict( + json = QuerySegmentTargetsRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return await self.post( "/api/v2/chat/segments/{id}/targets/query", QuerySegmentTargetsResponse, @@ -1523,6 +1566,26 @@ async def query_segment_targets( json=json, ) + @telemetry.operation_name("getstream.api.chat.query_team_usage_stats") + async def query_team_usage_stats( + self, + end_date: Optional[str] = None, + limit: Optional[int] = None, + month: Optional[str] = None, + next: Optional[str] = None, + start_date: Optional[str] = None, + ) -> StreamResponse[QueryTeamUsageStatsResponse]: + json = QueryTeamUsageStatsRequest( + end_date=end_date, + limit=limit, + month=month, + next=next, + start_date=start_date, + ).to_dict() + return await self.post( + "/api/v2/chat/stats/team_usage", QueryTeamUsageStatsResponse, json=json + ) + @telemetry.operation_name("getstream.api.chat.query_threads") async def query_threads( self, @@ -1537,7 +1600,7 @@ async def query_threads( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryThreadsResponse]: - json = build_body_dict( + json = QueryThreadsRequest( limit=limit, member_limit=member_limit, next=next, @@ -1548,7 +1611,7 @@ async def query_threads( sort=sort, filter=filter, user=user, - ) + ).to_dict() return await self.post("/api/v2/chat/threads", QueryThreadsResponse, json=json) @telemetry.operation_name("getstream.api.chat.get_thread") @@ -1560,9 +1623,11 @@ async def get_thread( member_limit: Optional[int] = None, ) -> StreamResponse[GetThreadResponse]: query_params = build_query_param( - reply_limit=reply_limit, - participant_limit=participant_limit, - member_limit=member_limit, + **{ + "reply_limit": reply_limit, + "participant_limit": participant_limit, + "member_limit": member_limit, + } ) path_params = { "message_id": message_id, @@ -1586,7 +1651,9 @@ async def update_thread_partial( path_params = { "message_id": message_id, } - json = build_body_dict(user_id=user_id, unset=unset, set=set, user=user) + json = UpdateThreadPartialRequest( + user_id=user_id, unset=unset, set=set, user=user + ).to_dict() return await self.patch( "/api/v2/chat/threads/{message_id}", UpdateThreadPartialResponse, @@ -1598,7 +1665,7 @@ async def update_thread_partial( async def unread_counts( self, user_id: Optional[str] = None ) -> StreamResponse[WrappedUnreadCountsResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) return await self.get( "/api/v2/chat/unread", WrappedUnreadCountsResponse, @@ -1609,7 +1676,7 @@ async def unread_counts( async def unread_counts_batch( self, user_ids: List[str] ) -> StreamResponse[UnreadCountsBatchResponse]: - json = build_body_dict(user_ids=user_ids) + json = UnreadCountsBatchRequest(user_ids=user_ids).to_dict() return await self.post( "/api/v2/chat/unread_batch", UnreadCountsBatchResponse, json=json ) @@ -1621,7 +1688,7 @@ async def send_user_custom_event( path_params = { "user_id": user_id, } - json = build_body_dict(event=event) + json = SendUserCustomEventRequest(event=event).to_dict() return await self.post( "/api/v2/chat/users/{user_id}/event", Response, diff --git a/getstream/chat/rest_client.py b/getstream/chat/rest_client.py index 7130c897..aed315a7 100644 --- a/getstream/chat/rest_client.py +++ b/getstream/chat/rest_client.py @@ -3,7 +3,7 @@ from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.utils import build_query_param, build_body_dict +from getstream.utils import build_query_param class ChatRestClient(BaseClient): @@ -41,14 +41,14 @@ def query_campaigns( sort: Optional[List[SortParamRequest]] = None, filter: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryCampaignsResponse]: - json = build_body_dict( + json = QueryCampaignsRequest( limit=limit, next=next, prev=prev, user_limit=user_limit, sort=sort, filter=filter, - ) + ).to_dict() return self.post( "/api/v2/chat/campaigns/query", QueryCampaignsResponse, json=json ) @@ -61,7 +61,7 @@ def get_campaign( next: Optional[str] = None, limit: Optional[int] = None, ) -> StreamResponse[GetCampaignResponse]: - query_params = build_query_param(prev=prev, next=next, limit=limit) + query_params = build_query_param(**{"prev": prev, "next": next, "limit": limit}) path_params = { "id": id, } @@ -82,7 +82,9 @@ def start_campaign( path_params = { "id": id, } - json = build_body_dict(scheduled_for=scheduled_for, stop_at=stop_at) + json = StartCampaignRequest( + scheduled_for=scheduled_for, stop_at=stop_at + ).to_dict() return self.post( "/api/v2/chat/campaigns/{id}/start", StartCampaignResponse, @@ -90,15 +92,15 @@ def start_campaign( json=json, ) - @telemetry.operation_name("getstream.api.chat.schedule_campaign") - def schedule_campaign( + @telemetry.operation_name("getstream.api.chat.stop_campaign") + def stop_campaign( self, id: str, ) -> StreamResponse[CampaignResponse]: path_params = { "id": id, } - json = build_body_dict() + json = StopCampaignRequest().to_dict() return self.post( "/api/v2/chat/campaigns/{id}/stop", CampaignResponse, @@ -113,30 +115,36 @@ def query_channels( member_limit: Optional[int] = None, message_limit: Optional[int] = None, offset: Optional[int] = None, + predefined_filter: Optional[str] = None, state: Optional[bool] = None, user_id: Optional[str] = None, sort: Optional[List[SortParamRequest]] = None, filter_conditions: Optional[Dict[str, object]] = None, + filter_values: Optional[Dict[str, object]] = None, + sort_values: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryChannelsResponse]: - json = build_body_dict( + json = QueryChannelsRequest( limit=limit, member_limit=member_limit, message_limit=message_limit, offset=offset, + predefined_filter=predefined_filter, state=state, user_id=user_id, sort=sort, filter_conditions=filter_conditions, + filter_values=filter_values, + sort_values=sort_values, user=user, - ) + ).to_dict() return self.post("/api/v2/chat/channels", QueryChannelsResponse, json=json) @telemetry.operation_name("getstream.api.chat.delete_channels") def delete_channels( self, cids: List[str], hard_delete: Optional[bool] = None ) -> StreamResponse[DeleteChannelsResponse]: - json = build_body_dict(cids=cids, hard_delete=hard_delete) + json = DeleteChannelsRequest(cids=cids, hard_delete=hard_delete).to_dict() return self.post( "/api/v2/chat/channels/delete", DeleteChannelsResponse, json=json ) @@ -147,8 +155,10 @@ def mark_delivered( user_id: Optional[str] = None, latest_delivered_messages: Optional[List[DeliveredMessagePayload]] = None, ) -> StreamResponse[MarkDeliveredResponse]: - query_params = build_query_param(user_id=user_id) - json = build_body_dict(latest_delivered_messages=latest_delivered_messages) + query_params = build_query_param(**{"user_id": user_id}) + json = MarkDeliveredRequest( + latest_delivered_messages=latest_delivered_messages + ).to_dict() return self.post( "/api/v2/chat/channels/delivered", MarkDeliveredResponse, @@ -163,9 +173,9 @@ def mark_channels_read( read_by_channel: Optional[Dict[str, str]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[MarkReadResponse]: - json = build_body_dict( + json = MarkChannelsReadRequest( user_id=user_id, read_by_channel=read_by_channel, user=user - ) + ).to_dict() return self.post("/api/v2/chat/channels/read", MarkReadResponse, json=json) @telemetry.operation_name("getstream.api.chat.get_or_create_distinct_channel") @@ -183,7 +193,7 @@ def get_or_create_distinct_channel( path_params = { "type": type, } - json = build_body_dict( + json = ChannelGetOrCreateRequest( hide_for_creator=hide_for_creator, state=state, thread_unread_counts=thread_unread_counts, @@ -191,7 +201,7 @@ def get_or_create_distinct_channel( members=members, messages=messages, watchers=watchers, - ) + ).to_dict() return self.post( "/api/v2/chat/channels/{type}/query", ChannelStateResponse, @@ -203,7 +213,7 @@ def get_or_create_distinct_channel( def delete_channel( self, type: str, id: str, hard_delete: Optional[bool] = None ) -> StreamResponse[DeleteChannelResponse]: - query_params = build_query_param(hard_delete=hard_delete) + query_params = build_query_param(**{"hard_delete": hard_delete}) path_params = { "type": type, "id": id, @@ -229,7 +239,9 @@ def update_channel_partial( "type": type, "id": id, } - json = build_body_dict(user_id=user_id, unset=unset, set=set, user=user) + json = UpdateChannelPartialRequest( + user_id=user_id, unset=unset, set=set, user=user + ).to_dict() return self.patch( "/api/v2/chat/channels/{type}/{id}", UpdateChannelPartialResponse, @@ -265,7 +277,7 @@ def update_channel( "type": type, "id": id, } - json = build_body_dict( + json = UpdateChannelRequest( accept_invite=accept_invite, cooldown=cooldown, hide_history=hide_history, @@ -284,7 +296,7 @@ def update_channel( data=data, message=message, user=user, - ) + ).to_dict() return self.post( "/api/v2/chat/channels/{type}/{id}", UpdateChannelResponse, @@ -300,7 +312,7 @@ def delete_draft( parent_id: Optional[str] = None, user_id: Optional[str] = None, ) -> StreamResponse[Response]: - query_params = build_query_param(parent_id=parent_id, user_id=user_id) + query_params = build_query_param(**{"parent_id": parent_id, "user_id": user_id}) path_params = { "type": type, "id": id, @@ -320,7 +332,7 @@ def get_draft( parent_id: Optional[str] = None, user_id: Optional[str] = None, ) -> StreamResponse[GetDraftResponse]: - query_params = build_query_param(parent_id=parent_id, user_id=user_id) + query_params = build_query_param(**{"parent_id": parent_id, "user_id": user_id}) path_params = { "type": type, "id": id, @@ -340,7 +352,7 @@ def send_event( "type": type, "id": id, } - json = build_body_dict(event=event) + json = SendEventRequest(event=event).to_dict() return self.post( "/api/v2/chat/channels/{type}/{id}/event", EventResponse, @@ -352,7 +364,7 @@ def send_event( def delete_channel_file( self, type: str, id: str, url: Optional[str] = None ) -> StreamResponse[Response]: - query_params = build_query_param(url=url) + query_params = build_query_param(**{"url": url}) path_params = { "type": type, "id": id, @@ -376,7 +388,7 @@ def upload_channel_file( "type": type, "id": id, } - json = build_body_dict(file=file, user=user) + json = UploadChannelFileRequest(file=file, user=user).to_dict() return self.post( "/api/v2/chat/channels/{type}/{id}/file", UploadChannelFileResponse, @@ -397,7 +409,9 @@ def hide_channel( "type": type, "id": id, } - json = build_body_dict(clear_history=clear_history, user_id=user_id, user=user) + json = HideChannelRequest( + clear_history=clear_history, user_id=user_id, user=user + ).to_dict() return self.post( "/api/v2/chat/channels/{type}/{id}/hide", HideChannelResponse, @@ -409,7 +423,7 @@ def hide_channel( def delete_channel_image( self, type: str, id: str, url: Optional[str] = None ) -> StreamResponse[Response]: - query_params = build_query_param(url=url) + query_params = build_query_param(**{"url": url}) path_params = { "type": type, "id": id, @@ -434,7 +448,9 @@ def upload_channel_image( "type": type, "id": id, } - json = build_body_dict(file=file, upload_sizes=upload_sizes, user=user) + json = UploadChannelRequest( + file=file, upload_sizes=upload_sizes, user=user + ).to_dict() return self.post( "/api/v2/chat/channels/{type}/{id}/image", UploadChannelResponse, @@ -451,12 +467,12 @@ def update_member_partial( unset: Optional[List[str]] = None, set: Optional[Dict[str, object]] = None, ) -> StreamResponse[UpdateMemberPartialResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "type": type, "id": id, } - json = build_body_dict(unset=unset, set=set) + json = UpdateMemberPartialRequest(unset=unset, set=set).to_dict() return self.patch( "/api/v2/chat/channels/{type}/{id}/member", UpdateMemberPartialResponse, @@ -482,7 +498,7 @@ def send_message( "type": type, "id": id, } - json = build_body_dict( + json = SendMessageRequest( message=message, force_moderation=force_moderation, keep_channel_hidden=keep_channel_hidden, @@ -490,7 +506,7 @@ def send_message( skip_enrich_url=skip_enrich_url, skip_push=skip_push, pending_message_metadata=pending_message_metadata, - ) + ).to_dict() return self.post( "/api/v2/chat/channels/{type}/{id}/message", SendMessageResponse, @@ -502,7 +518,7 @@ def send_message( def get_many_messages( self, type: str, id: str, ids: List[str] ) -> StreamResponse[GetManyMessagesResponse]: - query_params = build_query_param(ids=ids) + query_params = build_query_param(**{"ids": ids}) path_params = { "type": type, "id": id, @@ -531,7 +547,7 @@ def get_or_create_channel( "type": type, "id": id, } - json = build_body_dict( + json = ChannelGetOrCreateRequest( hide_for_creator=hide_for_creator, state=state, thread_unread_counts=thread_unread_counts, @@ -539,7 +555,7 @@ def get_or_create_channel( members=members, messages=messages, watchers=watchers, - ) + ).to_dict() return self.post( "/api/v2/chat/channels/{type}/{id}/query", ChannelStateResponse, @@ -561,9 +577,9 @@ def mark_read( "type": type, "id": id, } - json = build_body_dict( + json = MarkReadRequest( message_id=message_id, thread_id=thread_id, user_id=user_id, user=user - ) + ).to_dict() return self.post( "/api/v2/chat/channels/{type}/{id}/read", MarkReadResponse, @@ -583,7 +599,7 @@ def show_channel( "type": type, "id": id, } - json = build_body_dict(user_id=user_id, user=user) + json = ShowChannelRequest(user_id=user_id, user=user).to_dict() return self.post( "/api/v2/chat/channels/{type}/{id}/show", ShowChannelResponse, @@ -608,7 +624,7 @@ def truncate_channel( "type": type, "id": id, } - json = build_body_dict( + json = TruncateChannelRequest( hard_delete=hard_delete, skip_push=skip_push, truncated_at=truncated_at, @@ -616,7 +632,7 @@ def truncate_channel( member_ids=member_ids, message=message, user=user, - ) + ).to_dict() return self.post( "/api/v2/chat/channels/{type}/{id}/truncate", TruncateChannelResponse, @@ -639,13 +655,13 @@ def mark_unread( "type": type, "id": id, } - json = build_body_dict( + json = MarkUnreadRequest( message_id=message_id, message_timestamp=message_timestamp, thread_id=thread_id, user_id=user_id, user=user, - ) + ).to_dict() return self.post( "/api/v2/chat/channels/{type}/{id}/unread", Response, @@ -676,6 +692,7 @@ def create_channel_type( partition_size: Optional[int] = None, partition_ttl: Optional[str] = None, polls: Optional[bool] = None, + push_level: Optional[str] = None, push_notifications: Optional[bool] = None, reactions: Optional[bool] = None, read_events: Optional[bool] = None, @@ -692,7 +709,7 @@ def create_channel_type( permissions: Optional[List[PolicyRequest]] = None, grants: Optional[Dict[str, List[str]]] = None, ) -> StreamResponse[CreateChannelTypeResponse]: - json = build_body_dict( + json = CreateChannelTypeRequest( automod=automod, automod_behavior=automod_behavior, max_message_length=max_message_length, @@ -709,6 +726,7 @@ def create_channel_type( partition_size=partition_size, partition_ttl=partition_ttl, polls=polls, + push_level=push_level, push_notifications=push_notifications, reactions=reactions, read_events=read_events, @@ -724,7 +742,7 @@ def create_channel_type( commands=commands, permissions=permissions, grants=grants, - ) + ).to_dict() return self.post( "/api/v2/chat/channeltypes", CreateChannelTypeResponse, json=json ) @@ -767,6 +785,7 @@ def update_channel_type( partition_size: Optional[int] = None, partition_ttl: Optional[str] = None, polls: Optional[bool] = None, + push_level: Optional[str] = None, push_notifications: Optional[bool] = None, quotes: Optional[bool] = None, reactions: Optional[bool] = None, @@ -790,7 +809,7 @@ def update_channel_type( path_params = { "name": name, } - json = build_body_dict( + json = UpdateChannelTypeRequest( automod=automod, automod_behavior=automod_behavior, max_message_length=max_message_length, @@ -805,6 +824,7 @@ def update_channel_type( partition_size=partition_size, partition_ttl=partition_ttl, polls=polls, + push_level=push_level, push_notifications=push_notifications, quotes=quotes, reactions=reactions, @@ -824,7 +844,7 @@ def update_channel_type( permissions=permissions, automod_thresholds=automod_thresholds, grants=grants, - ) + ).to_dict() return self.put( "/api/v2/chat/channeltypes/{name}", UpdateChannelTypeResponse, @@ -844,7 +864,9 @@ def create_command( args: Optional[str] = None, set: Optional[str] = None, ) -> StreamResponse[CreateCommandResponse]: - json = build_body_dict(description=description, name=name, args=args, set=set) + json = CreateCommandRequest( + description=description, name=name, args=args, set=set + ).to_dict() return self.post("/api/v2/chat/commands", CreateCommandResponse, json=json) @telemetry.operation_name("getstream.api.chat.delete_command") @@ -878,7 +900,9 @@ def update_command( path_params = { "name": name, } - json = build_body_dict(description=description, args=args, set=set) + json = UpdateCommandRequest( + description=description, args=args, set=set + ).to_dict() return self.put( "/api/v2/chat/commands/{name}", UpdateCommandResponse, @@ -897,7 +921,7 @@ def query_drafts( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryDraftsResponse]: - json = build_body_dict( + json = QueryDraftsRequest( limit=limit, next=next, prev=prev, @@ -905,7 +929,7 @@ def query_drafts( sort=sort, filter=filter, user=user, - ) + ).to_dict() return self.post("/api/v2/chat/drafts/query", QueryDraftsResponse, json=json) @telemetry.operation_name("getstream.api.chat.export_channels") @@ -918,14 +942,14 @@ def export_channels( include_truncated_messages: Optional[bool] = None, version: Optional[str] = None, ) -> StreamResponse[ExportChannelsResponse]: - json = build_body_dict( + json = ExportChannelsRequest( channels=channels, clear_deleted_message_text=clear_deleted_message_text, export_users=export_users, include_soft_deleted_channels=include_soft_deleted_channels, include_truncated_messages=include_truncated_messages, version=version, - ) + ).to_dict() return self.post( "/api/v2/chat/export_channels", ExportChannelsResponse, json=json ) @@ -934,7 +958,7 @@ def export_channels( def query_members( self, payload: Optional[QueryMembersPayload] = None ) -> StreamResponse[MembersResponse]: - query_params = build_query_param(payload=payload) + query_params = build_query_param(**{"payload": payload}) return self.get( "/api/v2/chat/members", MembersResponse, query_params=query_params ) @@ -948,9 +972,9 @@ def query_message_history( prev: Optional[str] = None, sort: Optional[List[SortParamRequest]] = None, ) -> StreamResponse[QueryMessageHistoryResponse]: - json = build_body_dict( + json = QueryMessageHistoryRequest( filter=filter, limit=limit, next=next, prev=prev, sort=sort - ) + ).to_dict() return self.post( "/api/v2/chat/messages/history", QueryMessageHistoryResponse, json=json ) @@ -964,7 +988,7 @@ def delete_message( delete_for_me: Optional[bool] = None, ) -> StreamResponse[DeleteMessageResponse]: query_params = build_query_param( - hard=hard, deleted_by=deleted_by, delete_for_me=delete_for_me + **{"hard": hard, "deleted_by": deleted_by, "delete_for_me": delete_for_me} ) path_params = { "id": id, @@ -980,7 +1004,9 @@ def delete_message( def get_message( self, id: str, show_deleted_message: Optional[bool] = None ) -> StreamResponse[GetMessageResponse]: - query_params = build_query_param(show_deleted_message=show_deleted_message) + query_params = build_query_param( + **{"show_deleted_message": show_deleted_message} + ) path_params = { "id": id, } @@ -1002,9 +1028,9 @@ def update_message( path_params = { "id": id, } - json = build_body_dict( + json = UpdateMessageRequest( message=message, skip_enrich_url=skip_enrich_url, skip_push=skip_push - ) + ).to_dict() return self.post( "/api/v2/chat/messages/{id}", UpdateMessageResponse, @@ -1017,6 +1043,7 @@ def update_message_partial( self, id: str, skip_enrich_url: Optional[bool] = None, + skip_push: Optional[bool] = None, user_id: Optional[str] = None, unset: Optional[List[str]] = None, set: Optional[Dict[str, object]] = None, @@ -1025,13 +1052,14 @@ def update_message_partial( path_params = { "id": id, } - json = build_body_dict( + json = UpdateMessagePartialRequest( skip_enrich_url=skip_enrich_url, + skip_push=skip_push, user_id=user_id, unset=unset, set=set, user=user, - ) + ).to_dict() return self.put( "/api/v2/chat/messages/{id}", UpdateMessagePartialResponse, @@ -1046,14 +1074,16 @@ def run_message_action( form_data: Dict[str, str], user_id: Optional[str] = None, user: Optional[UserRequest] = None, - ) -> StreamResponse[MessageResponse]: + ) -> StreamResponse[MessageActionResponse]: path_params = { "id": id, } - json = build_body_dict(form_data=form_data, user_id=user_id, user=user) + json = MessageActionRequest( + form_data=form_data, user_id=user_id, user=user + ).to_dict() return self.post( "/api/v2/chat/messages/{id}/action", - MessageResponse, + MessageActionResponse, path_params=path_params, json=json, ) @@ -1062,14 +1092,14 @@ def run_message_action( def commit_message( self, id: str, - ) -> StreamResponse[MessageResponse]: + ) -> StreamResponse[MessageActionResponse]: path_params = { "id": id, } - json = build_body_dict() + json = CommitMessageRequest().to_dict() return self.post( "/api/v2/chat/messages/{id}/commit", - MessageResponse, + MessageActionResponse, path_params=path_params, json=json, ) @@ -1079,6 +1109,7 @@ def ephemeral_message_update( self, id: str, skip_enrich_url: Optional[bool] = None, + skip_push: Optional[bool] = None, user_id: Optional[str] = None, unset: Optional[List[str]] = None, set: Optional[Dict[str, object]] = None, @@ -1087,13 +1118,14 @@ def ephemeral_message_update( path_params = { "id": id, } - json = build_body_dict( + json = UpdateMessagePartialRequest( skip_enrich_url=skip_enrich_url, + skip_push=skip_push, user_id=user_id, unset=unset, set=set, user=user, - ) + ).to_dict() return self.patch( "/api/v2/chat/messages/{id}/ephemeral", UpdateMessagePartialResponse, @@ -1112,9 +1144,9 @@ def send_reaction( path_params = { "id": id, } - json = build_body_dict( + json = SendReactionRequest( reaction=reaction, enforce_unique=enforce_unique, skip_push=skip_push - ) + ).to_dict() return self.post( "/api/v2/chat/messages/{id}/reaction", SendReactionResponse, @@ -1126,7 +1158,7 @@ def send_reaction( def delete_reaction( self, id: str, type: str, user_id: Optional[str] = None ) -> StreamResponse[DeleteReactionResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "id": id, "type": type, @@ -1142,7 +1174,7 @@ def delete_reaction( def get_reactions( self, id: str, limit: Optional[int] = None, offset: Optional[int] = None ) -> StreamResponse[GetReactionsResponse]: - query_params = build_query_param(limit=limit, offset=offset) + query_params = build_query_param(**{"limit": limit, "offset": offset}) path_params = { "id": id, } @@ -1168,7 +1200,7 @@ def query_reactions( path_params = { "id": id, } - json = build_body_dict( + json = QueryReactionsRequest( limit=limit, next=next, prev=prev, @@ -1176,7 +1208,7 @@ def query_reactions( sort=sort, filter=filter, user=user, - ) + ).to_dict() return self.post( "/api/v2/chat/messages/{id}/reactions", QueryReactionsResponse, @@ -1187,35 +1219,29 @@ def query_reactions( @telemetry.operation_name("getstream.api.chat.translate_message") def translate_message( self, id: str, language: str - ) -> StreamResponse[MessageResponse]: + ) -> StreamResponse[MessageActionResponse]: path_params = { "id": id, } - json = build_body_dict(language=language) + json = TranslateMessageRequest(language=language).to_dict() return self.post( "/api/v2/chat/messages/{id}/translate", - MessageResponse, + MessageActionResponse, path_params=path_params, json=json, ) @telemetry.operation_name("getstream.api.chat.undelete_message") def undelete_message( - self, - id: str, - message: MessageRequest, - skip_enrich_url: Optional[bool] = None, - skip_push: Optional[bool] = None, - ) -> StreamResponse[UpdateMessageResponse]: + self, id: str, undeleted_by: str + ) -> StreamResponse[UndeleteMessageResponse]: path_params = { "id": id, } - json = build_body_dict( - message=message, skip_enrich_url=skip_enrich_url, skip_push=skip_push - ) + json = UndeleteMessageRequest(undeleted_by=undeleted_by).to_dict() return self.post( "/api/v2/chat/messages/{id}/undelete", - UpdateMessageResponse, + UndeleteMessageResponse, path_params=path_params, json=json, ) @@ -1233,7 +1259,7 @@ def cast_poll_vote( "message_id": message_id, "poll_id": poll_id, } - json = build_body_dict(user_id=user_id, user=user, vote=vote) + json = CastPollVoteRequest(user_id=user_id, user=user, vote=vote).to_dict() return self.post( "/api/v2/chat/messages/{message_id}/polls/{poll_id}/vote", PollVoteResponse, @@ -1245,7 +1271,7 @@ def cast_poll_vote( def delete_poll_vote( self, message_id: str, poll_id: str, vote_id: str, user_id: Optional[str] = None ) -> StreamResponse[PollVoteResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "message_id": message_id, "poll_id": poll_id, @@ -1262,7 +1288,7 @@ def delete_poll_vote( def delete_reminder( self, message_id: str, user_id: Optional[str] = None ) -> StreamResponse[DeleteReminderResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "message_id": message_id, } @@ -1284,7 +1310,9 @@ def update_reminder( path_params = { "message_id": message_id, } - json = build_body_dict(remind_at=remind_at, user_id=user_id, user=user) + json = UpdateReminderRequest( + remind_at=remind_at, user_id=user_id, user=user + ).to_dict() return self.patch( "/api/v2/chat/messages/{message_id}/reminders", UpdateReminderResponse, @@ -1303,7 +1331,9 @@ def create_reminder( path_params = { "message_id": message_id, } - json = build_body_dict(remind_at=remind_at, user_id=user_id, user=user) + json = CreateReminderRequest( + remind_at=remind_at, user_id=user_id, user=user + ).to_dict() return self.post( "/api/v2/chat/messages/{message_id}/reminders", ReminderResponseData, @@ -1324,13 +1354,15 @@ def get_replies( sort: Optional[List[SortParamRequest]] = None, ) -> StreamResponse[GetRepliesResponse]: query_params = build_query_param( - limit=limit, - id_gte=id_gte, - id_gt=id_gt, - id_lte=id_lte, - id_lt=id_lt, - id_around=id_around, - sort=sort, + **{ + "limit": limit, + "id_gte": id_gte, + "id_gt": id_gt, + "id_lte": id_lte, + "id_lt": id_lt, + "id_around": id_around, + "sort": sort, + } ) path_params = { "parent_id": parent_id, @@ -1346,7 +1378,7 @@ def get_replies( def query_message_flags( self, payload: Optional[QueryMessageFlagsPayload] = None ) -> StreamResponse[QueryMessageFlagsResponse]: - query_params = build_query_param(payload=payload) + query_params = build_query_param(**{"payload": payload}) return self.get( "/api/v2/chat/moderation/flags/message", QueryMessageFlagsResponse, @@ -1361,9 +1393,9 @@ def mute_channel( channel_cids: Optional[List[str]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[MuteChannelResponse]: - json = build_body_dict( + json = MuteChannelRequest( expiration=expiration, user_id=user_id, channel_cids=channel_cids, user=user - ) + ).to_dict() return self.post( "/api/v2/chat/moderation/mute/channel", MuteChannelResponse, json=json ) @@ -1376,9 +1408,9 @@ def unmute_channel( channel_cids: Optional[List[str]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[UnmuteResponse]: - json = build_body_dict( + json = UnmuteChannelRequest( expiration=expiration, user_id=user_id, channel_cids=channel_cids, user=user - ) + ).to_dict() return self.post( "/api/v2/chat/moderation/unmute/channel", UnmuteResponse, json=json ) @@ -1387,13 +1419,24 @@ def unmute_channel( def query_banned_users( self, payload: Optional[QueryBannedUsersPayload] = None ) -> StreamResponse[QueryBannedUsersResponse]: - query_params = build_query_param(payload=payload) + query_params = build_query_param(**{"payload": payload}) return self.get( "/api/v2/chat/query_banned_users", QueryBannedUsersResponse, query_params=query_params, ) + @telemetry.operation_name("getstream.api.chat.query_future_channel_bans") + def query_future_channel_bans( + self, payload: Optional[QueryFutureChannelBansPayload] = None + ) -> StreamResponse[QueryFutureChannelBansResponse]: + query_params = build_query_param(**{"payload": payload}) + return self.get( + "/api/v2/chat/query_future_channel_bans", + QueryFutureChannelBansResponse, + query_params=query_params, + ) + @telemetry.operation_name("getstream.api.chat.query_reminders") def query_reminders( self, @@ -1405,7 +1448,7 @@ def query_reminders( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryRemindersResponse]: - json = build_body_dict( + json = QueryRemindersRequest( limit=limit, next=next, prev=prev, @@ -1413,7 +1456,7 @@ def query_reminders( sort=sort, filter=filter, user=user, - ) + ).to_dict() return self.post( "/api/v2/chat/reminders/query", QueryRemindersResponse, json=json ) @@ -1422,7 +1465,7 @@ def query_reminders( def search( self, payload: Optional[SearchPayload] = None ) -> StreamResponse[SearchResponse]: - query_params = build_query_param(payload=payload) + query_params = build_query_param(**{"payload": payload}) return self.get( "/api/v2/chat/search", SearchResponse, query_params=query_params ) @@ -1436,9 +1479,9 @@ def query_segments( prev: Optional[str] = None, sort: Optional[List[SortParamRequest]] = None, ) -> StreamResponse[QuerySegmentsResponse]: - json = build_body_dict( + json = QuerySegmentsRequest( filter=filter, limit=limit, next=next, prev=prev, sort=sort - ) + ).to_dict() return self.post( "/api/v2/chat/segments/query", QuerySegmentsResponse, json=json ) @@ -1468,7 +1511,7 @@ def delete_segment_targets( path_params = { "id": id, } - json = build_body_dict(target_ids=target_ids) + json = DeleteSegmentTargetsRequest(target_ids=target_ids).to_dict() return self.post( "/api/v2/chat/segments/{id}/deletetargets", Response, @@ -1503,9 +1546,9 @@ def query_segment_targets( path_params = { "id": id, } - json = build_body_dict( + json = QuerySegmentTargetsRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return self.post( "/api/v2/chat/segments/{id}/targets/query", QuerySegmentTargetsResponse, @@ -1513,6 +1556,26 @@ def query_segment_targets( json=json, ) + @telemetry.operation_name("getstream.api.chat.query_team_usage_stats") + def query_team_usage_stats( + self, + end_date: Optional[str] = None, + limit: Optional[int] = None, + month: Optional[str] = None, + next: Optional[str] = None, + start_date: Optional[str] = None, + ) -> StreamResponse[QueryTeamUsageStatsResponse]: + json = QueryTeamUsageStatsRequest( + end_date=end_date, + limit=limit, + month=month, + next=next, + start_date=start_date, + ).to_dict() + return self.post( + "/api/v2/chat/stats/team_usage", QueryTeamUsageStatsResponse, json=json + ) + @telemetry.operation_name("getstream.api.chat.query_threads") def query_threads( self, @@ -1527,7 +1590,7 @@ def query_threads( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryThreadsResponse]: - json = build_body_dict( + json = QueryThreadsRequest( limit=limit, member_limit=member_limit, next=next, @@ -1538,7 +1601,7 @@ def query_threads( sort=sort, filter=filter, user=user, - ) + ).to_dict() return self.post("/api/v2/chat/threads", QueryThreadsResponse, json=json) @telemetry.operation_name("getstream.api.chat.get_thread") @@ -1550,9 +1613,11 @@ def get_thread( member_limit: Optional[int] = None, ) -> StreamResponse[GetThreadResponse]: query_params = build_query_param( - reply_limit=reply_limit, - participant_limit=participant_limit, - member_limit=member_limit, + **{ + "reply_limit": reply_limit, + "participant_limit": participant_limit, + "member_limit": member_limit, + } ) path_params = { "message_id": message_id, @@ -1576,7 +1641,9 @@ def update_thread_partial( path_params = { "message_id": message_id, } - json = build_body_dict(user_id=user_id, unset=unset, set=set, user=user) + json = UpdateThreadPartialRequest( + user_id=user_id, unset=unset, set=set, user=user + ).to_dict() return self.patch( "/api/v2/chat/threads/{message_id}", UpdateThreadPartialResponse, @@ -1588,7 +1655,7 @@ def update_thread_partial( def unread_counts( self, user_id: Optional[str] = None ) -> StreamResponse[WrappedUnreadCountsResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) return self.get( "/api/v2/chat/unread", WrappedUnreadCountsResponse, @@ -1599,7 +1666,7 @@ def unread_counts( def unread_counts_batch( self, user_ids: List[str] ) -> StreamResponse[UnreadCountsBatchResponse]: - json = build_body_dict(user_ids=user_ids) + json = UnreadCountsBatchRequest(user_ids=user_ids).to_dict() return self.post( "/api/v2/chat/unread_batch", UnreadCountsBatchResponse, json=json ) @@ -1611,7 +1678,7 @@ def send_user_custom_event( path_params = { "user_id": user_id, } - json = build_body_dict(event=event) + json = SendUserCustomEventRequest(event=event).to_dict() return self.post( "/api/v2/chat/users/{user_id}/event", Response, diff --git a/getstream/common/async_rest_client.py b/getstream/common/async_rest_client.py index 0f416c88..19e863af 100644 --- a/getstream/common/async_rest_client.py +++ b/getstream/common/async_rest_client.py @@ -3,7 +3,7 @@ from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.utils import build_query_param, build_body_dict +from getstream.utils import build_query_param class CommonRestClient(AsyncBaseClient): @@ -53,6 +53,7 @@ async def update_app( image_moderation_enabled: Optional[bool] = None, max_aggregated_activities_length: Optional[int] = None, migrate_permissions_to_v2: Optional[bool] = None, + moderation_analytics_enabled: Optional[bool] = None, moderation_enabled: Optional[bool] = None, moderation_webhook_url: Optional[str] = None, multi_tenant_enabled: Optional[bool] = None, @@ -88,7 +89,7 @@ async def update_app( push_config: Optional[PushConfig] = None, xiaomi_config: Optional[XiaomiConfig] = None, ) -> StreamResponse[Response]: - json = build_body_dict( + json = UpdateAppRequest( async_url_enrich_enabled=async_url_enrich_enabled, auto_translation_enabled=auto_translation_enabled, before_message_send_hook_url=before_message_send_hook_url, @@ -104,6 +105,7 @@ async def update_app( image_moderation_enabled=image_moderation_enabled, max_aggregated_activities_length=max_aggregated_activities_length, migrate_permissions_to_v2=migrate_permissions_to_v2, + moderation_analytics_enabled=moderation_analytics_enabled, moderation_enabled=moderation_enabled, moderation_webhook_url=moderation_webhook_url, multi_tenant_enabled=multi_tenant_enabled, @@ -136,14 +138,14 @@ async def update_app( moderation_dashboard_preferences=moderation_dashboard_preferences, push_config=push_config, xiaomi_config=xiaomi_config, - ) + ).to_dict() return await self.patch("/api/v2/app", Response, json=json) @telemetry.operation_name("getstream.api.common.list_block_lists") async def list_block_lists( self, team: Optional[str] = None ) -> StreamResponse[ListBlockListResponse]: - query_params = build_query_param(team=team) + query_params = build_query_param(**{"team": team}) return await self.get( "/api/v2/blocklists", ListBlockListResponse, query_params=query_params ) @@ -158,21 +160,21 @@ async def create_block_list( team: Optional[str] = None, type: Optional[str] = None, ) -> StreamResponse[CreateBlockListResponse]: - json = build_body_dict( + json = CreateBlockListRequest( name=name, words=words, is_leet_check_enabled=is_leet_check_enabled, is_plural_check_enabled=is_plural_check_enabled, team=team, type=type, - ) + ).to_dict() return await self.post("/api/v2/blocklists", CreateBlockListResponse, json=json) @telemetry.operation_name("getstream.api.common.delete_block_list") async def delete_block_list( self, name: str, team: Optional[str] = None ) -> StreamResponse[Response]: - query_params = build_query_param(team=team) + query_params = build_query_param(**{"team": team}) path_params = { "name": name, } @@ -187,7 +189,7 @@ async def delete_block_list( async def get_block_list( self, name: str, team: Optional[str] = None ) -> StreamResponse[GetBlockListResponse]: - query_params = build_query_param(team=team) + query_params = build_query_param(**{"team": team}) path_params = { "name": name, } @@ -210,12 +212,12 @@ async def update_block_list( path_params = { "name": name, } - json = build_body_dict( + json = UpdateBlockListRequest( is_leet_check_enabled=is_leet_check_enabled, is_plural_check_enabled=is_plural_check_enabled, team=team, words=words, - ) + ).to_dict() return await self.put( "/api/v2/blocklists/{name}", UpdateBlockListResponse, @@ -237,7 +239,7 @@ async def check_push( user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[CheckPushResponse]: - json = build_body_dict( + json = CheckPushRequest( apn_template=apn_template, event_type=event_type, firebase_data_template=firebase_data_template, @@ -248,7 +250,7 @@ async def check_push( skip_devices=skip_devices, user_id=user_id, user=user, - ) + ).to_dict() return await self.post("/api/v2/check_push", CheckPushResponse, json=json) @telemetry.operation_name("getstream.api.common.check_sns") @@ -258,9 +260,9 @@ async def check_sns( sns_secret: Optional[str] = None, sns_topic_arn: Optional[str] = None, ) -> StreamResponse[CheckSNSResponse]: - json = build_body_dict( + json = CheckSNSRequest( sns_key=sns_key, sns_secret=sns_secret, sns_topic_arn=sns_topic_arn - ) + ).to_dict() return await self.post("/api/v2/check_sns", CheckSNSResponse, json=json) @telemetry.operation_name("getstream.api.common.check_sqs") @@ -270,21 +272,23 @@ async def check_sqs( sqs_secret: Optional[str] = None, sqs_url: Optional[str] = None, ) -> StreamResponse[CheckSQSResponse]: - json = build_body_dict(sqs_key=sqs_key, sqs_secret=sqs_secret, sqs_url=sqs_url) + json = CheckSQSRequest( + sqs_key=sqs_key, sqs_secret=sqs_secret, sqs_url=sqs_url + ).to_dict() return await self.post("/api/v2/check_sqs", CheckSQSResponse, json=json) @telemetry.operation_name("getstream.api.common.delete_device") async def delete_device( self, id: str, user_id: Optional[str] = None ) -> StreamResponse[Response]: - query_params = build_query_param(id=id, user_id=user_id) + query_params = build_query_param(**{"id": id, "user_id": user_id}) return await self.delete("/api/v2/devices", Response, query_params=query_params) @telemetry.operation_name("getstream.api.common.list_devices") async def list_devices( self, user_id: Optional[str] = None ) -> StreamResponse[ListDevicesResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) return await self.get( "/api/v2/devices", ListDevicesResponse, query_params=query_params ) @@ -299,21 +303,21 @@ async def create_device( voip_token: Optional[bool] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[Response]: - json = build_body_dict( + json = CreateDeviceRequest( id=id, push_provider=push_provider, push_provider_name=push_provider_name, user_id=user_id, voip_token=voip_token, user=user, - ) + ).to_dict() return await self.post("/api/v2/devices", Response, json=json) @telemetry.operation_name("getstream.api.common.export_users") async def export_users( self, user_ids: List[str] ) -> StreamResponse[ExportUsersResponse]: - json = build_body_dict(user_ids=user_ids) + json = ExportUsersRequest(user_ids=user_ids).to_dict() return await self.post("/api/v2/export/users", ExportUsersResponse, json=json) @telemetry.operation_name("getstream.api.common.list_external_storage") @@ -333,7 +337,7 @@ async def create_external_storage( aws_s3: Optional[S3Request] = None, azure_blob: Optional[AzureRequest] = None, ) -> StreamResponse[CreateExternalStorageResponse]: - json = build_body_dict( + json = CreateExternalStorageRequest( bucket=bucket, name=name, storage_type=storage_type, @@ -341,7 +345,7 @@ async def create_external_storage( path=path, aws_s3=aws_s3, azure_blob=azure_blob, - ) + ).to_dict() return await self.post( "/api/v2/external_storage", CreateExternalStorageResponse, json=json ) @@ -373,14 +377,14 @@ async def update_external_storage( path_params = { "name": name, } - json = build_body_dict( + json = UpdateExternalStorageRequest( bucket=bucket, storage_type=storage_type, gcs_credentials=gcs_credentials, path=path, aws_s3=aws_s3, azure_blob=azure_blob, - ) + ).to_dict() return await self.put( "/api/v2/external_storage/{name}", UpdateExternalStorageResponse, @@ -405,14 +409,14 @@ async def check_external_storage( async def create_guest( self, user: UserRequest ) -> StreamResponse[CreateGuestResponse]: - json = build_body_dict(user=user) + json = CreateGuestRequest(user=user).to_dict() return await self.post("/api/v2/guest", CreateGuestResponse, json=json) @telemetry.operation_name("getstream.api.common.create_import_url") async def create_import_url( self, filename: Optional[str] = None ) -> StreamResponse[CreateImportURLResponse]: - json = build_body_dict(filename=filename) + json = CreateImportURLRequest(filename=filename).to_dict() return await self.post( "/api/v2/import_urls", CreateImportURLResponse, json=json ) @@ -423,16 +427,18 @@ async def list_imports(self) -> StreamResponse[ListImportsResponse]: @telemetry.operation_name("getstream.api.common.create_import") async def create_import( - self, mode: str, path: str + self, mode: str, path: str, merge_custom: Optional[bool] = None ) -> StreamResponse[CreateImportResponse]: - json = build_body_dict(mode=mode, path=path) + json = CreateImportRequest( + mode=mode, path=path, merge_custom=merge_custom + ).to_dict() return await self.post("/api/v2/imports", CreateImportResponse, json=json) @telemetry.operation_name("getstream.api.common.list_import_v2_tasks") async def list_import_v2_tasks( self, state: Optional[int] = None ) -> StreamResponse[ListImportV2TasksResponse]: - query_params = build_query_param(state=state) + query_params = build_query_param(**{"state": state}) return await self.get( "/api/v2/imports/v2", ListImportV2TasksResponse, query_params=query_params ) @@ -445,9 +451,9 @@ async def create_import_v2_task( user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[CreateImportV2TaskResponse]: - json = build_body_dict( + json = CreateImportV2TaskRequest( product=product, settings=settings, user_id=user_id, user=user - ) + ).to_dict() return await self.post( "/api/v2/imports/v2", CreateImportV2TaskResponse, json=json ) @@ -487,7 +493,7 @@ async def get_import(self, id: str) -> StreamResponse[GetImportResponse]: @telemetry.operation_name("getstream.api.common.get_og") async def get_og(self, url: str) -> StreamResponse[GetOGResponse]: - query_params = build_query_param(url=url) + query_params = build_query_param(**{"url": url}) return await self.get("/api/v2/og", GetOGResponse, query_params=query_params) @telemetry.operation_name("getstream.api.common.list_permissions") @@ -524,7 +530,7 @@ async def create_poll( custom: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[PollResponse]: - json = build_body_dict( + json = CreatePollRequest( name=name, allow_answers=allow_answers, allow_user_suggested_options=allow_user_suggested_options, @@ -538,7 +544,7 @@ async def create_poll( options=options, custom=custom, user=user, - ) + ).to_dict() return await self.post("/api/v2/polls", PollResponse, json=json) @telemetry.operation_name("getstream.api.common.update_poll") @@ -558,7 +564,7 @@ async def update_poll( custom: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[PollResponse]: - json = build_body_dict( + json = UpdatePollRequest( id=id, name=name, allow_answers=allow_answers, @@ -572,7 +578,7 @@ async def update_poll( options=options, custom=custom, user=user, - ) + ).to_dict() return await self.put("/api/v2/polls", PollResponse, json=json) @telemetry.operation_name("getstream.api.common.query_polls") @@ -585,10 +591,10 @@ async def query_polls( sort: Optional[List[SortParamRequest]] = None, filter: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryPollsResponse]: - query_params = build_query_param(user_id=user_id) - json = build_body_dict( + query_params = build_query_param(**{"user_id": user_id}) + json = QueryPollsRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return await self.post( "/api/v2/polls/query", QueryPollsResponse, @@ -600,7 +606,7 @@ async def query_polls( async def delete_poll( self, poll_id: str, user_id: Optional[str] = None ) -> StreamResponse[Response]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "poll_id": poll_id, } @@ -615,7 +621,7 @@ async def delete_poll( async def get_poll( self, poll_id: str, user_id: Optional[str] = None ) -> StreamResponse[PollResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "poll_id": poll_id, } @@ -638,7 +644,9 @@ async def update_poll_partial( path_params = { "poll_id": poll_id, } - json = build_body_dict(user_id=user_id, unset=unset, set=set, user=user) + json = UpdatePollPartialRequest( + user_id=user_id, unset=unset, set=set, user=user + ).to_dict() return await self.patch( "/api/v2/polls/{poll_id}", PollResponse, path_params=path_params, json=json ) @@ -655,7 +663,9 @@ async def create_poll_option( path_params = { "poll_id": poll_id, } - json = build_body_dict(text=text, user_id=user_id, custom=custom, user=user) + json = CreatePollOptionRequest( + text=text, user_id=user_id, custom=custom, user=user + ).to_dict() return await self.post( "/api/v2/polls/{poll_id}/options", PollOptionResponse, @@ -676,9 +686,9 @@ async def update_poll_option( path_params = { "poll_id": poll_id, } - json = build_body_dict( + json = UpdatePollOptionRequest( id=id, text=text, user_id=user_id, custom=custom, user=user - ) + ).to_dict() return await self.put( "/api/v2/polls/{poll_id}/options", PollOptionResponse, @@ -690,7 +700,7 @@ async def update_poll_option( async def delete_poll_option( self, poll_id: str, option_id: str, user_id: Optional[str] = None ) -> StreamResponse[Response]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "poll_id": poll_id, "option_id": option_id, @@ -706,7 +716,7 @@ async def delete_poll_option( async def get_poll_option( self, poll_id: str, option_id: str, user_id: Optional[str] = None ) -> StreamResponse[PollOptionResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "poll_id": poll_id, "option_id": option_id, @@ -729,13 +739,13 @@ async def query_poll_votes( sort: Optional[List[SortParamRequest]] = None, filter: Optional[Dict[str, object]] = None, ) -> StreamResponse[PollVotesResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "poll_id": poll_id, } - json = build_body_dict( + json = QueryPollVotesRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return await self.post( "/api/v2/polls/{poll_id}/votes", PollVotesResponse, @@ -750,7 +760,7 @@ async def query_poll_votes( async def update_push_notification_preferences( self, preferences: List[PushPreferenceInput] ) -> StreamResponse[UpsertPushPreferencesResponse]: - json = build_body_dict(preferences=preferences) + json = UpsertPushPreferencesRequest(preferences=preferences).to_dict() return await self.post( "/api/v2/push_preferences", UpsertPushPreferencesResponse, json=json ) @@ -761,9 +771,9 @@ async def list_push_providers(self) -> StreamResponse[ListPushProvidersResponse] @telemetry.operation_name("getstream.api.common.upsert_push_provider") async def upsert_push_provider( - self, push_provider: Optional[PushProvider] = None + self, push_provider: Optional[PushProviderRequest] = None ) -> StreamResponse[UpsertPushProviderResponse]: - json = build_body_dict(push_provider=push_provider) + json = UpsertPushProviderRequest(push_provider=push_provider).to_dict() return await self.post( "/api/v2/push_providers", UpsertPushProviderResponse, json=json ) @@ -785,7 +795,10 @@ async def get_push_templates( self, push_provider_type: str, push_provider_name: Optional[str] = None ) -> StreamResponse[GetPushTemplatesResponse]: query_params = build_query_param( - push_provider_type=push_provider_type, push_provider_name=push_provider_name + **{ + "push_provider_type": push_provider_type, + "push_provider_name": push_provider_name, + } ) return await self.get( "/api/v2/push_templates", @@ -802,13 +815,13 @@ async def upsert_push_template( push_provider_name: Optional[str] = None, template: Optional[str] = None, ) -> StreamResponse[UpsertPushTemplateResponse]: - json = build_body_dict( + json = UpsertPushTemplateRequest( event_type=event_type, push_provider_type=push_provider_type, enable_push=enable_push, push_provider_name=push_provider_name, template=template, - ) + ).to_dict() return await self.post( "/api/v2/push_templates", UpsertPushTemplateResponse, json=json ) @@ -823,11 +836,13 @@ async def get_rate_limits( endpoints: Optional[str] = None, ) -> StreamResponse[GetRateLimitsResponse]: query_params = build_query_param( - server_side=server_side, - android=android, - ios=ios, - web=web, - endpoints=endpoints, + **{ + "server_side": server_side, + "android": android, + "ios": ios, + "web": web, + "endpoints": endpoints, + } ) return await self.get( "/api/v2/rate_limits", GetRateLimitsResponse, query_params=query_params @@ -839,7 +854,7 @@ async def list_roles(self) -> StreamResponse[ListRolesResponse]: @telemetry.operation_name("getstream.api.common.create_role") async def create_role(self, name: str) -> StreamResponse[CreateRoleResponse]: - json = build_body_dict(name=name) + json = CreateRoleRequest(name=name).to_dict() return await self.post("/api/v2/roles", CreateRoleResponse, json=json) @telemetry.operation_name("getstream.api.common.delete_role") @@ -862,7 +877,7 @@ async def get_task(self, id: str) -> StreamResponse[GetTaskResponse]: @telemetry.operation_name("getstream.api.common.delete_file") async def delete_file(self, url: Optional[str] = None) -> StreamResponse[Response]: - query_params = build_query_param(url=url) + query_params = build_query_param(**{"url": url}) return await self.delete( "/api/v2/uploads/file", Response, query_params=query_params ) @@ -871,12 +886,12 @@ async def delete_file(self, url: Optional[str] = None) -> StreamResponse[Respons async def upload_file( self, file: Optional[str] = None, user: Optional[OnlyUserID] = None ) -> StreamResponse[FileUploadResponse]: - json = build_body_dict(file=file, user=user) + json = FileUploadRequest(file=file, user=user).to_dict() return await self.post("/api/v2/uploads/file", FileUploadResponse, json=json) @telemetry.operation_name("getstream.api.common.delete_image") async def delete_image(self, url: Optional[str] = None) -> StreamResponse[Response]: - query_params = build_query_param(url=url) + query_params = build_query_param(**{"url": url}) return await self.delete( "/api/v2/uploads/image", Response, query_params=query_params ) @@ -888,14 +903,159 @@ async def upload_image( upload_sizes: Optional[List[ImageSize]] = None, user: Optional[OnlyUserID] = None, ) -> StreamResponse[ImageUploadResponse]: - json = build_body_dict(file=file, upload_sizes=upload_sizes, user=user) + json = ImageUploadRequest( + file=file, upload_sizes=upload_sizes, user=user + ).to_dict() return await self.post("/api/v2/uploads/image", ImageUploadResponse, json=json) + @telemetry.operation_name("getstream.api.common.list_user_groups") + async def list_user_groups( + self, + limit: Optional[int] = None, + id_gt: Optional[str] = None, + created_at_gt: Optional[str] = None, + team_id: Optional[str] = None, + ) -> StreamResponse[ListUserGroupsResponse]: + query_params = build_query_param( + **{ + "limit": limit, + "id_gt": id_gt, + "created_at_gt": created_at_gt, + "team_id": team_id, + } + ) + return await self.get( + "/api/v2/usergroups", ListUserGroupsResponse, query_params=query_params + ) + + @telemetry.operation_name("getstream.api.common.create_user_group") + async def create_user_group( + self, + name: str, + description: Optional[str] = None, + id: Optional[str] = None, + team_id: Optional[str] = None, + member_ids: Optional[List[str]] = None, + ) -> StreamResponse[CreateUserGroupResponse]: + json = CreateUserGroupRequest( + name=name, + description=description, + id=id, + team_id=team_id, + member_ids=member_ids, + ).to_dict() + return await self.post("/api/v2/usergroups", CreateUserGroupResponse, json=json) + + @telemetry.operation_name("getstream.api.common.search_user_groups") + async def search_user_groups( + self, + query: str, + limit: Optional[int] = None, + name_gt: Optional[str] = None, + id_gt: Optional[str] = None, + team_id: Optional[str] = None, + ) -> StreamResponse[SearchUserGroupsResponse]: + query_params = build_query_param( + **{ + "query": query, + "limit": limit, + "name_gt": name_gt, + "id_gt": id_gt, + "team_id": team_id, + } + ) + return await self.get( + "/api/v2/usergroups/search", + SearchUserGroupsResponse, + query_params=query_params, + ) + + @telemetry.operation_name("getstream.api.common.delete_user_group") + async def delete_user_group( + self, id: str, team_id: Optional[str] = None + ) -> StreamResponse[Response]: + query_params = build_query_param(**{"team_id": team_id}) + path_params = { + "id": id, + } + return await self.delete( + "/api/v2/usergroups/{id}", + Response, + query_params=query_params, + path_params=path_params, + ) + + @telemetry.operation_name("getstream.api.common.get_user_group") + async def get_user_group( + self, id: str, team_id: Optional[str] = None + ) -> StreamResponse[GetUserGroupResponse]: + query_params = build_query_param(**{"team_id": team_id}) + path_params = { + "id": id, + } + return await self.get( + "/api/v2/usergroups/{id}", + GetUserGroupResponse, + query_params=query_params, + path_params=path_params, + ) + + @telemetry.operation_name("getstream.api.common.update_user_group") + async def update_user_group( + self, + id: str, + description: Optional[str] = None, + name: Optional[str] = None, + team_id: Optional[str] = None, + ) -> StreamResponse[UpdateUserGroupResponse]: + path_params = { + "id": id, + } + json = UpdateUserGroupRequest( + description=description, name=name, team_id=team_id + ).to_dict() + return await self.put( + "/api/v2/usergroups/{id}", + UpdateUserGroupResponse, + path_params=path_params, + json=json, + ) + + @telemetry.operation_name("getstream.api.common.remove_user_group_members") + async def remove_user_group_members( + self, id: str + ) -> StreamResponse[RemoveUserGroupMembersResponse]: + path_params = { + "id": id, + } + return await self.delete( + "/api/v2/usergroups/{id}/members", + RemoveUserGroupMembersResponse, + path_params=path_params, + ) + + @telemetry.operation_name("getstream.api.common.add_user_group_members") + async def add_user_group_members( + self, id: str, member_ids: List[str], team_id: Optional[str] = None + ) -> StreamResponse[AddUserGroupMembersResponse]: + path_params = { + "id": id, + } + json = AddUserGroupMembersRequest( + member_ids=member_ids, team_id=team_id + ).to_dict() + return await self.post( + "/api/v2/usergroups/{id}/members", + AddUserGroupMembersResponse, + path_params=path_params, + json=json, + ) + @telemetry.operation_name("getstream.api.common.query_users") async def query_users( self, payload: Optional[QueryUsersPayload] = None ) -> StreamResponse[QueryUsersResponse]: - query_params = build_query_param(payload=payload) + query_params = build_query_param(**{"payload": payload}) return await self.get( "/api/v2/users", QueryUsersResponse, query_params=query_params ) @@ -904,21 +1064,21 @@ async def query_users( async def update_users_partial( self, users: List[UpdateUserPartialRequest] ) -> StreamResponse[UpdateUsersResponse]: - json = build_body_dict(users=users) + json = UpdateUsersPartialRequest(users=users).to_dict() return await self.patch("/api/v2/users", UpdateUsersResponse, json=json) @telemetry.operation_name("getstream.api.common.update_users") async def update_users( self, users: Dict[str, UserRequest] ) -> StreamResponse[UpdateUsersResponse]: - json = build_body_dict(users=users) + json = UpdateUsersRequest(users=users).to_dict() return await self.post("/api/v2/users", UpdateUsersResponse, json=json) @telemetry.operation_name("getstream.api.common.get_blocked_users") async def get_blocked_users( self, user_id: Optional[str] = None ) -> StreamResponse[GetBlockedUsersResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) return await self.get( "/api/v2/users/block", GetBlockedUsersResponse, query_params=query_params ) @@ -930,9 +1090,9 @@ async def block_users( user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[BlockUsersResponse]: - json = build_body_dict( + json = BlockUsersRequest( blocked_user_id=blocked_user_id, user_id=user_id, user=user - ) + ).to_dict() return await self.post("/api/v2/users/block", BlockUsersResponse, json=json) @telemetry.operation_name("getstream.api.common.deactivate_users") @@ -943,12 +1103,12 @@ async def deactivate_users( mark_channels_deleted: Optional[bool] = None, mark_messages_deleted: Optional[bool] = None, ) -> StreamResponse[DeactivateUsersResponse]: - json = build_body_dict( + json = DeactivateUsersRequest( user_ids=user_ids, created_by_id=created_by_id, mark_channels_deleted=mark_channels_deleted, mark_messages_deleted=mark_messages_deleted, - ) + ).to_dict() return await self.post( "/api/v2/users/deactivate", DeactivateUsersResponse, json=json ) @@ -965,7 +1125,7 @@ async def delete_users( new_channel_owner_id: Optional[str] = None, user: Optional[str] = None, ) -> StreamResponse[DeleteUsersResponse]: - json = build_body_dict( + json = DeleteUsersRequest( user_ids=user_ids, calls=calls, conversations=conversations, @@ -974,14 +1134,14 @@ async def delete_users( new_call_owner_id=new_call_owner_id, new_channel_owner_id=new_channel_owner_id, user=user, - ) + ).to_dict() return await self.post("/api/v2/users/delete", DeleteUsersResponse, json=json) @telemetry.operation_name("getstream.api.common.get_user_live_locations") async def get_user_live_locations( self, user_id: Optional[str] = None ) -> StreamResponse[SharedLocationsResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) return await self.get( "/api/v2/users/live_locations", SharedLocationsResponse, @@ -997,10 +1157,10 @@ async def update_live_location( longitude: Optional[float] = None, user_id: Optional[str] = None, ) -> StreamResponse[SharedLocationResponse]: - query_params = build_query_param(user_id=user_id) - json = build_body_dict( + query_params = build_query_param(**{"user_id": user_id}) + json = UpdateLiveLocationRequest( message_id=message_id, end_at=end_at, latitude=latitude, longitude=longitude - ) + ).to_dict() return await self.put( "/api/v2/users/live_locations", SharedLocationResponse, @@ -1016,19 +1176,19 @@ async def reactivate_users( restore_channels: Optional[bool] = None, restore_messages: Optional[bool] = None, ) -> StreamResponse[ReactivateUsersResponse]: - json = build_body_dict( + json = ReactivateUsersRequest( user_ids=user_ids, created_by_id=created_by_id, restore_channels=restore_channels, restore_messages=restore_messages, - ) + ).to_dict() return await self.post( "/api/v2/users/reactivate", ReactivateUsersResponse, json=json ) @telemetry.operation_name("getstream.api.common.restore_users") async def restore_users(self, user_ids: List[str]) -> StreamResponse[Response]: - json = build_body_dict(user_ids=user_ids) + json = RestoreUsersRequest(user_ids=user_ids).to_dict() return await self.post("/api/v2/users/restore", Response, json=json) @telemetry.operation_name("getstream.api.common.unblock_users") @@ -1038,9 +1198,9 @@ async def unblock_users( user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[UnblockUsersResponse]: - json = build_body_dict( + json = UnblockUsersRequest( blocked_user_id=blocked_user_id, user_id=user_id, user=user - ) + ).to_dict() return await self.post("/api/v2/users/unblock", UnblockUsersResponse, json=json) @telemetry.operation_name("getstream.api.common.deactivate_user") @@ -1053,9 +1213,9 @@ async def deactivate_user( path_params = { "user_id": user_id, } - json = build_body_dict( + json = DeactivateUserRequest( created_by_id=created_by_id, mark_messages_deleted=mark_messages_deleted - ) + ).to_dict() return await self.post( "/api/v2/users/{user_id}/deactivate", DeactivateUserResponse, @@ -1085,9 +1245,9 @@ async def reactivate_user( path_params = { "user_id": user_id, } - json = build_body_dict( + json = ReactivateUserRequest( created_by_id=created_by_id, name=name, restore_messages=restore_messages - ) + ).to_dict() return await self.post( "/api/v2/users/{user_id}/reactivate", ReactivateUserResponse, diff --git a/getstream/common/rest_client.py b/getstream/common/rest_client.py index 49c1e524..890fa498 100644 --- a/getstream/common/rest_client.py +++ b/getstream/common/rest_client.py @@ -3,7 +3,7 @@ from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.utils import build_query_param, build_body_dict +from getstream.utils import build_query_param class CommonRestClient(BaseClient): @@ -53,6 +53,7 @@ def update_app( image_moderation_enabled: Optional[bool] = None, max_aggregated_activities_length: Optional[int] = None, migrate_permissions_to_v2: Optional[bool] = None, + moderation_analytics_enabled: Optional[bool] = None, moderation_enabled: Optional[bool] = None, moderation_webhook_url: Optional[str] = None, multi_tenant_enabled: Optional[bool] = None, @@ -88,7 +89,7 @@ def update_app( push_config: Optional[PushConfig] = None, xiaomi_config: Optional[XiaomiConfig] = None, ) -> StreamResponse[Response]: - json = build_body_dict( + json = UpdateAppRequest( async_url_enrich_enabled=async_url_enrich_enabled, auto_translation_enabled=auto_translation_enabled, before_message_send_hook_url=before_message_send_hook_url, @@ -104,6 +105,7 @@ def update_app( image_moderation_enabled=image_moderation_enabled, max_aggregated_activities_length=max_aggregated_activities_length, migrate_permissions_to_v2=migrate_permissions_to_v2, + moderation_analytics_enabled=moderation_analytics_enabled, moderation_enabled=moderation_enabled, moderation_webhook_url=moderation_webhook_url, multi_tenant_enabled=multi_tenant_enabled, @@ -136,14 +138,14 @@ def update_app( moderation_dashboard_preferences=moderation_dashboard_preferences, push_config=push_config, xiaomi_config=xiaomi_config, - ) + ).to_dict() return self.patch("/api/v2/app", Response, json=json) @telemetry.operation_name("getstream.api.common.list_block_lists") def list_block_lists( self, team: Optional[str] = None ) -> StreamResponse[ListBlockListResponse]: - query_params = build_query_param(team=team) + query_params = build_query_param(**{"team": team}) return self.get( "/api/v2/blocklists", ListBlockListResponse, query_params=query_params ) @@ -158,21 +160,21 @@ def create_block_list( team: Optional[str] = None, type: Optional[str] = None, ) -> StreamResponse[CreateBlockListResponse]: - json = build_body_dict( + json = CreateBlockListRequest( name=name, words=words, is_leet_check_enabled=is_leet_check_enabled, is_plural_check_enabled=is_plural_check_enabled, team=team, type=type, - ) + ).to_dict() return self.post("/api/v2/blocklists", CreateBlockListResponse, json=json) @telemetry.operation_name("getstream.api.common.delete_block_list") def delete_block_list( self, name: str, team: Optional[str] = None ) -> StreamResponse[Response]: - query_params = build_query_param(team=team) + query_params = build_query_param(**{"team": team}) path_params = { "name": name, } @@ -187,7 +189,7 @@ def delete_block_list( def get_block_list( self, name: str, team: Optional[str] = None ) -> StreamResponse[GetBlockListResponse]: - query_params = build_query_param(team=team) + query_params = build_query_param(**{"team": team}) path_params = { "name": name, } @@ -210,12 +212,12 @@ def update_block_list( path_params = { "name": name, } - json = build_body_dict( + json = UpdateBlockListRequest( is_leet_check_enabled=is_leet_check_enabled, is_plural_check_enabled=is_plural_check_enabled, team=team, words=words, - ) + ).to_dict() return self.put( "/api/v2/blocklists/{name}", UpdateBlockListResponse, @@ -237,7 +239,7 @@ def check_push( user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[CheckPushResponse]: - json = build_body_dict( + json = CheckPushRequest( apn_template=apn_template, event_type=event_type, firebase_data_template=firebase_data_template, @@ -248,7 +250,7 @@ def check_push( skip_devices=skip_devices, user_id=user_id, user=user, - ) + ).to_dict() return self.post("/api/v2/check_push", CheckPushResponse, json=json) @telemetry.operation_name("getstream.api.common.check_sns") @@ -258,9 +260,9 @@ def check_sns( sns_secret: Optional[str] = None, sns_topic_arn: Optional[str] = None, ) -> StreamResponse[CheckSNSResponse]: - json = build_body_dict( + json = CheckSNSRequest( sns_key=sns_key, sns_secret=sns_secret, sns_topic_arn=sns_topic_arn - ) + ).to_dict() return self.post("/api/v2/check_sns", CheckSNSResponse, json=json) @telemetry.operation_name("getstream.api.common.check_sqs") @@ -270,21 +272,23 @@ def check_sqs( sqs_secret: Optional[str] = None, sqs_url: Optional[str] = None, ) -> StreamResponse[CheckSQSResponse]: - json = build_body_dict(sqs_key=sqs_key, sqs_secret=sqs_secret, sqs_url=sqs_url) + json = CheckSQSRequest( + sqs_key=sqs_key, sqs_secret=sqs_secret, sqs_url=sqs_url + ).to_dict() return self.post("/api/v2/check_sqs", CheckSQSResponse, json=json) @telemetry.operation_name("getstream.api.common.delete_device") def delete_device( self, id: str, user_id: Optional[str] = None ) -> StreamResponse[Response]: - query_params = build_query_param(id=id, user_id=user_id) + query_params = build_query_param(**{"id": id, "user_id": user_id}) return self.delete("/api/v2/devices", Response, query_params=query_params) @telemetry.operation_name("getstream.api.common.list_devices") def list_devices( self, user_id: Optional[str] = None ) -> StreamResponse[ListDevicesResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) return self.get( "/api/v2/devices", ListDevicesResponse, query_params=query_params ) @@ -299,19 +303,19 @@ def create_device( voip_token: Optional[bool] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[Response]: - json = build_body_dict( + json = CreateDeviceRequest( id=id, push_provider=push_provider, push_provider_name=push_provider_name, user_id=user_id, voip_token=voip_token, user=user, - ) + ).to_dict() return self.post("/api/v2/devices", Response, json=json) @telemetry.operation_name("getstream.api.common.export_users") def export_users(self, user_ids: List[str]) -> StreamResponse[ExportUsersResponse]: - json = build_body_dict(user_ids=user_ids) + json = ExportUsersRequest(user_ids=user_ids).to_dict() return self.post("/api/v2/export/users", ExportUsersResponse, json=json) @telemetry.operation_name("getstream.api.common.list_external_storage") @@ -329,7 +333,7 @@ def create_external_storage( aws_s3: Optional[S3Request] = None, azure_blob: Optional[AzureRequest] = None, ) -> StreamResponse[CreateExternalStorageResponse]: - json = build_body_dict( + json = CreateExternalStorageRequest( bucket=bucket, name=name, storage_type=storage_type, @@ -337,7 +341,7 @@ def create_external_storage( path=path, aws_s3=aws_s3, azure_blob=azure_blob, - ) + ).to_dict() return self.post( "/api/v2/external_storage", CreateExternalStorageResponse, json=json ) @@ -369,14 +373,14 @@ def update_external_storage( path_params = { "name": name, } - json = build_body_dict( + json = UpdateExternalStorageRequest( bucket=bucket, storage_type=storage_type, gcs_credentials=gcs_credentials, path=path, aws_s3=aws_s3, azure_blob=azure_blob, - ) + ).to_dict() return self.put( "/api/v2/external_storage/{name}", UpdateExternalStorageResponse, @@ -399,14 +403,14 @@ def check_external_storage( @telemetry.operation_name("getstream.api.common.create_guest") def create_guest(self, user: UserRequest) -> StreamResponse[CreateGuestResponse]: - json = build_body_dict(user=user) + json = CreateGuestRequest(user=user).to_dict() return self.post("/api/v2/guest", CreateGuestResponse, json=json) @telemetry.operation_name("getstream.api.common.create_import_url") def create_import_url( self, filename: Optional[str] = None ) -> StreamResponse[CreateImportURLResponse]: - json = build_body_dict(filename=filename) + json = CreateImportURLRequest(filename=filename).to_dict() return self.post("/api/v2/import_urls", CreateImportURLResponse, json=json) @telemetry.operation_name("getstream.api.common.list_imports") @@ -415,16 +419,18 @@ def list_imports(self) -> StreamResponse[ListImportsResponse]: @telemetry.operation_name("getstream.api.common.create_import") def create_import( - self, mode: str, path: str + self, mode: str, path: str, merge_custom: Optional[bool] = None ) -> StreamResponse[CreateImportResponse]: - json = build_body_dict(mode=mode, path=path) + json = CreateImportRequest( + mode=mode, path=path, merge_custom=merge_custom + ).to_dict() return self.post("/api/v2/imports", CreateImportResponse, json=json) @telemetry.operation_name("getstream.api.common.list_import_v2_tasks") def list_import_v2_tasks( self, state: Optional[int] = None ) -> StreamResponse[ListImportV2TasksResponse]: - query_params = build_query_param(state=state) + query_params = build_query_param(**{"state": state}) return self.get( "/api/v2/imports/v2", ListImportV2TasksResponse, query_params=query_params ) @@ -437,9 +443,9 @@ def create_import_v2_task( user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[CreateImportV2TaskResponse]: - json = build_body_dict( + json = CreateImportV2TaskRequest( product=product, settings=settings, user_id=user_id, user=user - ) + ).to_dict() return self.post("/api/v2/imports/v2", CreateImportV2TaskResponse, json=json) @telemetry.operation_name("getstream.api.common.delete_import_v2_task") @@ -475,7 +481,7 @@ def get_import(self, id: str) -> StreamResponse[GetImportResponse]: @telemetry.operation_name("getstream.api.common.get_og") def get_og(self, url: str) -> StreamResponse[GetOGResponse]: - query_params = build_query_param(url=url) + query_params = build_query_param(**{"url": url}) return self.get("/api/v2/og", GetOGResponse, query_params=query_params) @telemetry.operation_name("getstream.api.common.list_permissions") @@ -510,7 +516,7 @@ def create_poll( custom: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[PollResponse]: - json = build_body_dict( + json = CreatePollRequest( name=name, allow_answers=allow_answers, allow_user_suggested_options=allow_user_suggested_options, @@ -524,7 +530,7 @@ def create_poll( options=options, custom=custom, user=user, - ) + ).to_dict() return self.post("/api/v2/polls", PollResponse, json=json) @telemetry.operation_name("getstream.api.common.update_poll") @@ -544,7 +550,7 @@ def update_poll( custom: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[PollResponse]: - json = build_body_dict( + json = UpdatePollRequest( id=id, name=name, allow_answers=allow_answers, @@ -558,7 +564,7 @@ def update_poll( options=options, custom=custom, user=user, - ) + ).to_dict() return self.put("/api/v2/polls", PollResponse, json=json) @telemetry.operation_name("getstream.api.common.query_polls") @@ -571,10 +577,10 @@ def query_polls( sort: Optional[List[SortParamRequest]] = None, filter: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryPollsResponse]: - query_params = build_query_param(user_id=user_id) - json = build_body_dict( + query_params = build_query_param(**{"user_id": user_id}) + json = QueryPollsRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return self.post( "/api/v2/polls/query", QueryPollsResponse, @@ -586,7 +592,7 @@ def query_polls( def delete_poll( self, poll_id: str, user_id: Optional[str] = None ) -> StreamResponse[Response]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "poll_id": poll_id, } @@ -601,7 +607,7 @@ def delete_poll( def get_poll( self, poll_id: str, user_id: Optional[str] = None ) -> StreamResponse[PollResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "poll_id": poll_id, } @@ -624,7 +630,9 @@ def update_poll_partial( path_params = { "poll_id": poll_id, } - json = build_body_dict(user_id=user_id, unset=unset, set=set, user=user) + json = UpdatePollPartialRequest( + user_id=user_id, unset=unset, set=set, user=user + ).to_dict() return self.patch( "/api/v2/polls/{poll_id}", PollResponse, path_params=path_params, json=json ) @@ -641,7 +649,9 @@ def create_poll_option( path_params = { "poll_id": poll_id, } - json = build_body_dict(text=text, user_id=user_id, custom=custom, user=user) + json = CreatePollOptionRequest( + text=text, user_id=user_id, custom=custom, user=user + ).to_dict() return self.post( "/api/v2/polls/{poll_id}/options", PollOptionResponse, @@ -662,9 +672,9 @@ def update_poll_option( path_params = { "poll_id": poll_id, } - json = build_body_dict( + json = UpdatePollOptionRequest( id=id, text=text, user_id=user_id, custom=custom, user=user - ) + ).to_dict() return self.put( "/api/v2/polls/{poll_id}/options", PollOptionResponse, @@ -676,7 +686,7 @@ def update_poll_option( def delete_poll_option( self, poll_id: str, option_id: str, user_id: Optional[str] = None ) -> StreamResponse[Response]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "poll_id": poll_id, "option_id": option_id, @@ -692,7 +702,7 @@ def delete_poll_option( def get_poll_option( self, poll_id: str, option_id: str, user_id: Optional[str] = None ) -> StreamResponse[PollOptionResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "poll_id": poll_id, "option_id": option_id, @@ -715,13 +725,13 @@ def query_poll_votes( sort: Optional[List[SortParamRequest]] = None, filter: Optional[Dict[str, object]] = None, ) -> StreamResponse[PollVotesResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "poll_id": poll_id, } - json = build_body_dict( + json = QueryPollVotesRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return self.post( "/api/v2/polls/{poll_id}/votes", PollVotesResponse, @@ -736,7 +746,7 @@ def query_poll_votes( def update_push_notification_preferences( self, preferences: List[PushPreferenceInput] ) -> StreamResponse[UpsertPushPreferencesResponse]: - json = build_body_dict(preferences=preferences) + json = UpsertPushPreferencesRequest(preferences=preferences).to_dict() return self.post( "/api/v2/push_preferences", UpsertPushPreferencesResponse, json=json ) @@ -747,9 +757,9 @@ def list_push_providers(self) -> StreamResponse[ListPushProvidersResponse]: @telemetry.operation_name("getstream.api.common.upsert_push_provider") def upsert_push_provider( - self, push_provider: Optional[PushProvider] = None + self, push_provider: Optional[PushProviderRequest] = None ) -> StreamResponse[UpsertPushProviderResponse]: - json = build_body_dict(push_provider=push_provider) + json = UpsertPushProviderRequest(push_provider=push_provider).to_dict() return self.post( "/api/v2/push_providers", UpsertPushProviderResponse, json=json ) @@ -769,7 +779,10 @@ def get_push_templates( self, push_provider_type: str, push_provider_name: Optional[str] = None ) -> StreamResponse[GetPushTemplatesResponse]: query_params = build_query_param( - push_provider_type=push_provider_type, push_provider_name=push_provider_name + **{ + "push_provider_type": push_provider_type, + "push_provider_name": push_provider_name, + } ) return self.get( "/api/v2/push_templates", @@ -786,13 +799,13 @@ def upsert_push_template( push_provider_name: Optional[str] = None, template: Optional[str] = None, ) -> StreamResponse[UpsertPushTemplateResponse]: - json = build_body_dict( + json = UpsertPushTemplateRequest( event_type=event_type, push_provider_type=push_provider_type, enable_push=enable_push, push_provider_name=push_provider_name, template=template, - ) + ).to_dict() return self.post( "/api/v2/push_templates", UpsertPushTemplateResponse, json=json ) @@ -807,11 +820,13 @@ def get_rate_limits( endpoints: Optional[str] = None, ) -> StreamResponse[GetRateLimitsResponse]: query_params = build_query_param( - server_side=server_side, - android=android, - ios=ios, - web=web, - endpoints=endpoints, + **{ + "server_side": server_side, + "android": android, + "ios": ios, + "web": web, + "endpoints": endpoints, + } ) return self.get( "/api/v2/rate_limits", GetRateLimitsResponse, query_params=query_params @@ -823,7 +838,7 @@ def list_roles(self) -> StreamResponse[ListRolesResponse]: @telemetry.operation_name("getstream.api.common.create_role") def create_role(self, name: str) -> StreamResponse[CreateRoleResponse]: - json = build_body_dict(name=name) + json = CreateRoleRequest(name=name).to_dict() return self.post("/api/v2/roles", CreateRoleResponse, json=json) @telemetry.operation_name("getstream.api.common.delete_role") @@ -842,19 +857,19 @@ def get_task(self, id: str) -> StreamResponse[GetTaskResponse]: @telemetry.operation_name("getstream.api.common.delete_file") def delete_file(self, url: Optional[str] = None) -> StreamResponse[Response]: - query_params = build_query_param(url=url) + query_params = build_query_param(**{"url": url}) return self.delete("/api/v2/uploads/file", Response, query_params=query_params) @telemetry.operation_name("getstream.api.common.upload_file") def upload_file( self, file: Optional[str] = None, user: Optional[OnlyUserID] = None ) -> StreamResponse[FileUploadResponse]: - json = build_body_dict(file=file, user=user) + json = FileUploadRequest(file=file, user=user).to_dict() return self.post("/api/v2/uploads/file", FileUploadResponse, json=json) @telemetry.operation_name("getstream.api.common.delete_image") def delete_image(self, url: Optional[str] = None) -> StreamResponse[Response]: - query_params = build_query_param(url=url) + query_params = build_query_param(**{"url": url}) return self.delete("/api/v2/uploads/image", Response, query_params=query_params) @telemetry.operation_name("getstream.api.common.upload_image") @@ -864,35 +879,180 @@ def upload_image( upload_sizes: Optional[List[ImageSize]] = None, user: Optional[OnlyUserID] = None, ) -> StreamResponse[ImageUploadResponse]: - json = build_body_dict(file=file, upload_sizes=upload_sizes, user=user) + json = ImageUploadRequest( + file=file, upload_sizes=upload_sizes, user=user + ).to_dict() return self.post("/api/v2/uploads/image", ImageUploadResponse, json=json) + @telemetry.operation_name("getstream.api.common.list_user_groups") + def list_user_groups( + self, + limit: Optional[int] = None, + id_gt: Optional[str] = None, + created_at_gt: Optional[str] = None, + team_id: Optional[str] = None, + ) -> StreamResponse[ListUserGroupsResponse]: + query_params = build_query_param( + **{ + "limit": limit, + "id_gt": id_gt, + "created_at_gt": created_at_gt, + "team_id": team_id, + } + ) + return self.get( + "/api/v2/usergroups", ListUserGroupsResponse, query_params=query_params + ) + + @telemetry.operation_name("getstream.api.common.create_user_group") + def create_user_group( + self, + name: str, + description: Optional[str] = None, + id: Optional[str] = None, + team_id: Optional[str] = None, + member_ids: Optional[List[str]] = None, + ) -> StreamResponse[CreateUserGroupResponse]: + json = CreateUserGroupRequest( + name=name, + description=description, + id=id, + team_id=team_id, + member_ids=member_ids, + ).to_dict() + return self.post("/api/v2/usergroups", CreateUserGroupResponse, json=json) + + @telemetry.operation_name("getstream.api.common.search_user_groups") + def search_user_groups( + self, + query: str, + limit: Optional[int] = None, + name_gt: Optional[str] = None, + id_gt: Optional[str] = None, + team_id: Optional[str] = None, + ) -> StreamResponse[SearchUserGroupsResponse]: + query_params = build_query_param( + **{ + "query": query, + "limit": limit, + "name_gt": name_gt, + "id_gt": id_gt, + "team_id": team_id, + } + ) + return self.get( + "/api/v2/usergroups/search", + SearchUserGroupsResponse, + query_params=query_params, + ) + + @telemetry.operation_name("getstream.api.common.delete_user_group") + def delete_user_group( + self, id: str, team_id: Optional[str] = None + ) -> StreamResponse[Response]: + query_params = build_query_param(**{"team_id": team_id}) + path_params = { + "id": id, + } + return self.delete( + "/api/v2/usergroups/{id}", + Response, + query_params=query_params, + path_params=path_params, + ) + + @telemetry.operation_name("getstream.api.common.get_user_group") + def get_user_group( + self, id: str, team_id: Optional[str] = None + ) -> StreamResponse[GetUserGroupResponse]: + query_params = build_query_param(**{"team_id": team_id}) + path_params = { + "id": id, + } + return self.get( + "/api/v2/usergroups/{id}", + GetUserGroupResponse, + query_params=query_params, + path_params=path_params, + ) + + @telemetry.operation_name("getstream.api.common.update_user_group") + def update_user_group( + self, + id: str, + description: Optional[str] = None, + name: Optional[str] = None, + team_id: Optional[str] = None, + ) -> StreamResponse[UpdateUserGroupResponse]: + path_params = { + "id": id, + } + json = UpdateUserGroupRequest( + description=description, name=name, team_id=team_id + ).to_dict() + return self.put( + "/api/v2/usergroups/{id}", + UpdateUserGroupResponse, + path_params=path_params, + json=json, + ) + + @telemetry.operation_name("getstream.api.common.remove_user_group_members") + def remove_user_group_members( + self, id: str + ) -> StreamResponse[RemoveUserGroupMembersResponse]: + path_params = { + "id": id, + } + return self.delete( + "/api/v2/usergroups/{id}/members", + RemoveUserGroupMembersResponse, + path_params=path_params, + ) + + @telemetry.operation_name("getstream.api.common.add_user_group_members") + def add_user_group_members( + self, id: str, member_ids: List[str], team_id: Optional[str] = None + ) -> StreamResponse[AddUserGroupMembersResponse]: + path_params = { + "id": id, + } + json = AddUserGroupMembersRequest( + member_ids=member_ids, team_id=team_id + ).to_dict() + return self.post( + "/api/v2/usergroups/{id}/members", + AddUserGroupMembersResponse, + path_params=path_params, + json=json, + ) + @telemetry.operation_name("getstream.api.common.query_users") def query_users( self, payload: Optional[QueryUsersPayload] = None ) -> StreamResponse[QueryUsersResponse]: - query_params = build_query_param(payload=payload) + query_params = build_query_param(**{"payload": payload}) return self.get("/api/v2/users", QueryUsersResponse, query_params=query_params) @telemetry.operation_name("getstream.api.common.update_users_partial") def update_users_partial( self, users: List[UpdateUserPartialRequest] ) -> StreamResponse[UpdateUsersResponse]: - json = build_body_dict(users=users) + json = UpdateUsersPartialRequest(users=users).to_dict() return self.patch("/api/v2/users", UpdateUsersResponse, json=json) @telemetry.operation_name("getstream.api.common.update_users") def update_users( self, users: Dict[str, UserRequest] ) -> StreamResponse[UpdateUsersResponse]: - json = build_body_dict(users=users) + json = UpdateUsersRequest(users=users).to_dict() return self.post("/api/v2/users", UpdateUsersResponse, json=json) @telemetry.operation_name("getstream.api.common.get_blocked_users") def get_blocked_users( self, user_id: Optional[str] = None ) -> StreamResponse[GetBlockedUsersResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) return self.get( "/api/v2/users/block", GetBlockedUsersResponse, query_params=query_params ) @@ -904,9 +1064,9 @@ def block_users( user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[BlockUsersResponse]: - json = build_body_dict( + json = BlockUsersRequest( blocked_user_id=blocked_user_id, user_id=user_id, user=user - ) + ).to_dict() return self.post("/api/v2/users/block", BlockUsersResponse, json=json) @telemetry.operation_name("getstream.api.common.deactivate_users") @@ -917,12 +1077,12 @@ def deactivate_users( mark_channels_deleted: Optional[bool] = None, mark_messages_deleted: Optional[bool] = None, ) -> StreamResponse[DeactivateUsersResponse]: - json = build_body_dict( + json = DeactivateUsersRequest( user_ids=user_ids, created_by_id=created_by_id, mark_channels_deleted=mark_channels_deleted, mark_messages_deleted=mark_messages_deleted, - ) + ).to_dict() return self.post("/api/v2/users/deactivate", DeactivateUsersResponse, json=json) @telemetry.operation_name("getstream.api.common.delete_users") @@ -937,7 +1097,7 @@ def delete_users( new_channel_owner_id: Optional[str] = None, user: Optional[str] = None, ) -> StreamResponse[DeleteUsersResponse]: - json = build_body_dict( + json = DeleteUsersRequest( user_ids=user_ids, calls=calls, conversations=conversations, @@ -946,14 +1106,14 @@ def delete_users( new_call_owner_id=new_call_owner_id, new_channel_owner_id=new_channel_owner_id, user=user, - ) + ).to_dict() return self.post("/api/v2/users/delete", DeleteUsersResponse, json=json) @telemetry.operation_name("getstream.api.common.get_user_live_locations") def get_user_live_locations( self, user_id: Optional[str] = None ) -> StreamResponse[SharedLocationsResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) return self.get( "/api/v2/users/live_locations", SharedLocationsResponse, @@ -969,10 +1129,10 @@ def update_live_location( longitude: Optional[float] = None, user_id: Optional[str] = None, ) -> StreamResponse[SharedLocationResponse]: - query_params = build_query_param(user_id=user_id) - json = build_body_dict( + query_params = build_query_param(**{"user_id": user_id}) + json = UpdateLiveLocationRequest( message_id=message_id, end_at=end_at, latitude=latitude, longitude=longitude - ) + ).to_dict() return self.put( "/api/v2/users/live_locations", SharedLocationResponse, @@ -988,17 +1148,17 @@ def reactivate_users( restore_channels: Optional[bool] = None, restore_messages: Optional[bool] = None, ) -> StreamResponse[ReactivateUsersResponse]: - json = build_body_dict( + json = ReactivateUsersRequest( user_ids=user_ids, created_by_id=created_by_id, restore_channels=restore_channels, restore_messages=restore_messages, - ) + ).to_dict() return self.post("/api/v2/users/reactivate", ReactivateUsersResponse, json=json) @telemetry.operation_name("getstream.api.common.restore_users") def restore_users(self, user_ids: List[str]) -> StreamResponse[Response]: - json = build_body_dict(user_ids=user_ids) + json = RestoreUsersRequest(user_ids=user_ids).to_dict() return self.post("/api/v2/users/restore", Response, json=json) @telemetry.operation_name("getstream.api.common.unblock_users") @@ -1008,9 +1168,9 @@ def unblock_users( user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[UnblockUsersResponse]: - json = build_body_dict( + json = UnblockUsersRequest( blocked_user_id=blocked_user_id, user_id=user_id, user=user - ) + ).to_dict() return self.post("/api/v2/users/unblock", UnblockUsersResponse, json=json) @telemetry.operation_name("getstream.api.common.deactivate_user") @@ -1023,9 +1183,9 @@ def deactivate_user( path_params = { "user_id": user_id, } - json = build_body_dict( + json = DeactivateUserRequest( created_by_id=created_by_id, mark_messages_deleted=mark_messages_deleted - ) + ).to_dict() return self.post( "/api/v2/users/{user_id}/deactivate", DeactivateUserResponse, @@ -1055,9 +1215,9 @@ def reactivate_user( path_params = { "user_id": user_id, } - json = build_body_dict( + json = ReactivateUserRequest( created_by_id=created_by_id, name=name, restore_messages=restore_messages - ) + ).to_dict() return self.post( "/api/v2/users/{user_id}/reactivate", ReactivateUserResponse, diff --git a/getstream/feeds/feeds.py b/getstream/feeds/feeds.py index d0c8c7a0..bd7e2f5b 100644 --- a/getstream/feeds/feeds.py +++ b/getstream/feeds/feeds.py @@ -38,6 +38,7 @@ def get_or_create( filter: Optional[Dict[str, object]] = None, followers_pagination: Optional[PagerRequest] = None, following_pagination: Optional[PagerRequest] = None, + friend_reactions_options: Optional[FriendReactionsOptions] = None, interest_weights: Optional[Dict[str, float]] = None, member_pagination: Optional[PagerRequest] = None, user: Optional[UserRequest] = None, @@ -58,6 +59,7 @@ def get_or_create( filter=filter, followers_pagination=followers_pagination, following_pagination=following_pagination, + friend_reactions_options=friend_reactions_options, interest_weights=interest_weights, member_pagination=member_pagination, user=user, @@ -195,6 +197,26 @@ def reject_feed_member_invite( self._sync_from_response(response.data) return response + def query_pinned_activities( + self, + limit: Optional[int] = None, + next: Optional[str] = None, + prev: Optional[str] = None, + sort: Optional[List[SortParamRequest]] = None, + filter: Optional[Dict[str, object]] = None, + ) -> StreamResponse[QueryPinnedActivitiesResponse]: + response = self.client.query_pinned_activities( + feed_group_id=self.feed_group, + feed_id=self.id, + limit=limit, + next=next, + prev=prev, + sort=sort, + filter=filter, + ) + self._sync_from_response(response.data) + return response + def get_feed_identifier(self): return self.feed_group + ":" + self.id diff --git a/getstream/feeds/rest_client.py b/getstream/feeds/rest_client.py index 9b43414b..e41fe017 100644 --- a/getstream/feeds/rest_client.py +++ b/getstream/feeds/rest_client.py @@ -3,7 +3,7 @@ from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.utils import build_query_param, build_body_dict +from getstream.utils import build_query_param class FeedsRestClient(BaseClient): @@ -36,12 +36,15 @@ def add_activity( self, type: str, feeds: List[str], + copy_custom_to_notification: Optional[bool] = None, + create_notification_activity: Optional[bool] = None, expires_at: Optional[str] = None, id: Optional[str] = None, parent_id: Optional[str] = None, poll_id: Optional[str] = None, restrict_replies: Optional[str] = None, skip_enrich_url: Optional[bool] = None, + skip_push: Optional[bool] = None, text: Optional[str] = None, user_id: Optional[str] = None, visibility: Optional[str] = None, @@ -55,15 +58,18 @@ def add_activity( location: Optional[ActivityLocation] = None, search_data: Optional[Dict[str, object]] = None, ) -> StreamResponse[AddActivityResponse]: - json = build_body_dict( + json = AddActivityRequest( type=type, feeds=feeds, + copy_custom_to_notification=copy_custom_to_notification, + create_notification_activity=create_notification_activity, expires_at=expires_at, id=id, parent_id=parent_id, poll_id=poll_id, restrict_replies=restrict_replies, skip_enrich_url=skip_enrich_url, + skip_push=skip_push, text=text, user_id=user_id, visibility=visibility, @@ -76,29 +82,45 @@ def add_activity( custom=custom, location=location, search_data=search_data, - ) + ).to_dict() return self.post("/api/v2/feeds/activities", AddActivityResponse, json=json) @telemetry.operation_name("getstream.api.feeds.upsert_activities") def upsert_activities( self, activities: List[ActivityRequest] ) -> StreamResponse[UpsertActivitiesResponse]: - json = build_body_dict(activities=activities) + json = UpsertActivitiesRequest(activities=activities).to_dict() return self.post( "/api/v2/feeds/activities/batch", UpsertActivitiesResponse, json=json ) + @telemetry.operation_name("getstream.api.feeds.update_activities_partial_batch") + def update_activities_partial_batch( + self, changes: List[UpdateActivityPartialChangeRequest] + ) -> StreamResponse[UpdateActivitiesPartialBatchResponse]: + json = UpdateActivitiesPartialBatchRequest(changes=changes).to_dict() + return self.patch( + "/api/v2/feeds/activities/batch/partial", + UpdateActivitiesPartialBatchResponse, + json=json, + ) + @telemetry.operation_name("getstream.api.feeds.delete_activities") def delete_activities( self, ids: List[str], + delete_notification_activity: Optional[bool] = None, hard_delete: Optional[bool] = None, user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[DeleteActivitiesResponse]: - json = build_body_dict( - ids=ids, hard_delete=hard_delete, user_id=user_id, user=user - ) + json = DeleteActivitiesRequest( + ids=ids, + delete_notification_activity=delete_notification_activity, + hard_delete=hard_delete, + user_id=user_id, + user=user, + ).to_dict() return self.post( "/api/v2/feeds/activities/delete", DeleteActivitiesResponse, json=json ) @@ -106,6 +128,7 @@ def delete_activities( @telemetry.operation_name("getstream.api.feeds.query_activities") def query_activities( self, + include_expired_activities: Optional[bool] = None, include_private_activities: Optional[bool] = None, limit: Optional[int] = None, next: Optional[str] = None, @@ -115,7 +138,8 @@ def query_activities( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryActivitiesResponse]: - json = build_body_dict( + json = QueryActivitiesRequest( + include_expired_activities=include_expired_activities, include_private_activities=include_private_activities, limit=limit, next=next, @@ -124,7 +148,7 @@ def query_activities( sort=sort, filter=filter, user=user, - ) + ).to_dict() return self.post( "/api/v2/feeds/activities/query", QueryActivitiesResponse, json=json ) @@ -136,7 +160,7 @@ def delete_bookmark( folder_id: Optional[str] = None, user_id: Optional[str] = None, ) -> StreamResponse[DeleteBookmarkResponse]: - query_params = build_query_param(folder_id=folder_id, user_id=user_id) + query_params = build_query_param(**{"folder_id": folder_id, "user_id": user_id}) path_params = { "activity_id": activity_id, } @@ -161,14 +185,14 @@ def update_bookmark( path_params = { "activity_id": activity_id, } - json = build_body_dict( + json = UpdateBookmarkRequest( folder_id=folder_id, new_folder_id=new_folder_id, user_id=user_id, custom=custom, new_folder=new_folder, user=user, - ) + ).to_dict() return self.patch( "/api/v2/feeds/activities/{activity_id}/bookmarks", UpdateBookmarkResponse, @@ -189,13 +213,13 @@ def add_bookmark( path_params = { "activity_id": activity_id, } - json = build_body_dict( + json = AddBookmarkRequest( folder_id=folder_id, user_id=user_id, custom=custom, new_folder=new_folder, user=user, - ) + ).to_dict() return self.post( "/api/v2/feeds/activities/{activity_id}/bookmarks", AddBookmarkResponse, @@ -216,13 +240,13 @@ def activity_feedback( path_params = { "activity_id": activity_id, } - json = build_body_dict( + json = ActivityFeedbackRequest( hide=hide, show_less=show_less, show_more=show_more, user_id=user_id, user=user, - ) + ).to_dict() return self.post( "/api/v2/feeds/activities/{activity_id}/feedback", ActivityFeedbackResponse, @@ -243,7 +267,7 @@ def cast_poll_vote( "activity_id": activity_id, "poll_id": poll_id, } - json = build_body_dict(user_id=user_id, user=user, vote=vote) + json = CastPollVoteRequest(user_id=user_id, user=user, vote=vote).to_dict() return self.post( "/api/v2/feeds/activities/{activity_id}/polls/{poll_id}/vote", PollVoteResponse, @@ -259,7 +283,7 @@ def delete_poll_vote( vote_id: str, user_id: Optional[str] = None, ) -> StreamResponse[PollVoteResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "activity_id": activity_id, "poll_id": poll_id, @@ -277,6 +301,7 @@ def add_activity_reaction( self, activity_id: str, type: str, + copy_custom_to_notification: Optional[bool] = None, create_notification_activity: Optional[bool] = None, enforce_unique: Optional[bool] = None, skip_push: Optional[bool] = None, @@ -287,15 +312,16 @@ def add_activity_reaction( path_params = { "activity_id": activity_id, } - json = build_body_dict( + json = AddReactionRequest( type=type, + copy_custom_to_notification=copy_custom_to_notification, create_notification_activity=create_notification_activity, enforce_unique=enforce_unique, skip_push=skip_push, user_id=user_id, custom=custom, user=user, - ) + ).to_dict() return self.post( "/api/v2/feeds/activities/{activity_id}/reactions", AddReactionResponse, @@ -316,9 +342,9 @@ def query_activity_reactions( path_params = { "activity_id": activity_id, } - json = build_body_dict( + json = QueryActivityReactionsRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return self.post( "/api/v2/feeds/activities/{activity_id}/reactions/query", QueryActivityReactionsResponse, @@ -328,9 +354,18 @@ def query_activity_reactions( @telemetry.operation_name("getstream.api.feeds.delete_activity_reaction") def delete_activity_reaction( - self, activity_id: str, type: str, user_id: Optional[str] = None + self, + activity_id: str, + type: str, + delete_notification_activity: Optional[bool] = None, + user_id: Optional[str] = None, ) -> StreamResponse[DeleteActivityReactionResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param( + **{ + "delete_notification_activity": delete_notification_activity, + "user_id": user_id, + } + ) path_params = { "activity_id": activity_id, "type": type, @@ -344,9 +379,17 @@ def delete_activity_reaction( @telemetry.operation_name("getstream.api.feeds.delete_activity") def delete_activity( - self, id: str, hard_delete: Optional[bool] = None + self, + id: str, + hard_delete: Optional[bool] = None, + delete_notification_activity: Optional[bool] = None, ) -> StreamResponse[DeleteActivityResponse]: - query_params = build_query_param(hard_delete=hard_delete) + query_params = build_query_param( + **{ + "hard_delete": hard_delete, + "delete_notification_activity": delete_notification_activity, + } + ) path_params = { "id": id, } @@ -372,6 +415,9 @@ def get_activity(self, id: str) -> StreamResponse[GetActivityResponse]: def update_activity_partial( self, id: str, + copy_custom_to_notification: Optional[bool] = None, + handle_mention_notifications: Optional[bool] = None, + run_activity_processors: Optional[bool] = None, user_id: Optional[str] = None, unset: Optional[List[str]] = None, set: Optional[Dict[str, object]] = None, @@ -380,7 +426,15 @@ def update_activity_partial( path_params = { "id": id, } - json = build_body_dict(user_id=user_id, unset=unset, set=set, user=user) + json = UpdateActivityPartialRequest( + copy_custom_to_notification=copy_custom_to_notification, + handle_mention_notifications=handle_mention_notifications, + run_activity_processors=run_activity_processors, + user_id=user_id, + unset=unset, + set=set, + user=user, + ).to_dict() return self.patch( "/api/v2/feeds/activities/{id}", UpdateActivityPartialResponse, @@ -392,13 +446,17 @@ def update_activity_partial( def update_activity( self, id: str, + copy_custom_to_notification: Optional[bool] = None, expires_at: Optional[datetime] = None, + handle_mention_notifications: Optional[bool] = None, poll_id: Optional[str] = None, restrict_replies: Optional[str] = None, + run_activity_processors: Optional[bool] = None, skip_enrich_url: Optional[bool] = None, text: Optional[str] = None, user_id: Optional[str] = None, visibility: Optional[str] = None, + visibility_tag: Optional[str] = None, attachments: Optional[List[Attachment]] = None, collection_refs: Optional[List[str]] = None, feeds: Optional[List[str]] = None, @@ -407,19 +465,24 @@ def update_activity( mentioned_user_ids: Optional[List[str]] = None, custom: Optional[Dict[str, object]] = None, location: Optional[ActivityLocation] = None, + search_data: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[UpdateActivityResponse]: path_params = { "id": id, } - json = build_body_dict( + json = UpdateActivityRequest( + copy_custom_to_notification=copy_custom_to_notification, expires_at=expires_at, + handle_mention_notifications=handle_mention_notifications, poll_id=poll_id, restrict_replies=restrict_replies, + run_activity_processors=run_activity_processors, skip_enrich_url=skip_enrich_url, text=text, user_id=user_id, visibility=visibility, + visibility_tag=visibility_tag, attachments=attachments, collection_refs=collection_refs, feeds=feeds, @@ -428,8 +491,9 @@ def update_activity( mentioned_user_ids=mentioned_user_ids, custom=custom, location=location, + search_data=search_data, user=user, - ) + ).to_dict() return self.put( "/api/v2/feeds/activities/{id}", UpdateActivityResponse, @@ -437,6 +501,21 @@ def update_activity( json=json, ) + @telemetry.operation_name("getstream.api.feeds.restore_activity") + def restore_activity( + self, id: str, user_id: Optional[str] = None, user: Optional[UserRequest] = None + ) -> StreamResponse[RestoreActivityResponse]: + path_params = { + "id": id, + } + json = RestoreActivityRequest(user_id=user_id, user=user).to_dict() + return self.post( + "/api/v2/feeds/activities/{id}/restore", + RestoreActivityResponse, + path_params=path_params, + json=json, + ) + @telemetry.operation_name("getstream.api.feeds.query_bookmark_folders") def query_bookmark_folders( self, @@ -446,9 +525,9 @@ def query_bookmark_folders( sort: Optional[List[SortParamRequest]] = None, filter: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryBookmarkFoldersResponse]: - json = build_body_dict( + json = QueryBookmarkFoldersRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return self.post( "/api/v2/feeds/bookmark_folders/query", QueryBookmarkFoldersResponse, @@ -480,7 +559,9 @@ def update_bookmark_folder( path_params = { "folder_id": folder_id, } - json = build_body_dict(name=name, user_id=user_id, custom=custom, user=user) + json = UpdateBookmarkFolderRequest( + name=name, user_id=user_id, custom=custom, user=user + ).to_dict() return self.patch( "/api/v2/feeds/bookmark_folders/{folder_id}", UpdateBookmarkFolderResponse, @@ -497,9 +578,9 @@ def query_bookmarks( sort: Optional[List[SortParamRequest]] = None, filter: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryBookmarksResponse]: - json = build_body_dict( + json = QueryBookmarksRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return self.post( "/api/v2/feeds/bookmarks/query", QueryBookmarksResponse, json=json ) @@ -508,7 +589,7 @@ def query_bookmarks( def delete_collections( self, collection_refs: List[str] ) -> StreamResponse[DeleteCollectionsResponse]: - query_params = build_query_param(collection_refs=collection_refs) + query_params = build_query_param(**{"collection_refs": collection_refs}) return self.delete( "/api/v2/feeds/collections", DeleteCollectionsResponse, @@ -517,10 +598,10 @@ def delete_collections( @telemetry.operation_name("getstream.api.feeds.read_collections") def read_collections( - self, collection_refs: List[str], user_id: Optional[str] = None + self, user_id: Optional[str] = None, collection_refs: Optional[List[str]] = None ) -> StreamResponse[ReadCollectionsResponse]: query_params = build_query_param( - collection_refs=collection_refs, user_id=user_id + **{"user_id": user_id, "collection_refs": collection_refs} ) return self.get( "/api/v2/feeds/collections", @@ -535,7 +616,9 @@ def update_collections( user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[UpdateCollectionsResponse]: - json = build_body_dict(collections=collections, user_id=user_id, user=user) + json = UpdateCollectionsRequest( + collections=collections, user_id=user_id, user=user + ).to_dict() return self.patch( "/api/v2/feeds/collections", UpdateCollectionsResponse, json=json ) @@ -547,7 +630,9 @@ def create_collections( user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[CreateCollectionsResponse]: - json = build_body_dict(collections=collections, user_id=user_id, user=user) + json = CreateCollectionsRequest( + collections=collections, user_id=user_id, user=user + ).to_dict() return self.post( "/api/v2/feeds/collections", CreateCollectionsResponse, json=json ) @@ -556,7 +641,7 @@ def create_collections( def upsert_collections( self, collections: List[CollectionRequest] ) -> StreamResponse[UpsertCollectionsResponse]: - json = build_body_dict(collections=collections) + json = UpsertCollectionsRequest(collections=collections).to_dict() return self.put( "/api/v2/feeds/collections", UpsertCollectionsResponse, json=json ) @@ -569,19 +654,23 @@ def get_comments( depth: Optional[int] = None, sort: Optional[str] = None, replies_limit: Optional[int] = None, + user_id: Optional[str] = None, limit: Optional[int] = None, prev: Optional[str] = None, next: Optional[str] = None, ) -> StreamResponse[GetCommentsResponse]: query_params = build_query_param( - object_id=object_id, - object_type=object_type, - depth=depth, - sort=sort, - replies_limit=replies_limit, - limit=limit, - prev=prev, - next=next, + **{ + "object_id": object_id, + "object_type": object_type, + "depth": depth, + "sort": sort, + "replies_limit": replies_limit, + "user_id": user_id, + "limit": limit, + "prev": prev, + "next": next, + } ) return self.get( "/api/v2/feeds/comments", GetCommentsResponse, query_params=query_params @@ -591,6 +680,7 @@ def get_comments( def add_comment( self, comment: Optional[str] = None, + copy_custom_to_notification: Optional[bool] = None, create_notification_activity: Optional[bool] = None, id: Optional[str] = None, object_id: Optional[str] = None, @@ -604,8 +694,9 @@ def add_comment( custom: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[AddCommentResponse]: - json = build_body_dict( + json = AddCommentRequest( comment=comment, + copy_custom_to_notification=copy_custom_to_notification, create_notification_activity=create_notification_activity, id=id, object_id=object_id, @@ -618,14 +709,14 @@ def add_comment( mentioned_user_ids=mentioned_user_ids, custom=custom, user=user, - ) + ).to_dict() return self.post("/api/v2/feeds/comments", AddCommentResponse, json=json) @telemetry.operation_name("getstream.api.feeds.add_comments_batch") def add_comments_batch( self, comments: List[AddCommentRequest] ) -> StreamResponse[AddCommentsBatchResponse]: - json = build_body_dict(comments=comments) + json = AddCommentsBatchRequest(comments=comments).to_dict() return self.post( "/api/v2/feeds/comments/batch", AddCommentsBatchResponse, json=json ) @@ -634,23 +725,37 @@ def add_comments_batch( def query_comments( self, filter: Dict[str, object], + id_around: Optional[str] = None, limit: Optional[int] = None, next: Optional[str] = None, prev: Optional[str] = None, sort: Optional[str] = None, ) -> StreamResponse[QueryCommentsResponse]: - json = build_body_dict( - filter=filter, limit=limit, next=next, prev=prev, sort=sort - ) + json = QueryCommentsRequest( + filter=filter, + id_around=id_around, + limit=limit, + next=next, + prev=prev, + sort=sort, + ).to_dict() return self.post( "/api/v2/feeds/comments/query", QueryCommentsResponse, json=json ) @telemetry.operation_name("getstream.api.feeds.delete_comment") def delete_comment( - self, id: str, hard_delete: Optional[bool] = None + self, + id: str, + hard_delete: Optional[bool] = None, + delete_notification_activity: Optional[bool] = None, ) -> StreamResponse[DeleteCommentResponse]: - query_params = build_query_param(hard_delete=hard_delete) + query_params = build_query_param( + **{ + "hard_delete": hard_delete, + "delete_notification_activity": delete_notification_activity, + } + ) path_params = { "id": id, } @@ -675,25 +780,31 @@ def update_comment( self, id: str, comment: Optional[str] = None, + copy_custom_to_notification: Optional[bool] = None, + handle_mention_notifications: Optional[bool] = None, skip_enrich_url: Optional[bool] = None, skip_push: Optional[bool] = None, user_id: Optional[str] = None, attachments: Optional[List[Attachment]] = None, + mentioned_user_ids: Optional[List[str]] = None, custom: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[UpdateCommentResponse]: path_params = { "id": id, } - json = build_body_dict( + json = UpdateCommentRequest( comment=comment, + copy_custom_to_notification=copy_custom_to_notification, + handle_mention_notifications=handle_mention_notifications, skip_enrich_url=skip_enrich_url, skip_push=skip_push, user_id=user_id, attachments=attachments, + mentioned_user_ids=mentioned_user_ids, custom=custom, user=user, - ) + ).to_dict() return self.patch( "/api/v2/feeds/comments/{id}", UpdateCommentResponse, @@ -706,6 +817,7 @@ def add_comment_reaction( self, id: str, type: str, + copy_custom_to_notification: Optional[bool] = None, create_notification_activity: Optional[bool] = None, enforce_unique: Optional[bool] = None, skip_push: Optional[bool] = None, @@ -716,15 +828,16 @@ def add_comment_reaction( path_params = { "id": id, } - json = build_body_dict( + json = AddCommentReactionRequest( type=type, + copy_custom_to_notification=copy_custom_to_notification, create_notification_activity=create_notification_activity, enforce_unique=enforce_unique, skip_push=skip_push, user_id=user_id, custom=custom, user=user, - ) + ).to_dict() return self.post( "/api/v2/feeds/comments/{id}/reactions", AddCommentReactionResponse, @@ -745,9 +858,9 @@ def query_comment_reactions( path_params = { "id": id, } - json = build_body_dict( + json = QueryCommentReactionsRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return self.post( "/api/v2/feeds/comments/{id}/reactions/query", QueryCommentReactionsResponse, @@ -757,9 +870,18 @@ def query_comment_reactions( @telemetry.operation_name("getstream.api.feeds.delete_comment_reaction") def delete_comment_reaction( - self, id: str, type: str, user_id: Optional[str] = None + self, + id: str, + type: str, + delete_notification_activity: Optional[bool] = None, + user_id: Optional[str] = None, ) -> StreamResponse[DeleteCommentReactionResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param( + **{ + "delete_notification_activity": delete_notification_activity, + "user_id": user_id, + } + ) path_params = { "id": id, "type": type, @@ -778,17 +900,21 @@ def get_comment_replies( depth: Optional[int] = None, sort: Optional[str] = None, replies_limit: Optional[int] = None, + user_id: Optional[str] = None, limit: Optional[int] = None, prev: Optional[str] = None, next: Optional[str] = None, ) -> StreamResponse[GetCommentRepliesResponse]: query_params = build_query_param( - depth=depth, - sort=sort, - replies_limit=replies_limit, - limit=limit, - prev=prev, - next=next, + **{ + "depth": depth, + "sort": sort, + "replies_limit": replies_limit, + "user_id": user_id, + "limit": limit, + "prev": prev, + "next": next, + } ) path_params = { "id": id, @@ -804,7 +930,9 @@ def get_comment_replies( def list_feed_groups( self, include_soft_deleted: Optional[bool] = None ) -> StreamResponse[ListFeedGroupsResponse]: - query_params = build_query_param(include_soft_deleted=include_soft_deleted) + query_params = build_query_param( + **{"include_soft_deleted": include_soft_deleted} + ) return self.get( "/api/v2/feeds/feed_groups", ListFeedGroupsResponse, @@ -825,7 +953,7 @@ def create_feed_group( ranking: Optional[RankingConfig] = None, stories: Optional[StoriesConfig] = None, ) -> StreamResponse[CreateFeedGroupResponse]: - json = build_body_dict( + json = CreateFeedGroupRequest( id=id, default_visibility=default_visibility, activity_processors=activity_processors, @@ -836,7 +964,7 @@ def create_feed_group( push_notification=push_notification, ranking=ranking, stories=stories, - ) + ).to_dict() return self.post( "/api/v2/feeds/feed_groups", CreateFeedGroupResponse, json=json ) @@ -845,7 +973,7 @@ def create_feed_group( def delete_feed( self, feed_group_id: str, feed_id: str, hard_delete: Optional[bool] = None ) -> StreamResponse[DeleteFeedResponse]: - query_params = build_query_param(hard_delete=hard_delete) + query_params = build_query_param(**{"hard_delete": hard_delete}) path_params = { "feed_group_id": feed_group_id, "feed_id": feed_id, @@ -875,6 +1003,7 @@ def get_or_create_feed( filter: Optional[Dict[str, object]] = None, followers_pagination: Optional[PagerRequest] = None, following_pagination: Optional[PagerRequest] = None, + friend_reactions_options: Optional[FriendReactionsOptions] = None, interest_weights: Optional[Dict[str, float]] = None, member_pagination: Optional[PagerRequest] = None, user: Optional[UserRequest] = None, @@ -883,7 +1012,7 @@ def get_or_create_feed( "feed_group_id": feed_group_id, "feed_id": feed_id, } - json = build_body_dict( + json = GetOrCreateFeedRequest( id_around=id_around, limit=limit, next=next, @@ -897,10 +1026,11 @@ def get_or_create_feed( filter=filter, followers_pagination=followers_pagination, following_pagination=following_pagination, + friend_reactions_options=friend_reactions_options, interest_weights=interest_weights, member_pagination=member_pagination, user=user, - ) + ).to_dict() return self.post( "/api/v2/feeds/feed_groups/{feed_group_id}/feeds/{feed_id}", GetOrCreateFeedResponse, @@ -923,13 +1053,13 @@ def update_feed( "feed_group_id": feed_group_id, "feed_id": feed_id, } - json = build_body_dict( + json = UpdateFeedRequest( created_by_id=created_by_id, description=description, name=name, filter_tags=filter_tags, custom=custom, - ) + ).to_dict() return self.put( "/api/v2/feeds/feed_groups/{feed_group_id}/feeds/{feed_id}", UpdateFeedResponse, @@ -954,7 +1084,7 @@ def mark_activity( "feed_group_id": feed_group_id, "feed_id": feed_id, } - json = build_body_dict( + json = MarkActivityRequest( mark_all_read=mark_all_read, mark_all_seen=mark_all_seen, user_id=user_id, @@ -962,7 +1092,7 @@ def mark_activity( mark_seen=mark_seen, mark_watched=mark_watched, user=user, - ) + ).to_dict() return self.post( "/api/v2/feeds/feed_groups/{feed_group_id}/feeds/{feed_id}/activities/mark/batch", Response, @@ -978,7 +1108,7 @@ def unpin_activity( activity_id: str, user_id: Optional[str] = None, ) -> StreamResponse[UnpinActivityResponse]: - query_params = build_query_param(user_id=user_id) + query_params = build_query_param(**{"user_id": user_id}) path_params = { "feed_group_id": feed_group_id, "feed_id": feed_id, @@ -1005,7 +1135,7 @@ def pin_activity( "feed_id": feed_id, "activity_id": activity_id, } - json = build_body_dict(user_id=user_id, user=user) + json = PinActivityRequest(user_id=user_id, user=user).to_dict() return self.post( "/api/v2/feeds/feed_groups/{feed_group_id}/feeds/{feed_id}/activities/{activity_id}/pin", PinActivityResponse, @@ -1028,9 +1158,9 @@ def update_feed_members( "feed_group_id": feed_group_id, "feed_id": feed_id, } - json = build_body_dict( + json = UpdateFeedMembersRequest( operation=operation, limit=limit, next=next, prev=prev, members=members - ) + ).to_dict() return self.patch( "/api/v2/feeds/feed_groups/{feed_group_id}/feeds/{feed_id}/members", UpdateFeedMembersResponse, @@ -1050,7 +1180,7 @@ def accept_feed_member_invite( "feed_id": feed_id, "feed_group_id": feed_group_id, } - json = build_body_dict(user_id=user_id, user=user) + json = AcceptFeedMemberInviteRequest(user_id=user_id, user=user).to_dict() return self.post( "/api/v2/feeds/feed_groups/{feed_group_id}/feeds/{feed_id}/members/accept", AcceptFeedMemberInviteResponse, @@ -1073,9 +1203,9 @@ def query_feed_members( "feed_group_id": feed_group_id, "feed_id": feed_id, } - json = build_body_dict( + json = QueryFeedMembersRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return self.post( "/api/v2/feeds/feed_groups/{feed_group_id}/feeds/{feed_id}/members/query", QueryFeedMembersResponse, @@ -1095,7 +1225,7 @@ def reject_feed_member_invite( "feed_group_id": feed_group_id, "feed_id": feed_id, } - json = build_body_dict(user_id=user_id, user=user) + json = RejectFeedMemberInviteRequest(user_id=user_id, user=user).to_dict() return self.post( "/api/v2/feeds/feed_groups/{feed_group_id}/feeds/{feed_id}/members/reject", RejectFeedMemberInviteResponse, @@ -1103,6 +1233,31 @@ def reject_feed_member_invite( json=json, ) + @telemetry.operation_name("getstream.api.feeds.query_pinned_activities") + def query_pinned_activities( + self, + feed_group_id: str, + feed_id: str, + limit: Optional[int] = None, + next: Optional[str] = None, + prev: Optional[str] = None, + sort: Optional[List[SortParamRequest]] = None, + filter: Optional[Dict[str, object]] = None, + ) -> StreamResponse[QueryPinnedActivitiesResponse]: + path_params = { + "feed_group_id": feed_group_id, + "feed_id": feed_id, + } + json = QueryPinnedActivitiesRequest( + limit=limit, next=next, prev=prev, sort=sort, filter=filter + ).to_dict() + return self.post( + "/api/v2/feeds/feed_groups/{feed_group_id}/feeds/{feed_id}/pinned_activities/query", + QueryPinnedActivitiesResponse, + path_params=path_params, + json=json, + ) + @telemetry.operation_name("getstream.api.feeds.get_follow_suggestions") def get_follow_suggestions( self, @@ -1110,7 +1265,7 @@ def get_follow_suggestions( limit: Optional[int] = None, user_id: Optional[str] = None, ) -> StreamResponse[GetFollowSuggestionsResponse]: - query_params = build_query_param(limit=limit, user_id=user_id) + query_params = build_query_param(**{"limit": limit, "user_id": user_id}) path_params = { "feed_group_id": feed_group_id, } @@ -1121,11 +1276,24 @@ def get_follow_suggestions( path_params=path_params, ) + @telemetry.operation_name("getstream.api.feeds.restore_feed_group") + def restore_feed_group( + self, feed_group_id: str + ) -> StreamResponse[RestoreFeedGroupResponse]: + path_params = { + "feed_group_id": feed_group_id, + } + return self.post( + "/api/v2/feeds/feed_groups/{feed_group_id}/restore", + RestoreFeedGroupResponse, + path_params=path_params, + ) + @telemetry.operation_name("getstream.api.feeds.delete_feed_group") def delete_feed_group( self, id: str, hard_delete: Optional[bool] = None ) -> StreamResponse[DeleteFeedGroupResponse]: - query_params = build_query_param(hard_delete=hard_delete) + query_params = build_query_param(**{"hard_delete": hard_delete}) path_params = { "id": id, } @@ -1140,7 +1308,9 @@ def delete_feed_group( def get_feed_group( self, id: str, include_soft_deleted: Optional[bool] = None ) -> StreamResponse[GetFeedGroupResponse]: - query_params = build_query_param(include_soft_deleted=include_soft_deleted) + query_params = build_query_param( + **{"include_soft_deleted": include_soft_deleted} + ) path_params = { "id": id, } @@ -1168,7 +1338,7 @@ def get_or_create_feed_group( path_params = { "id": id, } - json = build_body_dict( + json = GetOrCreateFeedGroupRequest( default_visibility=default_visibility, activity_processors=activity_processors, activity_selectors=activity_selectors, @@ -1178,7 +1348,7 @@ def get_or_create_feed_group( push_notification=push_notification, ranking=ranking, stories=stories, - ) + ).to_dict() return self.post( "/api/v2/feeds/feed_groups/{id}", GetOrCreateFeedGroupResponse, @@ -1203,7 +1373,7 @@ def update_feed_group( path_params = { "id": id, } - json = build_body_dict( + json = UpdateFeedGroupRequest( default_visibility=default_visibility, activity_processors=activity_processors, activity_selectors=activity_selectors, @@ -1213,7 +1383,7 @@ def update_feed_group( push_notification=push_notification, ranking=ranking, stories=stories, - ) + ).to_dict() return self.put( "/api/v2/feeds/feed_groups/{id}", UpdateFeedGroupResponse, @@ -1233,12 +1403,12 @@ def create_feed_view( aggregation: Optional[AggregationConfig] = None, ranking: Optional[RankingConfig] = None, ) -> StreamResponse[CreateFeedViewResponse]: - json = build_body_dict( + json = CreateFeedViewRequest( id=id, activity_selectors=activity_selectors, aggregation=aggregation, ranking=ranking, - ) + ).to_dict() return self.post("/api/v2/feeds/feed_views", CreateFeedViewResponse, json=json) @telemetry.operation_name("getstream.api.feeds.delete_feed_view") @@ -1274,11 +1444,11 @@ def get_or_create_feed_view( path_params = { "id": id, } - json = build_body_dict( + json = GetOrCreateFeedViewRequest( activity_selectors=activity_selectors, aggregation=aggregation, ranking=ranking, - ) + ).to_dict() return self.post( "/api/v2/feeds/feed_views/{id}", GetOrCreateFeedViewResponse, @@ -1297,11 +1467,11 @@ def update_feed_view( path_params = { "id": id, } - json = build_body_dict( + json = UpdateFeedViewRequest( activity_selectors=activity_selectors, aggregation=aggregation, ranking=ranking, - ) + ).to_dict() return self.put( "/api/v2/feeds/feed_views/{id}", UpdateFeedViewResponse, @@ -1333,7 +1503,7 @@ def update_feed_visibility( path_params = { "name": name, } - json = build_body_dict(grants=grants) + json = UpdateFeedVisibilityRequest(grants=grants).to_dict() return self.put( "/api/v2/feeds/feed_visibilities/{name}", UpdateFeedVisibilityResponse, @@ -1345,7 +1515,7 @@ def update_feed_visibility( def create_feeds_batch( self, feeds: List[FeedRequest] ) -> StreamResponse[CreateFeedsBatchResponse]: - json = build_body_dict(feeds=feeds) + json = CreateFeedsBatchRequest(feeds=feeds).to_dict() return self.post( "/api/v2/feeds/feeds/batch", CreateFeedsBatchResponse, json=json ) @@ -1354,7 +1524,7 @@ def create_feeds_batch( def delete_feeds_batch( self, feeds: List[str], hard_delete: Optional[bool] = None ) -> StreamResponse[DeleteFeedsBatchResponse]: - json = build_body_dict(feeds=feeds, hard_delete=hard_delete) + json = DeleteFeedsBatchRequest(feeds=feeds, hard_delete=hard_delete).to_dict() return self.post( "/api/v2/feeds/feeds/delete", DeleteFeedsBatchResponse, json=json ) @@ -1367,7 +1537,9 @@ def own_batch( fields: Optional[List[str]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[OwnBatchResponse]: - json = build_body_dict(feeds=feeds, user_id=user_id, fields=fields, user=user) + json = OwnBatchRequest( + feeds=feeds, user_id=user_id, fields=fields, user=user + ).to_dict() return self.post("/api/v2/feeds/feeds/own/batch", OwnBatchResponse, json=json) @telemetry.operation_name("getstream.api.feeds.query_feeds") @@ -1380,9 +1552,9 @@ def query_feeds( sort: Optional[List[SortParamRequest]] = None, filter: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryFeedsResponse]: - json = build_body_dict( + json = QueryFeedsRequest( limit=limit, next=next, prev=prev, watch=watch, sort=sort, filter=filter - ) + ).to_dict() return self.post("/api/v2/feeds/feeds/query", QueryFeedsResponse, json=json) @telemetry.operation_name("getstream.api.feeds.get_feeds_rate_limits") @@ -1395,11 +1567,13 @@ def get_feeds_rate_limits( server_side: Optional[bool] = None, ) -> StreamResponse[GetFeedsRateLimitsResponse]: query_params = build_query_param( - endpoints=endpoints, - android=android, - ios=ios, - web=web, - server_side=server_side, + **{ + "endpoints": endpoints, + "android": android, + "ios": ios, + "web": web, + "server_side": server_side, + } ) return self.get( "/api/v2/feeds/feeds/rate_limits", @@ -1412,21 +1586,25 @@ def update_follow( self, source: str, target: str, + copy_custom_to_notification: Optional[bool] = None, create_notification_activity: Optional[bool] = None, follower_role: Optional[str] = None, push_preference: Optional[str] = None, skip_push: Optional[bool] = None, + status: Optional[str] = None, custom: Optional[Dict[str, object]] = None, ) -> StreamResponse[UpdateFollowResponse]: - json = build_body_dict( + json = UpdateFollowRequest( source=source, target=target, + copy_custom_to_notification=copy_custom_to_notification, create_notification_activity=create_notification_activity, follower_role=follower_role, push_preference=push_preference, skip_push=skip_push, + status=status, custom=custom, - ) + ).to_dict() return self.patch("/api/v2/feeds/follows", UpdateFollowResponse, json=json) @telemetry.operation_name("getstream.api.feeds.follow") @@ -1434,28 +1612,32 @@ def follow( self, source: str, target: str, + copy_custom_to_notification: Optional[bool] = None, create_notification_activity: Optional[bool] = None, push_preference: Optional[str] = None, skip_push: Optional[bool] = None, + status: Optional[str] = None, custom: Optional[Dict[str, object]] = None, ) -> StreamResponse[SingleFollowResponse]: - json = build_body_dict( + json = FollowRequest( source=source, target=target, + copy_custom_to_notification=copy_custom_to_notification, create_notification_activity=create_notification_activity, push_preference=push_preference, skip_push=skip_push, + status=status, custom=custom, - ) + ).to_dict() return self.post("/api/v2/feeds/follows", SingleFollowResponse, json=json) @telemetry.operation_name("getstream.api.feeds.accept_follow") def accept_follow( self, source: str, target: str, follower_role: Optional[str] = None ) -> StreamResponse[AcceptFollowResponse]: - json = build_body_dict( + json = AcceptFollowRequest( source=source, target=target, follower_role=follower_role - ) + ).to_dict() return self.post( "/api/v2/feeds/follows/accept", AcceptFollowResponse, json=json ) @@ -1464,14 +1646,14 @@ def accept_follow( def follow_batch( self, follows: List[FollowRequest] ) -> StreamResponse[FollowBatchResponse]: - json = build_body_dict(follows=follows) + json = FollowBatchRequest(follows=follows).to_dict() return self.post("/api/v2/feeds/follows/batch", FollowBatchResponse, json=json) @telemetry.operation_name("getstream.api.feeds.get_or_create_follows") def get_or_create_follows( self, follows: List[FollowRequest] ) -> StreamResponse[FollowBatchResponse]: - json = build_body_dict(follows=follows) + json = FollowBatchRequest(follows=follows).to_dict() return self.post( "/api/v2/feeds/follows/batch/upsert", FollowBatchResponse, json=json ) @@ -1485,22 +1667,30 @@ def query_follows( sort: Optional[List[SortParamRequest]] = None, filter: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryFollowsResponse]: - json = build_body_dict( + json = QueryFollowsRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return self.post("/api/v2/feeds/follows/query", QueryFollowsResponse, json=json) @telemetry.operation_name("getstream.api.feeds.reject_follow") def reject_follow( self, source: str, target: str ) -> StreamResponse[RejectFollowResponse]: - json = build_body_dict(source=source, target=target) + json = RejectFollowRequest(source=source, target=target).to_dict() return self.post( "/api/v2/feeds/follows/reject", RejectFollowResponse, json=json ) @telemetry.operation_name("getstream.api.feeds.unfollow") - def unfollow(self, source: str, target: str) -> StreamResponse[UnfollowResponse]: + def unfollow( + self, + source: str, + target: str, + delete_notification_activity: Optional[bool] = None, + ) -> StreamResponse[UnfollowResponse]: + query_params = build_query_param( + **{"delete_notification_activity": delete_notification_activity} + ) path_params = { "source": source, "target": target, @@ -1508,6 +1698,7 @@ def unfollow(self, source: str, target: str) -> StreamResponse[UnfollowResponse] return self.delete( "/api/v2/feeds/follows/{source}/{target}", UnfollowResponse, + query_params=query_params, path_params=path_params, ) @@ -1521,14 +1712,14 @@ def create_membership_level( tags: Optional[List[str]] = None, custom: Optional[Dict[str, object]] = None, ) -> StreamResponse[CreateMembershipLevelResponse]: - json = build_body_dict( + json = CreateMembershipLevelRequest( id=id, name=name, description=description, priority=priority, tags=tags, custom=custom, - ) + ).to_dict() return self.post( "/api/v2/feeds/membership_levels", CreateMembershipLevelResponse, json=json ) @@ -1542,9 +1733,9 @@ def query_membership_levels( sort: Optional[List[SortParamRequest]] = None, filter: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryMembershipLevelsResponse]: - json = build_body_dict( + json = QueryMembershipLevelsRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return self.post( "/api/v2/feeds/membership_levels/query", QueryMembershipLevelsResponse, @@ -1573,13 +1764,13 @@ def update_membership_level( path_params = { "id": id, } - json = build_body_dict( + json = UpdateMembershipLevelRequest( description=description, name=name, priority=priority, tags=tags, custom=custom, - ) + ).to_dict() return self.patch( "/api/v2/feeds/membership_levels/{id}", UpdateMembershipLevelResponse, @@ -1591,25 +1782,33 @@ def update_membership_level( def query_feeds_usage_stats( self, _from: Optional[str] = None, to: Optional[str] = None ) -> StreamResponse[QueryFeedsUsageStatsResponse]: - json = build_body_dict(_from=_from, to=to) + json = QueryFeedsUsageStatsRequest(_from=_from, to=to).to_dict() return self.post( "/api/v2/feeds/stats/usage", QueryFeedsUsageStatsResponse, json=json ) @telemetry.operation_name("getstream.api.feeds.unfollow_batch") def unfollow_batch( - self, follows: List[FollowPair] + self, + follows: List[FollowPair], + delete_notification_activity: Optional[bool] = None, ) -> StreamResponse[UnfollowBatchResponse]: - json = build_body_dict(follows=follows) + json = UnfollowBatchRequest( + follows=follows, delete_notification_activity=delete_notification_activity + ).to_dict() return self.post( "/api/v2/feeds/unfollow/batch", UnfollowBatchResponse, json=json ) @telemetry.operation_name("getstream.api.feeds.get_or_create_unfollows") def get_or_create_unfollows( - self, follows: List[FollowPair] + self, + follows: List[FollowPair], + delete_notification_activity: Optional[bool] = None, ) -> StreamResponse[UnfollowBatchResponse]: - json = build_body_dict(follows=follows) + json = UnfollowBatchRequest( + follows=follows, delete_notification_activity=delete_notification_activity + ).to_dict() return self.post( "/api/v2/feeds/unfollow/batch/upsert", UnfollowBatchResponse, json=json ) @@ -1621,7 +1820,7 @@ def delete_feed_user_data( path_params = { "user_id": user_id, } - json = build_body_dict(hard_delete=hard_delete) + json = DeleteFeedUserDataRequest(hard_delete=hard_delete).to_dict() return self.post( "/api/v2/feeds/users/{user_id}/delete", DeleteFeedUserDataResponse, diff --git a/getstream/models/__init__.py b/getstream/models/__init__.py index 2006363c..521dbd6c 100644 --- a/getstream/models/__init__.py +++ b/getstream/models/__init__.py @@ -151,11 +151,34 @@ class APNS(DataClassJsonMixin): ) +@dataclass +class APNSPayload(DataClassJsonMixin): + body: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="body")) + content_available: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="content-available") + ) + mutable_content: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="mutable-content") + ) + sound: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="sound") + ) + title: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="title") + ) + data: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="data") + ) + + @dataclass class AWSRekognitionRule(DataClassJsonMixin): action: str = dc_field(metadata=dc_config(field_name="action")) label: str = dc_field(metadata=dc_config(field_name="label")) min_confidence: float = dc_field(metadata=dc_config(field_name="min_confidence")) + subclassifications: "Optional[Dict[str, bool]]" = dc_field( + default=None, metadata=dc_config(field_name="subclassifications") + ) @dataclass @@ -501,31 +524,6 @@ class ActivityMarkEvent(DataClassJsonMixin): ) -@dataclass -class ActivityMarkedEvent(DataClassJsonMixin): - all_read: bool = dc_field(metadata=dc_config(field_name="all_read")) - all_seen: bool = dc_field(metadata=dc_config(field_name="all_seen")) - 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"), - ) - ) - feed_id: str = dc_field(metadata=dc_config(field_name="feed_id")) - user_id: str = dc_field(metadata=dc_config(field_name="user_id")) - type: str = dc_field( - default="activity.marked", metadata=dc_config(field_name="type") - ) - marked_read: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="marked_read") - ) - marked_watched: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="marked_watched") - ) - - @dataclass class ActivityPinResponse(DataClassJsonMixin): created_at: datetime = dc_field( @@ -735,6 +733,12 @@ class ActivityRemovedFromFeedEvent(DataClassJsonMixin): class ActivityRequest(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) feeds: List[str] = dc_field(metadata=dc_config(field_name="feeds")) + copy_custom_to_notification: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="copy_custom_to_notification") + ) + create_notification_activity: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="create_notification_activity") + ) expires_at: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="expires_at") ) @@ -751,6 +755,9 @@ class ActivityRequest(DataClassJsonMixin): skip_enrich_url: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_enrich_url") ) + skip_push: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="skip_push") + ) text: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="text")) user_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="user_id") @@ -842,7 +849,7 @@ class ActivityResponse(DataClassJsonMixin): metadata=dc_config(field_name="collections") ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) - reaction_groups: "Dict[str, Optional[ReactionGroupResponse]]" = dc_field( + reaction_groups: "Dict[str, FeedsReactionGroupResponse]" = dc_field( metadata=dc_config(field_name="reaction_groups") ) search_data: Dict[str, object] = dc_field( @@ -876,16 +883,25 @@ class ActivityResponse(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ), ) + friend_reaction_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="friend_reaction_count") + ) is_watched: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="is_watched") ) moderation_action: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="moderation_action") ) + selector_source: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="selector_source") + ) text: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="text")) visibility_tag: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="visibility_tag") ) + friend_reactions: "Optional[List[FeedsReactionResponse]]" = dc_field( + default=None, metadata=dc_config(field_name="friend_reactions") + ) current_feed: "Optional[FeedResponse]" = dc_field( default=None, metadata=dc_config(field_name="current_feed") ) @@ -906,6 +922,39 @@ class ActivityResponse(DataClassJsonMixin): ) +@dataclass +class ActivityRestoredEvent(DataClassJsonMixin): + 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"), + ) + ) + fid: str = dc_field(metadata=dc_config(field_name="fid")) + activity: "ActivityResponse" = dc_field(metadata=dc_config(field_name="activity")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + type: str = dc_field( + default="feeds.activity.restored", metadata=dc_config(field_name="type") + ) + feed_visibility: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="feed_visibility") + ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + user: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) + + @dataclass class ActivitySelectorConfig(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) @@ -1030,6 +1079,12 @@ class ActivityUpdatedEvent(DataClassJsonMixin): class AddActivityRequest(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) feeds: List[str] = dc_field(metadata=dc_config(field_name="feeds")) + copy_custom_to_notification: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="copy_custom_to_notification") + ) + create_notification_activity: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="create_notification_activity") + ) expires_at: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="expires_at") ) @@ -1046,6 +1101,9 @@ class AddActivityRequest(DataClassJsonMixin): skip_enrich_url: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_enrich_url") ) + skip_push: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="skip_push") + ) text: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="text")) user_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="user_id") @@ -1086,6 +1144,9 @@ class AddActivityRequest(DataClassJsonMixin): class AddActivityResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) activity: "ActivityResponse" = dc_field(metadata=dc_config(field_name="activity")) + mention_notifications_created: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="mention_notifications_created") + ) @dataclass @@ -1116,6 +1177,9 @@ class AddBookmarkResponse(DataClassJsonMixin): @dataclass class AddCommentReactionRequest(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) + copy_custom_to_notification: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="copy_custom_to_notification") + ) create_notification_activity: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="create_notification_activity") ) @@ -1143,6 +1207,9 @@ class AddCommentReactionResponse(DataClassJsonMixin): reaction: "FeedsReactionResponse" = dc_field( metadata=dc_config(field_name="reaction") ) + notification_created: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="notification_created") + ) @dataclass @@ -1150,6 +1217,9 @@ class AddCommentRequest(DataClassJsonMixin): comment: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="comment") ) + copy_custom_to_notification: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="copy_custom_to_notification") + ) create_notification_activity: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="create_notification_activity") ) @@ -1190,6 +1260,12 @@ class AddCommentRequest(DataClassJsonMixin): class AddCommentResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) comment: "CommentResponse" = dc_field(metadata=dc_config(field_name="comment")) + mention_notifications_created: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="mention_notifications_created") + ) + notification_created: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="notification_created") + ) @dataclass @@ -1218,6 +1294,9 @@ class AddFolderRequest(DataClassJsonMixin): @dataclass class AddReactionRequest(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) + copy_custom_to_notification: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="copy_custom_to_notification") + ) create_notification_activity: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="create_notification_activity") ) @@ -1245,6 +1324,25 @@ class AddReactionResponse(DataClassJsonMixin): reaction: "FeedsReactionResponse" = dc_field( metadata=dc_config(field_name="reaction") ) + notification_created: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="notification_created") + ) + + +@dataclass +class AddUserGroupMembersRequest(DataClassJsonMixin): + member_ids: List[str] = dc_field(metadata=dc_config(field_name="member_ids")) + team_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="team_id") + ) + + +@dataclass +class AddUserGroupMembersResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + user_group: "Optional[UserGroupResponse]" = dc_field( + default=None, metadata=dc_config(field_name="user_group") + ) @dataclass @@ -1287,19 +1385,6 @@ class AggregationConfig(DataClassJsonMixin): ) -@dataclass -class AnyEvent(DataClassJsonMixin): - 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"), - ) - ) - type: str = dc_field(default="*", metadata=dc_config(field_name="type")) - - @dataclass class AppResponseFields(DataClassJsonMixin): allow_multi_user_devices: bool = dc_field( @@ -1337,8 +1422,8 @@ class AppResponseFields(DataClassJsonMixin): max_aggregated_activities_length: int = dc_field( metadata=dc_config(field_name="max_aggregated_activities_length") ) - moderation_bulk_submit_action_enabled: bool = dc_field( - metadata=dc_config(field_name="moderation_bulk_submit_action_enabled") + moderation_audio_call_moderation_enabled: bool = dc_field( + metadata=dc_config(field_name="moderation_audio_call_moderation_enabled") ) moderation_enabled: bool = dc_field( metadata=dc_config(field_name="moderation_enabled") @@ -1349,6 +1434,9 @@ class AppResponseFields(DataClassJsonMixin): moderation_multitenant_blocklist_enabled: bool = dc_field( metadata=dc_config(field_name="moderation_multitenant_blocklist_enabled") ) + moderation_video_call_moderation_enabled: bool = dc_field( + metadata=dc_config(field_name="moderation_video_call_moderation_enabled") + ) moderation_webhook_url: str = dc_field( metadata=dc_config(field_name="moderation_webhook_url") ) @@ -1699,7 +1787,8 @@ class AsyncExportErrorEvent(DataClassJsonMixin): task_id: str = dc_field(metadata=dc_config(field_name="task_id")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="export.users.error", metadata=dc_config(field_name="type") + default="export.bulk_image_moderation.error", + metadata=dc_config(field_name="type"), ) received_at: Optional[datetime] = dc_field( default=None, @@ -1951,7 +2040,7 @@ class AudioSettingsResponse(DataClassJsonMixin): @dataclass -class AutomodDetails(DataClassJsonMixin): +class AutomodDetailsResponse(DataClassJsonMixin): action: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="action") ) @@ -1961,7 +2050,7 @@ class AutomodDetails(DataClassJsonMixin): image_labels: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="image_labels") ) - message_details: "Optional[FlagMessageDetails]" = dc_field( + message_details: "Optional[FlagMessageDetailsResponse]" = dc_field( default=None, metadata=dc_config(field_name="message_details") ) result: "Optional[MessageModerationResult]" = dc_field( @@ -2059,7 +2148,32 @@ class BackstageSettingsResponse(DataClassJsonMixin): @dataclass -class Ban(DataClassJsonMixin): +class BanActionRequestPayload(DataClassJsonMixin): + channel_ban_only: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="channel_ban_only") + ) + delete_messages: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="delete_messages") + ) + ip_ban: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="ip_ban") + ) + reason: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="reason") + ) + shadow: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="shadow") + ) + target_user_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="target_user_id") + ) + timeout: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="timeout") + ) + + +@dataclass +class BanInfoResponse(DataClassJsonMixin): created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -2068,7 +2182,6 @@ class Ban(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - shadow: bool = dc_field(metadata=dc_config(field_name="shadow")) expires: Optional[datetime] = dc_field( default=None, metadata=dc_config( @@ -2081,36 +2194,14 @@ class Ban(DataClassJsonMixin): reason: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="reason") ) - channel: "Optional[Channel]" = dc_field( - default=None, metadata=dc_config(field_name="channel") - ) - created_by: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="created_by") - ) - target: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="target") - ) - - -@dataclass -class BanActionRequest(DataClassJsonMixin): - channel_ban_only: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="channel_ban_only") - ) - delete_messages: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="delete_messages") - ) - ip_ban: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="ip_ban") - ) - reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") - ) shadow: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="shadow") ) - timeout: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="timeout") + created_by: "Optional[UserResponse]" = dc_field( + default=None, metadata=dc_config(field_name="created_by") + ) + user: "Optional[UserResponse]" = dc_field( + default=None, metadata=dc_config(field_name="user") ) @@ -2199,7 +2290,7 @@ class BanResponse(DataClassJsonMixin): @dataclass -class BlockActionRequest(DataClassJsonMixin): +class BlockActionRequestPayload(DataClassJsonMixin): reason: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="reason") ) @@ -2639,6 +2730,31 @@ class CallAcceptedEvent(DataClassJsonMixin): type: str = dc_field(default="call.accepted", metadata=dc_config(field_name="type")) +@dataclass +class CallActionOptions(DataClassJsonMixin): + duration: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="duration") + ) + flag_reason: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="flag_reason") + ) + kick_reason: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="kick_reason") + ) + mute_audio: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="mute_audio") + ) + mute_video: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="mute_video") + ) + reason: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="reason") + ) + warning_text: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="warning_text") + ) + + @dataclass class CallClosedCaption(DataClassJsonMixin): end_time: datetime = dc_field( @@ -2732,6 +2848,42 @@ class CallCreatedEvent(DataClassJsonMixin): type: str = dc_field(default="call.created", metadata=dc_config(field_name="type")) +@dataclass +class CallCustomPropertyParameters(DataClassJsonMixin): + operator: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="operator") + ) + property_key: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="property_key") + ) + + +@dataclass +class CallDTMFEvent(DataClassJsonMixin): + call_cid: str = dc_field(metadata=dc_config(field_name="call_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"), + ) + ) + digit: str = dc_field(metadata=dc_config(field_name="digit")) + duration_ms: int = dc_field(metadata=dc_config(field_name="duration_ms")) + seq_number: int = dc_field(metadata=dc_config(field_name="seq_number")) + timestamp: datetime = dc_field( + metadata=dc_config( + field_name="timestamp", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ) + ) + user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) + type: str = dc_field(default="call.dtmf", metadata=dc_config(field_name="type")) + + @dataclass class CallDeletedEvent(DataClassJsonMixin): call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) @@ -3145,7 +3297,9 @@ class CallReactionEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - reaction: "ReactionResponse" = dc_field(metadata=dc_config(field_name="reaction")) + reaction: "VideoReactionResponse" = dc_field( + metadata=dc_config(field_name="reaction") + ) type: str = dc_field( default="call.reaction_new", metadata=dc_config(field_name="type") ) @@ -3381,6 +3535,9 @@ class CallResponse(DataClassJsonMixin): join_ahead_time_seconds: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="join_ahead_time_seconds") ) + routing_number: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="routing_number") + ) starts_at: Optional[datetime] = dc_field( default=None, metadata=dc_config( @@ -3469,6 +3626,19 @@ class CallRtmpBroadcastStoppedEvent(DataClassJsonMixin): ) +@dataclass +class CallRuleActionSequence(DataClassJsonMixin): + violation_number: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="violation_number") + ) + actions: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="actions") + ) + call_options: "Optional[CallActionOptions]" = dc_field( + default=None, metadata=dc_config(field_name="call_options") + ) + + @dataclass class CallSessionEndedEvent(DataClassJsonMixin): call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) @@ -3933,6 +4103,9 @@ class CallStatsParticipantCounts(DataClassJsonMixin): ) publishers: int = dc_field(metadata=dc_config(field_name="publishers")) sessions: int = dc_field(metadata=dc_config(field_name="sessions")) + total_participant_duration: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="total_participant_duration") + ) @dataclass @@ -4012,6 +4185,12 @@ class CallStatsReportReadyEvent(DataClassJsonMixin): type: str = dc_field( default="call.stats_report_ready", metadata=dc_config(field_name="type") ) + is_trimmed: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="is_trimmed") + ) + participants_overview: "Optional[List[CallStatsParticipant]]" = dc_field( + default=None, metadata=dc_config(field_name="participants_overview") + ) @dataclass @@ -4196,7 +4375,7 @@ class CallTypeResponse(DataClassJsonMixin): ) ) grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) - notification_settings: "NotificationSettings" = dc_field( + notification_settings: "NotificationSettingsResponse" = dc_field( metadata=dc_config(field_name="notification_settings") ) settings: "CallSettingsResponse" = dc_field( @@ -4207,6 +4386,13 @@ class CallTypeResponse(DataClassJsonMixin): ) +@dataclass +class CallTypeRuleParameters(DataClassJsonMixin): + call_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="call_type") + ) + + @dataclass class CallUpdatedEvent(DataClassJsonMixin): call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) @@ -4275,6 +4461,16 @@ class CallUserMutedEvent(DataClassJsonMixin): ) +@dataclass +class CallViolationCountParameters(DataClassJsonMixin): + threshold: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="threshold") + ) + time_window: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="time_window") + ) + + @dataclass class CallsPerDayReport(DataClassJsonMixin): count: int = dc_field(metadata=dc_config(field_name="count")) @@ -4487,117 +4683,18 @@ class CastPollVoteRequest(DataClassJsonMixin): @dataclass -class Channel(DataClassJsonMixin): - auto_translation_language: str = dc_field( - metadata=dc_config(field_name="auto_translation_language") - ) - cid: str = dc_field(metadata=dc_config(field_name="cid")) - created_at: datetime = dc_field( +class ChannelBatchCompletedEvent(DataClassJsonMixin): + batch_created_at: datetime = dc_field( metadata=dc_config( - field_name="created_at", + field_name="batch_created_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), ) ) - disabled: bool = dc_field(metadata=dc_config(field_name="disabled")) - frozen: bool = dc_field(metadata=dc_config(field_name="frozen")) - id: str = dc_field(metadata=dc_config(field_name="id")) - type: str = dc_field(metadata=dc_config(field_name="type")) - updated_at: datetime = dc_field( + created_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"), - ) - ) - custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) - auto_translation_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="auto_translation_enabled") - ) - cooldown: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="cooldown") - ) - deleted_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="deleted_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - last_campaigns: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="last_campaigns") - ) - last_message_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="last_message_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - member_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="member_count") - ) - message_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="message_count") - ) - message_count_updated_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="message_count_updated_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - 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") - ) - filter_tags: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="filter_tags") - ) - invites: "Optional[List[ChannelMember]]" = dc_field( - default=None, metadata=dc_config(field_name="invites") - ) - members: "Optional[List[ChannelMember]]" = dc_field( - default=None, metadata=dc_config(field_name="members") - ) - config: "Optional[ChannelConfig]" = dc_field( - default=None, metadata=dc_config(field_name="config") - ) - config_overrides: "Optional[ConfigOverrides]" = dc_field( - default=None, metadata=dc_config(field_name="config_overrides") - ) - created_by: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="created_by") - ) - members_lookup: "Optional[Dict[str, Optional[ChannelMemberLookup]]]" = dc_field( - default=None, metadata=dc_config(field_name="members_lookup") - ) - truncated_by: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="truncated_by") - ) - - -@dataclass -class ChannelBatchCompletedEvent(DataClassJsonMixin): - batch_created_at: datetime = dc_field( - metadata=dc_config( - field_name="batch_created_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - created_at: datetime = dc_field( - metadata=dc_config( - field_name="created_at", + field_name="created_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), @@ -4750,6 +4847,9 @@ class ChannelConfig(DataClassJsonMixin): partition_ttl: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="partition_ttl") ) + push_level: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="push_level") + ) allowed_flag_reasons: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="allowed_flag_reasons") ) @@ -4826,6 +4926,9 @@ class ChannelConfigWithInfo(DataClassJsonMixin): partition_ttl: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="partition_ttl") ) + push_level: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="push_level") + ) allowed_flag_reasons: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="allowed_flag_reasons") ) @@ -4850,19 +4953,44 @@ class ChannelCreatedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( default="channel.created", metadata=dc_config(field_name="type") ) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") + ) + user: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) @dataclass class ChannelDeletedEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_member_count: int = dc_field( - metadata=dc_config(field_name="channel_member_count") - ) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -4871,12 +4999,39 @@ class ChannelDeletedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( default="channel.deleted", metadata=dc_config(field_name="type") ) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") + ) + user: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="user") ) @@ -4907,9 +5062,6 @@ class ChannelExport(DataClassJsonMixin): @dataclass class ChannelFrozenEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -4918,9 +5070,26 @@ class ChannelFrozenEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( default="channel.frozen", metadata=dc_config(field_name="type") ) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) @dataclass @@ -4950,12 +5119,6 @@ class ChannelGetOrCreateRequest(DataClassJsonMixin): @dataclass class ChannelHiddenEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_member_count: int = dc_field( - metadata=dc_config(field_name="channel_member_count") - ) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) clear_history: bool = dc_field(metadata=dc_config(field_name="clear_history")) created_at: datetime = dc_field( metadata=dc_config( @@ -4965,13 +5128,38 @@ class ChannelHiddenEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( default="channel.hidden", metadata=dc_config(field_name="type") ) - channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") ) - user: "Optional[User]" = dc_field( + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") + ) + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -5032,16 +5220,16 @@ class ChannelInputRequest(DataClassJsonMixin): default=None, metadata=dc_config(field_name="frozen") ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - invites: "Optional[List[ChannelMember]]" = dc_field( + invites: "Optional[List[ChannelMemberRequest]]" = dc_field( default=None, metadata=dc_config(field_name="invites") ) - members: "Optional[List[ChannelMember]]" = dc_field( + members: "Optional[List[ChannelMemberRequest]]" = dc_field( default=None, metadata=dc_config(field_name="members") ) - config_overrides: "Optional[ConfigOverrides]" = dc_field( + config_overrides: "Optional[ConfigOverridesRequest]" = dc_field( default=None, metadata=dc_config(field_name="config_overrides") ) - created_by: "Optional[User]" = dc_field( + created_by: "Optional[UserRequest]" = dc_field( default=None, metadata=dc_config(field_name="created_by") ) custom: Optional[Dict[str, object]] = dc_field( @@ -5050,7 +5238,44 @@ class ChannelInputRequest(DataClassJsonMixin): @dataclass -class ChannelMember(DataClassJsonMixin): +class ChannelMemberRequest(DataClassJsonMixin): + user_id: str = dc_field(metadata=dc_config(field_name="user_id")) + channel_role: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_role") + ) + custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="custom") + ) + user: "Optional[UserResponse]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) + + +@dataclass +class ChannelMemberResponse(DataClassJsonMixin): + banned: bool = dc_field(metadata=dc_config(field_name="banned")) + channel_role: str = dc_field(metadata=dc_config(field_name="channel_role")) + 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"), + ) + ) + notifications_muted: bool = dc_field( + metadata=dc_config(field_name="notifications_muted") + ) + shadow_banned: bool = dc_field(metadata=dc_config(field_name="shadow_banned")) + 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"), + ) + ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) archived_at: Optional[datetime] = dc_field( default=None, metadata=dc_config( @@ -5069,40 +5294,19 @@ class ChannelMember(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ), ) - banned: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="banned") - ) - blocked: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="blocked") - ) - channel_role: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="channel_role") - ) - created_at: Optional[datetime] = dc_field( + deleted_at: Optional[datetime] = dc_field( default=None, metadata=dc_config( - field_name="created_at", + field_name="deleted_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), ), ) - deleted_at: Optional[datetime] = dc_field( + invite_accepted_at: Optional[datetime] = dc_field( default=None, metadata=dc_config( - field_name="deleted_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - hidden: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="hidden") - ) - invite_accepted_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="invite_accepted_at", + field_name="invite_accepted_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), @@ -5120,15 +5324,9 @@ class ChannelMember(DataClassJsonMixin): invited: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="invited") ) - is_global_banned: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="is_global_banned") - ) is_moderator: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="is_moderator") ) - notifications_muted: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="notifications_muted") - ) pinned_at: Optional[datetime] = dc_field( default=None, metadata=dc_config( @@ -5138,92 +5336,31 @@ class ChannelMember(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ), ) - shadow_banned: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="shadow_banned") - ) + role: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="role")) status: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="status") ) - updated_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="updated_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) user_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="user_id") ) deleted_messages: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="deleted_messages") ) - channel: "Optional[DenormalizedChannelFields]" = dc_field( - default=None, metadata=dc_config(field_name="channel") - ) - custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") - ) - user: "Optional[User]" = dc_field( + user: "Optional[UserResponse]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @dataclass -class ChannelMemberLookup(DataClassJsonMixin): - archived: bool = dc_field(metadata=dc_config(field_name="archived")) - banned: bool = dc_field(metadata=dc_config(field_name="banned")) - blocked: bool = dc_field(metadata=dc_config(field_name="blocked")) - hidden: bool = dc_field(metadata=dc_config(field_name="hidden")) - pinned: bool = dc_field(metadata=dc_config(field_name="pinned")) - archived_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="archived_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - ban_expires: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="ban_expires", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - pinned_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="pinned_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - - -@dataclass -class ChannelMemberRequest(DataClassJsonMixin): - user_id: str = dc_field(metadata=dc_config(field_name="user_id")) - channel_role: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="channel_role") - ) - custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") - ) - user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") +class ChannelMessagesResponse(DataClassJsonMixin): + messages: "List[MessageResponse]" = dc_field( + metadata=dc_config(field_name="messages") ) + channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) @dataclass -class ChannelMemberResponse(DataClassJsonMixin): - banned: bool = dc_field(metadata=dc_config(field_name="banned")) - channel_role: str = dc_field(metadata=dc_config(field_name="channel_role")) +class ChannelMute(DataClassJsonMixin): created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -5232,10 +5369,6 @@ class ChannelMemberResponse(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - notifications_muted: bool = dc_field( - metadata=dc_config(field_name="notifications_muted") - ) - shadow_banned: bool = dc_field(metadata=dc_config(field_name="shadow_banned")) updated_at: datetime = dc_field( metadata=dc_config( field_name="updated_at", @@ -5244,76 +5377,17 @@ class ChannelMemberResponse(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) - archived_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="archived_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - ban_expires: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="ban_expires", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - deleted_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="deleted_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - invite_accepted_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="invite_accepted_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - invite_rejected_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="invite_rejected_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - invited: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="invited") - ) - is_moderator: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="is_moderator") - ) - pinned_at: Optional[datetime] = dc_field( + expires: Optional[datetime] = dc_field( default=None, metadata=dc_config( - field_name="pinned_at", + field_name="expires", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), ), ) - role: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="role")) - status: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="status") - ) - user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") - ) - deleted_messages: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="deleted_messages") + channel: "Optional[ChannelResponse]" = dc_field( + default=None, metadata=dc_config(field_name="channel") ) user: "Optional[UserResponse]" = dc_field( default=None, metadata=dc_config(field_name="user") @@ -5321,15 +5395,7 @@ class ChannelMemberResponse(DataClassJsonMixin): @dataclass -class ChannelMessages(DataClassJsonMixin): - messages: "List[Message]" = dc_field(metadata=dc_config(field_name="messages")) - channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") - ) - - -@dataclass -class ChannelMute(DataClassJsonMixin): +class ChannelMutedEvent(DataClassJsonMixin): created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -5338,42 +5404,26 @@ class ChannelMute(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - 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"), - ) - ) - expires: Optional[datetime] = dc_field( + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + type: str = dc_field(default="channel.muted", metadata=dc_config(field_name="type")) + received_at: Optional[datetime] = dc_field( default=None, metadata=dc_config( - field_name="expires", + field_name="received_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") + mutes: "Optional[List[ChannelMute]]" = dc_field( + default=None, metadata=dc_config(field_name="mutes") ) - user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + mute: "Optional[ChannelMute]" = dc_field( + default=None, metadata=dc_config(field_name="mute") ) - - -@dataclass -class ChannelMutedEvent(DataClassJsonMixin): - 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"), - ) + user: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="user") ) - type: str = dc_field(default="channel.muted", metadata=dc_config(field_name="type")) ChannelOwnCapabilityType = NewType("ChannelOwnCapabilityType", str) @@ -5421,22 +5471,6 @@ class ChannelOwnCapability: UPLOAD_FILE: Final[ChannelOwnCapabilityType] = "upload-file" -@dataclass -class ChannelPushPreferences(DataClassJsonMixin): - chat_level: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="chat_level") - ) - disabled_until: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="disabled_until", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - - @dataclass class ChannelPushPreferencesResponse(DataClassJsonMixin): chat_level: Optional[str] = dc_field( @@ -5680,12 +5714,6 @@ class ChannelStateResponseFields(DataClassJsonMixin): @dataclass class ChannelTruncatedEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_member_count: int = dc_field( - metadata=dc_config(field_name="channel_member_count") - ) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -5694,11 +5722,45 @@ class ChannelTruncatedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( default="channel.truncated", metadata=dc_config(field_name="type") ) - channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + message_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="message_id") + ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") + ) + message: "Optional[MessageResponse]" = dc_field( + default=None, metadata=dc_config(field_name="message") + ) + user: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="user") ) @@ -5771,6 +5833,9 @@ class ChannelTypeConfig(DataClassJsonMixin): partition_ttl: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="partition_ttl") ) + push_level: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="push_level") + ) allowed_flag_reasons: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="allowed_flag_reasons") ) @@ -5784,9 +5849,6 @@ class ChannelTypeConfig(DataClassJsonMixin): @dataclass class ChannelUnFrozenEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -5795,9 +5857,26 @@ class ChannelUnFrozenEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( default="channel.unfrozen", metadata=dc_config(field_name="type") ) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) @dataclass @@ -5810,19 +5889,32 @@ class ChannelUnmutedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( default="channel.unmuted", metadata=dc_config(field_name="type") ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + mutes: "Optional[List[ChannelMute]]" = dc_field( + default=None, metadata=dc_config(field_name="mutes") + ) + mute: "Optional[ChannelMute]" = dc_field( + default=None, metadata=dc_config(field_name="mute") + ) + user: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) @dataclass class ChannelUpdatedEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_member_count: int = dc_field( - metadata=dc_config(field_name="channel_member_count") - ) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -5831,26 +5923,50 @@ class ChannelUpdatedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( default="channel.updated", metadata=dc_config(field_name="type") ) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + message_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="message_id") + ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") ) - message: "Optional[Message]" = dc_field( + message: "Optional[MessageResponse]" = dc_field( default=None, metadata=dc_config(field_name="message") ) - user: "Optional[User]" = dc_field( + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @dataclass class ChannelVisibleEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -5859,10 +5975,38 @@ class ChannelVisibleEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( default="channel.visible", metadata=dc_config(field_name="type") ) - user: "Optional[User]" = dc_field( + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") + ) + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -6068,6 +6212,19 @@ class ClosedCaptionEvent(DataClassJsonMixin): ) +@dataclass +class ClosedCaptionRuleParameters(DataClassJsonMixin): + threshold: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="threshold") + ) + harm_labels: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="harm_labels") + ) + llm_harm_labels: "Optional[Dict[str, str]]" = dc_field( + default=None, metadata=dc_config(field_name="llm_harm_labels") + ) + + @dataclass class CollectUserFeedbackRequest(DataClassJsonMixin): rating: int = dc_field(metadata=dc_config(field_name="rating")) @@ -6378,6 +6535,15 @@ class CommentResponse(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ), ) + edited_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="edited_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) parent_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="parent_id") ) @@ -6394,7 +6560,7 @@ class CommentResponse(DataClassJsonMixin): moderation: "Optional[ModerationV2Response]" = dc_field( default=None, metadata=dc_config(field_name="moderation") ) - reaction_groups: "Optional[Dict[str, Optional[ReactionGroupResponse]]]" = dc_field( + reaction_groups: "Optional[Dict[str, FeedsReactionGroupResponse]]" = dc_field( default=None, metadata=dc_config(field_name="reaction_groups") ) @@ -6443,7 +6609,7 @@ class CompositeRecordingResponse(DataClassJsonMixin): @dataclass -class ConfigOverrides(DataClassJsonMixin): +class ConfigOverridesRequest(DataClassJsonMixin): blocklist: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="blocklist") ) @@ -6515,6 +6681,9 @@ class ConfigResponse(DataClassJsonMixin): ai_image_config: "Optional[AIImageConfig]" = dc_field( default=None, metadata=dc_config(field_name="ai_image_config") ) + ai_image_subclassifications: "Optional[Dict[str, List[str]]]" = dc_field( + default=None, metadata=dc_config(field_name="ai_image_subclassifications") + ) ai_text_config: "Optional[AITextConfig]" = dc_field( default=None, metadata=dc_config(field_name="ai_text_config") ) @@ -6559,7 +6728,7 @@ class ContentCountRuleParameters(DataClassJsonMixin): @dataclass -class Coordinates(DataClassJsonMixin): +class CoordinatesResponse(DataClassJsonMixin): latitude: float = dc_field(metadata=dc_config(field_name="latitude")) longitude: float = dc_field(metadata=dc_config(field_name="longitude")) @@ -6608,7 +6777,7 @@ class CreateCallTypeRequest(DataClassJsonMixin): grants: "Optional[Dict[str, List[str]]]" = dc_field( default=None, metadata=dc_config(field_name="grants") ) - notification_settings: "Optional[NotificationSettings]" = dc_field( + notification_settings: "Optional[NotificationSettingsRequest]" = dc_field( default=None, metadata=dc_config(field_name="notification_settings") ) settings: "Optional[CallSettingsRequest]" = dc_field( @@ -6637,7 +6806,7 @@ class CreateCallTypeResponse(DataClassJsonMixin): ) ) grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) - notification_settings: "NotificationSettings" = dc_field( + notification_settings: "NotificationSettingsResponse" = dc_field( metadata=dc_config(field_name="notification_settings") ) settings: "CallSettingsResponse" = dc_field( @@ -6692,6 +6861,9 @@ class CreateChannelTypeRequest(DataClassJsonMixin): polls: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="polls") ) + push_level: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="push_level") + ) push_notifications: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="push_notifications") ) @@ -6810,6 +6982,9 @@ class CreateChannelTypeResponse(DataClassJsonMixin): partition_ttl: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="partition_ttl") ) + push_level: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="push_level") + ) allowed_flag_reasons: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="allowed_flag_reasons") ) @@ -6985,6 +7160,9 @@ class CreateGuestResponse(DataClassJsonMixin): class CreateImportRequest(DataClassJsonMixin): mode: str = dc_field(metadata=dc_config(field_name="mode")) path: str = dc_field(metadata=dc_config(field_name="path")) + merge_custom: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="merge_custom") + ) @dataclass @@ -7175,7 +7353,30 @@ class CreateSIPTrunkResponse(DataClassJsonMixin): @dataclass -class CustomActionRequest(DataClassJsonMixin): +class CreateUserGroupRequest(DataClassJsonMixin): + name: str = dc_field(metadata=dc_config(field_name="name")) + description: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="description") + ) + id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) + team_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="team_id") + ) + member_ids: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="member_ids") + ) + + +@dataclass +class CreateUserGroupResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + user_group: "Optional[UserGroupResponse]" = dc_field( + default=None, metadata=dc_config(field_name="user_group") + ) + + +@dataclass +class CustomActionRequestPayload(DataClassJsonMixin): id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) options: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="options") @@ -7207,7 +7408,7 @@ class CustomCheckRequest(DataClassJsonMixin): user_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="user_id") ) - moderation_payload: "Optional[ModerationPayload]" = dc_field( + moderation_payload: "Optional[ModerationPayloadRequest]" = dc_field( default=None, metadata=dc_config(field_name="moderation_payload") ) user: "Optional[UserRequest]" = dc_field( @@ -7225,6 +7426,29 @@ class CustomCheckResponse(DataClassJsonMixin): ) +@dataclass +class CustomEvent(DataClassJsonMixin): + 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"), + ) + ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + type: str = dc_field(default="*", metadata=dc_config(field_name="type")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + + @dataclass class CustomVideoEvent(DataClassJsonMixin): call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) @@ -7293,6 +7517,12 @@ class DailyMetricStatsResponse(DataClassJsonMixin): ) +@dataclass +class DailyValue(DataClassJsonMixin): + date: str = dc_field(metadata=dc_config(field_name="date")) + value: int = dc_field(metadata=dc_config(field_name="value")) + + @dataclass class Data(DataClassJsonMixin): id: str = dc_field(metadata=dc_config(field_name="id")) @@ -7370,6 +7600,9 @@ class DecayFunctionConfig(DataClassJsonMixin): @dataclass class DeleteActivitiesRequest(DataClassJsonMixin): ids: List[str] = dc_field(metadata=dc_config(field_name="ids")) + delete_notification_activity: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="delete_notification_activity") + ) hard_delete: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="hard_delete") ) @@ -7397,7 +7630,13 @@ class DeleteActivityReactionResponse(DataClassJsonMixin): @dataclass -class DeleteActivityRequest(DataClassJsonMixin): +class DeleteActivityRequestPayload(DataClassJsonMixin): + entity_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_id") + ) + entity_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_type") + ) hard_delete: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="hard_delete") ) @@ -7492,7 +7731,13 @@ class DeleteCommentReactionResponse(DataClassJsonMixin): @dataclass -class DeleteCommentRequest(DataClassJsonMixin): +class DeleteCommentRequestPayload(DataClassJsonMixin): + entity_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_id") + ) + entity_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_type") + ) hard_delete: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="hard_delete") ) @@ -7562,7 +7807,13 @@ class DeleteImportV2TaskResponse(DataClassJsonMixin): @dataclass -class DeleteMessageRequest(DataClassJsonMixin): +class DeleteMessageRequestPayload(DataClassJsonMixin): + entity_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_id") + ) + entity_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_type") + ) hard_delete: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="hard_delete") ) @@ -7593,7 +7844,13 @@ class DeleteModerationTemplateResponse(DataClassJsonMixin): @dataclass -class DeleteReactionRequest(DataClassJsonMixin): +class DeleteReactionRequestPayload(DataClassJsonMixin): + entity_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_id") + ) + entity_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_type") + ) hard_delete: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="hard_delete") ) @@ -7640,13 +7897,19 @@ class DeleteTranscriptionResponse(DataClassJsonMixin): @dataclass -class DeleteUserRequest(DataClassJsonMixin): +class DeleteUserRequestPayload(DataClassJsonMixin): delete_conversation_channels: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="delete_conversation_channels") ) delete_feeds_content: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="delete_feeds_content") ) + entity_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_id") + ) + entity_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_type") + ) hard_delete: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="hard_delete") ) @@ -7694,13 +7957,6 @@ class DeliveredMessagePayload(DataClassJsonMixin): id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) -@dataclass -class DeliveryReceipts(DataClassJsonMixin): - enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") - ) - - @dataclass class DeliveryReceiptsResponse(DataClassJsonMixin): enabled: Optional[bool] = dc_field( @@ -7708,62 +7964,6 @@ class DeliveryReceiptsResponse(DataClassJsonMixin): ) -@dataclass -class DenormalizedChannelFields(DataClassJsonMixin): - created_at: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="created_at") - ) - created_by_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="created_by_id") - ) - disabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="disabled") - ) - frozen: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="frozen") - ) - id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) - last_message_at: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="last_message_at") - ) - member_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="member_count") - ) - team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) - updated_at: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="updated_at") - ) - custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") - ) - - -@dataclass -class Device(DataClassJsonMixin): - 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"), - ) - ) - id: str = dc_field(metadata=dc_config(field_name="id")) - push_provider: str = dc_field(metadata=dc_config(field_name="push_provider")) - user_id: str = dc_field(metadata=dc_config(field_name="user_id")) - disabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="disabled") - ) - disabled_reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="disabled_reason") - ) - push_provider_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="push_provider_name") - ) - voip: Optional[bool] = dc_field(default=None, metadata=dc_config(field_name="voip")) - - @dataclass class DeviceDataResponse(DataClassJsonMixin): name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) @@ -8299,6 +8499,26 @@ class EventNotificationSettings(DataClassJsonMixin): fcm: "FCM" = dc_field(metadata=dc_config(field_name="fcm")) +@dataclass +class EventNotificationSettingsRequest(DataClassJsonMixin): + enabled: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="enabled") + ) + apns: "Optional[APNSPayload]" = dc_field( + default=None, metadata=dc_config(field_name="apns") + ) + fcm: "Optional[FCMPayload]" = dc_field( + default=None, metadata=dc_config(field_name="fcm") + ) + + +@dataclass +class EventNotificationSettingsResponse(DataClassJsonMixin): + enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) + apns: "APNSPayload" = dc_field(metadata=dc_config(field_name="apns")) + fcm: "FCMPayload" = dc_field(metadata=dc_config(field_name="fcm")) + + @dataclass class EventRequest(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) @@ -8401,6 +8621,13 @@ class FCM(DataClassJsonMixin): ) +@dataclass +class FCMPayload(DataClassJsonMixin): + data: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="data") + ) + + @dataclass class FailedChannelUpdates(DataClassJsonMixin): reason: str = dc_field(metadata=dc_config(field_name="reason")) @@ -8663,6 +8890,36 @@ class FeedGroupResponse(DataClassJsonMixin): ) +@dataclass +class FeedGroupRestoredEvent(DataClassJsonMixin): + 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"), + ) + ) + fid: str = dc_field(metadata=dc_config(field_name="fid")) + group_id: str = dc_field(metadata=dc_config(field_name="group_id")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + type: str = dc_field( + default="feeds.feed_group.restored", metadata=dc_config(field_name="type") + ) + feed_visibility: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="feed_visibility") + ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + + @dataclass class FeedInput(DataClassJsonMixin): description: Optional[str] = dc_field( @@ -9125,9 +9382,11 @@ class FeedVisibilityResponse(DataClassJsonMixin): @dataclass -class FeedsModerationTemplateConfig(DataClassJsonMixin): - config_key: str = dc_field(metadata=dc_config(field_name="config_key")) +class FeedsModerationTemplateConfigPayload(DataClassJsonMixin): data_types: "Dict[str, str]" = dc_field(metadata=dc_config(field_name="data_types")) + config_key: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="config_key") + ) @dataclass @@ -9163,6 +9422,9 @@ class FeedsPreferencesResponse(DataClassJsonMixin): comment_reaction: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="comment_reaction") ) + comment_reply: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="comment_reply") + ) follow: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="follow") ) @@ -9177,6 +9439,27 @@ class FeedsPreferencesResponse(DataClassJsonMixin): ) +@dataclass +class FeedsReactionGroupResponse(DataClassJsonMixin): + count: int = dc_field(metadata=dc_config(field_name="count")) + first_reaction_at: datetime = dc_field( + metadata=dc_config( + field_name="first_reaction_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ) + ) + last_reaction_at: datetime = dc_field( + metadata=dc_config( + field_name="last_reaction_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ) + ) + + @dataclass class FeedsReactionResponse(DataClassJsonMixin): activity_id: str = dc_field(metadata=dc_config(field_name="activity_id")) @@ -9297,80 +9580,6 @@ class FirebaseConfigFields(DataClassJsonMixin): ) -@dataclass -class Flag(DataClassJsonMixin): - 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_automod: bool = dc_field( - metadata=dc_config(field_name="created_by_automod") - ) - 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"), - ) - ) - approved_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="approved_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") - ) - rejected_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="rejected_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - reviewed_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="reviewed_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - reviewed_by: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reviewed_by") - ) - target_message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="target_message_id") - ) - custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") - ) - details: "Optional[FlagDetails]" = dc_field( - default=None, metadata=dc_config(field_name="details") - ) - target_message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="target_message") - ) - target_user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="target_user") - ) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") - ) - - @dataclass class FlagCountRuleParameters(DataClassJsonMixin): threshold: Optional[int] = dc_field( @@ -9379,16 +9588,18 @@ class FlagCountRuleParameters(DataClassJsonMixin): @dataclass -class FlagDetails(DataClassJsonMixin): +class FlagDetailsResponse(DataClassJsonMixin): original_text: str = dc_field(metadata=dc_config(field_name="original_text")) - extra: Dict[str, object] = dc_field(metadata=dc_config(field_name="Extra")) - automod: "Optional[AutomodDetails]" = dc_field( + automod: "Optional[AutomodDetailsResponse]" = dc_field( default=None, metadata=dc_config(field_name="automod") ) + extra: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="extra") + ) @dataclass -class FlagFeedback(DataClassJsonMixin): +class FlagFeedbackResponse(DataClassJsonMixin): created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -9398,11 +9609,11 @@ class FlagFeedback(DataClassJsonMixin): ) ) message_id: str = dc_field(metadata=dc_config(field_name="message_id")) - labels: "List[Label]" = dc_field(metadata=dc_config(field_name="labels")) + labels: "List[LabelResponse]" = dc_field(metadata=dc_config(field_name="labels")) @dataclass -class FlagMessageDetails(DataClassJsonMixin): +class FlagMessageDetailsResponse(DataClassJsonMixin): pin_changed: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="pin_changed") ) @@ -9568,6 +9779,9 @@ class FollowPair(DataClassJsonMixin): class FollowRequest(DataClassJsonMixin): source: str = dc_field(metadata=dc_config(field_name="source")) target: str = dc_field(metadata=dc_config(field_name="target")) + copy_custom_to_notification: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="copy_custom_to_notification") + ) create_notification_activity: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="create_notification_activity") ) @@ -9577,6 +9791,9 @@ class FollowRequest(DataClassJsonMixin): skip_push: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_push") ) + status: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="status") + ) custom: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="custom") ) @@ -9696,6 +9913,17 @@ class FrameRecordingSettingsResponse(DataClassJsonMixin): ) +@dataclass +class FriendReactionsOptions(DataClassJsonMixin): + enabled: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="enabled") + ) + limit: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="limit") + ) + type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) + + @dataclass class FullUserResponse(DataClassJsonMixin): banned: bool = dc_field(metadata=dc_config(field_name="banned")) @@ -9800,6 +10028,39 @@ class FullUserResponse(DataClassJsonMixin): ) +@dataclass +class FutureChannelBanResponse(DataClassJsonMixin): + 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"), + ) + ) + expires: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="expires", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + reason: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="reason") + ) + shadow: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="shadow") + ) + banned_by: "Optional[UserResponse]" = dc_field( + default=None, metadata=dc_config(field_name="banned_by") + ) + user: "Optional[UserResponse]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) + + @dataclass class GeofenceResponse(DataClassJsonMixin): name: str = dc_field(metadata=dc_config(field_name="name")) @@ -9892,6 +10153,41 @@ class GetBlockedUsersResponse(DataClassJsonMixin): ) +@dataclass +class GetCallParticipantSessionMetricsResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + is_publisher: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="is_publisher") + ) + is_subscriber: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="is_subscriber") + ) + joined_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="joined_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + publisher_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="publisher_type") + ) + user_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="user_id") + ) + user_session_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="user_session_id") + ) + published_tracks: "Optional[List[PublishedTrackMetrics]]" = dc_field( + default=None, metadata=dc_config(field_name="published_tracks") + ) + client: "Optional[SessionClient]" = dc_field( + default=None, metadata=dc_config(field_name="client") + ) + + @dataclass class GetCallReportResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) @@ -9961,7 +10257,7 @@ class GetCallTypeResponse(DataClassJsonMixin): ) ) grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) - notification_settings: "NotificationSettings" = dc_field( + notification_settings: "NotificationSettingsResponse" = dc_field( metadata=dc_config(field_name="notification_settings") ) settings: "CallSettingsResponse" = dc_field( @@ -10053,6 +10349,9 @@ class GetChannelTypeResponse(DataClassJsonMixin): partition_ttl: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="partition_ttl") ) + push_level: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="push_level") + ) allowed_flag_reasons: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="allowed_flag_reasons") ) @@ -10094,6 +10393,7 @@ class GetCommandResponse(DataClassJsonMixin): @dataclass class GetCommentRepliesResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) + sort: str = dc_field(metadata=dc_config(field_name="sort")) comments: "List[ThreadedCommentResponse]" = dc_field( metadata=dc_config(field_name="comments") ) @@ -10110,6 +10410,7 @@ class GetCommentResponse(DataClassJsonMixin): @dataclass class GetCommentsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) + sort: str = dc_field(metadata=dc_config(field_name="sort")) comments: "List[ThreadedCommentResponse]" = dc_field( metadata=dc_config(field_name="comments") ) @@ -10168,16 +10469,16 @@ class GetFeedVisibilityResponse(DataClassJsonMixin): @dataclass class GetFeedsRateLimitsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) - android: "Optional[Dict[str, LimitInfo]]" = dc_field( + android: "Optional[Dict[str, LimitInfoResponse]]" = dc_field( default=None, metadata=dc_config(field_name="android") ) - ios: "Optional[Dict[str, LimitInfo]]" = dc_field( + ios: "Optional[Dict[str, LimitInfoResponse]]" = dc_field( default=None, metadata=dc_config(field_name="ios") ) - server_side: "Optional[Dict[str, LimitInfo]]" = dc_field( + server_side: "Optional[Dict[str, LimitInfoResponse]]" = dc_field( default=None, metadata=dc_config(field_name="server_side") ) - web: "Optional[Dict[str, LimitInfo]]" = dc_field( + web: "Optional[Dict[str, LimitInfoResponse]]" = dc_field( default=None, metadata=dc_config(field_name="web") ) @@ -10424,6 +10725,9 @@ class GetOrCreateFeedRequest(DataClassJsonMixin): following_pagination: "Optional[PagerRequest]" = dc_field( default=None, metadata=dc_config(field_name="following_pagination") ) + friend_reactions_options: "Optional[FriendReactionsOptions]" = dc_field( + default=None, metadata=dc_config(field_name="friend_reactions_options") + ) interest_weights: "Optional[Dict[str, float]]" = dc_field( default=None, metadata=dc_config(field_name="interest_weights") ) @@ -10497,7 +10801,7 @@ class GetOrCreateFeedViewResponse(DataClassJsonMixin): @dataclass class GetPushTemplatesResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) - templates: "List[PushTemplate]" = dc_field( + templates: "List[PushTemplateResponse]" = dc_field( metadata=dc_config(field_name="templates") ) @@ -10505,16 +10809,16 @@ class GetPushTemplatesResponse(DataClassJsonMixin): @dataclass class GetRateLimitsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) - android: "Optional[Dict[str, LimitInfo]]" = dc_field( + android: "Optional[Dict[str, LimitInfoResponse]]" = dc_field( default=None, metadata=dc_config(field_name="android") ) - ios: "Optional[Dict[str, LimitInfo]]" = dc_field( + ios: "Optional[Dict[str, LimitInfoResponse]]" = dc_field( default=None, metadata=dc_config(field_name="ios") ) - server_side: "Optional[Dict[str, LimitInfo]]" = dc_field( + server_side: "Optional[Dict[str, LimitInfoResponse]]" = dc_field( default=None, metadata=dc_config(field_name="server_side") ) - web: "Optional[Dict[str, LimitInfo]]" = dc_field( + web: "Optional[Dict[str, LimitInfoResponse]]" = dc_field( default=None, metadata=dc_config(field_name="web") ) @@ -10586,6 +10890,14 @@ class GetThreadResponse(DataClassJsonMixin): thread: "ThreadStateResponse" = dc_field(metadata=dc_config(field_name="thread")) +@dataclass +class GetUserGroupResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + user_group: "Optional[UserGroupResponse]" = dc_field( + default=None, metadata=dc_config(field_name="user_group") + ) + + @dataclass class GoLiveRequest(DataClassJsonMixin): recording_storage_name: Optional[str] = dc_field( @@ -10887,6 +11199,11 @@ class ImportV2TaskItem(DataClassJsonMixin): @dataclass class ImportV2TaskSettings(DataClassJsonMixin): + merge_custom: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="merge_custom") + ) + mode: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="mode")) + path: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="path")) skip_references_check: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_references_check") ) @@ -10909,6 +11226,9 @@ class ImportV2TaskSettingsS3(DataClassJsonMixin): @dataclass class IndividualRecordSettings(DataClassJsonMixin): mode: str = dc_field(metadata=dc_config(field_name="mode")) + output_types: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="output_types") + ) @dataclass @@ -10919,11 +11239,17 @@ class IndividualRecordingResponse(DataClassJsonMixin): @dataclass class IndividualRecordingSettingsRequest(DataClassJsonMixin): mode: str = dc_field(metadata=dc_config(field_name="mode")) + output_types: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="output_types") + ) @dataclass class IndividualRecordingSettingsResponse(DataClassJsonMixin): mode: str = dc_field(metadata=dc_config(field_name="mode")) + output_types: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="output_types") + ) @dataclass @@ -10949,6 +11275,26 @@ class IngressAudioEncodingResponse(DataClassJsonMixin): enable_dtx: bool = dc_field(metadata=dc_config(field_name="enable_dtx")) +@dataclass +class IngressErrorEvent(DataClassJsonMixin): + call_cid: str = dc_field(metadata=dc_config(field_name="call_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"), + ) + ) + error: str = dc_field(metadata=dc_config(field_name="error")) + ingress_stream_id: str = dc_field( + metadata=dc_config(field_name="ingress_stream_id") + ) + user_id: str = dc_field(metadata=dc_config(field_name="user_id")) + type: str = dc_field(default="ingress.error", metadata=dc_config(field_name="type")) + code: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="code")) + + @dataclass class IngressSettings(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) @@ -11006,27 +11352,77 @@ class IngressSourceResponse(DataClassJsonMixin): @dataclass -class IngressVideoEncodingOptions(DataClassJsonMixin): - layers: "List[IngressVideoLayer]" = dc_field( - metadata=dc_config(field_name="layers") +class IngressStartedEvent(DataClassJsonMixin): + call_cid: str = dc_field(metadata=dc_config(field_name="call_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"), + ) ) - source: "Optional[IngressSource]" = dc_field( - default=None, metadata=dc_config(field_name="source") + ingress_stream_id: str = dc_field( + metadata=dc_config(field_name="ingress_stream_id") ) - - -@dataclass -class IngressVideoEncodingOptionsRequest(DataClassJsonMixin): - layers: "List[IngressVideoLayerRequest]" = dc_field( - metadata=dc_config(field_name="layers") + publisher_type: str = dc_field(metadata=dc_config(field_name="publisher_type")) + user_id: str = dc_field(metadata=dc_config(field_name="user_id")) + type: str = dc_field( + default="ingress.started", metadata=dc_config(field_name="type") + ) + client_ip: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="client_ip") + ) + client_name: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="client_name") + ) + version: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="version") ) - source: "IngressSourceRequest" = dc_field(metadata=dc_config(field_name="source")) @dataclass -class IngressVideoEncodingResponse(DataClassJsonMixin): - layers: "List[IngressVideoLayerResponse]" = dc_field( - metadata=dc_config(field_name="layers") +class IngressStoppedEvent(DataClassJsonMixin): + call_cid: str = dc_field(metadata=dc_config(field_name="call_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"), + ) + ) + ingress_stream_id: str = dc_field( + metadata=dc_config(field_name="ingress_stream_id") + ) + user_id: str = dc_field(metadata=dc_config(field_name="user_id")) + type: str = dc_field( + default="ingress.stopped", metadata=dc_config(field_name="type") + ) + + +@dataclass +class IngressVideoEncodingOptions(DataClassJsonMixin): + layers: "List[IngressVideoLayer]" = dc_field( + metadata=dc_config(field_name="layers") + ) + source: "Optional[IngressSource]" = dc_field( + default=None, metadata=dc_config(field_name="source") + ) + + +@dataclass +class IngressVideoEncodingOptionsRequest(DataClassJsonMixin): + layers: "List[IngressVideoLayerRequest]" = dc_field( + metadata=dc_config(field_name="layers") + ) + source: "IngressSourceRequest" = dc_field(metadata=dc_config(field_name="source")) + + +@dataclass +class IngressVideoEncodingResponse(DataClassJsonMixin): + layers: "List[IngressVideoLayerResponse]" = dc_field( + metadata=dc_config(field_name="layers") ) source: "IngressSourceResponse" = dc_field(metadata=dc_config(field_name="source")) @@ -11067,6 +11463,19 @@ class JoinCallAPIMetrics(DataClassJsonMixin): ) +@dataclass +class KeyframeRuleParameters(DataClassJsonMixin): + min_confidence: Optional[float] = dc_field( + default=None, metadata=dc_config(field_name="min_confidence") + ) + threshold: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="threshold") + ) + harm_labels: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="harm_labels") + ) + + @dataclass class KickUserRequest(DataClassJsonMixin): user_id: str = dc_field(metadata=dc_config(field_name="user_id")) @@ -11138,7 +11547,7 @@ class LLMRule(DataClassJsonMixin): @dataclass -class Label(DataClassJsonMixin): +class LabelResponse(DataClassJsonMixin): name: str = dc_field(metadata=dc_config(field_name="name")) harm_labels: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="harm_labels") @@ -11202,7 +11611,7 @@ class LayoutSettingsResponse(DataClassJsonMixin): @dataclass -class LimitInfo(DataClassJsonMixin): +class LimitInfoResponse(DataClassJsonMixin): limit: int = dc_field(metadata=dc_config(field_name="limit")) remaining: int = dc_field(metadata=dc_config(field_name="remaining")) reset: int = dc_field(metadata=dc_config(field_name="reset")) @@ -11397,7 +11806,15 @@ class ListTranscriptionsResponse(DataClassJsonMixin): @dataclass -class Location(DataClassJsonMixin): +class ListUserGroupsResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + user_groups: "List[UserGroupResponse]" = dc_field( + metadata=dc_config(field_name="user_groups") + ) + + +@dataclass +class LocationResponse(DataClassJsonMixin): continent_code: str = dc_field(metadata=dc_config(field_name="continent_code")) country_iso_code: str = dc_field(metadata=dc_config(field_name="country_iso_code")) subdivision_iso_code: str = dc_field( @@ -11474,13 +11891,51 @@ class MarkReadRequest(DataClassJsonMixin): @dataclass class MarkReadResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) - event: "Optional[MessageReadEvent]" = dc_field( + event: "Optional[MarkReadResponseEvent]" = dc_field( default=None, metadata=dc_config(field_name="event") ) @dataclass -class MarkReviewedRequest(DataClassJsonMixin): +class MarkReadResponseEvent(DataClassJsonMixin): + channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) + channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) + cid: str = dc_field(metadata=dc_config(field_name="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"), + ) + ) + type: str = dc_field(metadata=dc_config(field_name="type")) + channel_last_message_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="channel_last_message_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + last_read_message_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="last_read_message_id") + ) + team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) + channel: "Optional[ChannelResponse]" = dc_field( + default=None, metadata=dc_config(field_name="channel") + ) + thread: "Optional[ThreadResponse]" = dc_field( + default=None, metadata=dc_config(field_name="thread") + ) + user: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) + + +@dataclass +class MarkReviewedRequestPayload(DataClassJsonMixin): content_to_mark_as_reviewed_limit: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="content_to_mark_as_reviewed_limit") ) @@ -11518,11 +11973,33 @@ class MarkUnreadRequest(DataClassJsonMixin): ) +@dataclass +class MaxStreakChangedEvent(DataClassJsonMixin): + 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"), + ) + ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + type: str = dc_field( + default="channel.max_streak_changed", metadata=dc_config(field_name="type") + ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + + @dataclass class MemberAddedEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -11531,21 +12008,43 @@ class MemberAddedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + member: "ChannelMemberResponse" = dc_field(metadata=dc_config(field_name="member")) type: str = dc_field(default="member.added", metadata=dc_config(field_name="type")) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - member: "Optional[ChannelMember]" = dc_field( - default=None, metadata=dc_config(field_name="member") + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") ) - user: "Optional[User]" = dc_field( + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @dataclass class MemberRemovedEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -11554,13 +12053,39 @@ class MemberRemovedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + member: "ChannelMemberResponse" = dc_field(metadata=dc_config(field_name="member")) type: str = dc_field( default="member.removed", metadata=dc_config(field_name="type") ) - member: "Optional[ChannelMember]" = dc_field( - default=None, metadata=dc_config(field_name="member") + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") ) - user: "Optional[User]" = dc_field( + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") + ) + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -11609,9 +12134,6 @@ class MemberResponse(DataClassJsonMixin): @dataclass class MemberUpdatedEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -11620,14 +12142,39 @@ class MemberUpdatedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + member: "ChannelMemberResponse" = dc_field(metadata=dc_config(field_name="member")) type: str = dc_field( default="member.updated", metadata=dc_config(field_name="type") ) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - member: "Optional[ChannelMember]" = dc_field( - default=None, metadata=dc_config(field_name="member") + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") ) - user: "Optional[User]" = dc_field( + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -11671,8 +12218,43 @@ class MembershipLevelResponse(DataClassJsonMixin): @dataclass -class Message(DataClassJsonMixin): - cid: str = dc_field(metadata=dc_config(field_name="cid")) +class MessageActionRequest(DataClassJsonMixin): + form_data: "Dict[str, str]" = dc_field(metadata=dc_config(field_name="form_data")) + user_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="user_id") + ) + user: "Optional[UserRequest]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) + + +@dataclass +class MessageActionResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + message: "Optional[MessageResponse]" = dc_field( + default=None, metadata=dc_config(field_name="message") + ) + + +@dataclass +class MessageChangeSet(DataClassJsonMixin): + attachments: bool = dc_field(metadata=dc_config(field_name="attachments")) + custom: bool = dc_field(metadata=dc_config(field_name="custom")) + html: bool = dc_field(metadata=dc_config(field_name="html")) + mentioned_user_ids: bool = dc_field( + metadata=dc_config(field_name="mentioned_user_ids") + ) + mml: bool = dc_field(metadata=dc_config(field_name="mml")) + pin: bool = dc_field(metadata=dc_config(field_name="pin")) + quoted_message_id: bool = dc_field( + metadata=dc_config(field_name="quoted_message_id") + ) + silent: bool = dc_field(metadata=dc_config(field_name="silent")) + text: bool = dc_field(metadata=dc_config(field_name="text")) + + +@dataclass +class MessageDeletedEvent(DataClassJsonMixin): created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -11681,199 +12263,43 @@ class Message(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - deleted_reply_count: int = dc_field( - metadata=dc_config(field_name="deleted_reply_count") + hard_delete: bool = dc_field(metadata=dc_config(field_name="hard_delete")) + message_id: str = dc_field(metadata=dc_config(field_name="message_id")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + message: "MessageResponse" = dc_field(metadata=dc_config(field_name="message")) + type: str = dc_field( + default="message.deleted", metadata=dc_config(field_name="type") ) - html: str = dc_field(metadata=dc_config(field_name="html")) - id: str = dc_field(metadata=dc_config(field_name="id")) - pinned: bool = dc_field(metadata=dc_config(field_name="pinned")) - reply_count: int = dc_field(metadata=dc_config(field_name="reply_count")) - shadowed: bool = dc_field(metadata=dc_config(field_name="shadowed")) - silent: bool = dc_field(metadata=dc_config(field_name="silent")) - text: str = dc_field(metadata=dc_config(field_name="text")) - type: str = dc_field(metadata=dc_config(field_name="type")) - 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"), - ) - ) - attachments: "List[Attachment]" = dc_field( - metadata=dc_config(field_name="attachments") - ) - latest_reactions: "List[Reaction]" = dc_field( - metadata=dc_config(field_name="latest_reactions") - ) - mentioned_users: "List[User]" = dc_field( - metadata=dc_config(field_name="mentioned_users") - ) - own_reactions: "List[Reaction]" = dc_field( - metadata=dc_config(field_name="own_reactions") - ) - restricted_visibility: List[str] = dc_field( - metadata=dc_config(field_name="restricted_visibility") - ) - custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) - reaction_counts: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="reaction_counts") - ) - reaction_groups: "Dict[str, Optional[ReactionGroupResponse]]" = dc_field( - metadata=dc_config(field_name="reaction_groups") - ) - reaction_scores: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="reaction_scores") + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") ) - before_message_send_failed: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="before_message_send_failed") + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") ) - command: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="command") + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") ) - deleted_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="deleted_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) deleted_for_me: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="deleted_for_me") ) - message_text_updated_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="message_text_updated_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - mml: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="mml")) - parent_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="parent_id") - ) - pin_expires: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="pin_expires", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - pinned_at: Optional[datetime] = dc_field( + received_at: Optional[datetime] = dc_field( default=None, metadata=dc_config( - field_name="pinned_at", + field_name="received_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), ), ) - poll_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="poll_id") - ) - quoted_message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quoted_message_id") - ) - show_in_channel: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="show_in_channel") - ) - thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") - ) - i18n: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="i18n") - ) - image_labels: "Optional[Dict[str, List[str]]]" = dc_field( - default=None, metadata=dc_config(field_name="image_labels") - ) - member: "Optional[ChannelMember]" = dc_field( - default=None, metadata=dc_config(field_name="member") - ) - moderation: "Optional[ModerationV2Response]" = dc_field( - default=None, metadata=dc_config(field_name="moderation") - ) - pinned_by: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="pinned_by") - ) - poll: "Optional[Poll]" = dc_field( - default=None, metadata=dc_config(field_name="poll") - ) - quoted_message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="quoted_message") - ) - 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") - ) - - -@dataclass -class MessageActionRequest(DataClassJsonMixin): - form_data: "Dict[str, str]" = dc_field(metadata=dc_config(field_name="form_data")) - user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") - ) - user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") - ) - - -@dataclass -class MessageChangeSet(DataClassJsonMixin): - attachments: bool = dc_field(metadata=dc_config(field_name="attachments")) - custom: bool = dc_field(metadata=dc_config(field_name="custom")) - html: bool = dc_field(metadata=dc_config(field_name="html")) - mentioned_user_ids: bool = dc_field( - metadata=dc_config(field_name="mentioned_user_ids") - ) - mml: bool = dc_field(metadata=dc_config(field_name="mml")) - pin: bool = dc_field(metadata=dc_config(field_name="pin")) - quoted_message_id: bool = dc_field( - metadata=dc_config(field_name="quoted_message_id") - ) - silent: bool = dc_field(metadata=dc_config(field_name="silent")) - text: bool = dc_field(metadata=dc_config(field_name="text")) - - -@dataclass -class MessageDeletedEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="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"), - ) - ) - hard_delete: bool = dc_field(metadata=dc_config(field_name="hard_delete")) - type: str = dc_field( - default="message.deleted", metadata=dc_config(field_name="type") - ) - deleted_for_me: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="deleted_for_me") - ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") - ) - message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") ) - user: "Optional[User]" = dc_field( + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -11932,13 +12358,13 @@ class MessageFlagResponse(DataClassJsonMixin): custom: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="custom") ) - details: "Optional[FlagDetails]" = dc_field( + details: "Optional[FlagDetailsResponse]" = dc_field( default=None, metadata=dc_config(field_name="details") ) - message: "Optional[Message]" = dc_field( + message: "Optional[MessageResponse]" = dc_field( default=None, metadata=dc_config(field_name="message") ) - moderation_feedback: "Optional[FlagFeedback]" = dc_field( + moderation_feedback: "Optional[FlagFeedbackResponse]" = dc_field( default=None, metadata=dc_config(field_name="moderation_feedback") ) moderation_result: "Optional[MessageModerationResult]" = dc_field( @@ -11954,7 +12380,6 @@ class MessageFlagResponse(DataClassJsonMixin): @dataclass class MessageFlaggedEvent(DataClassJsonMixin): - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -11963,19 +12388,53 @@ class MessageFlaggedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + message_id: str = dc_field(metadata=dc_config(field_name="message_id")) + message: "MessageResponse" = dc_field(metadata=dc_config(field_name="message")) type: str = dc_field( default="message.flagged", metadata=dc_config(field_name="type") ) - thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") ) - flag: "Optional[Flag]" = dc_field( - default=None, metadata=dc_config(field_name="flag") + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") ) - message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") ) - user: "Optional[User]" = dc_field( + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + reason: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="reason") + ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) + total_flags: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="total_flags") + ) + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") + ) + custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="custom") + ) + details: "Optional[MessageModerationResult]" = dc_field( + default=None, metadata=dc_config(field_name="details") + ) + flag: "Optional[FlagResponse]" = dc_field( + default=None, metadata=dc_config(field_name="flag") + ) + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -12043,9 +12502,6 @@ class MessageModerationResult(DataClassJsonMixin): @dataclass class MessageNewEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -12054,18 +12510,56 @@ class MessageNewEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + message_id: str = dc_field(metadata=dc_config(field_name="message_id")) watcher_count: int = dc_field(metadata=dc_config(field_name="watcher_count")) - type: str = dc_field( - default="notification.thread_message_new", metadata=dc_config(field_name="type") + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + message: "MessageResponse" = dc_field(metadata=dc_config(field_name="message")) + type: str = dc_field(default="message.new", metadata=dc_config(field_name="type")) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + parent_author: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="parent_author") + ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - thread_participants: "Optional[List[User]]" = dc_field( + total_unread_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="total_unread_count") + ) + unread_channels: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="unread_channels") + ) + unread_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="unread_count") + ) + thread_participants: "Optional[List[UserResponseCommonFields]]" = dc_field( default=None, metadata=dc_config(field_name="thread_participants") ) - message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + channel: "Optional[ChannelResponse]" = dc_field( + default=None, metadata=dc_config(field_name="channel") ) - user: "Optional[User]" = dc_field( + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") + ) + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -12146,9 +12640,6 @@ class MessagePaginationParams(DataClassJsonMixin): @dataclass class MessageReadEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -12157,69 +12648,44 @@ class MessageReadEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field(default="message.read", metadata=dc_config(field_name="type")) - channel_last_message_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="channel_last_message_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - last_read_message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="last_read_message_id") - ) - team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") ) - thread: "Optional[ThreadResponse]" = dc_field( - default=None, metadata=dc_config(field_name="thread") + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") ) - user: "Optional[UserResponseCommonFields]" = dc_field( - default=None, metadata=dc_config(field_name="user") + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") ) - - -@dataclass -class MessageReminder(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"), - ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") ) - message_id: str = dc_field(metadata=dc_config(field_name="message_id")) - task_id: str = dc_field(metadata=dc_config(field_name="task_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"), - ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + last_read_message_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="last_read_message_id") ) - user_id: str = dc_field(metadata=dc_config(field_name="user_id")) - remind_at: Optional[datetime] = dc_field( + received_at: Optional[datetime] = dc_field( default=None, metadata=dc_config( - field_name="remind_at", + field_name="received_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), ), ) - channel: "Optional[Channel]" = dc_field( + team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) + channel: "Optional[ChannelResponse]" = dc_field( default=None, metadata=dc_config(field_name="channel") ) - message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") ) - user: "Optional[User]" = dc_field( + thread: "Optional[ThreadResponse]" = dc_field( + default=None, metadata=dc_config(field_name="thread") + ) + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -12228,6 +12694,12 @@ class MessageReminder(DataClassJsonMixin): class MessageRequest(DataClassJsonMixin): html: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="html")) id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) + mentioned_channel: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="mentioned_channel") + ) + mentioned_here: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="mentioned_here") + ) mml: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="mml")) parent_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="parent_id") @@ -12273,6 +12745,9 @@ class MessageRequest(DataClassJsonMixin): attachments: "Optional[List[Attachment]]" = dc_field( default=None, metadata=dc_config(field_name="attachments") ) + mentioned_roles: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="mentioned_roles") + ) mentioned_users: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="mentioned_users") ) @@ -12306,6 +12781,10 @@ class MessageResponse(DataClassJsonMixin): ) html: str = dc_field(metadata=dc_config(field_name="html")) id: str = dc_field(metadata=dc_config(field_name="id")) + mentioned_channel: bool = dc_field( + metadata=dc_config(field_name="mentioned_channel") + ) + mentioned_here: bool = dc_field(metadata=dc_config(field_name="mentioned_here")) pinned: bool = dc_field(metadata=dc_config(field_name="pinned")) reply_count: int = dc_field(metadata=dc_config(field_name="reply_count")) shadowed: bool = dc_field(metadata=dc_config(field_name="shadowed")) @@ -12398,6 +12877,9 @@ class MessageResponse(DataClassJsonMixin): show_in_channel: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="show_in_channel") ) + mentioned_roles: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="mentioned_roles") + ) thread_participants: "Optional[List[UserResponse]]" = dc_field( default=None, metadata=dc_config(field_name="thread_participants") ) @@ -12445,7 +12927,6 @@ class MessageStatsResponse(DataClassJsonMixin): @dataclass class MessageUnblockedEvent(DataClassJsonMixin): - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -12454,25 +12935,29 @@ class MessageUnblockedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + message_id: str = dc_field(metadata=dc_config(field_name="message_id")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + message: "MessageResponse" = dc_field(metadata=dc_config(field_name="message")) type: str = dc_field( default="message.unblocked", metadata=dc_config(field_name="type") ) - thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") - ) - message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), ) - user: "Optional[User]" = dc_field( + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @dataclass class MessageUndeletedEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -12481,18 +12966,37 @@ class MessageUndeletedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + message_id: str = dc_field(metadata=dc_config(field_name="message_id")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + message: "MessageResponse" = dc_field(metadata=dc_config(field_name="message")) type: str = dc_field( default="message.undeleted", metadata=dc_config(field_name="type") ) - team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") ) - message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") ) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") ) @@ -12508,9 +13012,6 @@ class MessageUpdate(DataClassJsonMixin): @dataclass class MessageUpdatedEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -12519,17 +13020,42 @@ class MessageUpdatedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + message_id: str = dc_field(metadata=dc_config(field_name="message_id")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + message: "MessageResponse" = dc_field(metadata=dc_config(field_name="message")) type: str = dc_field( default="message.updated", metadata=dc_config(field_name="type") ) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") ) - message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + message_update: "Optional[MessageUpdate]" = dc_field( + default=None, metadata=dc_config(field_name="message_update") ) - user: "Optional[User]" = dc_field( + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -12550,6 +13076,10 @@ class MessageWithChannelResponse(DataClassJsonMixin): ) html: str = dc_field(metadata=dc_config(field_name="html")) id: str = dc_field(metadata=dc_config(field_name="id")) + mentioned_channel: bool = dc_field( + metadata=dc_config(field_name="mentioned_channel") + ) + mentioned_here: bool = dc_field(metadata=dc_config(field_name="mentioned_here")) pinned: bool = dc_field(metadata=dc_config(field_name="pinned")) reply_count: int = dc_field(metadata=dc_config(field_name="reply_count")) shadowed: bool = dc_field(metadata=dc_config(field_name="shadowed")) @@ -12643,6 +13173,9 @@ class MessageWithChannelResponse(DataClassJsonMixin): show_in_channel: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="show_in_channel") ) + mentioned_roles: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="mentioned_roles") + ) thread_participants: "Optional[List[UserResponse]]" = dc_field( default=None, metadata=dc_config(field_name="thread_participants") ) @@ -12690,6 +13223,14 @@ class MetricDescriptor(DataClassJsonMixin): unit: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="unit")) +@dataclass +class MetricStats(DataClassJsonMixin): + total: int = dc_field(metadata=dc_config(field_name="total")) + daily: "Optional[List[DailyValue]]" = dc_field( + default=None, metadata=dc_config(field_name="daily") + ) + + @dataclass class MetricThreshold(DataClassJsonMixin): level: str = dc_field(metadata=dc_config(field_name="level")) @@ -12704,13 +13245,22 @@ class MetricThreshold(DataClassJsonMixin): @dataclass -class ModerationActionConfig(DataClassJsonMixin): +class MetricTimeSeries(DataClassJsonMixin): + data_points: "Optional[List[List[float]]]" = dc_field( + default=None, metadata=dc_config(field_name="data_points") + ) + + +@dataclass +class ModerationActionConfigResponse(DataClassJsonMixin): action: str = dc_field(metadata=dc_config(field_name="action")) description: str = dc_field(metadata=dc_config(field_name="description")) entity_type: str = dc_field(metadata=dc_config(field_name="entity_type")) icon: str = dc_field(metadata=dc_config(field_name="icon")) order: int = dc_field(metadata=dc_config(field_name="order")) - custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="custom") + ) @dataclass @@ -12853,6 +13403,12 @@ class ModerationCustomActionEvent(DataClassJsonMixin): @dataclass class ModerationDashboardPreferences(DataClassJsonMixin): + async_review_queue_upsert: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="async_review_queue_upsert") + ) + disable_audit_logs: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="disable_audit_logs") + ) disable_flagging_reviewed_entity: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="disable_flagging_reviewed_entity") ) @@ -12910,7 +13466,7 @@ class ModerationFlagResponse(DataClassJsonMixin): custom: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="custom") ) - moderation_payload: "Optional[ModerationPayload]" = dc_field( + moderation_payload: "Optional[ModerationPayloadResponse]" = dc_field( default=None, metadata=dc_config(field_name="moderation_payload") ) review_queue_item: "Optional[ReviewQueueItemResponse]" = dc_field( @@ -12923,6 +13479,7 @@ class ModerationFlagResponse(DataClassJsonMixin): @dataclass class ModerationFlaggedEvent(DataClassJsonMixin): + content_type: str = dc_field(metadata=dc_config(field_name="content_type")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -12931,15 +13488,19 @@ class ModerationFlaggedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + object_id: str = dc_field(metadata=dc_config(field_name="object_id")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( default="moderation.flagged", metadata=dc_config(field_name="type") ) - item: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="item")) - object_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="object_id") - ) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), ) @@ -12988,6 +13549,38 @@ class ModerationPayload(DataClassJsonMixin): ) +@dataclass +class ModerationPayloadRequest(DataClassJsonMixin): + images: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="images") + ) + texts: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="texts") + ) + videos: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="videos") + ) + custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="custom") + ) + + +@dataclass +class ModerationPayloadResponse(DataClassJsonMixin): + images: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="images") + ) + texts: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="texts") + ) + videos: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="videos") + ) + custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="custom") + ) + + @dataclass class ModerationResponse(DataClassJsonMixin): action: str = dc_field(metadata=dc_config(field_name="action")) @@ -12996,6 +13589,14 @@ class ModerationResponse(DataClassJsonMixin): toxic: float = dc_field(metadata=dc_config(field_name="toxic")) +@dataclass +class ModerationRuleInfo(DataClassJsonMixin): + description: str = dc_field(metadata=dc_config(field_name="description")) + id: str = dc_field(metadata=dc_config(field_name="id")) + name: str = dc_field(metadata=dc_config(field_name="name")) + type: str = dc_field(metadata=dc_config(field_name="type")) + + @dataclass class ModerationRuleV2Response(DataClassJsonMixin): created_at: datetime = dc_field( @@ -13021,33 +13622,79 @@ class ModerationRuleV2Response(DataClassJsonMixin): ) ) config_keys: List[str] = dc_field(metadata=dc_config(field_name="config_keys")) - action: "RuleBuilderAction" = dc_field(metadata=dc_config(field_name="action")) cooldown_period: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="cooldown_period") ) logic: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="logic") ) + action_sequences: "Optional[List[CallRuleActionSequence]]" = dc_field( + default=None, metadata=dc_config(field_name="action_sequences") + ) conditions: "Optional[List[RuleBuilderCondition]]" = dc_field( default=None, metadata=dc_config(field_name="conditions") ) groups: "Optional[List[RuleBuilderConditionGroup]]" = dc_field( default=None, metadata=dc_config(field_name="groups") ) + action: "Optional[RuleBuilderAction]" = dc_field( + default=None, metadata=dc_config(field_name="action") + ) @dataclass -class ModerationV2Response(DataClassJsonMixin): - action: str = dc_field(metadata=dc_config(field_name="action")) - original_text: str = dc_field(metadata=dc_config(field_name="original_text")) - blocklist_matched: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist_matched") - ) - platform_circumvented: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="platform_circumvented") +class ModerationRulesTriggeredEvent(DataClassJsonMixin): + 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"), + ) ) - semantic_filter_matched: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="semantic_filter_matched") + entity_id: str = dc_field(metadata=dc_config(field_name="entity_id")) + entity_type: str = dc_field(metadata=dc_config(field_name="entity_type")) + user_id: str = dc_field(metadata=dc_config(field_name="user_id")) + triggered_actions: List[str] = dc_field( + metadata=dc_config(field_name="triggered_actions") + ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + rule: "ModerationRuleInfo" = dc_field(metadata=dc_config(field_name="rule")) + type: str = dc_field( + default="moderation_rule.triggered", metadata=dc_config(field_name="type") + ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + review_queue_item_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="review_queue_item_id") + ) + violation_number: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="violation_number") + ) + + +@dataclass +class ModerationV2Response(DataClassJsonMixin): + action: str = dc_field(metadata=dc_config(field_name="action")) + original_text: str = dc_field(metadata=dc_config(field_name="original_text")) + blocklist_matched: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="blocklist_matched") + ) + platform_circumvented: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="platform_circumvented") + ) + semantic_filter_matched: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="semantic_filter_matched") + ) + blocklists_matched: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="blocklists_matched") ) image_harms: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="image_harms") @@ -13082,7 +13729,7 @@ class MuteChannelResponse(DataClassJsonMixin): channel_mute: "Optional[ChannelMute]" = dc_field( default=None, metadata=dc_config(field_name="channel_mute") ) - own_user: "Optional[OwnUser]" = dc_field( + own_user: "Optional[OwnUserResponse]" = dc_field( default=None, metadata=dc_config(field_name="own_user") ) @@ -13104,13 +13751,13 @@ class MuteRequest(DataClassJsonMixin): @dataclass class MuteResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) - mutes: "Optional[List[UserMute]]" = dc_field( + mutes: "Optional[List[UserMuteResponse]]" = dc_field( default=None, metadata=dc_config(field_name="mutes") ) non_existing_users: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="non_existing_users") ) - own_user: "Optional[OwnUser]" = dc_field( + own_user: "Optional[OwnUserResponse]" = dc_field( default=None, metadata=dc_config(field_name="own_user") ) @@ -13242,12 +13889,6 @@ class NotificationFeedUpdatedEvent(DataClassJsonMixin): @dataclass class NotificationMarkUnreadEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_member_count: int = dc_field( - metadata=dc_config(field_name="channel_member_count") - ) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -13256,42 +13897,93 @@ class NotificationMarkUnreadEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - first_unread_message_id: str = dc_field( - metadata=dc_config(field_name="first_unread_message_id") + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + type: str = dc_field( + default="notification.mark_unread", metadata=dc_config(field_name="type") ) - last_read_at: datetime = dc_field( + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + first_unread_message_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="first_unread_message_id") + ) + last_read_at: Optional[datetime] = dc_field( + default=None, metadata=dc_config( field_name="last_read_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) - ) - total_unread_count: int = dc_field( - metadata=dc_config(field_name="total_unread_count") - ) - unread_channels: int = dc_field(metadata=dc_config(field_name="unread_channels")) - unread_count: int = dc_field(metadata=dc_config(field_name="unread_count")) - unread_messages: int = dc_field(metadata=dc_config(field_name="unread_messages")) - unread_threads: int = dc_field(metadata=dc_config(field_name="unread_threads")) - type: str = dc_field( - default="notification.mark_unread", metadata=dc_config(field_name="type") + ), ) last_read_message_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="last_read_message_id") ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) thread_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="thread_id") ) + total_unread_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="total_unread_count") + ) + unread_channels: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="unread_channels") + ) + unread_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="unread_count") + ) + unread_messages: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="unread_messages") + ) + unread_thread_messages: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="unread_thread_messages") + ) + unread_threads: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="unread_threads") + ) channel: "Optional[ChannelResponse]" = dc_field( default=None, metadata=dc_config(field_name="channel") ) - user: "Optional[User]" = dc_field( + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") + ) + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) +@dataclass +class NotificationParentActivity(DataClassJsonMixin): + id: str = dc_field(metadata=dc_config(field_name="id")) + text: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="text")) + type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) + user_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="user_id") + ) + attachments: "Optional[List[Attachment]]" = dc_field( + default=None, metadata=dc_config(field_name="attachments") + ) + + @dataclass class NotificationSettings(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) @@ -13312,6 +14004,48 @@ class NotificationSettings(DataClassJsonMixin): ) +@dataclass +class NotificationSettingsRequest(DataClassJsonMixin): + enabled: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="enabled") + ) + call_live_started: "Optional[EventNotificationSettingsRequest]" = dc_field( + default=None, metadata=dc_config(field_name="call_live_started") + ) + call_missed: "Optional[EventNotificationSettingsRequest]" = dc_field( + default=None, metadata=dc_config(field_name="call_missed") + ) + call_notification: "Optional[EventNotificationSettingsRequest]" = dc_field( + default=None, metadata=dc_config(field_name="call_notification") + ) + call_ring: "Optional[EventNotificationSettingsRequest]" = dc_field( + default=None, metadata=dc_config(field_name="call_ring") + ) + session_started: "Optional[EventNotificationSettingsRequest]" = dc_field( + default=None, metadata=dc_config(field_name="session_started") + ) + + +@dataclass +class NotificationSettingsResponse(DataClassJsonMixin): + enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) + call_live_started: "EventNotificationSettingsResponse" = dc_field( + metadata=dc_config(field_name="call_live_started") + ) + call_missed: "EventNotificationSettingsResponse" = dc_field( + metadata=dc_config(field_name="call_missed") + ) + call_notification: "EventNotificationSettingsResponse" = dc_field( + metadata=dc_config(field_name="call_notification") + ) + call_ring: "EventNotificationSettingsResponse" = dc_field( + metadata=dc_config(field_name="call_ring") + ) + session_started: "EventNotificationSettingsResponse" = dc_field( + metadata=dc_config(field_name="session_started") + ) + + @dataclass class NotificationStatusResponse(DataClassJsonMixin): unread: int = dc_field(metadata=dc_config(field_name="unread")) @@ -13357,6 +14091,68 @@ class NotificationTarget(DataClassJsonMixin): comment: "Optional[NotificationComment]" = dc_field( default=None, metadata=dc_config(field_name="comment") ) + parent_activity: "Optional[NotificationParentActivity]" = dc_field( + default=None, metadata=dc_config(field_name="parent_activity") + ) + + +@dataclass +class NotificationThreadMessageNewEvent(DataClassJsonMixin): + 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"), + ) + ) + message_id: str = dc_field(metadata=dc_config(field_name="message_id")) + thread_id: str = dc_field(metadata=dc_config(field_name="thread_id")) + watcher_count: int = dc_field(metadata=dc_config(field_name="watcher_count")) + channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + message: "MessageResponse" = dc_field(metadata=dc_config(field_name="message")) + type: str = dc_field( + default="notification.thread_message_new", metadata=dc_config(field_name="type") + ) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + parent_author: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="parent_author") + ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) + unread_thread_messages: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="unread_thread_messages") + ) + unread_threads: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="unread_threads") + ) + thread_participants: "Optional[List[UserResponseCommonFields]]" = dc_field( + default=None, metadata=dc_config(field_name="thread_participants") + ) + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") + ) @dataclass @@ -13456,7 +14252,7 @@ class OwnCapability: @dataclass -class OwnUser(DataClassJsonMixin): +class OwnUserResponse(DataClassJsonMixin): banned: bool = dc_field(metadata=dc_config(field_name="banned")) created_at: datetime = dc_field( metadata=dc_config( @@ -13467,6 +14263,7 @@ class OwnUser(DataClassJsonMixin): ) ) id: str = dc_field(metadata=dc_config(field_name="id")) + invisible: bool = dc_field(metadata=dc_config(field_name="invisible")) language: str = dc_field(metadata=dc_config(field_name="language")) online: bool = dc_field(metadata=dc_config(field_name="online")) role: str = dc_field(metadata=dc_config(field_name="role")) @@ -13487,12 +14284,10 @@ class OwnUser(DataClassJsonMixin): channel_mutes: "List[ChannelMute]" = dc_field( metadata=dc_config(field_name="channel_mutes") ) - devices: "List[Device]" = dc_field(metadata=dc_config(field_name="devices")) - mutes: "List[UserMute]" = dc_field(metadata=dc_config(field_name="mutes")) + devices: "List[DeviceResponse]" = dc_field(metadata=dc_config(field_name="devices")) + mutes: "List[UserMuteResponse]" = dc_field(metadata=dc_config(field_name="mutes")) + teams: List[str] = dc_field(metadata=dc_config(field_name="teams")) 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") - ) avg_response_time: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="avg_response_time") ) @@ -13514,8 +14309,8 @@ class OwnUser(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ), ) - invisible: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="invisible") + image: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="image") ) last_active: Optional[datetime] = dc_field( default=None, @@ -13526,10 +14321,11 @@ class OwnUser(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ), ) - last_engaged_at: Optional[datetime] = dc_field( + name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) + revoke_tokens_issued_before: Optional[datetime] = dc_field( default=None, metadata=dc_config( - field_name="last_engaged_at", + field_name="revoke_tokens_issued_before", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), @@ -13541,154 +14337,63 @@ class OwnUser(DataClassJsonMixin): latest_hidden_channels: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="latest_hidden_channels") ) - teams: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="teams") - ) - privacy_settings: "Optional[PrivacySettings]" = dc_field( + privacy_settings: "Optional[PrivacySettingsResponse]" = dc_field( default=None, metadata=dc_config(field_name="privacy_settings") ) - push_preferences: "Optional[PushPreferences]" = dc_field( + push_preferences: "Optional[PushPreferencesResponse]" = dc_field( default=None, metadata=dc_config(field_name="push_preferences") ) 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 -class OwnUserResponse(DataClassJsonMixin): - banned: bool = dc_field(metadata=dc_config(field_name="banned")) - 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"), - ) +class PagerRequest(DataClassJsonMixin): + limit: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="limit") ) - id: str = dc_field(metadata=dc_config(field_name="id")) - invisible: bool = dc_field(metadata=dc_config(field_name="invisible")) - language: str = dc_field(metadata=dc_config(field_name="language")) - online: bool = dc_field(metadata=dc_config(field_name="online")) - role: str = dc_field(metadata=dc_config(field_name="role")) - total_unread_count: int = dc_field( - metadata=dc_config(field_name="total_unread_count") + next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) + prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) + + +@dataclass +class PagerResponse(DataClassJsonMixin): + next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) + prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) + + +@dataclass +class PaginationParams(DataClassJsonMixin): + limit: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="limit") ) - unread_channels: int = dc_field(metadata=dc_config(field_name="unread_channels")) - unread_count: int = dc_field(metadata=dc_config(field_name="unread_count")) - unread_threads: int = dc_field(metadata=dc_config(field_name="unread_threads")) - updated_at: datetime = dc_field( + offset: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="offset") + ) + + +@dataclass +class ParsedPredefinedFilterResponse(DataClassJsonMixin): + name: str = dc_field(metadata=dc_config(field_name="name")) + filter: Dict[str, object] = dc_field(metadata=dc_config(field_name="filter")) + sort: "Optional[List[SortParamRequest]]" = dc_field( + default=None, metadata=dc_config(field_name="sort") + ) + + +@dataclass +class ParticipantCountByMinuteResponse(DataClassJsonMixin): + first: int = dc_field(metadata=dc_config(field_name="first")) + last: int = dc_field(metadata=dc_config(field_name="last")) + max: int = dc_field(metadata=dc_config(field_name="max")) + min: int = dc_field(metadata=dc_config(field_name="min")) + start_ts: datetime = dc_field( metadata=dc_config( - field_name="updated_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - channel_mutes: "List[ChannelMute]" = dc_field( - metadata=dc_config(field_name="channel_mutes") - ) - devices: "List[DeviceResponse]" = dc_field(metadata=dc_config(field_name="devices")) - mutes: "List[UserMuteResponse]" = dc_field(metadata=dc_config(field_name="mutes")) - teams: List[str] = dc_field(metadata=dc_config(field_name="teams")) - custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) - avg_response_time: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="avg_response_time") - ) - deactivated_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="deactivated_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - deleted_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="deleted_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - image: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="image") - ) - last_active: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="last_active", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) - revoke_tokens_issued_before: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="revoke_tokens_issued_before", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - blocked_user_ids: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="blocked_user_ids") - ) - latest_hidden_channels: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="latest_hidden_channels") - ) - privacy_settings: "Optional[PrivacySettingsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="privacy_settings") - ) - push_preferences: "Optional[PushPreferencesResponse]" = dc_field( - default=None, metadata=dc_config(field_name="push_preferences") - ) - 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 -class PagerRequest(DataClassJsonMixin): - limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") - ) - next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) - prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) - - -@dataclass -class PagerResponse(DataClassJsonMixin): - next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) - prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) - - -@dataclass -class PaginationParams(DataClassJsonMixin): - limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") - ) - offset: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="offset") - ) - - -@dataclass -class ParticipantCountByMinuteResponse(DataClassJsonMixin): - first: int = dc_field(metadata=dc_config(field_name="first")) - last: int = dc_field(metadata=dc_config(field_name="last")) - max: int = dc_field(metadata=dc_config(field_name="max")) - min: int = dc_field(metadata=dc_config(field_name="min")) - start_ts: datetime = dc_field( - metadata=dc_config( - field_name="start_ts", + field_name="start_ts", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), @@ -13852,6 +14557,35 @@ class ParticipantSeriesUserStats(DataClassJsonMixin): ) +@dataclass +class ParticipantSessionDetails(DataClassJsonMixin): + publisher_type: str = dc_field(metadata=dc_config(field_name="publisher_type")) + user_id: str = dc_field(metadata=dc_config(field_name="user_id")) + user_session_id: str = dc_field(metadata=dc_config(field_name="user_session_id")) + roles: List[str] = dc_field(metadata=dc_config(field_name="roles")) + duration_in_seconds: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="duration_in_seconds") + ) + joined_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="joined_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + left_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="left_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + + @dataclass class PendingMessageEvent(DataClassJsonMixin): created_at: datetime = dc_field( @@ -13876,16 +14610,16 @@ class PendingMessageEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ), ) - channel: "Optional[Channel]" = dc_field( + channel: "Optional[ChannelResponse]" = dc_field( default=None, metadata=dc_config(field_name="channel") ) - message: "Optional[Message]" = dc_field( + message: "Optional[MessageResponse]" = dc_field( default=None, metadata=dc_config(field_name="message") ) metadata: "Optional[Dict[str, str]]" = dc_field( default=None, metadata=dc_config(field_name="metadata") ) - user: "Optional[User]" = dc_field( + user: "Optional[UserResponse]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -14026,70 +14760,6 @@ class PolicyRequest(DataClassJsonMixin): roles: List[str] = dc_field(metadata=dc_config(field_name="roles")) -@dataclass -class Poll(DataClassJsonMixin): - allow_answers: bool = dc_field(metadata=dc_config(field_name="allow_answers")) - allow_user_suggested_options: bool = dc_field( - metadata=dc_config(field_name="allow_user_suggested_options") - ) - answers_count: int = dc_field(metadata=dc_config(field_name="answers_count")) - 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_id: str = dc_field(metadata=dc_config(field_name="created_by_id")) - description: str = dc_field(metadata=dc_config(field_name="description")) - enforce_unique_vote: bool = dc_field( - metadata=dc_config(field_name="enforce_unique_vote") - ) - id: str = dc_field(metadata=dc_config(field_name="id")) - name: str = dc_field(metadata=dc_config(field_name="name")) - 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"), - ) - ) - vote_count: int = dc_field(metadata=dc_config(field_name="vote_count")) - latest_answers: "List[PollVote]" = dc_field( - metadata=dc_config(field_name="latest_answers") - ) - options: "List[PollOption]" = dc_field(metadata=dc_config(field_name="options")) - own_votes: "List[PollVote]" = dc_field(metadata=dc_config(field_name="own_votes")) - custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="Custom")) - latest_votes_by_option: "Dict[str, List[PollVote]]" = dc_field( - metadata=dc_config(field_name="latest_votes_by_option") - ) - vote_counts_by_option: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="vote_counts_by_option") - ) - is_closed: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="is_closed") - ) - max_votes_allowed: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="max_votes_allowed") - ) - voting_visibility: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="voting_visibility") - ) - created_by: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="created_by") - ) - - -@dataclass -class PollOption(DataClassJsonMixin): - id: str = dc_field(metadata=dc_config(field_name="id")) - text: str = dc_field(metadata=dc_config(field_name="text")) - custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) - - @dataclass class PollOptionInput(DataClassJsonMixin): text: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="text")) @@ -14189,41 +14859,6 @@ class PollResponseData(DataClassJsonMixin): ) -@dataclass -class PollVote(DataClassJsonMixin): - 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"), - ) - ) - id: str = dc_field(metadata=dc_config(field_name="id")) - option_id: str = dc_field(metadata=dc_config(field_name="option_id")) - poll_id: str = dc_field(metadata=dc_config(field_name="poll_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"), - ) - ) - answer_text: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="answer_text") - ) - is_answer: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="is_answer") - ) - user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") - ) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") - ) - - @dataclass class PollVoteResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) @@ -14280,19 +14915,6 @@ class PollVotesResponse(DataClassJsonMixin): prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) -@dataclass -class PrivacySettings(DataClassJsonMixin): - delivery_receipts: "Optional[DeliveryReceipts]" = dc_field( - default=None, metadata=dc_config(field_name="delivery_receipts") - ) - read_receipts: "Optional[ReadReceipts]" = dc_field( - default=None, metadata=dc_config(field_name="read_receipts") - ) - typing_indicators: "Optional[TypingIndicators]" = dc_field( - default=None, metadata=dc_config(field_name="typing_indicators") - ) - - @dataclass class PrivacySettingsResponse(DataClassJsonMixin): delivery_receipts: "Optional[DeliveryReceiptsResponse]" = dc_field( @@ -14316,6 +14938,31 @@ class PublishedTrackFlags(DataClassJsonMixin): video: bool = dc_field(metadata=dc_config(field_name="video")) +@dataclass +class PublishedTrackMetrics(DataClassJsonMixin): + codec: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="codec") + ) + track_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="track_id") + ) + track_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="track_type") + ) + warnings: "Optional[List[SessionWarningResponse]]" = dc_field( + default=None, metadata=dc_config(field_name="warnings") + ) + bitrate: "Optional[MetricTimeSeries]" = dc_field( + default=None, metadata=dc_config(field_name="bitrate") + ) + framerate: "Optional[MetricTimeSeries]" = dc_field( + default=None, metadata=dc_config(field_name="framerate") + ) + resolution: "Optional[ResolutionMetricsTimeSeries]" = dc_field( + default=None, metadata=dc_config(field_name="resolution") + ) + + @dataclass class PublisherAllMetrics(DataClassJsonMixin): audio: "Optional[PublisherAudioMetrics]" = dc_field( @@ -14455,7 +15102,7 @@ class PushPreferenceInput(DataClassJsonMixin): @dataclass -class PushPreferences(DataClassJsonMixin): +class PushPreferencesResponse(DataClassJsonMixin): call_level: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="call_level") ) @@ -14474,32 +15121,7 @@ class PushPreferences(DataClassJsonMixin): feeds_level: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="feeds_level") ) - feeds_preferences: "Optional[FeedsPreferences]" = dc_field( - default=None, metadata=dc_config(field_name="feeds_preferences") - ) - - -@dataclass -class PushPreferencesResponse(DataClassJsonMixin): - call_level: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="call_level") - ) - chat_level: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="chat_level") - ) - disabled_until: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="disabled_until", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - feeds_level: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="feeds_level") - ) - feeds_preferences: "Optional[FeedsPreferencesResponse]" = dc_field( + feeds_preferences: "Optional[FeedsPreferencesResponse]" = dc_field( default=None, metadata=dc_config(field_name="feeds_preferences") ) @@ -14604,6 +15226,84 @@ class PushProvider(DataClassJsonMixin): ) +@dataclass +class PushProviderRequest(DataClassJsonMixin): + name: str = dc_field(metadata=dc_config(field_name="name")) + apn_auth_key: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="apn_auth_key") + ) + apn_auth_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="apn_auth_type") + ) + apn_development: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="apn_development") + ) + apn_host: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="apn_host") + ) + apn_key_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="apn_key_id") + ) + apn_notification_template: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="apn_notification_template") + ) + apn_p12_cert: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="apn_p12_cert") + ) + apn_team_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="apn_team_id") + ) + apn_topic: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="apn_topic") + ) + description: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="description") + ) + disabled_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="disabled_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + disabled_reason: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="disabled_reason") + ) + firebase_apn_template: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="firebase_apn_template") + ) + firebase_credentials: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="firebase_credentials") + ) + firebase_data_template: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="firebase_data_template") + ) + firebase_host: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="firebase_host") + ) + firebase_notification_template: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="firebase_notification_template") + ) + firebase_server_key: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="firebase_server_key") + ) + huawei_app_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="huawei_app_id") + ) + huawei_app_secret: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="huawei_app_secret") + ) + type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) + xiaomi_app_secret: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="xiaomi_app_secret") + ) + xiaomi_package_name: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="xiaomi_package_name") + ) + + @dataclass class PushProviderResponse(DataClassJsonMixin): created_at: datetime = dc_field( @@ -14729,6 +15429,34 @@ class PushTemplate(DataClassJsonMixin): ) +@dataclass +class PushTemplateResponse(DataClassJsonMixin): + 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"), + ) + ) + enable_push: bool = dc_field(metadata=dc_config(field_name="enable_push")) + event_type: str = dc_field(metadata=dc_config(field_name="event_type")) + push_provider_internal_id: str = dc_field( + metadata=dc_config(field_name="push_provider_internal_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"), + ) + ) + template: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="template") + ) + + @dataclass class QualityScoreReport(DataClassJsonMixin): histogram: "List[ReportByHistogramBucket]" = dc_field( @@ -14745,6 +15473,9 @@ class QualityScoreReportResponse(DataClassJsonMixin): @dataclass class QueryActivitiesRequest(DataClassJsonMixin): + include_expired_activities: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="include_expired_activities") + ) include_private_activities: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="include_private_activities") ) @@ -14974,6 +15705,28 @@ class QueryCallMembersResponse(DataClassJsonMixin): prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) +@dataclass +class QueryCallParticipantSessionsResponse(DataClassJsonMixin): + call_id: str = dc_field(metadata=dc_config(field_name="call_id")) + call_session_id: str = dc_field(metadata=dc_config(field_name="call_session_id")) + call_type: str = dc_field(metadata=dc_config(field_name="call_type")) + duration: int = dc_field(metadata=dc_config(field_name="duration")) + total_participant_duration: int = dc_field( + metadata=dc_config(field_name="total_participant_duration") + ) + total_participant_sessions: int = dc_field( + metadata=dc_config(field_name="total_participant_sessions") + ) + participants_sessions: "List[ParticipantSessionDetails]" = dc_field( + metadata=dc_config(field_name="participants_sessions") + ) + next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) + prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) + session: "Optional[CallSessionResponse]" = dc_field( + default=None, metadata=dc_config(field_name="session") + ) + + @dataclass class QueryCallParticipantsRequest(DataClassJsonMixin): filter_conditions: Optional[Dict[str, object]] = dc_field( @@ -15207,6 +15960,9 @@ class QueryChannelsRequest(DataClassJsonMixin): offset: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="offset") ) + predefined_filter: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="predefined_filter") + ) state: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="state") ) @@ -15219,6 +15975,12 @@ class QueryChannelsRequest(DataClassJsonMixin): filter_conditions: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="filter_conditions") ) + filter_values: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="filter_values") + ) + sort_values: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="sort_values") + ) user: "Optional[UserRequest]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -15230,6 +15992,9 @@ class QueryChannelsResponse(DataClassJsonMixin): channels: "List[ChannelStateResponseFields]" = dc_field( metadata=dc_config(field_name="channels") ) + predefined_filter: "Optional[ParsedPredefinedFilterResponse]" = dc_field( + default=None, metadata=dc_config(field_name="predefined_filter") + ) @dataclass @@ -15260,6 +16025,9 @@ class QueryCommentReactionsResponse(DataClassJsonMixin): @dataclass class QueryCommentsRequest(DataClassJsonMixin): filter: Dict[str, object] = dc_field(metadata=dc_config(field_name="filter")) + id_around: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="id_around") + ) limit: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="limit") ) @@ -15351,7 +16119,7 @@ class QueryFeedModerationTemplate(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - config: "Optional[FeedsModerationTemplateConfig]" = dc_field( + config: "Optional[FeedsModerationTemplateConfigPayload]" = dc_field( default=None, metadata=dc_config(field_name="config") ) @@ -15436,6 +16204,36 @@ class QueryFollowsResponse(DataClassJsonMixin): prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) +@dataclass +class QueryFutureChannelBansPayload(DataClassJsonMixin): + exclude_expired_bans: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="exclude_expired_bans") + ) + limit: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="limit") + ) + offset: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="offset") + ) + target_user_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="target_user_id") + ) + user_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="user_id") + ) + user: "Optional[UserRequest]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) + + +@dataclass +class QueryFutureChannelBansResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + bans: "List[FutureChannelBanResponse]" = dc_field( + metadata=dc_config(field_name="bans") + ) + + @dataclass class QueryMembersPayload(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) @@ -15580,7 +16378,7 @@ class QueryModerationFlagsRequest(DataClassJsonMixin): ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) - sort: "Optional[List[SortParam]]" = dc_field( + sort: "Optional[List[SortParamRequest]]" = dc_field( default=None, metadata=dc_config(field_name="sort") ) filter: Optional[Dict[str, object]] = dc_field( @@ -15651,6 +16449,12 @@ class QueryModerationRulesRequest(DataClassJsonMixin): @dataclass class QueryModerationRulesResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) + closed_caption_labels: List[str] = dc_field( + metadata=dc_config(field_name="closed_caption_labels") + ) + keyframe_labels: List[str] = dc_field( + metadata=dc_config(field_name="keyframe_labels") + ) rules: "List[ModerationRuleV2Response]" = dc_field( metadata=dc_config(field_name="rules") ) @@ -15661,6 +16465,31 @@ class QueryModerationRulesResponse(DataClassJsonMixin): prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) +@dataclass +class QueryPinnedActivitiesRequest(DataClassJsonMixin): + limit: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="limit") + ) + next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) + prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) + sort: "Optional[List[SortParamRequest]]" = dc_field( + default=None, metadata=dc_config(field_name="sort") + ) + filter: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="filter") + ) + + +@dataclass +class QueryPinnedActivitiesResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + pinned_activities: "List[ActivityPinResponse]" = dc_field( + metadata=dc_config(field_name="pinned_activities") + ) + next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) + prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) + + @dataclass class QueryPollVotesRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( @@ -15800,7 +16629,7 @@ class QueryReviewQueueResponse(DataClassJsonMixin): items: "List[ReviewQueueItemResponse]" = dc_field( metadata=dc_config(field_name="items") ) - action_config: "Dict[str, List[ModerationActionConfig]]" = dc_field( + action_config: "Dict[str, List[ModerationActionConfigResponse]]" = dc_field( metadata=dc_config(field_name="action_config") ) stats: Dict[str, object] = dc_field(metadata=dc_config(field_name="stats")) @@ -15859,6 +16688,30 @@ class QuerySegmentsResponse(DataClassJsonMixin): prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) +@dataclass +class QueryTeamUsageStatsRequest(DataClassJsonMixin): + end_date: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="end_date") + ) + limit: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="limit") + ) + month: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="month") + ) + next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) + start_date: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="start_date") + ) + + +@dataclass +class QueryTeamUsageStatsResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + teams: "List[TeamUsageStats]" = dc_field(metadata=dc_config(field_name="teams")) + next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) + + @dataclass class QueryThreadsRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( @@ -16055,6 +16908,7 @@ class RawRecordingSettingsResponse(DataClassJsonMixin): @dataclass class Reaction(DataClassJsonMixin): + activity_id: str = dc_field(metadata=dc_config(field_name="activity_id")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -16063,9 +16917,7 @@ class Reaction(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - message_id: str = dc_field(metadata=dc_config(field_name="message_id")) - score: int = dc_field(metadata=dc_config(field_name="score")) - type: str = dc_field(metadata=dc_config(field_name="type")) + kind: str = dc_field(metadata=dc_config(field_name="kind")) updated_at: datetime = dc_field( metadata=dc_config( field_name="updated_at", @@ -16074,9 +16926,43 @@ class Reaction(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) - user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + user_id: str = dc_field(metadata=dc_config(field_name="user_id")) + deleted_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="deleted_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) + parent: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="parent") + ) + score: Optional[float] = dc_field( + default=None, metadata=dc_config(field_name="score") + ) + target_feeds: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="target_feeds") + ) + children_counts: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="children_counts") + ) + data: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="data") + ) + latest_children: "Optional[Dict[str, List[Reaction]]]" = dc_field( + default=None, metadata=dc_config(field_name="latest_children") + ) + moderation: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="moderation") + ) + own_children: "Optional[Dict[str, List[Reaction]]]" = dc_field( + default=None, metadata=dc_config(field_name="own_children") + ) + target_feeds_extra_data: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="target_feeds_extra_data") ) user: "Optional[User]" = dc_field( default=None, metadata=dc_config(field_name="user") @@ -16085,9 +16971,6 @@ class Reaction(DataClassJsonMixin): @dataclass class ReactionDeletedEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -16096,20 +16979,50 @@ class ReactionDeletedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( default="reaction.deleted", metadata=dc_config(field_name="type") ) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + message_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="message_id") + ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - thread_participants: "Optional[List[User]]" = dc_field( + thread_participants: "Optional[List[UserResponseCommonFields]]" = dc_field( default=None, metadata=dc_config(field_name="thread_participants") ) - message: "Optional[Message]" = dc_field( + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") + ) + message: "Optional[MessageResponse]" = dc_field( default=None, metadata=dc_config(field_name="message") ) - reaction: "Optional[Reaction]" = dc_field( + reaction: "Optional[ReactionResponse]" = dc_field( default=None, metadata=dc_config(field_name="reaction") ) - user: "Optional[User]" = dc_field( + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -16138,9 +17051,6 @@ class ReactionGroupResponse(DataClassJsonMixin): @dataclass class ReactionNewEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -16149,18 +17059,48 @@ class ReactionNewEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field(default="reaction.new", metadata=dc_config(field_name="type")) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + message_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="message_id") + ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - thread_participants: "Optional[List[User]]" = dc_field( + thread_participants: "Optional[List[UserResponseCommonFields]]" = dc_field( default=None, metadata=dc_config(field_name="thread_participants") ) - message: "Optional[Message]" = dc_field( + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") + ) + message: "Optional[MessageResponse]" = dc_field( default=None, metadata=dc_config(field_name="message") ) - reaction: "Optional[Reaction]" = dc_field( + reaction: "Optional[ReactionResponse]" = dc_field( default=None, metadata=dc_config(field_name="reaction") ) - user: "Optional[User]" = dc_field( + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -16228,9 +17168,6 @@ class ReactionResponse(DataClassJsonMixin): @dataclass class ReactionUpdatedEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -16239,13 +17176,43 @@ class ReactionUpdatedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - message: "Message" = dc_field(metadata=dc_config(field_name="message")) - reaction: "Reaction" = dc_field(metadata=dc_config(field_name="reaction")) + message_id: str = dc_field(metadata=dc_config(field_name="message_id")) + channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + message: "MessageResponse" = dc_field(metadata=dc_config(field_name="message")) type: str = dc_field( default="reaction.updated", metadata=dc_config(field_name="type") ) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - user: "Optional[User]" = dc_field( + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") + ) + reaction: "Optional[ReactionResponse]" = dc_field( + default=None, metadata=dc_config(field_name="reaction") + ) + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -16295,13 +17262,8 @@ class ReadCollectionsResponse(DataClassJsonMixin): collections: "List[CollectionResponse]" = dc_field( metadata=dc_config(field_name="collections") ) - - -@dataclass -class ReadReceipts(DataClassJsonMixin): - enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") - ) + next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) + prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) @dataclass @@ -16377,7 +17339,7 @@ class RecordSettingsResponse(DataClassJsonMixin): @dataclass -class RejectAppealRequest(DataClassJsonMixin): +class RejectAppealRequestPayload(DataClassJsonMixin): decision_reason: str = dc_field(metadata=dc_config(field_name="decision_reason")) @@ -16586,6 +17548,14 @@ class ReminderUpdatedEvent(DataClassJsonMixin): ) +@dataclass +class RemoveUserGroupMembersResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + user_group: "Optional[UserGroupResponse]" = dc_field( + default=None, metadata=dc_config(field_name="user_group") + ) + + @dataclass class RepliesMeta(DataClassJsonMixin): depth_truncated: bool = dc_field(metadata=dc_config(field_name="depth_truncated")) @@ -16620,13 +17590,28 @@ class ReportResponse(DataClassJsonMixin): ) +@dataclass +class ResolutionMetricsTimeSeries(DataClassJsonMixin): + height: "Optional[MetricTimeSeries]" = dc_field( + default=None, metadata=dc_config(field_name="height") + ) + width: "Optional[MetricTimeSeries]" = dc_field( + default=None, metadata=dc_config(field_name="width") + ) + + @dataclass class ResolveSipInboundRequest(DataClassJsonMixin): sip_caller_number: str = dc_field( metadata=dc_config(field_name="sip_caller_number") ) sip_trunk_number: str = dc_field(metadata=dc_config(field_name="sip_trunk_number")) - challenge: "SIPChallenge" = dc_field(metadata=dc_config(field_name="challenge")) + challenge: "SIPChallengeRequest" = dc_field( + metadata=dc_config(field_name="challenge") + ) + routing_number: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="routing_number") + ) sip_headers: "Optional[Dict[str, str]]" = dc_field( default=None, metadata=dc_config(field_name="sip_headers") ) @@ -16652,12 +17637,41 @@ class Response(DataClassJsonMixin): @dataclass -class RestoreActionRequest(DataClassJsonMixin): +class RestoreActionRequestPayload(DataClassJsonMixin): decision_reason: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="decision_reason") ) +@dataclass +class RestoreActivityRequest(DataClassJsonMixin): + user_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="user_id") + ) + user: "Optional[UserRequest]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) + + +@dataclass +class RestoreActivityResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + activity: "ActivityResponse" = dc_field(metadata=dc_config(field_name="activity")) + + +@dataclass +class RestoreFeedGroupRequest(DataClassJsonMixin): + pass + + +@dataclass +class RestoreFeedGroupResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + feed_group: "FeedGroupResponse" = dc_field( + metadata=dc_config(field_name="feed_group") + ) + + @dataclass class RestoreUsersRequest(DataClassJsonMixin): user_ids: List[str] = dc_field(metadata=dc_config(field_name="user_ids")) @@ -16732,7 +17746,7 @@ class ReviewQueueItemResponse(DataClassJsonMixin): actions: "List[ActionLogResponse]" = dc_field( metadata=dc_config(field_name="actions") ) - bans: "List[Ban]" = dc_field(metadata=dc_config(field_name="bans")) + bans: "List[BanInfoResponse]" = dc_field(metadata=dc_config(field_name="bans")) flags: "List[ModerationFlagResponse]" = dc_field( metadata=dc_config(field_name="flags") ) @@ -16794,7 +17808,7 @@ class ReviewQueueItemResponse(DataClassJsonMixin): message: "Optional[MessageResponse]" = dc_field( default=None, metadata=dc_config(field_name="message") ) - moderation_payload: "Optional[ModerationPayload]" = dc_field( + moderation_payload: "Optional[ModerationPayloadResponse]" = dc_field( default=None, metadata=dc_config(field_name="moderation_payload") ) reaction: "Optional[Reaction]" = dc_field( @@ -16916,10 +17930,13 @@ class Role(DataClassJsonMixin): @dataclass class RuleBuilderAction(DataClassJsonMixin): - type: str = dc_field(metadata=dc_config(field_name="type")) + type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) ban_options: "Optional[BanOptions]" = dc_field( default=None, metadata=dc_config(field_name="ban_options") ) + call_options: "Optional[CallActionOptions]" = dc_field( + default=None, metadata=dc_config(field_name="call_options") + ) flag_user_options: "Optional[FlagUserOptions]" = dc_field( default=None, metadata=dc_config(field_name="flag_user_options") ) @@ -16931,6 +17948,18 @@ class RuleBuilderCondition(DataClassJsonMixin): default=None, metadata=dc_config(field_name="confidence") ) type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) + call_custom_property_params: "Optional[CallCustomPropertyParameters]" = dc_field( + default=None, metadata=dc_config(field_name="call_custom_property_params") + ) + call_type_rule_params: "Optional[CallTypeRuleParameters]" = dc_field( + default=None, metadata=dc_config(field_name="call_type_rule_params") + ) + call_violation_count_params: "Optional[CallViolationCountParameters]" = dc_field( + default=None, metadata=dc_config(field_name="call_violation_count_params") + ) + closed_caption_rule_params: "Optional[ClosedCaptionRuleParameters]" = dc_field( + default=None, metadata=dc_config(field_name="closed_caption_rule_params") + ) content_count_rule_params: "Optional[ContentCountRuleParameters]" = dc_field( default=None, metadata=dc_config(field_name="content_count_rule_params") ) @@ -16943,6 +17972,9 @@ class RuleBuilderCondition(DataClassJsonMixin): image_rule_params: "Optional[ImageRuleParameters]" = dc_field( default=None, metadata=dc_config(field_name="image_rule_params") ) + keyframe_rule_params: "Optional[KeyframeRuleParameters]" = dc_field( + default=None, metadata=dc_config(field_name="keyframe_rule_params") + ) text_content_params: "Optional[TextContentParameters]" = dc_field( default=None, metadata=dc_config(field_name="text_content_params") ) @@ -16999,7 +18031,6 @@ class RuleBuilderConfig(DataClassJsonMixin): @dataclass class RuleBuilderRule(DataClassJsonMixin): rule_type: str = dc_field(metadata=dc_config(field_name="rule_type")) - action: "RuleBuilderAction" = dc_field(metadata=dc_config(field_name="action")) cooldown_period: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="cooldown_period") ) @@ -17007,12 +18038,18 @@ class RuleBuilderRule(DataClassJsonMixin): logic: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="logic") ) + action_sequences: "Optional[List[CallRuleActionSequence]]" = dc_field( + default=None, metadata=dc_config(field_name="action_sequences") + ) conditions: "Optional[List[RuleBuilderCondition]]" = dc_field( default=None, metadata=dc_config(field_name="conditions") ) groups: "Optional[List[RuleBuilderConditionGroup]]" = dc_field( default=None, metadata=dc_config(field_name="groups") ) + action: "Optional[RuleBuilderAction]" = dc_field( + default=None, metadata=dc_config(field_name="action") + ) @dataclass @@ -17047,8 +18084,10 @@ class SDKUsageReportResponse(DataClassJsonMixin): class SFULocationResponse(DataClassJsonMixin): datacenter: str = dc_field(metadata=dc_config(field_name="datacenter")) id: str = dc_field(metadata=dc_config(field_name="id")) - coordinates: "Coordinates" = dc_field(metadata=dc_config(field_name="coordinates")) - location: "Location" = dc_field(metadata=dc_config(field_name="location")) + coordinates: "CoordinatesResponse" = dc_field( + metadata=dc_config(field_name="coordinates") + ) + location: "LocationResponse" = dc_field(metadata=dc_config(field_name="location")) count: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="count") ) @@ -17085,7 +18124,7 @@ class SIPCallerConfigsResponse(DataClassJsonMixin): @dataclass -class SIPChallenge(DataClassJsonMixin): +class SIPChallengeRequest(DataClassJsonMixin): a1: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="a1")) algorithm: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="algorithm") @@ -17416,6 +18455,10 @@ class SearchResultMessage(DataClassJsonMixin): ) html: str = dc_field(metadata=dc_config(field_name="html")) id: str = dc_field(metadata=dc_config(field_name="id")) + mentioned_channel: bool = dc_field( + metadata=dc_config(field_name="mentioned_channel") + ) + mentioned_here: bool = dc_field(metadata=dc_config(field_name="mentioned_here")) pinned: bool = dc_field(metadata=dc_config(field_name="pinned")) reply_count: int = dc_field(metadata=dc_config(field_name="reply_count")) shadowed: bool = dc_field(metadata=dc_config(field_name="shadowed")) @@ -17508,6 +18551,9 @@ class SearchResultMessage(DataClassJsonMixin): show_in_channel: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="show_in_channel") ) + mentioned_roles: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="mentioned_roles") + ) thread_participants: "Optional[List[UserResponse]]" = dc_field( default=None, metadata=dc_config(field_name="thread_participants") ) @@ -17549,6 +18595,14 @@ class SearchResultMessage(DataClassJsonMixin): ) +@dataclass +class SearchUserGroupsResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + user_groups: "List[UserGroupResponse]" = dc_field( + metadata=dc_config(field_name="user_groups") + ) + + @dataclass class SearchWarning(DataClassJsonMixin): warning_code: int = dc_field(metadata=dc_config(field_name="warning_code")) @@ -17784,6 +18838,21 @@ class SendUserCustomEventRequest(DataClassJsonMixin): event: "UserCustomEventRequest" = dc_field(metadata=dc_config(field_name="event")) +@dataclass +class SessionClient(DataClassJsonMixin): + ip: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="ip")) + name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) + network_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="network_type") + ) + version: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="version") + ) + location: "Optional[CallStatsLocation]" = dc_field( + default=None, metadata=dc_config(field_name="location") + ) + + @dataclass class SessionSettings(DataClassJsonMixin): inactivity_timeout_seconds: int = dc_field( @@ -17806,7 +18875,22 @@ class SessionSettingsResponse(DataClassJsonMixin): @dataclass -class ShadowBlockActionRequest(DataClassJsonMixin): +class SessionWarningResponse(DataClassJsonMixin): + code: str = dc_field(metadata=dc_config(field_name="code")) + warning: str = dc_field(metadata=dc_config(field_name="warning")) + time: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="time", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + + +@dataclass +class ShadowBlockActionRequestPayload(DataClassJsonMixin): reason: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="reason") ) @@ -17944,10 +19028,14 @@ class ShowChannelResponse(DataClassJsonMixin): class SingleFollowResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) follow: "FollowResponse" = dc_field(metadata=dc_config(field_name="follow")) + notification_created: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="notification_created") + ) @dataclass class SipInboundCredentials(DataClassJsonMixin): + api_key: str = dc_field(metadata=dc_config(field_name="api_key")) call_id: str = dc_field(metadata=dc_config(field_name="call_id")) call_type: str = dc_field(metadata=dc_config(field_name="call_type")) token: str = dc_field(metadata=dc_config(field_name="token")) @@ -17960,17 +19048,6 @@ class SipInboundCredentials(DataClassJsonMixin): ) -@dataclass -class SortParam(DataClassJsonMixin): - direction: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="direction") - ) - field: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="field") - ) - type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) - - @dataclass class SortParamRequest(DataClassJsonMixin): direction: Optional[int] = dc_field( @@ -18284,46 +19361,49 @@ class SubmitActionRequest(DataClassJsonMixin): user_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="user_id") ) - ban: "Optional[BanActionRequest]" = dc_field( + ban: "Optional[BanActionRequestPayload]" = dc_field( default=None, metadata=dc_config(field_name="ban") ) - block: "Optional[BlockActionRequest]" = dc_field( + block: "Optional[BlockActionRequestPayload]" = dc_field( default=None, metadata=dc_config(field_name="block") ) - custom: "Optional[CustomActionRequest]" = dc_field( + custom: "Optional[CustomActionRequestPayload]" = dc_field( default=None, metadata=dc_config(field_name="custom") ) - delete_activity: "Optional[DeleteActivityRequest]" = dc_field( + delete_activity: "Optional[DeleteActivityRequestPayload]" = dc_field( default=None, metadata=dc_config(field_name="delete_activity") ) - delete_comment: "Optional[DeleteCommentRequest]" = dc_field( + delete_comment: "Optional[DeleteCommentRequestPayload]" = dc_field( default=None, metadata=dc_config(field_name="delete_comment") ) - delete_message: "Optional[DeleteMessageRequest]" = dc_field( + delete_message: "Optional[DeleteMessageRequestPayload]" = dc_field( default=None, metadata=dc_config(field_name="delete_message") ) - delete_reaction: "Optional[DeleteReactionRequest]" = dc_field( + delete_reaction: "Optional[DeleteReactionRequestPayload]" = dc_field( default=None, metadata=dc_config(field_name="delete_reaction") ) - delete_user: "Optional[DeleteUserRequest]" = dc_field( + delete_user: "Optional[DeleteUserRequestPayload]" = dc_field( default=None, metadata=dc_config(field_name="delete_user") ) - mark_reviewed: "Optional[MarkReviewedRequest]" = dc_field( + flag: "Optional[FlagRequest]" = dc_field( + default=None, metadata=dc_config(field_name="flag") + ) + mark_reviewed: "Optional[MarkReviewedRequestPayload]" = dc_field( default=None, metadata=dc_config(field_name="mark_reviewed") ) - reject_appeal: "Optional[RejectAppealRequest]" = dc_field( + reject_appeal: "Optional[RejectAppealRequestPayload]" = dc_field( default=None, metadata=dc_config(field_name="reject_appeal") ) - restore: "Optional[RestoreActionRequest]" = dc_field( + restore: "Optional[RestoreActionRequestPayload]" = dc_field( default=None, metadata=dc_config(field_name="restore") ) - shadow_block: "Optional[ShadowBlockActionRequest]" = dc_field( + shadow_block: "Optional[ShadowBlockActionRequestPayload]" = dc_field( default=None, metadata=dc_config(field_name="shadow_block") ) - unban: "Optional[UnbanActionRequest]" = dc_field( + unban: "Optional[UnbanActionRequestPayload]" = dc_field( default=None, metadata=dc_config(field_name="unban") ) - unblock: "Optional[UnblockActionRequest]" = dc_field( + unblock: "Optional[UnblockActionRequestPayload]" = dc_field( default=None, metadata=dc_config(field_name="unblock") ) user: "Optional[UserRequest]" = dc_field( @@ -18404,6 +19484,55 @@ class TargetResolution(DataClassJsonMixin): width: int = dc_field(metadata=dc_config(field_name="width")) +@dataclass +class TeamUsageStats(DataClassJsonMixin): + team: str = dc_field(metadata=dc_config(field_name="team")) + concurrent_connections: "MetricStats" = dc_field( + metadata=dc_config(field_name="concurrent_connections") + ) + concurrent_users: "MetricStats" = dc_field( + metadata=dc_config(field_name="concurrent_users") + ) + image_moderations_daily: "MetricStats" = dc_field( + metadata=dc_config(field_name="image_moderations_daily") + ) + messages_daily: "MetricStats" = dc_field( + metadata=dc_config(field_name="messages_daily") + ) + messages_last_24_hours: "MetricStats" = dc_field( + metadata=dc_config(field_name="messages_last_24_hours") + ) + messages_last_30_days: "MetricStats" = dc_field( + metadata=dc_config(field_name="messages_last_30_days") + ) + messages_month_to_date: "MetricStats" = dc_field( + metadata=dc_config(field_name="messages_month_to_date") + ) + messages_total: "MetricStats" = dc_field( + metadata=dc_config(field_name="messages_total") + ) + translations_daily: "MetricStats" = dc_field( + metadata=dc_config(field_name="translations_daily") + ) + users_daily: "MetricStats" = dc_field(metadata=dc_config(field_name="users_daily")) + users_engaged_last_30_days: "MetricStats" = dc_field( + metadata=dc_config(field_name="users_engaged_last_30_days") + ) + users_engaged_month_to_date: "MetricStats" = dc_field( + metadata=dc_config(field_name="users_engaged_month_to_date") + ) + users_last_24_hours: "MetricStats" = dc_field( + metadata=dc_config(field_name="users_last_24_hours") + ) + users_last_30_days: "MetricStats" = dc_field( + metadata=dc_config(field_name="users_last_30_days") + ) + users_month_to_date: "MetricStats" = dc_field( + metadata=dc_config(field_name="users_month_to_date") + ) + users_total: "MetricStats" = dc_field(metadata=dc_config(field_name="users_total")) + + @dataclass class TextContentParameters(DataClassJsonMixin): contains_url: Optional[bool] = dc_field( @@ -18651,9 +19780,6 @@ class ThreadStateResponse(DataClassJsonMixin): @dataclass class ThreadUpdatedEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -18662,15 +19788,29 @@ class ThreadUpdatedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( default="thread.updated", metadata=dc_config(field_name="type") ) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) thread: "Optional[ThreadResponse]" = dc_field( default=None, metadata=dc_config(field_name="thread") ) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") - ) @dataclass @@ -18722,11 +19862,20 @@ class ThreadedCommentResponse(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ), ) - parent_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="parent_id") - ) - text: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="text")) - attachments: "Optional[List[Attachment]]" = dc_field( + edited_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="edited_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + parent_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="parent_id") + ) + text: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="text")) + attachments: "Optional[List[Attachment]]" = dc_field( default=None, metadata=dc_config(field_name="attachments") ) latest_reactions: "Optional[List[FeedsReactionResponse]]" = dc_field( @@ -18744,7 +19893,7 @@ class ThreadedCommentResponse(DataClassJsonMixin): moderation: "Optional[ModerationV2Response]" = dc_field( default=None, metadata=dc_config(field_name="moderation") ) - reaction_groups: "Optional[Dict[str, Optional[ReactionGroupResponse]]]" = dc_field( + reaction_groups: "Optional[Dict[str, FeedsReactionGroupResponse]]" = dc_field( default=None, metadata=dc_config(field_name="reaction_groups") ) @@ -18895,13 +20044,6 @@ class TruncateChannelResponse(DataClassJsonMixin): ) -@dataclass -class TypingIndicators(DataClassJsonMixin): - enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") - ) - - @dataclass class TypingIndicatorsResponse(DataClassJsonMixin): enabled: Optional[bool] = dc_field( @@ -18910,7 +20052,10 @@ class TypingIndicatorsResponse(DataClassJsonMixin): @dataclass -class UnbanActionRequest(DataClassJsonMixin): +class UnbanActionRequestPayload(DataClassJsonMixin): + channel_cid: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_cid") + ) decision_reason: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="decision_reason") ) @@ -18932,7 +20077,7 @@ class UnbanResponse(DataClassJsonMixin): @dataclass -class UnblockActionRequest(DataClassJsonMixin): +class UnblockActionRequestPayload(DataClassJsonMixin): decision_reason: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="decision_reason") ) @@ -18981,9 +20126,23 @@ class UnblockedUserEvent(DataClassJsonMixin): ) +@dataclass +class UndeleteMessageRequest(DataClassJsonMixin): + undeleted_by: str = dc_field(metadata=dc_config(field_name="undeleted_by")) + + +@dataclass +class UndeleteMessageResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + message: "MessageResponse" = dc_field(metadata=dc_config(field_name="message")) + + @dataclass class UnfollowBatchRequest(DataClassJsonMixin): follows: "List[FollowPair]" = dc_field(metadata=dc_config(field_name="follows")) + delete_notification_activity: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="delete_notification_activity") + ) @dataclass @@ -19127,8 +20286,49 @@ class UnreadCountsThread(DataClassJsonMixin): unread_count: int = dc_field(metadata=dc_config(field_name="unread_count")) +@dataclass +class UpdateActivitiesPartialBatchRequest(DataClassJsonMixin): + changes: "List[UpdateActivityPartialChangeRequest]" = dc_field( + metadata=dc_config(field_name="changes") + ) + + +@dataclass +class UpdateActivitiesPartialBatchResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + activities: "List[ActivityResponse]" = dc_field( + metadata=dc_config(field_name="activities") + ) + + +@dataclass +class UpdateActivityPartialChangeRequest(DataClassJsonMixin): + activity_id: str = dc_field(metadata=dc_config(field_name="activity_id")) + copy_custom_to_notification: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="copy_custom_to_notification") + ) + handle_mention_notifications: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="handle_mention_notifications") + ) + unset: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="unset") + ) + set: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="set") + ) + + @dataclass class UpdateActivityPartialRequest(DataClassJsonMixin): + copy_custom_to_notification: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="copy_custom_to_notification") + ) + handle_mention_notifications: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="handle_mention_notifications") + ) + run_activity_processors: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="run_activity_processors") + ) user_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="user_id") ) @@ -19151,6 +20351,9 @@ class UpdateActivityPartialResponse(DataClassJsonMixin): @dataclass class UpdateActivityRequest(DataClassJsonMixin): + copy_custom_to_notification: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="copy_custom_to_notification") + ) expires_at: Optional[datetime] = dc_field( default=None, metadata=dc_config( @@ -19160,12 +20363,18 @@ class UpdateActivityRequest(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ), ) + handle_mention_notifications: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="handle_mention_notifications") + ) poll_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="poll_id") ) restrict_replies: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="restrict_replies") ) + run_activity_processors: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="run_activity_processors") + ) skip_enrich_url: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_enrich_url") ) @@ -19176,6 +20385,9 @@ class UpdateActivityRequest(DataClassJsonMixin): visibility: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="visibility") ) + visibility_tag: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="visibility_tag") + ) attachments: "Optional[List[Attachment]]" = dc_field( default=None, metadata=dc_config(field_name="attachments") ) @@ -19200,6 +20412,9 @@ class UpdateActivityRequest(DataClassJsonMixin): location: "Optional[ActivityLocation]" = dc_field( default=None, metadata=dc_config(field_name="location") ) + search_data: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="search_data") + ) user: "Optional[UserRequest]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -19258,6 +20473,9 @@ class UpdateAppRequest(DataClassJsonMixin): migrate_permissions_to_v2: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="migrate_permissions_to_v2") ) + moderation_analytics_enabled: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="moderation_analytics_enabled") + ) moderation_enabled: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="moderation_enabled") ) @@ -19490,7 +20708,7 @@ class UpdateCallTypeRequest(DataClassJsonMixin): grants: "Optional[Dict[str, List[str]]]" = dc_field( default=None, metadata=dc_config(field_name="grants") ) - notification_settings: "Optional[NotificationSettings]" = dc_field( + notification_settings: "Optional[NotificationSettingsRequest]" = dc_field( default=None, metadata=dc_config(field_name="notification_settings") ) settings: "Optional[CallSettingsRequest]" = dc_field( @@ -19519,7 +20737,7 @@ class UpdateCallTypeResponse(DataClassJsonMixin): ) ) grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) - notification_settings: "NotificationSettings" = dc_field( + notification_settings: "NotificationSettingsResponse" = dc_field( metadata=dc_config(field_name="notification_settings") ) settings: "CallSettingsResponse" = dc_field( @@ -19675,6 +20893,9 @@ class UpdateChannelTypeRequest(DataClassJsonMixin): polls: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="polls") ) + push_level: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="push_level") + ) push_notifications: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="push_notifications") ) @@ -19805,6 +21026,9 @@ class UpdateChannelTypeResponse(DataClassJsonMixin): partition_ttl: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="partition_ttl") ) + push_level: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="push_level") + ) allowed_flag_reasons: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="allowed_flag_reasons") ) @@ -19864,6 +21088,12 @@ class UpdateCommentRequest(DataClassJsonMixin): comment: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="comment") ) + copy_custom_to_notification: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="copy_custom_to_notification") + ) + handle_mention_notifications: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="handle_mention_notifications") + ) skip_enrich_url: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_enrich_url") ) @@ -19876,6 +21106,9 @@ class UpdateCommentRequest(DataClassJsonMixin): attachments: "Optional[List[Attachment]]" = dc_field( default=None, metadata=dc_config(field_name="attachments") ) + mentioned_user_ids: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="mentioned_user_ids") + ) custom: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="custom") ) @@ -20038,6 +21271,9 @@ class UpdateFeedVisibilityResponse(DataClassJsonMixin): class UpdateFollowRequest(DataClassJsonMixin): source: str = dc_field(metadata=dc_config(field_name="source")) target: str = dc_field(metadata=dc_config(field_name="target")) + copy_custom_to_notification: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="copy_custom_to_notification") + ) create_notification_activity: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="create_notification_activity") ) @@ -20050,6 +21286,9 @@ class UpdateFollowRequest(DataClassJsonMixin): skip_push: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_push") ) + status: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="status") + ) custom: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="custom") ) @@ -20129,6 +21368,9 @@ class UpdateMessagePartialRequest(DataClassJsonMixin): skip_enrich_url: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_enrich_url") ) + skip_push: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="skip_push") + ) user_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="user_id") ) @@ -20342,6 +21584,25 @@ class UpdateThreadPartialResponse(DataClassJsonMixin): thread: "ThreadResponse" = dc_field(metadata=dc_config(field_name="thread")) +@dataclass +class UpdateUserGroupRequest(DataClassJsonMixin): + description: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="description") + ) + name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) + team_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="team_id") + ) + + +@dataclass +class UpdateUserGroupResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + user_group: "Optional[UserGroupResponse]" = dc_field( + default=None, metadata=dc_config(field_name="user_group") + ) + + @dataclass class UpdateUserPartialRequest(DataClassJsonMixin): id: str = dc_field(metadata=dc_config(field_name="id")) @@ -20471,6 +21732,9 @@ class UpsertActivitiesResponse(DataClassJsonMixin): activities: "List[ActivityResponse]" = dc_field( metadata=dc_config(field_name="activities") ) + mention_notifications_created: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="mention_notifications_created") + ) @dataclass @@ -20561,7 +21825,6 @@ class UpsertConfigResponse(DataClassJsonMixin): class UpsertModerationRuleRequest(DataClassJsonMixin): name: str = dc_field(metadata=dc_config(field_name="name")) rule_type: str = dc_field(metadata=dc_config(field_name="rule_type")) - action: "RuleBuilderAction" = dc_field(metadata=dc_config(field_name="action")) cooldown_period: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="cooldown_period") ) @@ -20575,6 +21838,9 @@ class UpsertModerationRuleRequest(DataClassJsonMixin): default=None, metadata=dc_config(field_name="logic") ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) + action_sequences: "Optional[List[CallRuleActionSequence]]" = dc_field( + default=None, metadata=dc_config(field_name="action_sequences") + ) conditions: "Optional[List[RuleBuilderCondition]]" = dc_field( default=None, metadata=dc_config(field_name="conditions") ) @@ -20584,6 +21850,9 @@ class UpsertModerationRuleRequest(DataClassJsonMixin): groups: "Optional[List[RuleBuilderConditionGroup]]" = dc_field( default=None, metadata=dc_config(field_name="groups") ) + action: "Optional[RuleBuilderAction]" = dc_field( + default=None, metadata=dc_config(field_name="action") + ) @dataclass @@ -20597,7 +21866,7 @@ class UpsertModerationRuleResponse(DataClassJsonMixin): @dataclass class UpsertModerationTemplateRequest(DataClassJsonMixin): name: str = dc_field(metadata=dc_config(field_name="name")) - config: "FeedsModerationTemplateConfig" = dc_field( + config: "FeedsModerationTemplateConfigPayload" = dc_field( metadata=dc_config(field_name="config") ) @@ -20622,7 +21891,7 @@ class UpsertModerationTemplateResponse(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - config: "Optional[FeedsModerationTemplateConfig]" = dc_field( + config: "Optional[FeedsModerationTemplateConfigPayload]" = dc_field( default=None, metadata=dc_config(field_name="config") ) @@ -20637,17 +21906,17 @@ class UpsertPushPreferencesRequest(DataClassJsonMixin): @dataclass class UpsertPushPreferencesResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) - user_channel_preferences: "Dict[str, Dict[str, Optional[ChannelPushPreferences]]]" = dc_field( + user_channel_preferences: "Dict[str, Dict[str, Optional[ChannelPushPreferencesResponse]]]" = dc_field( metadata=dc_config(field_name="user_channel_preferences") ) - user_preferences: "Dict[str, Optional[PushPreferences]]" = dc_field( + user_preferences: "Dict[str, Optional[PushPreferencesResponse]]" = dc_field( metadata=dc_config(field_name="user_preferences") ) @dataclass class UpsertPushProviderRequest(DataClassJsonMixin): - push_provider: "Optional[PushProvider]" = dc_field( + push_provider: "Optional[PushProviderRequest]" = dc_field( default=None, metadata=dc_config(field_name="push_provider") ) @@ -20680,7 +21949,7 @@ class UpsertPushTemplateRequest(DataClassJsonMixin): @dataclass class UpsertPushTemplateResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) - template: "Optional[PushTemplate]" = dc_field( + template: "Optional[PushTemplateResponse]" = dc_field( default=None, metadata=dc_config(field_name="template") ) @@ -20688,53 +21957,100 @@ class UpsertPushTemplateResponse(DataClassJsonMixin): @dataclass class User(DataClassJsonMixin): id: str = dc_field(metadata=dc_config(field_name="id")) - ban_expires: Optional[datetime] = dc_field( - default=None, + data: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="data") + ) + + +@dataclass +class UserBannedEvent(DataClassJsonMixin): + created_at: datetime = dc_field( metadata=dc_config( - field_name="ban_expires", + field_name="created_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ), + ) + ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + user: "UserResponseCommonFields" = dc_field(metadata=dc_config(field_name="user")) + type: str = dc_field(default="user.banned", metadata=dc_config(field_name="type")) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") ) - banned: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="banned") + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") ) - invisible: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="invisible") + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") ) - language: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="language") + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") ) - revoke_tokens_issued_before: Optional[datetime] = dc_field( + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + expiration: Optional[datetime] = dc_field( default=None, metadata=dc_config( - field_name="revoke_tokens_issued_before", + field_name="expiration", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), ), ) - role: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="role")) - teams: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="teams") + reason: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="reason") + ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + shadow: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="shadow") + ) + team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) + total_bans: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="total_bans") + ) + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") ) + created_by: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="created_by") + ) + + +@dataclass +class UserCreatedWithinParameters(DataClassJsonMixin): + max_age: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="max_age") + ) + + +@dataclass +class UserCustomEventRequest(DataClassJsonMixin): + type: str = dc_field(metadata=dc_config(field_name="type")) custom: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="custom") ) - privacy_settings: "Optional[PrivacySettings]" = dc_field( - default=None, metadata=dc_config(field_name="privacy_settings") + + +@dataclass +class UserCustomPropertyParameters(DataClassJsonMixin): + operator: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="operator") ) - teams_role: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="teams_role") + property_key: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="property_key") ) @dataclass -class UserBannedEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) +class UserDeactivatedEvent(DataClassJsonMixin): created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -20743,54 +22059,239 @@ class UserBannedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - shadow: bool = dc_field(metadata=dc_config(field_name="shadow")) - created_by: "User" = dc_field(metadata=dc_config(field_name="created_by")) - type: str = dc_field(default="user.banned", metadata=dc_config(field_name="type")) - expiration: Optional[datetime] = dc_field( + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + user: "UserResponseCommonFields" = dc_field(metadata=dc_config(field_name="user")) + type: str = dc_field( + default="user.deactivated", metadata=dc_config(field_name="type") + ) + received_at: Optional[datetime] = dc_field( default=None, metadata=dc_config( - field_name="expiration", + field_name="received_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), ), ) - reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + created_by: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="created_by") ) - team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + + +@dataclass +class UserDeletedEvent(DataClassJsonMixin): + 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"), + ) + ) + delete_conversation: str = dc_field( + metadata=dc_config(field_name="delete_conversation") + ) + delete_conversation_channels: bool = dc_field( + metadata=dc_config(field_name="delete_conversation_channels") + ) + delete_messages: str = dc_field(metadata=dc_config(field_name="delete_messages")) + delete_user: str = dc_field(metadata=dc_config(field_name="delete_user")) + hard_delete: bool = dc_field(metadata=dc_config(field_name="hard_delete")) + mark_messages_deleted: bool = dc_field( + metadata=dc_config(field_name="mark_messages_deleted") + ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + user: "UserResponseCommonFields" = dc_field(metadata=dc_config(field_name="user")) + type: str = dc_field(default="user.deleted", metadata=dc_config(field_name="type")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), ) @dataclass -class UserCreatedWithinParameters(DataClassJsonMixin): - max_age: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="max_age") +class UserFeedbackReport(DataClassJsonMixin): + unreported_count: int = dc_field(metadata=dc_config(field_name="unreported_count")) + count_by_rating: "Dict[str, int]" = dc_field( + metadata=dc_config(field_name="count_by_rating") ) @dataclass -class UserCustomEventRequest(DataClassJsonMixin): - type: str = dc_field(metadata=dc_config(field_name="type")) +class UserFeedbackReportResponse(DataClassJsonMixin): + daily: "List[DailyAggregateUserFeedbackReportResponse]" = dc_field( + metadata=dc_config(field_name="daily") + ) + + +@dataclass +class UserFeedbackResponse(DataClassJsonMixin): + cid: str = dc_field(metadata=dc_config(field_name="cid")) + rating: int = dc_field(metadata=dc_config(field_name="rating")) + reason: str = dc_field(metadata=dc_config(field_name="reason")) + sdk: str = dc_field(metadata=dc_config(field_name="sdk")) + sdk_version: str = dc_field(metadata=dc_config(field_name="sdk_version")) + session_id: str = dc_field(metadata=dc_config(field_name="session_id")) + user_id: str = dc_field(metadata=dc_config(field_name="user_id")) + platform: "PlatformDataResponse" = dc_field( + metadata=dc_config(field_name="platform") + ) custom: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="custom") ) @dataclass -class UserCustomPropertyParameters(DataClassJsonMixin): - operator: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="operator") +class UserFlaggedEvent(DataClassJsonMixin): + 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"), + ) + ) + reason: str = dc_field(metadata=dc_config(field_name="reason")) + total_flags: int = dc_field(metadata=dc_config(field_name="total_flags")) + user: "UserResponseCommonFields" = dc_field(metadata=dc_config(field_name="user")) + type: str = dc_field(default="user.flagged", metadata=dc_config(field_name="type")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="custom") + ) + target_user: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="target_user") + ) + + +@dataclass +class UserGroup(DataClassJsonMixin): + app_pk: int = dc_field(metadata=dc_config(field_name="app_pk")) + 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"), + ) + ) + id: str = dc_field(metadata=dc_config(field_name="id")) + name: str = dc_field(metadata=dc_config(field_name="name")) + 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"), + ) + ) + created_by: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="created_by") + ) + description: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="description") + ) + team_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="team_id") + ) + members: "Optional[List[UserGroupMember]]" = dc_field( + default=None, metadata=dc_config(field_name="members") + ) + + +@dataclass +class UserGroupCreatedEvent(DataClassJsonMixin): + 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"), + ) + ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + type: str = dc_field( + default="user_group.created", metadata=dc_config(field_name="type") + ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + user: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) + user_group: "Optional[UserGroup]" = dc_field( + default=None, metadata=dc_config(field_name="user_group") + ) + + +@dataclass +class UserGroupDeletedEvent(DataClassJsonMixin): + 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"), + ) + ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + type: str = dc_field( + default="user_group.deleted", metadata=dc_config(field_name="type") + ) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + user: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) + user_group: "Optional[UserGroup]" = dc_field( + default=None, metadata=dc_config(field_name="user_group") ) - property_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="property_key") + + +@dataclass +class UserGroupMember(DataClassJsonMixin): + app_pk: int = dc_field(metadata=dc_config(field_name="app_pk")) + 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"), + ) ) + group_id: str = dc_field(metadata=dc_config(field_name="group_id")) + is_admin: bool = dc_field(metadata=dc_config(field_name="is_admin")) + user_id: str = dc_field(metadata=dc_config(field_name="user_id")) @dataclass -class UserDeactivatedEvent(DataClassJsonMixin): +class UserGroupMemberAddedEvent(DataClassJsonMixin): created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -20799,17 +22300,30 @@ class UserDeactivatedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - created_by: "User" = dc_field(metadata=dc_config(field_name="created_by")) + members: List[str] = dc_field(metadata=dc_config(field_name="members")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="user.deactivated", metadata=dc_config(field_name="type") + default="user_group.member_added", metadata=dc_config(field_name="type") ) - user: "Optional[User]" = dc_field( + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) + user_group: "Optional[UserGroup]" = dc_field( + default=None, metadata=dc_config(field_name="user_group") + ) @dataclass -class UserDeletedEvent(DataClassJsonMixin): +class UserGroupMemberRemovedEvent(DataClassJsonMixin): created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -20818,53 +22332,64 @@ class UserDeletedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - delete_conversation_channels: bool = dc_field( - metadata=dc_config(field_name="delete_conversation_channels") + members: List[str] = dc_field(metadata=dc_config(field_name="members")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + type: str = dc_field( + default="user_group.member_removed", metadata=dc_config(field_name="type") ) - hard_delete: bool = dc_field(metadata=dc_config(field_name="hard_delete")) - mark_messages_deleted: bool = dc_field( - metadata=dc_config(field_name="mark_messages_deleted") + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), ) - type: str = dc_field(default="user.deleted", metadata=dc_config(field_name="type")) - user: "Optional[User]" = dc_field( + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) - - -@dataclass -class UserFeedbackReport(DataClassJsonMixin): - unreported_count: int = dc_field(metadata=dc_config(field_name="unreported_count")) - count_by_rating: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="count_by_rating") + user_group: "Optional[UserGroup]" = dc_field( + default=None, metadata=dc_config(field_name="user_group") ) @dataclass -class UserFeedbackReportResponse(DataClassJsonMixin): - daily: "List[DailyAggregateUserFeedbackReportResponse]" = dc_field( - metadata=dc_config(field_name="daily") +class UserGroupResponse(DataClassJsonMixin): + 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"), + ) ) - - -@dataclass -class UserFeedbackResponse(DataClassJsonMixin): - cid: str = dc_field(metadata=dc_config(field_name="cid")) - rating: int = dc_field(metadata=dc_config(field_name="rating")) - reason: str = dc_field(metadata=dc_config(field_name="reason")) - sdk: str = dc_field(metadata=dc_config(field_name="sdk")) - sdk_version: str = dc_field(metadata=dc_config(field_name="sdk_version")) - session_id: str = dc_field(metadata=dc_config(field_name="session_id")) - user_id: str = dc_field(metadata=dc_config(field_name="user_id")) - platform: "PlatformDataResponse" = dc_field( - metadata=dc_config(field_name="platform") + id: str = dc_field(metadata=dc_config(field_name="id")) + name: str = dc_field(metadata=dc_config(field_name="name")) + 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"), + ) ) - custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + created_by: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="created_by") + ) + description: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="description") + ) + team_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="team_id") + ) + members: "Optional[List[UserGroupMember]]" = dc_field( + default=None, metadata=dc_config(field_name="members") ) @dataclass -class UserFlaggedEvent(DataClassJsonMixin): +class UserGroupUpdatedEvent(DataClassJsonMixin): created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -20873,16 +22398,25 @@ class UserFlaggedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - type: str = dc_field(default="user.flagged", metadata=dc_config(field_name="type")) - target_user: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="target_user") + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + type: str = dc_field( + default="user_group.updated", metadata=dc_config(field_name="type") ) - target_users: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="target_users") + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), ) - user: "Optional[User]" = dc_field( + user: "Optional[UserResponseCommonFields]" = dc_field( default=None, metadata=dc_config(field_name="user") ) + user_group: "Optional[UserGroup]" = dc_field( + default=None, metadata=dc_config(field_name="user_group") + ) @dataclass @@ -20942,7 +22476,7 @@ class UserMessagesDeletedEvent(DataClassJsonMixin): @dataclass -class UserMute(DataClassJsonMixin): +class UserMuteResponse(DataClassJsonMixin): created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -20968,16 +22502,16 @@ class UserMute(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ), ) - target: "Optional[User]" = dc_field( + target: "Optional[UserResponse]" = dc_field( default=None, metadata=dc_config(field_name="target") ) - user: "Optional[User]" = dc_field( + user: "Optional[UserResponse]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @dataclass -class UserMuteResponse(DataClassJsonMixin): +class UserMutedEvent(DataClassJsonMixin): created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -20986,50 +22520,23 @@ class UserMuteResponse(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - 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"), - ) - ) - expires: Optional[datetime] = dc_field( + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + user: "UserResponseCommonFields" = dc_field(metadata=dc_config(field_name="user")) + type: str = dc_field(default="user.muted", metadata=dc_config(field_name="type")) + received_at: Optional[datetime] = dc_field( default=None, metadata=dc_config( - field_name="expires", + field_name="received_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), ), ) - target: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="target") - ) - user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") - ) - - -@dataclass -class UserMutedEvent(DataClassJsonMixin): - 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"), - ) - ) - type: str = dc_field(default="user.muted", metadata=dc_config(field_name="type")) - target_user: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="target_user") - ) - target_users: Optional[List[str]] = dc_field( + target_users: "Optional[List[UserResponseCommonFields]]" = dc_field( default=None, metadata=dc_config(field_name="target_users") ) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + target_user: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="target_user") ) @@ -21049,11 +22556,22 @@ class UserReactivatedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + user: "UserResponseCommonFields" = dc_field(metadata=dc_config(field_name="user")) type: str = dc_field( default="user.reactivated", metadata=dc_config(field_name="type") ) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + created_by: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="created_by") ) @@ -21356,9 +22874,6 @@ class UserRuleParameters(DataClassJsonMixin): @dataclass class UserUnbannedEvent(DataClassJsonMixin): - channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) - channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) - cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -21367,11 +22882,40 @@ class UserUnbannedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - shadow: bool = dc_field(metadata=dc_config(field_name="shadow")) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + user: "UserResponseCommonFields" = dc_field(metadata=dc_config(field_name="user")) type: str = dc_field(default="user.unbanned", metadata=dc_config(field_name="type")) + channel_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_id") + ) + channel_member_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_member_count") + ) + channel_message_count: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="channel_message_count") + ) + channel_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_type") + ) + cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + shadow: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="shadow") + ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + channel_custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="channel_custom") + ) + created_by: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="created_by") ) @@ -21385,15 +22929,23 @@ class UserUnmutedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + user: "UserResponseCommonFields" = dc_field(metadata=dc_config(field_name="user")) type: str = dc_field(default="user.unmuted", metadata=dc_config(field_name="type")) - target_user: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="target_user") + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), ) - target_users: Optional[List[str]] = dc_field( + target_users: "Optional[List[UserResponseCommonFields]]" = dc_field( default=None, metadata=dc_config(field_name="target_users") ) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + target_user: "Optional[UserResponseCommonFields]" = dc_field( + default=None, metadata=dc_config(field_name="target_user") ) @@ -21407,14 +22959,22 @@ class UserUnreadReminderEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - channels: "Dict[str, Optional[ChannelMessages]]" = dc_field( + channels: "Dict[str, Optional[ChannelMessagesResponse]]" = dc_field( metadata=dc_config(field_name="channels") ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + user: "UserResponseCommonFields" = dc_field(metadata=dc_config(field_name="user")) type: str = dc_field( default="user.unread_message_reminder", metadata=dc_config(field_name="type") ) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), ) @@ -21532,12 +23092,12 @@ class VideoContentParameters(DataClassJsonMixin): @dataclass -class VideoEndCallRequest(DataClassJsonMixin): +class VideoEndCallRequestPayload(DataClassJsonMixin): pass @dataclass -class VideoKickUserRequest(DataClassJsonMixin): +class VideoKickUserRequestPayload(DataClassJsonMixin): pass @@ -21548,6 +23108,18 @@ class VideoReactionOverTimeResponse(DataClassJsonMixin): ) +@dataclass +class VideoReactionResponse(DataClassJsonMixin): + type: str = dc_field(metadata=dc_config(field_name="type")) + user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) + emoji_code: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="emoji_code") + ) + custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="custom") + ) + + @dataclass class VideoReactionsResponse(DataClassJsonMixin): reaction: str = dc_field(metadata=dc_config(field_name="reaction")) @@ -21628,6 +23200,11 @@ class VoteData(DataClassJsonMixin): ) +@dataclass +class WHEvent(DataClassJsonMixin): + type: str = dc_field(metadata=dc_config(field_name="type")) + + @dataclass class WHIPIngress(DataClassJsonMixin): address: str = dc_field(metadata=dc_config(field_name="address")) @@ -21721,11 +23298,6 @@ class WSEvent(DataClassJsonMixin): ) -@dataclass -class WebhookEvent(DataClassJsonMixin): - type: str = dc_field(metadata=dc_config(field_name="type")) - - @dataclass class WrappedUnreadCountsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) diff --git a/getstream/moderation/async_rest_client.py b/getstream/moderation/async_rest_client.py index 1272a45d..93082596 100644 --- a/getstream/moderation/async_rest_client.py +++ b/getstream/moderation/async_rest_client.py @@ -3,7 +3,7 @@ from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.utils import build_query_param, build_body_dict +from getstream.utils import build_query_param class ModerationRestClient(AsyncBaseClient): @@ -41,14 +41,14 @@ async def appeal( attachments: Optional[List[str]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[AppealResponse]: - json = build_body_dict( + json = AppealRequest( appeal_reason=appeal_reason, entity_id=entity_id, entity_type=entity_type, user_id=user_id, attachments=attachments, user=user, - ) + ).to_dict() return await self.post("/api/v2/moderation/appeal", AppealResponse, json=json) @telemetry.operation_name("getstream.api.moderation.get_appeal") @@ -71,7 +71,7 @@ async def query_appeals( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryAppealsResponse]: - json = build_body_dict( + json = QueryAppealsRequest( limit=limit, next=next, prev=prev, @@ -79,7 +79,7 @@ async def query_appeals( sort=sort, filter=filter, user=user, - ) + ).to_dict() return await self.post( "/api/v2/moderation/appeals", QueryAppealsResponse, json=json ) @@ -97,7 +97,7 @@ async def ban( timeout: Optional[int] = None, banned_by: Optional[UserRequest] = None, ) -> StreamResponse[BanResponse]: - json = build_body_dict( + json = BanRequest( target_user_id=target_user_id, banned_by_id=banned_by_id, channel_cid=channel_cid, @@ -107,14 +107,14 @@ async def ban( shadow=shadow, timeout=timeout, banned_by=banned_by, - ) + ).to_dict() return await self.post("/api/v2/moderation/ban", BanResponse, json=json) @telemetry.operation_name("getstream.api.moderation.bulk_image_moderation") async def bulk_image_moderation( self, csv_file: str ) -> StreamResponse[BulkImageModerationResponse]: - json = build_body_dict(csv_file=csv_file) + json = BulkImageModerationRequest(csv_file=csv_file).to_dict() return await self.post( "/api/v2/moderation/bulk_image_moderation", BulkImageModerationResponse, @@ -136,7 +136,7 @@ async def check( options: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[CheckResponse]: - json = build_body_dict( + json = CheckRequest( entity_creator_id=entity_creator_id, entity_id=entity_id, entity_type=entity_type, @@ -148,7 +148,7 @@ async def check( moderation_payload=moderation_payload, options=options, user=user, - ) + ).to_dict() return await self.post("/api/v2/moderation/check", CheckResponse, json=json) @telemetry.operation_name("getstream.api.moderation.upsert_config") @@ -176,7 +176,7 @@ async def upsert_config( velocity_filter_config: Optional[VelocityFilterConfig] = None, video_call_rule_config: Optional[VideoCallRuleConfig] = None, ) -> StreamResponse[UpsertConfigResponse]: - json = build_body_dict( + json = UpsertConfigRequest( key=key, _async=_async, team=team, @@ -196,7 +196,7 @@ async def upsert_config( user=user, velocity_filter_config=velocity_filter_config, video_call_rule_config=video_call_rule_config, - ) + ).to_dict() return await self.post( "/api/v2/moderation/config", UpsertConfigResponse, json=json ) @@ -205,7 +205,7 @@ async def upsert_config( async def delete_config( self, key: str, team: Optional[str] = None ) -> StreamResponse[DeleteModerationConfigResponse]: - query_params = build_query_param(team=team) + query_params = build_query_param(**{"team": team}) path_params = { "key": key, } @@ -220,7 +220,7 @@ async def delete_config( async def get_config( self, key: str, team: Optional[str] = None ) -> StreamResponse[GetConfigResponse]: - query_params = build_query_param(team=team) + query_params = build_query_param(**{"team": team}) path_params = { "key": key, } @@ -242,7 +242,7 @@ async def query_moderation_configs( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryModerationConfigsResponse]: - json = build_body_dict( + json = QueryModerationConfigsRequest( limit=limit, next=next, prev=prev, @@ -250,7 +250,7 @@ async def query_moderation_configs( sort=sort, filter=filter, user=user, - ) + ).to_dict() return await self.post( "/api/v2/moderation/configs", QueryModerationConfigsResponse, json=json ) @@ -263,10 +263,10 @@ async def custom_check( flags: List[CustomCheckFlag], entity_creator_id: Optional[str] = None, user_id: Optional[str] = None, - moderation_payload: Optional[ModerationPayload] = None, + moderation_payload: Optional[ModerationPayloadRequest] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[CustomCheckResponse]: - json = build_body_dict( + json = CustomCheckRequest( entity_id=entity_id, entity_type=entity_type, flags=flags, @@ -274,7 +274,7 @@ async def custom_check( user_id=user_id, moderation_payload=moderation_payload, user=user, - ) + ).to_dict() return await self.post( "/api/v2/moderation/custom_check", CustomCheckResponse, json=json ) @@ -299,9 +299,9 @@ async def v2_query_templates( @telemetry.operation_name("getstream.api.moderation.v2_upsert_template") async def v2_upsert_template( - self, name: str, config: FeedsModerationTemplateConfig + self, name: str, config: FeedsModerationTemplateConfigPayload ) -> StreamResponse[UpsertModerationTemplateResponse]: - json = build_body_dict(name=name, config=config) + json = UpsertModerationTemplateRequest(name=name, config=config).to_dict() return await self.post( "/api/v2/moderation/feeds_moderation_template", UpsertModerationTemplateResponse, @@ -320,7 +320,7 @@ async def flag( moderation_payload: Optional[ModerationPayload] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[FlagResponse]: - json = build_body_dict( + json = FlagRequest( entity_id=entity_id, entity_type=entity_type, entity_creator_id=entity_creator_id, @@ -329,7 +329,7 @@ async def flag( custom=custom, moderation_payload=moderation_payload, user=user, - ) + ).to_dict() return await self.post("/api/v2/moderation/flag", FlagResponse, json=json) @telemetry.operation_name("getstream.api.moderation.query_moderation_flags") @@ -338,12 +338,12 @@ async def query_moderation_flags( limit: Optional[int] = None, next: Optional[str] = None, prev: Optional[str] = None, - sort: Optional[List[SortParam]] = None, + sort: Optional[List[SortParamRequest]] = None, filter: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryModerationFlagsResponse]: - json = build_body_dict( + json = QueryModerationFlagsRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return await self.post( "/api/v2/moderation/flags", QueryModerationFlagsResponse, json=json ) @@ -359,7 +359,7 @@ async def query_moderation_logs( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryModerationLogsResponse]: - json = build_body_dict( + json = QueryModerationLogsRequest( limit=limit, next=next, prev=prev, @@ -367,7 +367,7 @@ async def query_moderation_logs( sort=sort, filter=filter, user=user, - ) + ).to_dict() return await self.post( "/api/v2/moderation/logs", QueryModerationLogsResponse, json=json ) @@ -377,29 +377,31 @@ async def upsert_moderation_rule( self, name: str, rule_type: str, - action: RuleBuilderAction, cooldown_period: Optional[str] = None, description: Optional[str] = None, enabled: Optional[bool] = None, logic: Optional[str] = None, team: Optional[str] = None, + action_sequences: Optional[List[CallRuleActionSequence]] = None, conditions: Optional[List[RuleBuilderCondition]] = None, config_keys: Optional[List[str]] = None, groups: Optional[List[RuleBuilderConditionGroup]] = None, + action: Optional[RuleBuilderAction] = None, ) -> StreamResponse[UpsertModerationRuleResponse]: - json = build_body_dict( + json = UpsertModerationRuleRequest( name=name, rule_type=rule_type, - action=action, cooldown_period=cooldown_period, description=description, enabled=enabled, logic=logic, team=team, + action_sequences=action_sequences, conditions=conditions, config_keys=config_keys, groups=groups, - ) + action=action, + ).to_dict() return await self.post( "/api/v2/moderation/moderation_rule", UpsertModerationRuleResponse, @@ -431,7 +433,7 @@ async def query_moderation_rules( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryModerationRulesResponse]: - json = build_body_dict( + json = QueryModerationRulesRequest( limit=limit, next=next, prev=prev, @@ -439,7 +441,7 @@ async def query_moderation_rules( sort=sort, filter=filter, user=user, - ) + ).to_dict() return await self.post( "/api/v2/moderation/moderation_rules", QueryModerationRulesResponse, @@ -454,9 +456,9 @@ async def mute( user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[MuteResponse]: - json = build_body_dict( + json = MuteRequest( target_ids=target_ids, timeout=timeout, user_id=user_id, user=user - ) + ).to_dict() return await self.post("/api/v2/moderation/mute", MuteResponse, json=json) @telemetry.operation_name("getstream.api.moderation.query_review_queue") @@ -474,7 +476,7 @@ async def query_review_queue( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryReviewQueueResponse]: - json = build_body_dict( + json = QueryReviewQueueRequest( limit=limit, lock_count=lock_count, lock_duration=lock_duration, @@ -486,7 +488,7 @@ async def query_review_queue( sort=sort, filter=filter, user=user, - ) + ).to_dict() return await self.post( "/api/v2/moderation/review_queue", QueryReviewQueueResponse, json=json ) @@ -511,23 +513,24 @@ async def submit_action( appeal_id: Optional[str] = None, item_id: Optional[str] = None, user_id: Optional[str] = None, - ban: Optional[BanActionRequest] = None, - block: Optional[BlockActionRequest] = None, - custom: Optional[CustomActionRequest] = None, - delete_activity: Optional[DeleteActivityRequest] = None, - delete_comment: Optional[DeleteCommentRequest] = None, - delete_message: Optional[DeleteMessageRequest] = None, - delete_reaction: Optional[DeleteReactionRequest] = None, - delete_user: Optional[DeleteUserRequest] = None, - mark_reviewed: Optional[MarkReviewedRequest] = None, - reject_appeal: Optional[RejectAppealRequest] = None, - restore: Optional[RestoreActionRequest] = None, - shadow_block: Optional[ShadowBlockActionRequest] = None, - unban: Optional[UnbanActionRequest] = None, - unblock: Optional[UnblockActionRequest] = None, + ban: Optional[BanActionRequestPayload] = None, + block: Optional[BlockActionRequestPayload] = None, + custom: Optional[CustomActionRequestPayload] = None, + delete_activity: Optional[DeleteActivityRequestPayload] = None, + delete_comment: Optional[DeleteCommentRequestPayload] = None, + delete_message: Optional[DeleteMessageRequestPayload] = None, + delete_reaction: Optional[DeleteReactionRequestPayload] = None, + delete_user: Optional[DeleteUserRequestPayload] = None, + flag: Optional[FlagRequest] = None, + mark_reviewed: Optional[MarkReviewedRequestPayload] = None, + reject_appeal: Optional[RejectAppealRequestPayload] = None, + restore: Optional[RestoreActionRequestPayload] = None, + shadow_block: Optional[ShadowBlockActionRequestPayload] = None, + unban: Optional[UnbanActionRequestPayload] = None, + unblock: Optional[UnblockActionRequestPayload] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[SubmitActionResponse]: - json = build_body_dict( + json = SubmitActionRequest( action_type=action_type, appeal_id=appeal_id, item_id=item_id, @@ -540,6 +543,7 @@ async def submit_action( delete_message=delete_message, delete_reaction=delete_reaction, delete_user=delete_user, + flag=flag, mark_reviewed=mark_reviewed, reject_appeal=reject_appeal, restore=restore, @@ -547,7 +551,7 @@ async def submit_action( unban=unban, unblock=unblock, user=user, - ) + ).to_dict() return await self.post( "/api/v2/moderation/submit_action", SubmitActionResponse, json=json ) @@ -562,11 +566,15 @@ async def unban( unbanned_by: Optional[UserRequest] = None, ) -> StreamResponse[UnbanResponse]: query_params = build_query_param( - target_user_id=target_user_id, - channel_cid=channel_cid, - created_by=created_by, - ) - json = build_body_dict(unbanned_by_id=unbanned_by_id, unbanned_by=unbanned_by) + **{ + "target_user_id": target_user_id, + "channel_cid": channel_cid, + "created_by": created_by, + } + ) + json = UnbanRequest( + unbanned_by_id=unbanned_by_id, unbanned_by=unbanned_by + ).to_dict() return await self.post( "/api/v2/moderation/unban", UnbanResponse, @@ -581,5 +589,7 @@ async def unmute( user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[UnmuteResponse]: - json = build_body_dict(target_ids=target_ids, user_id=user_id, user=user) + json = UnmuteRequest( + target_ids=target_ids, user_id=user_id, user=user + ).to_dict() return await self.post("/api/v2/moderation/unmute", UnmuteResponse, json=json) diff --git a/getstream/moderation/rest_client.py b/getstream/moderation/rest_client.py index e82f9a41..b07021e8 100644 --- a/getstream/moderation/rest_client.py +++ b/getstream/moderation/rest_client.py @@ -3,7 +3,7 @@ from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.utils import build_query_param, build_body_dict +from getstream.utils import build_query_param class ModerationRestClient(BaseClient): @@ -41,14 +41,14 @@ def appeal( attachments: Optional[List[str]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[AppealResponse]: - json = build_body_dict( + json = AppealRequest( appeal_reason=appeal_reason, entity_id=entity_id, entity_type=entity_type, user_id=user_id, attachments=attachments, user=user, - ) + ).to_dict() return self.post("/api/v2/moderation/appeal", AppealResponse, json=json) @telemetry.operation_name("getstream.api.moderation.get_appeal") @@ -71,7 +71,7 @@ def query_appeals( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryAppealsResponse]: - json = build_body_dict( + json = QueryAppealsRequest( limit=limit, next=next, prev=prev, @@ -79,7 +79,7 @@ def query_appeals( sort=sort, filter=filter, user=user, - ) + ).to_dict() return self.post("/api/v2/moderation/appeals", QueryAppealsResponse, json=json) @telemetry.operation_name("getstream.api.moderation.ban") @@ -95,7 +95,7 @@ def ban( timeout: Optional[int] = None, banned_by: Optional[UserRequest] = None, ) -> StreamResponse[BanResponse]: - json = build_body_dict( + json = BanRequest( target_user_id=target_user_id, banned_by_id=banned_by_id, channel_cid=channel_cid, @@ -105,14 +105,14 @@ def ban( shadow=shadow, timeout=timeout, banned_by=banned_by, - ) + ).to_dict() return self.post("/api/v2/moderation/ban", BanResponse, json=json) @telemetry.operation_name("getstream.api.moderation.bulk_image_moderation") def bulk_image_moderation( self, csv_file: str ) -> StreamResponse[BulkImageModerationResponse]: - json = build_body_dict(csv_file=csv_file) + json = BulkImageModerationRequest(csv_file=csv_file).to_dict() return self.post( "/api/v2/moderation/bulk_image_moderation", BulkImageModerationResponse, @@ -134,7 +134,7 @@ def check( options: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[CheckResponse]: - json = build_body_dict( + json = CheckRequest( entity_creator_id=entity_creator_id, entity_id=entity_id, entity_type=entity_type, @@ -146,7 +146,7 @@ def check( moderation_payload=moderation_payload, options=options, user=user, - ) + ).to_dict() return self.post("/api/v2/moderation/check", CheckResponse, json=json) @telemetry.operation_name("getstream.api.moderation.upsert_config") @@ -174,7 +174,7 @@ def upsert_config( velocity_filter_config: Optional[VelocityFilterConfig] = None, video_call_rule_config: Optional[VideoCallRuleConfig] = None, ) -> StreamResponse[UpsertConfigResponse]: - json = build_body_dict( + json = UpsertConfigRequest( key=key, _async=_async, team=team, @@ -194,14 +194,14 @@ def upsert_config( user=user, velocity_filter_config=velocity_filter_config, video_call_rule_config=video_call_rule_config, - ) + ).to_dict() return self.post("/api/v2/moderation/config", UpsertConfigResponse, json=json) @telemetry.operation_name("getstream.api.moderation.delete_config") def delete_config( self, key: str, team: Optional[str] = None ) -> StreamResponse[DeleteModerationConfigResponse]: - query_params = build_query_param(team=team) + query_params = build_query_param(**{"team": team}) path_params = { "key": key, } @@ -216,7 +216,7 @@ def delete_config( def get_config( self, key: str, team: Optional[str] = None ) -> StreamResponse[GetConfigResponse]: - query_params = build_query_param(team=team) + query_params = build_query_param(**{"team": team}) path_params = { "key": key, } @@ -238,7 +238,7 @@ def query_moderation_configs( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryModerationConfigsResponse]: - json = build_body_dict( + json = QueryModerationConfigsRequest( limit=limit, next=next, prev=prev, @@ -246,7 +246,7 @@ def query_moderation_configs( sort=sort, filter=filter, user=user, - ) + ).to_dict() return self.post( "/api/v2/moderation/configs", QueryModerationConfigsResponse, json=json ) @@ -259,10 +259,10 @@ def custom_check( flags: List[CustomCheckFlag], entity_creator_id: Optional[str] = None, user_id: Optional[str] = None, - moderation_payload: Optional[ModerationPayload] = None, + moderation_payload: Optional[ModerationPayloadRequest] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[CustomCheckResponse]: - json = build_body_dict( + json = CustomCheckRequest( entity_id=entity_id, entity_type=entity_type, flags=flags, @@ -270,7 +270,7 @@ def custom_check( user_id=user_id, moderation_payload=moderation_payload, user=user, - ) + ).to_dict() return self.post( "/api/v2/moderation/custom_check", CustomCheckResponse, json=json ) @@ -293,9 +293,9 @@ def v2_query_templates( @telemetry.operation_name("getstream.api.moderation.v2_upsert_template") def v2_upsert_template( - self, name: str, config: FeedsModerationTemplateConfig + self, name: str, config: FeedsModerationTemplateConfigPayload ) -> StreamResponse[UpsertModerationTemplateResponse]: - json = build_body_dict(name=name, config=config) + json = UpsertModerationTemplateRequest(name=name, config=config).to_dict() return self.post( "/api/v2/moderation/feeds_moderation_template", UpsertModerationTemplateResponse, @@ -314,7 +314,7 @@ def flag( moderation_payload: Optional[ModerationPayload] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[FlagResponse]: - json = build_body_dict( + json = FlagRequest( entity_id=entity_id, entity_type=entity_type, entity_creator_id=entity_creator_id, @@ -323,7 +323,7 @@ def flag( custom=custom, moderation_payload=moderation_payload, user=user, - ) + ).to_dict() return self.post("/api/v2/moderation/flag", FlagResponse, json=json) @telemetry.operation_name("getstream.api.moderation.query_moderation_flags") @@ -332,12 +332,12 @@ def query_moderation_flags( limit: Optional[int] = None, next: Optional[str] = None, prev: Optional[str] = None, - sort: Optional[List[SortParam]] = None, + sort: Optional[List[SortParamRequest]] = None, filter: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryModerationFlagsResponse]: - json = build_body_dict( + json = QueryModerationFlagsRequest( limit=limit, next=next, prev=prev, sort=sort, filter=filter - ) + ).to_dict() return self.post( "/api/v2/moderation/flags", QueryModerationFlagsResponse, json=json ) @@ -353,7 +353,7 @@ def query_moderation_logs( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryModerationLogsResponse]: - json = build_body_dict( + json = QueryModerationLogsRequest( limit=limit, next=next, prev=prev, @@ -361,7 +361,7 @@ def query_moderation_logs( sort=sort, filter=filter, user=user, - ) + ).to_dict() return self.post( "/api/v2/moderation/logs", QueryModerationLogsResponse, json=json ) @@ -371,29 +371,31 @@ def upsert_moderation_rule( self, name: str, rule_type: str, - action: RuleBuilderAction, cooldown_period: Optional[str] = None, description: Optional[str] = None, enabled: Optional[bool] = None, logic: Optional[str] = None, team: Optional[str] = None, + action_sequences: Optional[List[CallRuleActionSequence]] = None, conditions: Optional[List[RuleBuilderCondition]] = None, config_keys: Optional[List[str]] = None, groups: Optional[List[RuleBuilderConditionGroup]] = None, + action: Optional[RuleBuilderAction] = None, ) -> StreamResponse[UpsertModerationRuleResponse]: - json = build_body_dict( + json = UpsertModerationRuleRequest( name=name, rule_type=rule_type, - action=action, cooldown_period=cooldown_period, description=description, enabled=enabled, logic=logic, team=team, + action_sequences=action_sequences, conditions=conditions, config_keys=config_keys, groups=groups, - ) + action=action, + ).to_dict() return self.post( "/api/v2/moderation/moderation_rule", UpsertModerationRuleResponse, @@ -423,7 +425,7 @@ def query_moderation_rules( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryModerationRulesResponse]: - json = build_body_dict( + json = QueryModerationRulesRequest( limit=limit, next=next, prev=prev, @@ -431,7 +433,7 @@ def query_moderation_rules( sort=sort, filter=filter, user=user, - ) + ).to_dict() return self.post( "/api/v2/moderation/moderation_rules", QueryModerationRulesResponse, @@ -446,9 +448,9 @@ def mute( user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[MuteResponse]: - json = build_body_dict( + json = MuteRequest( target_ids=target_ids, timeout=timeout, user_id=user_id, user=user - ) + ).to_dict() return self.post("/api/v2/moderation/mute", MuteResponse, json=json) @telemetry.operation_name("getstream.api.moderation.query_review_queue") @@ -466,7 +468,7 @@ def query_review_queue( filter: Optional[Dict[str, object]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[QueryReviewQueueResponse]: - json = build_body_dict( + json = QueryReviewQueueRequest( limit=limit, lock_count=lock_count, lock_duration=lock_duration, @@ -478,7 +480,7 @@ def query_review_queue( sort=sort, filter=filter, user=user, - ) + ).to_dict() return self.post( "/api/v2/moderation/review_queue", QueryReviewQueueResponse, json=json ) @@ -503,23 +505,24 @@ def submit_action( appeal_id: Optional[str] = None, item_id: Optional[str] = None, user_id: Optional[str] = None, - ban: Optional[BanActionRequest] = None, - block: Optional[BlockActionRequest] = None, - custom: Optional[CustomActionRequest] = None, - delete_activity: Optional[DeleteActivityRequest] = None, - delete_comment: Optional[DeleteCommentRequest] = None, - delete_message: Optional[DeleteMessageRequest] = None, - delete_reaction: Optional[DeleteReactionRequest] = None, - delete_user: Optional[DeleteUserRequest] = None, - mark_reviewed: Optional[MarkReviewedRequest] = None, - reject_appeal: Optional[RejectAppealRequest] = None, - restore: Optional[RestoreActionRequest] = None, - shadow_block: Optional[ShadowBlockActionRequest] = None, - unban: Optional[UnbanActionRequest] = None, - unblock: Optional[UnblockActionRequest] = None, + ban: Optional[BanActionRequestPayload] = None, + block: Optional[BlockActionRequestPayload] = None, + custom: Optional[CustomActionRequestPayload] = None, + delete_activity: Optional[DeleteActivityRequestPayload] = None, + delete_comment: Optional[DeleteCommentRequestPayload] = None, + delete_message: Optional[DeleteMessageRequestPayload] = None, + delete_reaction: Optional[DeleteReactionRequestPayload] = None, + delete_user: Optional[DeleteUserRequestPayload] = None, + flag: Optional[FlagRequest] = None, + mark_reviewed: Optional[MarkReviewedRequestPayload] = None, + reject_appeal: Optional[RejectAppealRequestPayload] = None, + restore: Optional[RestoreActionRequestPayload] = None, + shadow_block: Optional[ShadowBlockActionRequestPayload] = None, + unban: Optional[UnbanActionRequestPayload] = None, + unblock: Optional[UnblockActionRequestPayload] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[SubmitActionResponse]: - json = build_body_dict( + json = SubmitActionRequest( action_type=action_type, appeal_id=appeal_id, item_id=item_id, @@ -532,6 +535,7 @@ def submit_action( delete_message=delete_message, delete_reaction=delete_reaction, delete_user=delete_user, + flag=flag, mark_reviewed=mark_reviewed, reject_appeal=reject_appeal, restore=restore, @@ -539,7 +543,7 @@ def submit_action( unban=unban, unblock=unblock, user=user, - ) + ).to_dict() return self.post( "/api/v2/moderation/submit_action", SubmitActionResponse, json=json ) @@ -554,11 +558,15 @@ def unban( unbanned_by: Optional[UserRequest] = None, ) -> StreamResponse[UnbanResponse]: query_params = build_query_param( - target_user_id=target_user_id, - channel_cid=channel_cid, - created_by=created_by, - ) - json = build_body_dict(unbanned_by_id=unbanned_by_id, unbanned_by=unbanned_by) + **{ + "target_user_id": target_user_id, + "channel_cid": channel_cid, + "created_by": created_by, + } + ) + json = UnbanRequest( + unbanned_by_id=unbanned_by_id, unbanned_by=unbanned_by + ).to_dict() return self.post( "/api/v2/moderation/unban", UnbanResponse, @@ -573,5 +581,7 @@ def unmute( user_id: Optional[str] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[UnmuteResponse]: - json = build_body_dict(target_ids=target_ids, user_id=user_id, user=user) + json = UnmuteRequest( + target_ids=target_ids, user_id=user_id, user=user + ).to_dict() return self.post("/api/v2/moderation/unmute", UnmuteResponse, json=json) diff --git a/getstream/tests/test_webhook.py b/getstream/tests/test_webhook.py new file mode 100644 index 00000000..4098c454 --- /dev/null +++ b/getstream/tests/test_webhook.py @@ -0,0 +1,1090 @@ +# Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. + +import hmac +import hashlib + +import warnings + +import pytest + +from getstream.webhook import ( + get_event_type, + parse_webhook_event, + verify_webhook_signature, +) + + +class TestVerifyWebhookSignature: + def setup_method(self): + self.secret = "test-webhook-secret" + self.body = b'{"type":"test.event"}' + self.valid_signature = hmac.new( + self.secret.encode("utf-8"), self.body, hashlib.sha256 + ).hexdigest() + + def test_valid_signature(self): + assert ( + verify_webhook_signature(self.body, self.valid_signature, self.secret) + is True + ) + + def test_wrong_signature(self): + assert ( + verify_webhook_signature(self.body, "invalidsignature", self.secret) + is False + ) + + def test_tampered_body(self): + assert ( + verify_webhook_signature( + b'{"type":"tampered"}', self.valid_signature, self.secret + ) + is False + ) + + def test_wrong_secret(self): + assert ( + verify_webhook_signature(self.body, self.valid_signature, "wrong-secret") + is False + ) + + def test_string_body(self): + body_str = '{"type":"test.event"}' + sig = hmac.new( + self.secret.encode("utf-8"), body_str.encode("utf-8"), hashlib.sha256 + ).hexdigest() + assert verify_webhook_signature(body_str, sig, self.secret) is True + + +class TestGetEventType: + def test_from_string(self): + assert get_event_type('{"type":"message.new"}') == "message.new" + + def test_from_bytes(self): + assert get_event_type(b'{"type":"message.new"}') == "message.new" + + def test_from_dict(self): + assert get_event_type({"type": "message.new"}) == "message.new" + + def test_missing_type(self): + assert get_event_type('{"foo":"bar"}') == "" + + def test_invalid_json(self): + assert get_event_type("not json") == "" + + def test_empty_object(self): + assert get_event_type("{}") == "" + + +class TestParseWebhookEvent: + def test_parse_(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "*"}) + assert type(event).__name__ == "CustomEvent" + + def test_parse_appeal_accepted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "appeal.accepted"}) + assert type(event).__name__ == "AppealAcceptedEvent" + + def test_parse_appeal_created(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "appeal.created"}) + assert type(event).__name__ == "AppealCreatedEvent" + + def test_parse_appeal_rejected(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "appeal.rejected"}) + assert type(event).__name__ == "AppealRejectedEvent" + + def test_parse_call_accepted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.accepted"}) + assert type(event).__name__ == "CallAcceptedEvent" + + def test_parse_call_blocked_user(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.blocked_user"}) + assert type(event).__name__ == "BlockedUserEvent" + + def test_parse_call_closed_caption(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.closed_caption"}) + assert type(event).__name__ == "ClosedCaptionEvent" + + def test_parse_call_closed_captions_failed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.closed_captions_failed"}) + assert type(event).__name__ == "CallClosedCaptionsFailedEvent" + + def test_parse_call_closed_captions_started(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.closed_captions_started"}) + assert type(event).__name__ == "CallClosedCaptionsStartedEvent" + + def test_parse_call_closed_captions_stopped(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.closed_captions_stopped"}) + assert type(event).__name__ == "CallClosedCaptionsStoppedEvent" + + def test_parse_call_created(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.created"}) + assert type(event).__name__ == "CallCreatedEvent" + + def test_parse_call_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.deleted"}) + assert type(event).__name__ == "CallDeletedEvent" + + def test_parse_call_dtmf(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.dtmf"}) + assert type(event).__name__ == "CallDTMFEvent" + + def test_parse_call_ended(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.ended"}) + assert type(event).__name__ == "CallEndedEvent" + + def test_parse_call_frame_recording_failed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.frame_recording_failed"}) + assert type(event).__name__ == "CallFrameRecordingFailedEvent" + + def test_parse_call_frame_recording_ready(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.frame_recording_ready"}) + assert type(event).__name__ == "CallFrameRecordingFrameReadyEvent" + + def test_parse_call_frame_recording_started(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.frame_recording_started"}) + assert type(event).__name__ == "CallFrameRecordingStartedEvent" + + def test_parse_call_frame_recording_stopped(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.frame_recording_stopped"}) + assert type(event).__name__ == "CallFrameRecordingStoppedEvent" + + def test_parse_call_hls_broadcasting_failed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.hls_broadcasting_failed"}) + assert type(event).__name__ == "CallHLSBroadcastingFailedEvent" + + def test_parse_call_hls_broadcasting_started(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.hls_broadcasting_started"}) + assert type(event).__name__ == "CallHLSBroadcastingStartedEvent" + + def test_parse_call_hls_broadcasting_stopped(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.hls_broadcasting_stopped"}) + assert type(event).__name__ == "CallHLSBroadcastingStoppedEvent" + + def test_parse_call_kicked_user(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.kicked_user"}) + assert type(event).__name__ == "KickedUserEvent" + + def test_parse_call_live_started(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.live_started"}) + assert type(event).__name__ == "CallLiveStartedEvent" + + def test_parse_call_member_added(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.member_added"}) + assert type(event).__name__ == "CallMemberAddedEvent" + + def test_parse_call_member_removed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.member_removed"}) + assert type(event).__name__ == "CallMemberRemovedEvent" + + def test_parse_call_member_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.member_updated"}) + assert type(event).__name__ == "CallMemberUpdatedEvent" + + def test_parse_call_member_updated_permission(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.member_updated_permission"}) + assert type(event).__name__ == "CallMemberUpdatedPermissionEvent" + + def test_parse_call_missed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.missed"}) + assert type(event).__name__ == "CallMissedEvent" + + def test_parse_call_moderation_blur(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.moderation_blur"}) + assert type(event).__name__ == "CallModerationBlurEvent" + + def test_parse_call_moderation_warning(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.moderation_warning"}) + assert type(event).__name__ == "CallModerationWarningEvent" + + def test_parse_call_notification(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.notification"}) + assert type(event).__name__ == "CallNotificationEvent" + + def test_parse_call_permission_request(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.permission_request"}) + assert type(event).__name__ == "PermissionRequestEvent" + + def test_parse_call_permissions_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.permissions_updated"}) + assert type(event).__name__ == "UpdatedCallPermissionsEvent" + + def test_parse_call_reaction_new(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.reaction_new"}) + assert type(event).__name__ == "CallReactionEvent" + + def test_parse_call_recording_failed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.recording_failed"}) + assert type(event).__name__ == "CallRecordingFailedEvent" + + def test_parse_call_recording_ready(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.recording_ready"}) + assert type(event).__name__ == "CallRecordingReadyEvent" + + def test_parse_call_recording_started(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.recording_started"}) + assert type(event).__name__ == "CallRecordingStartedEvent" + + def test_parse_call_recording_stopped(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.recording_stopped"}) + assert type(event).__name__ == "CallRecordingStoppedEvent" + + def test_parse_call_rejected(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.rejected"}) + assert type(event).__name__ == "CallRejectedEvent" + + def test_parse_call_ring(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.ring"}) + assert type(event).__name__ == "CallRingEvent" + + def test_parse_call_rtmp_broadcast_failed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.rtmp_broadcast_failed"}) + assert type(event).__name__ == "CallRtmpBroadcastFailedEvent" + + def test_parse_call_rtmp_broadcast_started(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.rtmp_broadcast_started"}) + assert type(event).__name__ == "CallRtmpBroadcastStartedEvent" + + def test_parse_call_rtmp_broadcast_stopped(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.rtmp_broadcast_stopped"}) + assert type(event).__name__ == "CallRtmpBroadcastStoppedEvent" + + def test_parse_call_session_ended(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.session_ended"}) + assert type(event).__name__ == "CallSessionEndedEvent" + + def test_parse_call_session_participant_count_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event( + {"type": "call.session_participant_count_updated"} + ) + assert type(event).__name__ == "CallSessionParticipantCountsUpdatedEvent" + + def test_parse_call_session_participant_joined(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.session_participant_joined"}) + assert type(event).__name__ == "CallSessionParticipantJoinedEvent" + + def test_parse_call_session_participant_left(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.session_participant_left"}) + assert type(event).__name__ == "CallSessionParticipantLeftEvent" + + def test_parse_call_session_started(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.session_started"}) + assert type(event).__name__ == "CallSessionStartedEvent" + + def test_parse_call_stats_report_ready(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.stats_report_ready"}) + assert type(event).__name__ == "CallStatsReportReadyEvent" + + def test_parse_call_transcription_failed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.transcription_failed"}) + assert type(event).__name__ == "CallTranscriptionFailedEvent" + + def test_parse_call_transcription_ready(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.transcription_ready"}) + assert type(event).__name__ == "CallTranscriptionReadyEvent" + + def test_parse_call_transcription_started(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.transcription_started"}) + assert type(event).__name__ == "CallTranscriptionStartedEvent" + + def test_parse_call_transcription_stopped(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.transcription_stopped"}) + assert type(event).__name__ == "CallTranscriptionStoppedEvent" + + def test_parse_call_unblocked_user(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.unblocked_user"}) + assert type(event).__name__ == "UnblockedUserEvent" + + def test_parse_call_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.updated"}) + assert type(event).__name__ == "CallUpdatedEvent" + + def test_parse_call_user_feedback_submitted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.user_feedback_submitted"}) + assert type(event).__name__ == "CallUserFeedbackSubmittedEvent" + + def test_parse_call_user_muted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "call.user_muted"}) + assert type(event).__name__ == "CallUserMutedEvent" + + def test_parse_campaign_completed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "campaign.completed"}) + assert type(event).__name__ == "CampaignCompletedEvent" + + def test_parse_campaign_started(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "campaign.started"}) + assert type(event).__name__ == "CampaignStartedEvent" + + def test_parse_channel_created(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "channel.created"}) + assert type(event).__name__ == "ChannelCreatedEvent" + + def test_parse_channel_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "channel.deleted"}) + assert type(event).__name__ == "ChannelDeletedEvent" + + def test_parse_channel_frozen(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "channel.frozen"}) + assert type(event).__name__ == "ChannelFrozenEvent" + + def test_parse_channel_hidden(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "channel.hidden"}) + assert type(event).__name__ == "ChannelHiddenEvent" + + def test_parse_channel_max_streak_changed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "channel.max_streak_changed"}) + assert type(event).__name__ == "MaxStreakChangedEvent" + + def test_parse_channel_muted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "channel.muted"}) + assert type(event).__name__ == "ChannelMutedEvent" + + def test_parse_channel_truncated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "channel.truncated"}) + assert type(event).__name__ == "ChannelTruncatedEvent" + + def test_parse_channel_unfrozen(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "channel.unfrozen"}) + assert type(event).__name__ == "ChannelUnFrozenEvent" + + def test_parse_channel_unmuted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "channel.unmuted"}) + assert type(event).__name__ == "ChannelUnmutedEvent" + + def test_parse_channel_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "channel.updated"}) + assert type(event).__name__ == "ChannelUpdatedEvent" + + def test_parse_channel_visible(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "channel.visible"}) + assert type(event).__name__ == "ChannelVisibleEvent" + + def test_parse_channel_batch_update_completed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "channel_batch_update.completed"}) + assert type(event).__name__ == "ChannelBatchCompletedEvent" + + def test_parse_channel_batch_update_started(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "channel_batch_update.started"}) + assert type(event).__name__ == "ChannelBatchStartedEvent" + + def test_parse_custom(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "custom"}) + assert type(event).__name__ == "CustomVideoEvent" + + def test_parse_export_bulk_image_moderation_error(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "export.bulk_image_moderation.error"}) + assert type(event).__name__ == "AsyncExportErrorEvent" + + def test_parse_export_bulk_image_moderation_success(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event( + {"type": "export.bulk_image_moderation.success"} + ) + assert type(event).__name__ == "AsyncBulkImageModerationEvent" + + def test_parse_export_channels_error(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "export.channels.error"}) + assert type(event).__name__ == "AsyncExportErrorEvent" + + def test_parse_export_channels_success(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "export.channels.success"}) + assert type(event).__name__ == "AsyncExportChannelsEvent" + + def test_parse_export_moderation_logs_error(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "export.moderation_logs.error"}) + assert type(event).__name__ == "AsyncExportErrorEvent" + + def test_parse_export_moderation_logs_success(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "export.moderation_logs.success"}) + assert type(event).__name__ == "AsyncExportModerationLogsEvent" + + def test_parse_export_users_error(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "export.users.error"}) + assert type(event).__name__ == "AsyncExportErrorEvent" + + def test_parse_export_users_success(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "export.users.success"}) + assert type(event).__name__ == "AsyncExportUsersEvent" + + def test_parse_feeds_activity_added(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.activity.added"}) + assert type(event).__name__ == "ActivityAddedEvent" + + def test_parse_feeds_activity_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.activity.deleted"}) + assert type(event).__name__ == "ActivityDeletedEvent" + + def test_parse_feeds_activity_feedback(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.activity.feedback"}) + assert type(event).__name__ == "ActivityFeedbackEvent" + + def test_parse_feeds_activity_marked(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.activity.marked"}) + assert type(event).__name__ == "ActivityMarkEvent" + + def test_parse_feeds_activity_pinned(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.activity.pinned"}) + assert type(event).__name__ == "ActivityPinnedEvent" + + def test_parse_feeds_activity_reaction_added(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.activity.reaction.added"}) + assert type(event).__name__ == "ActivityReactionAddedEvent" + + def test_parse_feeds_activity_reaction_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.activity.reaction.deleted"}) + assert type(event).__name__ == "ActivityReactionDeletedEvent" + + def test_parse_feeds_activity_reaction_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.activity.reaction.updated"}) + assert type(event).__name__ == "ActivityReactionUpdatedEvent" + + def test_parse_feeds_activity_removed_from_feed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.activity.removed_from_feed"}) + assert type(event).__name__ == "ActivityRemovedFromFeedEvent" + + def test_parse_feeds_activity_restored(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.activity.restored"}) + assert type(event).__name__ == "ActivityRestoredEvent" + + def test_parse_feeds_activity_unpinned(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.activity.unpinned"}) + assert type(event).__name__ == "ActivityUnpinnedEvent" + + def test_parse_feeds_activity_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.activity.updated"}) + assert type(event).__name__ == "ActivityUpdatedEvent" + + def test_parse_feeds_bookmark_added(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.bookmark.added"}) + assert type(event).__name__ == "BookmarkAddedEvent" + + def test_parse_feeds_bookmark_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.bookmark.deleted"}) + assert type(event).__name__ == "BookmarkDeletedEvent" + + def test_parse_feeds_bookmark_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.bookmark.updated"}) + assert type(event).__name__ == "BookmarkUpdatedEvent" + + def test_parse_feeds_bookmark_folder_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.bookmark_folder.deleted"}) + assert type(event).__name__ == "BookmarkFolderDeletedEvent" + + def test_parse_feeds_bookmark_folder_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.bookmark_folder.updated"}) + assert type(event).__name__ == "BookmarkFolderUpdatedEvent" + + def test_parse_feeds_comment_added(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.comment.added"}) + assert type(event).__name__ == "CommentAddedEvent" + + def test_parse_feeds_comment_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.comment.deleted"}) + assert type(event).__name__ == "CommentDeletedEvent" + + def test_parse_feeds_comment_reaction_added(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.comment.reaction.added"}) + assert type(event).__name__ == "CommentReactionAddedEvent" + + def test_parse_feeds_comment_reaction_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.comment.reaction.deleted"}) + assert type(event).__name__ == "CommentReactionDeletedEvent" + + def test_parse_feeds_comment_reaction_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.comment.reaction.updated"}) + assert type(event).__name__ == "CommentReactionUpdatedEvent" + + def test_parse_feeds_comment_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.comment.updated"}) + assert type(event).__name__ == "CommentUpdatedEvent" + + def test_parse_feeds_feed_created(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.feed.created"}) + assert type(event).__name__ == "FeedCreatedEvent" + + def test_parse_feeds_feed_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.feed.deleted"}) + assert type(event).__name__ == "FeedDeletedEvent" + + def test_parse_feeds_feed_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.feed.updated"}) + assert type(event).__name__ == "FeedUpdatedEvent" + + def test_parse_feeds_feed_group_changed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.feed_group.changed"}) + assert type(event).__name__ == "FeedGroupChangedEvent" + + def test_parse_feeds_feed_group_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.feed_group.deleted"}) + assert type(event).__name__ == "FeedGroupDeletedEvent" + + def test_parse_feeds_feed_group_restored(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.feed_group.restored"}) + assert type(event).__name__ == "FeedGroupRestoredEvent" + + def test_parse_feeds_feed_member_added(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.feed_member.added"}) + assert type(event).__name__ == "FeedMemberAddedEvent" + + def test_parse_feeds_feed_member_removed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.feed_member.removed"}) + assert type(event).__name__ == "FeedMemberRemovedEvent" + + def test_parse_feeds_feed_member_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.feed_member.updated"}) + assert type(event).__name__ == "FeedMemberUpdatedEvent" + + def test_parse_feeds_follow_created(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.follow.created"}) + assert type(event).__name__ == "FollowCreatedEvent" + + def test_parse_feeds_follow_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.follow.deleted"}) + assert type(event).__name__ == "FollowDeletedEvent" + + def test_parse_feeds_follow_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.follow.updated"}) + assert type(event).__name__ == "FollowUpdatedEvent" + + def test_parse_feeds_notification_feed_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.notification_feed.updated"}) + assert type(event).__name__ == "NotificationFeedUpdatedEvent" + + def test_parse_feeds_stories_feed_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "feeds.stories_feed.updated"}) + assert type(event).__name__ == "StoriesFeedUpdatedEvent" + + def test_parse_flag_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "flag.updated"}) + assert type(event).__name__ == "FlagUpdatedEvent" + + def test_parse_ingress_error(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "ingress.error"}) + assert type(event).__name__ == "IngressErrorEvent" + + def test_parse_ingress_started(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "ingress.started"}) + assert type(event).__name__ == "IngressStartedEvent" + + def test_parse_ingress_stopped(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "ingress.stopped"}) + assert type(event).__name__ == "IngressStoppedEvent" + + def test_parse_member_added(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "member.added"}) + assert type(event).__name__ == "MemberAddedEvent" + + def test_parse_member_removed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "member.removed"}) + assert type(event).__name__ == "MemberRemovedEvent" + + def test_parse_member_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "member.updated"}) + assert type(event).__name__ == "MemberUpdatedEvent" + + def test_parse_message_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "message.deleted"}) + assert type(event).__name__ == "MessageDeletedEvent" + + def test_parse_message_flagged(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "message.flagged"}) + assert type(event).__name__ == "MessageFlaggedEvent" + + def test_parse_message_new(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "message.new"}) + assert type(event).__name__ == "MessageNewEvent" + + def test_parse_message_pending(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "message.pending"}) + assert type(event).__name__ == "PendingMessageEvent" + + def test_parse_message_read(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "message.read"}) + assert type(event).__name__ == "MessageReadEvent" + + def test_parse_message_unblocked(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "message.unblocked"}) + assert type(event).__name__ == "MessageUnblockedEvent" + + def test_parse_message_undeleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "message.undeleted"}) + assert type(event).__name__ == "MessageUndeletedEvent" + + def test_parse_message_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "message.updated"}) + assert type(event).__name__ == "MessageUpdatedEvent" + + def test_parse_moderation_custom_action(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "moderation.custom_action"}) + assert type(event).__name__ == "ModerationCustomActionEvent" + + def test_parse_moderation_flagged(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "moderation.flagged"}) + assert type(event).__name__ == "ModerationFlaggedEvent" + + def test_parse_moderation_mark_reviewed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "moderation.mark_reviewed"}) + assert type(event).__name__ == "ModerationMarkReviewedEvent" + + def test_parse_moderation_check_completed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "moderation_check.completed"}) + assert type(event).__name__ == "ModerationCheckCompletedEvent" + + def test_parse_moderation_rule_triggered(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "moderation_rule.triggered"}) + assert type(event).__name__ == "ModerationRulesTriggeredEvent" + + def test_parse_notification_mark_unread(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "notification.mark_unread"}) + assert type(event).__name__ == "NotificationMarkUnreadEvent" + + def test_parse_notification_reminder_due(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "notification.reminder_due"}) + assert type(event).__name__ == "ReminderNotificationEvent" + + def test_parse_notification_thread_message_new(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "notification.thread_message_new"}) + assert type(event).__name__ == "NotificationThreadMessageNewEvent" + + def test_parse_reaction_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "reaction.deleted"}) + assert type(event).__name__ == "ReactionDeletedEvent" + + def test_parse_reaction_new(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "reaction.new"}) + assert type(event).__name__ == "ReactionNewEvent" + + def test_parse_reaction_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "reaction.updated"}) + assert type(event).__name__ == "ReactionUpdatedEvent" + + def test_parse_reminder_created(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "reminder.created"}) + assert type(event).__name__ == "ReminderCreatedEvent" + + def test_parse_reminder_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "reminder.deleted"}) + assert type(event).__name__ == "ReminderDeletedEvent" + + def test_parse_reminder_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "reminder.updated"}) + assert type(event).__name__ == "ReminderUpdatedEvent" + + def test_parse_review_queue_item_new(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "review_queue_item.new"}) + assert type(event).__name__ == "ReviewQueueItemNewEvent" + + def test_parse_review_queue_item_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "review_queue_item.updated"}) + assert type(event).__name__ == "ReviewQueueItemUpdatedEvent" + + def test_parse_thread_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "thread.updated"}) + assert type(event).__name__ == "ThreadUpdatedEvent" + + def test_parse_user_banned(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user.banned"}) + assert type(event).__name__ == "UserBannedEvent" + + def test_parse_user_deactivated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user.deactivated"}) + assert type(event).__name__ == "UserDeactivatedEvent" + + def test_parse_user_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user.deleted"}) + assert type(event).__name__ == "UserDeletedEvent" + + def test_parse_user_flagged(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user.flagged"}) + assert type(event).__name__ == "UserFlaggedEvent" + + def test_parse_user_messages_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user.messages.deleted"}) + assert type(event).__name__ == "UserMessagesDeletedEvent" + + def test_parse_user_muted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user.muted"}) + assert type(event).__name__ == "UserMutedEvent" + + def test_parse_user_reactivated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user.reactivated"}) + assert type(event).__name__ == "UserReactivatedEvent" + + def test_parse_user_unbanned(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user.unbanned"}) + assert type(event).__name__ == "UserUnbannedEvent" + + def test_parse_user_unmuted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user.unmuted"}) + assert type(event).__name__ == "UserUnmutedEvent" + + def test_parse_user_unread_message_reminder(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user.unread_message_reminder"}) + assert type(event).__name__ == "UserUnreadReminderEvent" + + def test_parse_user_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user.updated"}) + assert type(event).__name__ == "UserUpdatedEvent" + + def test_parse_user_group_created(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user_group.created"}) + assert type(event).__name__ == "UserGroupCreatedEvent" + + def test_parse_user_group_deleted(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user_group.deleted"}) + assert type(event).__name__ == "UserGroupDeletedEvent" + + def test_parse_user_group_member_added(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user_group.member_added"}) + assert type(event).__name__ == "UserGroupMemberAddedEvent" + + def test_parse_user_group_member_removed(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user_group.member_removed"}) + assert type(event).__name__ == "UserGroupMemberRemovedEvent" + + def test_parse_user_group_updated(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "user_group.updated"}) + assert type(event).__name__ == "UserGroupUpdatedEvent" + + def test_unknown_event_type(self): + with pytest.raises(ValueError, match="Unknown webhook event type"): + parse_webhook_event({"type": "unknown.event"}) + + def test_missing_type_field(self): + with pytest.raises(ValueError, match="missing 'type' field"): + parse_webhook_event({"foo": "bar"}) + + def test_invalid_json(self): + with pytest.raises(ValueError, match="Failed to parse"): + parse_webhook_event("not json") diff --git a/getstream/utils/__init__.py b/getstream/utils/__init__.py index 25756ecd..f1daec6b 100644 --- a/getstream/utils/__init__.py +++ b/getstream/utils/__init__.py @@ -93,12 +93,22 @@ def datetime_from_unix_ns( raise TypeError(f"Unsupported type for ts: {type(ts)}") +def _serialize_query_value(value) -> str: + """Serialize a single value for use in a query parameter.""" + if hasattr(value, "to_json") and callable(value.to_json): + return str(value.to_json()) + if isinstance(value, datetime): + return value.isoformat() + return str(value) + + def build_query_param(**kwargs): """ Constructs a dictionary of query parameters from keyword arguments. This function handles various data types: - JSON-serializable objects with a `to_json` method will be serialized using that method. + - datetime objects are converted to ISO 8601 strings. - Booleans are converted to lowercase strings. - Lists are converted to comma-separated strings with URL-encoded values. - Other types (strings, integers, dictionaries) are handled appropriately. @@ -115,13 +125,14 @@ def build_query_param(**kwargs): continue if hasattr(value, "to_json") and callable(value.to_json): params[key] = value.to_json() + elif isinstance(value, datetime): + params[key] = encode_datetime(value) elif isinstance(value, bool): params[key] = str(value).lower() elif isinstance(value, (str, int)): params[key] = str(value) elif isinstance(value, list): - # Process each element, escaping commas in the string representation - params[key] = ",".join(quote(str(v)) for v in value) + params[key] = ",".join(quote(_serialize_query_value(v)) for v in value) else: # For dictionaries or any other types of objects params[key] = json.dumps(value) diff --git a/getstream/video/async_call.py b/getstream/video/async_call.py index 2fe0b7ea..ec7fd972 100644 --- a/getstream/video/async_call.py +++ b/getstream/video/async_call.py @@ -273,6 +273,32 @@ async def list_recordings(self) -> StreamResponse[ListRecordingsResponse]: self._sync_from_response(response.data) return response + @attach_call_cid_async + async def start_recording( + self, recording_type: str, recording_external_storage: Optional[str] = None + ) -> StreamResponse[StartRecordingResponse]: + response = await self.client.start_recording( + type=self.call_type, + id=self.id, + recording_type=recording_type, + recording_external_storage=recording_external_storage, + ) + self._sync_from_response(response.data) + return response + + @attach_call_cid_async + async def stop_recording( + self, + recording_type: str, + ) -> StreamResponse[StopRecordingResponse]: + response = await self.client.stop_recording( + type=self.call_type, + id=self.id, + recording_type=recording_type, + ) + self._sync_from_response(response.data) + return response + @attach_call_cid_async async def get_call_report( self, session_id: Optional[str] = None @@ -326,6 +352,48 @@ async def stop_rtmp_broadcast( self._sync_from_response(response.data) return response + @attach_call_cid_async + async def get_call_participant_session_metrics( + self, + session: str, + user: str, + user_session: str, + since: Optional[datetime] = None, + until: Optional[datetime] = None, + ) -> StreamResponse[GetCallParticipantSessionMetricsResponse]: + response = await self.client.get_call_participant_session_metrics( + type=self.call_type, + id=self.id, + session=session, + user=user, + user_session=user_session, + since=since, + until=until, + ) + self._sync_from_response(response.data) + return response + + @attach_call_cid_async + async def query_call_participant_sessions( + self, + session: str, + limit: Optional[int] = None, + prev: Optional[str] = None, + next: Optional[str] = None, + filter_conditions: Optional[Dict[str, object]] = None, + ) -> StreamResponse[QueryCallParticipantSessionsResponse]: + response = await self.client.query_call_participant_sessions( + type=self.call_type, + id=self.id, + session=session, + limit=limit, + prev=prev, + next=next, + filter_conditions=filter_conditions, + ) + self._sync_from_response(response.data) + return response + @attach_call_cid_async async def start_hls_broadcasting( self, @@ -367,18 +435,6 @@ async def start_frame_recording( self._sync_from_response(response.data) return response - @attach_call_cid_async - async def start_recording( - self, recording_external_storage: Optional[str] = None - ) -> StreamResponse[StartRecordingResponse]: - response = await self.client.start_recording( - type=self.call_type, - id=self.id, - recording_external_storage=recording_external_storage, - ) - self._sync_from_response(response.data) - return response - @attach_call_cid_async async def start_transcription( self, @@ -451,17 +507,6 @@ async def stop_live( self._sync_from_response(response.data) return response - @attach_call_cid_async - async def stop_recording( - self, - ) -> StreamResponse[StopRecordingResponse]: - response = await self.client.stop_recording( - type=self.call_type, - id=self.id, - ) - self._sync_from_response(response.data) - return response - @attach_call_cid_async async def stop_transcription( self, stop_closed_captions: Optional[bool] = None diff --git a/getstream/video/async_rest_client.py b/getstream/video/async_rest_client.py index 815a4059..4766e143 100644 --- a/getstream/video/async_rest_client.py +++ b/getstream/video/async_rest_client.py @@ -3,7 +3,7 @@ from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.utils import build_query_param, build_body_dict +from getstream.utils import build_query_param class VideoRestClient(AsyncBaseClient): @@ -49,14 +49,14 @@ async def query_user_feedback( sort: Optional[List[SortParamRequest]] = None, filter_conditions: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryUserFeedbackResponse]: - query_params = build_query_param(full=full) - json = build_body_dict( + query_params = build_query_param(**{"full": full}) + json = QueryUserFeedbackRequest( limit=limit, next=next, prev=prev, sort=sort, filter_conditions=filter_conditions, - ) + ).to_dict() return await self.post( "/api/v2/video/call/feedback", QueryUserFeedbackResponse, @@ -75,7 +75,7 @@ async def query_call_members( sort: Optional[List[SortParamRequest]] = None, filter_conditions: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryCallMembersResponse]: - json = build_body_dict( + json = QueryCallMembersRequest( id=id, type=type, limit=limit, @@ -83,7 +83,7 @@ async def query_call_members( prev=prev, sort=sort, filter_conditions=filter_conditions, - ) + ).to_dict() return await self.post( "/api/v2/video/call/members", QueryCallMembersResponse, json=json ) @@ -97,13 +97,13 @@ async def query_call_stats( sort: Optional[List[SortParamRequest]] = None, filter_conditions: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryCallStatsResponse]: - json = build_body_dict( + json = QueryCallStatsRequest( limit=limit, next=next, prev=prev, sort=sort, filter_conditions=filter_conditions, - ) + ).to_dict() return await self.post( "/api/v2/video/call/stats", QueryCallStatsResponse, json=json ) @@ -119,7 +119,12 @@ async def get_call( video: Optional[bool] = 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, + } ) path_params = { "type": type, @@ -145,9 +150,9 @@ async def update_call( "type": type, "id": id, } - json = build_body_dict( + json = UpdateCallRequest( starts_at=starts_at, custom=custom, settings_override=settings_override - ) + ).to_dict() return await self.patch( "/api/v2/video/call/{type}/{id}", UpdateCallResponse, @@ -170,13 +175,13 @@ async def get_or_create_call( "type": type, "id": id, } - json = build_body_dict( + json = GetOrCreateCallRequest( members_limit=members_limit, notify=notify, ring=ring, video=video, data=data, - ) + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}", GetOrCreateCallResponse, @@ -192,7 +197,7 @@ async def block_user( "type": type, "id": id, } - json = build_body_dict(user_id=user_id) + json = BlockUserRequest(user_id=user_id).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/block", BlockUserResponse, @@ -219,7 +224,7 @@ async def send_closed_caption( "type": type, "id": id, } - json = build_body_dict( + json = SendClosedCaptionRequest( speaker_id=speaker_id, text=text, end_time=end_time, @@ -229,7 +234,7 @@ async def send_closed_caption( translated=translated, user_id=user_id, user=user, - ) + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/closed_captions", SendClosedCaptionResponse, @@ -245,7 +250,7 @@ async def delete_call( "type": type, "id": id, } - json = build_body_dict(hard=hard) + json = DeleteCallRequest(hard=hard).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/delete", DeleteCallResponse, @@ -266,7 +271,7 @@ async def send_call_event( "type": type, "id": id, } - json = build_body_dict(user_id=user_id, custom=custom, user=user) + json = SendCallEventRequest(user_id=user_id, custom=custom, user=user).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/event", SendCallEventResponse, @@ -290,14 +295,14 @@ async def collect_user_feedback( "type": type, "id": id, } - json = build_body_dict( + json = CollectUserFeedbackRequest( rating=rating, sdk=sdk, sdk_version=sdk_version, reason=reason, user_session_id=user_session_id, custom=custom, - ) + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/feedback", CollectUserFeedbackResponse, @@ -324,7 +329,7 @@ async def go_live( "type": type, "id": id, } - json = build_body_dict( + json = GoLiveRequest( recording_storage_name=recording_storage_name, start_closed_caption=start_closed_caption, start_composite_recording=start_composite_recording, @@ -334,7 +339,7 @@ async def go_live( start_recording=start_recording, start_transcription=start_transcription, transcription_storage_name=transcription_storage_name, - ) + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/go_live", GoLiveResponse, @@ -356,9 +361,9 @@ async def kick_user( "type": type, "id": id, } - json = build_body_dict( + json = KickUserRequest( user_id=user_id, block=block, kicked_by_id=kicked_by_id, kicked_by=kicked_by - ) + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/kick", KickUserResponse, @@ -390,9 +395,9 @@ async def update_call_members( "type": type, "id": id, } - json = build_body_dict( + json = UpdateCallMembersRequest( remove_members=remove_members, update_members=update_members - ) + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/members", UpdateCallMembersResponse, @@ -418,7 +423,7 @@ async def mute_users( "type": type, "id": id, } - json = build_body_dict( + json = MuteUsersRequest( audio=audio, mute_all_users=mute_all_users, muted_by_id=muted_by_id, @@ -427,7 +432,7 @@ async def mute_users( video=video, user_ids=user_ids, muted_by=muted_by, - ) + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/mute_users", MuteUsersResponse, @@ -443,12 +448,14 @@ async def query_call_participants( limit: Optional[int] = None, filter_conditions: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryCallParticipantsResponse]: - query_params = build_query_param(limit=limit) + query_params = build_query_param(**{"limit": limit}) path_params = { "id": id, "type": type, } - json = build_body_dict(filter_conditions=filter_conditions) + json = QueryCallParticipantsRequest( + filter_conditions=filter_conditions + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/participants", QueryCallParticipantsResponse, @@ -465,7 +472,7 @@ async def video_pin( "type": type, "id": id, } - json = build_body_dict(session_id=session_id, user_id=user_id) + json = PinRequest(session_id=session_id, user_id=user_id).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/pin", PinResponse, @@ -487,11 +494,54 @@ async def list_recordings( path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.start_recording") + async def start_recording( + self, + type: str, + id: str, + recording_type: str, + recording_external_storage: Optional[str] = None, + ) -> StreamResponse[StartRecordingResponse]: + path_params = { + "type": type, + "id": id, + "recording_type": recording_type, + } + json = StartRecordingRequest( + recording_external_storage=recording_external_storage + ).to_dict() + return await self.post( + "/api/v2/video/call/{type}/{id}/recordings/{recording_type}/start", + StartRecordingResponse, + path_params=path_params, + json=json, + ) + + @telemetry.operation_name("getstream.api.video.stop_recording") + async def stop_recording( + self, + type: str, + id: str, + recording_type: str, + ) -> StreamResponse[StopRecordingResponse]: + path_params = { + "type": type, + "id": id, + "recording_type": recording_type, + } + json = StopRecordingRequest().to_dict() + return await self.post( + "/api/v2/video/call/{type}/{id}/recordings/{recording_type}/stop", + StopRecordingResponse, + path_params=path_params, + json=json, + ) + @telemetry.operation_name("getstream.api.video.get_call_report") async def get_call_report( self, type: str, id: str, session_id: Optional[str] = None ) -> StreamResponse[GetCallReportResponse]: - query_params = build_query_param(session_id=session_id) + query_params = build_query_param(**{"session_id": session_id}) path_params = { "type": type, "id": id, @@ -515,7 +565,7 @@ async def ring_call( "type": type, "id": id, } - json = build_body_dict(video=video, members_ids=members_ids) + json = RingCallRequest(video=video, members_ids=members_ids).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/ring", RingCallResponse, @@ -531,7 +581,7 @@ async def start_rtmp_broadcasts( "type": type, "id": id, } - json = build_body_dict(broadcasts=broadcasts) + json = StartRTMPBroadcastsRequest(broadcasts=broadcasts).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/rtmp_broadcasts", StartRTMPBroadcastsResponse, @@ -565,7 +615,7 @@ async def stop_rtmp_broadcast( "id": id, "name": name, } - json = build_body_dict() + json = StopRTMPBroadcastsRequest().to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/rtmp_broadcasts/{name}/stop", StopRTMPBroadcastsResponse, @@ -573,6 +623,65 @@ async def stop_rtmp_broadcast( json=json, ) + @telemetry.operation_name( + "getstream.api.video.get_call_participant_session_metrics" + ) + async def get_call_participant_session_metrics( + self, + type: str, + id: str, + session: str, + user: str, + user_session: str, + since: Optional[datetime] = None, + until: Optional[datetime] = None, + ) -> StreamResponse[GetCallParticipantSessionMetricsResponse]: + query_params = build_query_param(**{"since": since, "until": until}) + path_params = { + "type": type, + "id": id, + "session": session, + "user": user, + "user_session": user_session, + } + return await self.get( + "/api/v2/video/call/{type}/{id}/session/{session}/participant/{user}/{user_session}/details/track", + GetCallParticipantSessionMetricsResponse, + query_params=query_params, + path_params=path_params, + ) + + @telemetry.operation_name("getstream.api.video.query_call_participant_sessions") + async def query_call_participant_sessions( + self, + type: str, + id: str, + session: str, + limit: Optional[int] = None, + prev: Optional[str] = None, + next: Optional[str] = None, + filter_conditions: Optional[Dict[str, object]] = None, + ) -> StreamResponse[QueryCallParticipantSessionsResponse]: + query_params = build_query_param( + **{ + "limit": limit, + "prev": prev, + "next": next, + "filter_conditions": filter_conditions, + } + ) + path_params = { + "type": type, + "id": id, + "session": session, + } + return await self.get( + "/api/v2/video/call/{type}/{id}/session/{session}/participant_sessions", + QueryCallParticipantSessionsResponse, + query_params=query_params, + path_params=path_params, + ) + @telemetry.operation_name("getstream.api.video.start_hls_broadcasting") async def start_hls_broadcasting( self, type: str, id: str @@ -601,12 +710,12 @@ async def start_closed_captions( "type": type, "id": id, } - json = build_body_dict( + json = StartClosedCaptionsRequest( enable_transcription=enable_transcription, external_storage=external_storage, language=language, speech_segment_config=speech_segment_config, - ) + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/start_closed_captions", StartClosedCaptionsResponse, @@ -622,7 +731,9 @@ async def start_frame_recording( "type": type, "id": id, } - json = build_body_dict(recording_external_storage=recording_external_storage) + json = StartFrameRecordingRequest( + recording_external_storage=recording_external_storage + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/start_frame_recording", StartFrameRecordingResponse, @@ -630,22 +741,6 @@ async def start_frame_recording( json=json, ) - @telemetry.operation_name("getstream.api.video.start_recording") - async def start_recording( - self, type: str, id: str, recording_external_storage: Optional[str] = None - ) -> StreamResponse[StartRecordingResponse]: - path_params = { - "type": type, - "id": id, - } - json = build_body_dict(recording_external_storage=recording_external_storage) - return await self.post( - "/api/v2/video/call/{type}/{id}/start_recording", - StartRecordingResponse, - path_params=path_params, - json=json, - ) - @telemetry.operation_name("getstream.api.video.start_transcription") async def start_transcription( self, @@ -659,11 +754,11 @@ async def start_transcription( "type": type, "id": id, } - json = build_body_dict( + json = StartTranscriptionRequest( enable_closed_captions=enable_closed_captions, language=language, transcription_external_storage=transcription_external_storage, - ) + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/start_transcription", StartTranscriptionResponse, @@ -693,7 +788,9 @@ async def stop_closed_captions( "type": type, "id": id, } - json = build_body_dict(stop_transcription=stop_transcription) + json = StopClosedCaptionsRequest( + stop_transcription=stop_transcription + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/stop_closed_captions", StopClosedCaptionsResponse, @@ -733,7 +830,7 @@ async def stop_live( "type": type, "id": id, } - json = build_body_dict( + json = StopLiveRequest( continue_closed_caption=continue_closed_caption, continue_composite_recording=continue_composite_recording, continue_hls=continue_hls, @@ -742,7 +839,7 @@ async def stop_live( continue_recording=continue_recording, continue_rtmp_broadcasts=continue_rtmp_broadcasts, continue_transcription=continue_transcription, - ) + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/stop_live", StopLiveResponse, @@ -750,24 +847,6 @@ async def stop_live( json=json, ) - @telemetry.operation_name("getstream.api.video.stop_recording") - async def stop_recording( - self, - type: str, - id: str, - ) -> StreamResponse[StopRecordingResponse]: - path_params = { - "type": type, - "id": id, - } - json = build_body_dict() - return await self.post( - "/api/v2/video/call/{type}/{id}/stop_recording", - StopRecordingResponse, - path_params=path_params, - json=json, - ) - @telemetry.operation_name("getstream.api.video.stop_transcription") async def stop_transcription( self, type: str, id: str, stop_closed_captions: Optional[bool] = None @@ -776,7 +855,9 @@ async def stop_transcription( "type": type, "id": id, } - json = build_body_dict(stop_closed_captions=stop_closed_captions) + json = StopTranscriptionRequest( + stop_closed_captions=stop_closed_captions + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/stop_transcription", StopTranscriptionResponse, @@ -806,7 +887,7 @@ async def unblock_user( "type": type, "id": id, } - json = build_body_dict(user_id=user_id) + json = UnblockUserRequest(user_id=user_id).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/unblock", UnblockUserResponse, @@ -822,7 +903,7 @@ async def video_unpin( "type": type, "id": id, } - json = build_body_dict(session_id=session_id, user_id=user_id) + json = UnpinRequest(session_id=session_id, user_id=user_id).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/unpin", UnpinResponse, @@ -843,11 +924,11 @@ async def update_user_permissions( "type": type, "id": id, } - json = build_body_dict( + json = UpdateUserPermissionsRequest( user_id=user_id, grant_permissions=grant_permissions, revoke_permissions=revoke_permissions, - ) + ).to_dict() return await self.post( "/api/v2/video/call/{type}/{id}/user_permissions", UpdateUserPermissionsResponse, @@ -900,11 +981,13 @@ async def get_call_stats_map( exclude_sfus: Optional[bool] = None, ) -> StreamResponse[QueryCallStatsMapResponse]: query_params = build_query_param( - start_time=start_time, - end_time=end_time, - exclude_publishers=exclude_publishers, - exclude_subscribers=exclude_subscribers, - exclude_sfus=exclude_sfus, + **{ + "start_time": start_time, + "end_time": end_time, + "exclude_publishers": exclude_publishers, + "exclude_subscribers": exclude_subscribers, + "exclude_sfus": exclude_sfus, + } ) path_params = { "call_type": call_type, @@ -933,7 +1016,7 @@ async def get_call_session_participant_stats_details( max_points: Optional[int] = None, ) -> StreamResponse[GetCallSessionParticipantStatsDetailsResponse]: query_params = build_query_param( - since=since, until=until, max_points=max_points + **{"since": since, "until": until, "max_points": max_points} ) path_params = { "call_type": call_type, @@ -964,11 +1047,13 @@ async def query_call_session_participant_stats( filter_conditions: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryCallSessionParticipantStatsResponse]: query_params = build_query_param( - limit=limit, - prev=prev, - next=next, - sort=sort, - filter_conditions=filter_conditions, + **{ + "limit": limit, + "prev": prev, + "next": next, + "sort": sort, + "filter_conditions": filter_conditions, + } ) path_params = { "call_type": call_type, @@ -997,7 +1082,7 @@ async def get_call_session_participant_stats_timeline( severity: Optional[List[str]] = None, ) -> StreamResponse[QueryCallSessionParticipantStatsTimelineResponse]: query_params = build_query_param( - start_time=start_time, end_time=end_time, severity=severity + **{"start_time": start_time, "end_time": end_time, "severity": severity} ) path_params = { "call_type": call_type, @@ -1022,13 +1107,13 @@ async def query_calls( sort: Optional[List[SortParamRequest]] = None, filter_conditions: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryCallsResponse]: - json = build_body_dict( + json = QueryCallsRequest( limit=limit, next=next, prev=prev, sort=sort, filter_conditions=filter_conditions, - ) + ).to_dict() return await self.post("/api/v2/video/calls", QueryCallsResponse, json=json) @telemetry.operation_name("getstream.api.video.list_call_types") @@ -1041,16 +1126,16 @@ async def create_call_type( name: str, external_storage: Optional[str] = None, grants: Optional[Dict[str, List[str]]] = None, - notification_settings: Optional[NotificationSettings] = None, + notification_settings: Optional[NotificationSettingsRequest] = None, settings: Optional[CallSettingsRequest] = None, ) -> StreamResponse[CreateCallTypeResponse]: - json = build_body_dict( + json = CreateCallTypeRequest( name=name, external_storage=external_storage, grants=grants, notification_settings=notification_settings, settings=settings, - ) + ).to_dict() return await self.post( "/api/v2/video/calltypes", CreateCallTypeResponse, json=json ) @@ -1081,18 +1166,18 @@ async def update_call_type( name: str, external_storage: Optional[str] = None, grants: Optional[Dict[str, List[str]]] = None, - notification_settings: Optional[NotificationSettings] = None, + notification_settings: Optional[NotificationSettingsRequest] = None, settings: Optional[CallSettingsRequest] = None, ) -> StreamResponse[UpdateCallTypeResponse]: path_params = { "name": name, } - json = build_body_dict( + json = UpdateCallTypeRequest( external_storage=external_storage, grants=grants, notification_settings=notification_settings, settings=settings, - ) + ).to_dict() return await self.put( "/api/v2/video/calltypes/{name}", UpdateCallTypeResponse, @@ -1104,30 +1189,12 @@ async def update_call_type( async def get_edges(self) -> StreamResponse[GetEdgesResponse]: return await self.get("/api/v2/video/edges", GetEdgesResponse) - @telemetry.operation_name("getstream.api.video.resolve_sip_inbound") - async def resolve_sip_inbound( - self, - sip_caller_number: str, - sip_trunk_number: str, - challenge: SIPChallenge, - sip_headers: Optional[Dict[str, str]] = None, - ) -> StreamResponse[ResolveSipInboundResponse]: - json = build_body_dict( - sip_caller_number=sip_caller_number, - sip_trunk_number=sip_trunk_number, - challenge=challenge, - sip_headers=sip_headers, - ) - return await self.post( - "/api/v2/video/sip/resolve", ResolveSipInboundResponse, json=json - ) - @telemetry.operation_name("getstream.api.video.list_sip_inbound_routing_rule") async def list_sip_inbound_routing_rule( self, ) -> StreamResponse[ListSIPInboundRoutingRuleResponse]: return await self.get( - "/api/v2/video/sip/routing_rules", ListSIPInboundRoutingRuleResponse + "/api/v2/video/sip/inbound_routing_rules", ListSIPInboundRoutingRuleResponse ) @telemetry.operation_name("getstream.api.video.create_sip_inbound_routing_rule") @@ -1143,7 +1210,7 @@ async def create_sip_inbound_routing_rule( pin_protection_configs: Optional[SIPPinProtectionConfigsRequest] = None, pin_routing_configs: Optional[SIPInboundRoutingRulePinConfigsRequest] = None, ) -> StreamResponse[SIPInboundRoutingRuleResponse]: - json = build_body_dict( + json = SIPInboundRoutingRuleRequest( name=name, trunk_ids=trunk_ids, caller_configs=caller_configs, @@ -1153,9 +1220,11 @@ async def create_sip_inbound_routing_rule( direct_routing_configs=direct_routing_configs, pin_protection_configs=pin_protection_configs, pin_routing_configs=pin_routing_configs, - ) + ).to_dict() return await self.post( - "/api/v2/video/sip/routing_rules", SIPInboundRoutingRuleResponse, json=json + "/api/v2/video/sip/inbound_routing_rules", + SIPInboundRoutingRuleResponse, + json=json, ) @telemetry.operation_name("getstream.api.video.delete_sip_inbound_routing_rule") @@ -1166,7 +1235,7 @@ async def delete_sip_inbound_routing_rule( "id": id, } return await self.delete( - "/api/v2/video/sip/routing_rules/{id}", + "/api/v2/video/sip/inbound_routing_rules/{id}", DeleteSIPInboundRoutingRuleResponse, path_params=path_params, ) @@ -1188,7 +1257,7 @@ async def update_sip_inbound_routing_rule( path_params = { "id": id, } - json = build_body_dict( + json = UpdateSIPInboundRoutingRuleRequest( name=name, called_numbers=called_numbers, trunk_ids=trunk_ids, @@ -1198,9 +1267,9 @@ async def update_sip_inbound_routing_rule( direct_routing_configs=direct_routing_configs, pin_protection_configs=pin_protection_configs, pin_routing_configs=pin_routing_configs, - ) + ).to_dict() return await self.put( - "/api/v2/video/sip/routing_rules/{id}", + "/api/v2/video/sip/inbound_routing_rules/{id}", UpdateSIPInboundRoutingRuleResponse, path_params=path_params, json=json, @@ -1208,15 +1277,15 @@ async def update_sip_inbound_routing_rule( @telemetry.operation_name("getstream.api.video.list_sip_trunks") async def list_sip_trunks(self) -> StreamResponse[ListSIPTrunksResponse]: - return await self.get("/api/v2/video/sip/trunks", ListSIPTrunksResponse) + return await self.get("/api/v2/video/sip/inbound_trunks", ListSIPTrunksResponse) @telemetry.operation_name("getstream.api.video.create_sip_trunk") async def create_sip_trunk( self, name: str, numbers: List[str] ) -> StreamResponse[CreateSIPTrunkResponse]: - json = build_body_dict(name=name, numbers=numbers) + json = CreateSIPTrunkRequest(name=name, numbers=numbers).to_dict() return await self.post( - "/api/v2/video/sip/trunks", CreateSIPTrunkResponse, json=json + "/api/v2/video/sip/inbound_trunks", CreateSIPTrunkResponse, json=json ) @telemetry.operation_name("getstream.api.video.delete_sip_trunk") @@ -1225,7 +1294,7 @@ async def delete_sip_trunk(self, id: str) -> StreamResponse[DeleteSIPTrunkRespon "id": id, } return await self.delete( - "/api/v2/video/sip/trunks/{id}", + "/api/v2/video/sip/inbound_trunks/{id}", DeleteSIPTrunkResponse, path_params=path_params, ) @@ -1237,14 +1306,34 @@ async def update_sip_trunk( path_params = { "id": id, } - json = build_body_dict(name=name, numbers=numbers) + json = UpdateSIPTrunkRequest(name=name, numbers=numbers).to_dict() return await self.put( - "/api/v2/video/sip/trunks/{id}", + "/api/v2/video/sip/inbound_trunks/{id}", UpdateSIPTrunkResponse, path_params=path_params, json=json, ) + @telemetry.operation_name("getstream.api.video.resolve_sip_inbound") + async def resolve_sip_inbound( + self, + sip_caller_number: str, + sip_trunk_number: str, + challenge: SIPChallengeRequest, + routing_number: Optional[str] = None, + sip_headers: Optional[Dict[str, str]] = None, + ) -> StreamResponse[ResolveSipInboundResponse]: + json = ResolveSipInboundRequest( + sip_caller_number=sip_caller_number, + sip_trunk_number=sip_trunk_number, + challenge=challenge, + routing_number=routing_number, + sip_headers=sip_headers, + ).to_dict() + return await self.post( + "/api/v2/video/sip/resolve", ResolveSipInboundResponse, json=json + ) + @telemetry.operation_name("getstream.api.video.query_aggregate_call_stats") async def query_aggregate_call_stats( self, @@ -1252,7 +1341,9 @@ async def query_aggregate_call_stats( to: Optional[str] = None, report_types: Optional[List[str]] = None, ) -> StreamResponse[QueryAggregateCallStatsResponse]: - json = build_body_dict(_from=_from, to=to, report_types=report_types) + json = QueryAggregateCallStatsRequest( + _from=_from, to=to, report_types=report_types + ).to_dict() return await self.post( "/api/v2/video/stats", QueryAggregateCallStatsResponse, json=json ) diff --git a/getstream/video/call.py b/getstream/video/call.py index 5dfef99d..197f5d0e 100644 --- a/getstream/video/call.py +++ b/getstream/video/call.py @@ -267,6 +267,32 @@ def list_recordings(self) -> StreamResponse[ListRecordingsResponse]: self._sync_from_response(response.data) return response + @attach_call_cid + def start_recording( + self, recording_type: str, recording_external_storage: Optional[str] = None + ) -> StreamResponse[StartRecordingResponse]: + response = self.client.start_recording( + type=self.call_type, + id=self.id, + recording_type=recording_type, + recording_external_storage=recording_external_storage, + ) + self._sync_from_response(response.data) + return response + + @attach_call_cid + def stop_recording( + self, + recording_type: str, + ) -> StreamResponse[StopRecordingResponse]: + response = self.client.stop_recording( + type=self.call_type, + id=self.id, + recording_type=recording_type, + ) + self._sync_from_response(response.data) + return response + @attach_call_cid def get_call_report( self, session_id: Optional[str] = None @@ -316,6 +342,48 @@ def stop_rtmp_broadcast( self._sync_from_response(response.data) return response + @attach_call_cid + def get_call_participant_session_metrics( + self, + session: str, + user: str, + user_session: str, + since: Optional[datetime] = None, + until: Optional[datetime] = None, + ) -> StreamResponse[GetCallParticipantSessionMetricsResponse]: + response = self.client.get_call_participant_session_metrics( + type=self.call_type, + id=self.id, + session=session, + user=user, + user_session=user_session, + since=since, + until=until, + ) + self._sync_from_response(response.data) + return response + + @attach_call_cid + def query_call_participant_sessions( + self, + session: str, + limit: Optional[int] = None, + prev: Optional[str] = None, + next: Optional[str] = None, + filter_conditions: Optional[Dict[str, object]] = None, + ) -> StreamResponse[QueryCallParticipantSessionsResponse]: + response = self.client.query_call_participant_sessions( + type=self.call_type, + id=self.id, + session=session, + limit=limit, + prev=prev, + next=next, + filter_conditions=filter_conditions, + ) + self._sync_from_response(response.data) + return response + @attach_call_cid def start_hls_broadcasting(self) -> StreamResponse[StartHLSBroadcastingResponse]: response = self.client.start_hls_broadcasting(type=self.call_type, id=self.id) @@ -353,18 +421,6 @@ def start_frame_recording( self._sync_from_response(response.data) return response - @attach_call_cid - def start_recording( - self, recording_external_storage: Optional[str] = None - ) -> StreamResponse[StartRecordingResponse]: - response = self.client.start_recording( - type=self.call_type, - id=self.id, - recording_external_storage=recording_external_storage, - ) - self._sync_from_response(response.data) - return response - @attach_call_cid def start_transcription( self, @@ -431,17 +487,6 @@ def stop_live( self._sync_from_response(response.data) return response - @attach_call_cid - def stop_recording( - self, - ) -> StreamResponse[StopRecordingResponse]: - response = self.client.stop_recording( - type=self.call_type, - id=self.id, - ) - self._sync_from_response(response.data) - return response - @attach_call_cid def stop_transcription( self, stop_closed_captions: Optional[bool] = None diff --git a/getstream/video/rest_client.py b/getstream/video/rest_client.py index 17dcd2d8..279c3c6d 100644 --- a/getstream/video/rest_client.py +++ b/getstream/video/rest_client.py @@ -3,7 +3,7 @@ from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.utils import build_query_param, build_body_dict +from getstream.utils import build_query_param class VideoRestClient(BaseClient): @@ -47,14 +47,14 @@ def query_user_feedback( sort: Optional[List[SortParamRequest]] = None, filter_conditions: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryUserFeedbackResponse]: - query_params = build_query_param(full=full) - json = build_body_dict( + query_params = build_query_param(**{"full": full}) + json = QueryUserFeedbackRequest( limit=limit, next=next, prev=prev, sort=sort, filter_conditions=filter_conditions, - ) + ).to_dict() return self.post( "/api/v2/video/call/feedback", QueryUserFeedbackResponse, @@ -73,7 +73,7 @@ def query_call_members( sort: Optional[List[SortParamRequest]] = None, filter_conditions: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryCallMembersResponse]: - json = build_body_dict( + json = QueryCallMembersRequest( id=id, type=type, limit=limit, @@ -81,7 +81,7 @@ def query_call_members( prev=prev, sort=sort, filter_conditions=filter_conditions, - ) + ).to_dict() return self.post( "/api/v2/video/call/members", QueryCallMembersResponse, json=json ) @@ -95,13 +95,13 @@ def query_call_stats( sort: Optional[List[SortParamRequest]] = None, filter_conditions: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryCallStatsResponse]: - json = build_body_dict( + json = QueryCallStatsRequest( limit=limit, next=next, prev=prev, sort=sort, filter_conditions=filter_conditions, - ) + ).to_dict() return self.post("/api/v2/video/call/stats", QueryCallStatsResponse, json=json) @telemetry.operation_name("getstream.api.video.get_call") @@ -115,7 +115,12 @@ def get_call( video: Optional[bool] = 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, + } ) path_params = { "type": type, @@ -141,9 +146,9 @@ def update_call( "type": type, "id": id, } - json = build_body_dict( + json = UpdateCallRequest( starts_at=starts_at, custom=custom, settings_override=settings_override - ) + ).to_dict() return self.patch( "/api/v2/video/call/{type}/{id}", UpdateCallResponse, @@ -166,13 +171,13 @@ def get_or_create_call( "type": type, "id": id, } - json = build_body_dict( + json = GetOrCreateCallRequest( members_limit=members_limit, notify=notify, ring=ring, video=video, data=data, - ) + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}", GetOrCreateCallResponse, @@ -188,7 +193,7 @@ def block_user( "type": type, "id": id, } - json = build_body_dict(user_id=user_id) + json = BlockUserRequest(user_id=user_id).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/block", BlockUserResponse, @@ -215,7 +220,7 @@ def send_closed_caption( "type": type, "id": id, } - json = build_body_dict( + json = SendClosedCaptionRequest( speaker_id=speaker_id, text=text, end_time=end_time, @@ -225,7 +230,7 @@ def send_closed_caption( translated=translated, user_id=user_id, user=user, - ) + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/closed_captions", SendClosedCaptionResponse, @@ -241,7 +246,7 @@ def delete_call( "type": type, "id": id, } - json = build_body_dict(hard=hard) + json = DeleteCallRequest(hard=hard).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/delete", DeleteCallResponse, @@ -262,7 +267,7 @@ def send_call_event( "type": type, "id": id, } - json = build_body_dict(user_id=user_id, custom=custom, user=user) + json = SendCallEventRequest(user_id=user_id, custom=custom, user=user).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/event", SendCallEventResponse, @@ -286,14 +291,14 @@ def collect_user_feedback( "type": type, "id": id, } - json = build_body_dict( + json = CollectUserFeedbackRequest( rating=rating, sdk=sdk, sdk_version=sdk_version, reason=reason, user_session_id=user_session_id, custom=custom, - ) + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/feedback", CollectUserFeedbackResponse, @@ -320,7 +325,7 @@ def go_live( "type": type, "id": id, } - json = build_body_dict( + json = GoLiveRequest( recording_storage_name=recording_storage_name, start_closed_caption=start_closed_caption, start_composite_recording=start_composite_recording, @@ -330,7 +335,7 @@ def go_live( start_recording=start_recording, start_transcription=start_transcription, transcription_storage_name=transcription_storage_name, - ) + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/go_live", GoLiveResponse, @@ -352,9 +357,9 @@ def kick_user( "type": type, "id": id, } - json = build_body_dict( + json = KickUserRequest( user_id=user_id, block=block, kicked_by_id=kicked_by_id, kicked_by=kicked_by - ) + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/kick", KickUserResponse, @@ -386,9 +391,9 @@ def update_call_members( "type": type, "id": id, } - json = build_body_dict( + json = UpdateCallMembersRequest( remove_members=remove_members, update_members=update_members - ) + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/members", UpdateCallMembersResponse, @@ -414,7 +419,7 @@ def mute_users( "type": type, "id": id, } - json = build_body_dict( + json = MuteUsersRequest( audio=audio, mute_all_users=mute_all_users, muted_by_id=muted_by_id, @@ -423,7 +428,7 @@ def mute_users( video=video, user_ids=user_ids, muted_by=muted_by, - ) + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/mute_users", MuteUsersResponse, @@ -439,12 +444,14 @@ def query_call_participants( limit: Optional[int] = None, filter_conditions: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryCallParticipantsResponse]: - query_params = build_query_param(limit=limit) + query_params = build_query_param(**{"limit": limit}) path_params = { "id": id, "type": type, } - json = build_body_dict(filter_conditions=filter_conditions) + json = QueryCallParticipantsRequest( + filter_conditions=filter_conditions + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/participants", QueryCallParticipantsResponse, @@ -461,7 +468,7 @@ def video_pin( "type": type, "id": id, } - json = build_body_dict(session_id=session_id, user_id=user_id) + json = PinRequest(session_id=session_id, user_id=user_id).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/pin", PinResponse, @@ -483,11 +490,54 @@ def list_recordings( path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.start_recording") + def start_recording( + self, + type: str, + id: str, + recording_type: str, + recording_external_storage: Optional[str] = None, + ) -> StreamResponse[StartRecordingResponse]: + path_params = { + "type": type, + "id": id, + "recording_type": recording_type, + } + json = StartRecordingRequest( + recording_external_storage=recording_external_storage + ).to_dict() + return self.post( + "/api/v2/video/call/{type}/{id}/recordings/{recording_type}/start", + StartRecordingResponse, + path_params=path_params, + json=json, + ) + + @telemetry.operation_name("getstream.api.video.stop_recording") + def stop_recording( + self, + type: str, + id: str, + recording_type: str, + ) -> StreamResponse[StopRecordingResponse]: + path_params = { + "type": type, + "id": id, + "recording_type": recording_type, + } + json = StopRecordingRequest().to_dict() + return self.post( + "/api/v2/video/call/{type}/{id}/recordings/{recording_type}/stop", + StopRecordingResponse, + path_params=path_params, + json=json, + ) + @telemetry.operation_name("getstream.api.video.get_call_report") def get_call_report( self, type: str, id: str, session_id: Optional[str] = None ) -> StreamResponse[GetCallReportResponse]: - query_params = build_query_param(session_id=session_id) + query_params = build_query_param(**{"session_id": session_id}) path_params = { "type": type, "id": id, @@ -511,7 +561,7 @@ def ring_call( "type": type, "id": id, } - json = build_body_dict(video=video, members_ids=members_ids) + json = RingCallRequest(video=video, members_ids=members_ids).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/ring", RingCallResponse, @@ -527,7 +577,7 @@ def start_rtmp_broadcasts( "type": type, "id": id, } - json = build_body_dict(broadcasts=broadcasts) + json = StartRTMPBroadcastsRequest(broadcasts=broadcasts).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/rtmp_broadcasts", StartRTMPBroadcastsResponse, @@ -561,7 +611,7 @@ def stop_rtmp_broadcast( "id": id, "name": name, } - json = build_body_dict() + json = StopRTMPBroadcastsRequest().to_dict() return self.post( "/api/v2/video/call/{type}/{id}/rtmp_broadcasts/{name}/stop", StopRTMPBroadcastsResponse, @@ -569,6 +619,65 @@ def stop_rtmp_broadcast( json=json, ) + @telemetry.operation_name( + "getstream.api.video.get_call_participant_session_metrics" + ) + def get_call_participant_session_metrics( + self, + type: str, + id: str, + session: str, + user: str, + user_session: str, + since: Optional[datetime] = None, + until: Optional[datetime] = None, + ) -> StreamResponse[GetCallParticipantSessionMetricsResponse]: + query_params = build_query_param(**{"since": since, "until": until}) + path_params = { + "type": type, + "id": id, + "session": session, + "user": user, + "user_session": user_session, + } + return self.get( + "/api/v2/video/call/{type}/{id}/session/{session}/participant/{user}/{user_session}/details/track", + GetCallParticipantSessionMetricsResponse, + query_params=query_params, + path_params=path_params, + ) + + @telemetry.operation_name("getstream.api.video.query_call_participant_sessions") + def query_call_participant_sessions( + self, + type: str, + id: str, + session: str, + limit: Optional[int] = None, + prev: Optional[str] = None, + next: Optional[str] = None, + filter_conditions: Optional[Dict[str, object]] = None, + ) -> StreamResponse[QueryCallParticipantSessionsResponse]: + query_params = build_query_param( + **{ + "limit": limit, + "prev": prev, + "next": next, + "filter_conditions": filter_conditions, + } + ) + path_params = { + "type": type, + "id": id, + "session": session, + } + return self.get( + "/api/v2/video/call/{type}/{id}/session/{session}/participant_sessions", + QueryCallParticipantSessionsResponse, + query_params=query_params, + path_params=path_params, + ) + @telemetry.operation_name("getstream.api.video.start_hls_broadcasting") def start_hls_broadcasting( self, type: str, id: str @@ -597,12 +706,12 @@ def start_closed_captions( "type": type, "id": id, } - json = build_body_dict( + json = StartClosedCaptionsRequest( enable_transcription=enable_transcription, external_storage=external_storage, language=language, speech_segment_config=speech_segment_config, - ) + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/start_closed_captions", StartClosedCaptionsResponse, @@ -618,7 +727,9 @@ def start_frame_recording( "type": type, "id": id, } - json = build_body_dict(recording_external_storage=recording_external_storage) + json = StartFrameRecordingRequest( + recording_external_storage=recording_external_storage + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/start_frame_recording", StartFrameRecordingResponse, @@ -626,22 +737,6 @@ def start_frame_recording( json=json, ) - @telemetry.operation_name("getstream.api.video.start_recording") - def start_recording( - self, type: str, id: str, recording_external_storage: Optional[str] = None - ) -> StreamResponse[StartRecordingResponse]: - path_params = { - "type": type, - "id": id, - } - json = build_body_dict(recording_external_storage=recording_external_storage) - return self.post( - "/api/v2/video/call/{type}/{id}/start_recording", - StartRecordingResponse, - path_params=path_params, - json=json, - ) - @telemetry.operation_name("getstream.api.video.start_transcription") def start_transcription( self, @@ -655,11 +750,11 @@ def start_transcription( "type": type, "id": id, } - json = build_body_dict( + json = StartTranscriptionRequest( enable_closed_captions=enable_closed_captions, language=language, transcription_external_storage=transcription_external_storage, - ) + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/start_transcription", StartTranscriptionResponse, @@ -689,7 +784,9 @@ def stop_closed_captions( "type": type, "id": id, } - json = build_body_dict(stop_transcription=stop_transcription) + json = StopClosedCaptionsRequest( + stop_transcription=stop_transcription + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/stop_closed_captions", StopClosedCaptionsResponse, @@ -729,7 +826,7 @@ def stop_live( "type": type, "id": id, } - json = build_body_dict( + json = StopLiveRequest( continue_closed_caption=continue_closed_caption, continue_composite_recording=continue_composite_recording, continue_hls=continue_hls, @@ -738,7 +835,7 @@ def stop_live( continue_recording=continue_recording, continue_rtmp_broadcasts=continue_rtmp_broadcasts, continue_transcription=continue_transcription, - ) + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/stop_live", StopLiveResponse, @@ -746,24 +843,6 @@ def stop_live( json=json, ) - @telemetry.operation_name("getstream.api.video.stop_recording") - def stop_recording( - self, - type: str, - id: str, - ) -> StreamResponse[StopRecordingResponse]: - path_params = { - "type": type, - "id": id, - } - json = build_body_dict() - return self.post( - "/api/v2/video/call/{type}/{id}/stop_recording", - StopRecordingResponse, - path_params=path_params, - json=json, - ) - @telemetry.operation_name("getstream.api.video.stop_transcription") def stop_transcription( self, type: str, id: str, stop_closed_captions: Optional[bool] = None @@ -772,7 +851,9 @@ def stop_transcription( "type": type, "id": id, } - json = build_body_dict(stop_closed_captions=stop_closed_captions) + json = StopTranscriptionRequest( + stop_closed_captions=stop_closed_captions + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/stop_transcription", StopTranscriptionResponse, @@ -802,7 +883,7 @@ def unblock_user( "type": type, "id": id, } - json = build_body_dict(user_id=user_id) + json = UnblockUserRequest(user_id=user_id).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/unblock", UnblockUserResponse, @@ -818,7 +899,7 @@ def video_unpin( "type": type, "id": id, } - json = build_body_dict(session_id=session_id, user_id=user_id) + json = UnpinRequest(session_id=session_id, user_id=user_id).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/unpin", UnpinResponse, @@ -839,11 +920,11 @@ def update_user_permissions( "type": type, "id": id, } - json = build_body_dict( + json = UpdateUserPermissionsRequest( user_id=user_id, grant_permissions=grant_permissions, revoke_permissions=revoke_permissions, - ) + ).to_dict() return self.post( "/api/v2/video/call/{type}/{id}/user_permissions", UpdateUserPermissionsResponse, @@ -896,11 +977,13 @@ def get_call_stats_map( exclude_sfus: Optional[bool] = None, ) -> StreamResponse[QueryCallStatsMapResponse]: query_params = build_query_param( - start_time=start_time, - end_time=end_time, - exclude_publishers=exclude_publishers, - exclude_subscribers=exclude_subscribers, - exclude_sfus=exclude_sfus, + **{ + "start_time": start_time, + "end_time": end_time, + "exclude_publishers": exclude_publishers, + "exclude_subscribers": exclude_subscribers, + "exclude_sfus": exclude_sfus, + } ) path_params = { "call_type": call_type, @@ -929,7 +1012,7 @@ def get_call_session_participant_stats_details( max_points: Optional[int] = None, ) -> StreamResponse[GetCallSessionParticipantStatsDetailsResponse]: query_params = build_query_param( - since=since, until=until, max_points=max_points + **{"since": since, "until": until, "max_points": max_points} ) path_params = { "call_type": call_type, @@ -960,11 +1043,13 @@ def query_call_session_participant_stats( filter_conditions: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryCallSessionParticipantStatsResponse]: query_params = build_query_param( - limit=limit, - prev=prev, - next=next, - sort=sort, - filter_conditions=filter_conditions, + **{ + "limit": limit, + "prev": prev, + "next": next, + "sort": sort, + "filter_conditions": filter_conditions, + } ) path_params = { "call_type": call_type, @@ -993,7 +1078,7 @@ def get_call_session_participant_stats_timeline( severity: Optional[List[str]] = None, ) -> StreamResponse[QueryCallSessionParticipantStatsTimelineResponse]: query_params = build_query_param( - start_time=start_time, end_time=end_time, severity=severity + **{"start_time": start_time, "end_time": end_time, "severity": severity} ) path_params = { "call_type": call_type, @@ -1018,13 +1103,13 @@ def query_calls( sort: Optional[List[SortParamRequest]] = None, filter_conditions: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryCallsResponse]: - json = build_body_dict( + json = QueryCallsRequest( limit=limit, next=next, prev=prev, sort=sort, filter_conditions=filter_conditions, - ) + ).to_dict() return self.post("/api/v2/video/calls", QueryCallsResponse, json=json) @telemetry.operation_name("getstream.api.video.list_call_types") @@ -1037,16 +1122,16 @@ def create_call_type( name: str, external_storage: Optional[str] = None, grants: Optional[Dict[str, List[str]]] = None, - notification_settings: Optional[NotificationSettings] = None, + notification_settings: Optional[NotificationSettingsRequest] = None, settings: Optional[CallSettingsRequest] = None, ) -> StreamResponse[CreateCallTypeResponse]: - json = build_body_dict( + json = CreateCallTypeRequest( name=name, external_storage=external_storage, grants=grants, notification_settings=notification_settings, settings=settings, - ) + ).to_dict() return self.post("/api/v2/video/calltypes", CreateCallTypeResponse, json=json) @telemetry.operation_name("getstream.api.video.delete_call_type") @@ -1075,18 +1160,18 @@ def update_call_type( name: str, external_storage: Optional[str] = None, grants: Optional[Dict[str, List[str]]] = None, - notification_settings: Optional[NotificationSettings] = None, + notification_settings: Optional[NotificationSettingsRequest] = None, settings: Optional[CallSettingsRequest] = None, ) -> StreamResponse[UpdateCallTypeResponse]: path_params = { "name": name, } - json = build_body_dict( + json = UpdateCallTypeRequest( external_storage=external_storage, grants=grants, notification_settings=notification_settings, settings=settings, - ) + ).to_dict() return self.put( "/api/v2/video/calltypes/{name}", UpdateCallTypeResponse, @@ -1098,30 +1183,12 @@ def update_call_type( def get_edges(self) -> StreamResponse[GetEdgesResponse]: return self.get("/api/v2/video/edges", GetEdgesResponse) - @telemetry.operation_name("getstream.api.video.resolve_sip_inbound") - def resolve_sip_inbound( - self, - sip_caller_number: str, - sip_trunk_number: str, - challenge: SIPChallenge, - sip_headers: Optional[Dict[str, str]] = None, - ) -> StreamResponse[ResolveSipInboundResponse]: - json = build_body_dict( - sip_caller_number=sip_caller_number, - sip_trunk_number=sip_trunk_number, - challenge=challenge, - sip_headers=sip_headers, - ) - return self.post( - "/api/v2/video/sip/resolve", ResolveSipInboundResponse, json=json - ) - @telemetry.operation_name("getstream.api.video.list_sip_inbound_routing_rule") def list_sip_inbound_routing_rule( self, ) -> StreamResponse[ListSIPInboundRoutingRuleResponse]: return self.get( - "/api/v2/video/sip/routing_rules", ListSIPInboundRoutingRuleResponse + "/api/v2/video/sip/inbound_routing_rules", ListSIPInboundRoutingRuleResponse ) @telemetry.operation_name("getstream.api.video.create_sip_inbound_routing_rule") @@ -1137,7 +1204,7 @@ def create_sip_inbound_routing_rule( pin_protection_configs: Optional[SIPPinProtectionConfigsRequest] = None, pin_routing_configs: Optional[SIPInboundRoutingRulePinConfigsRequest] = None, ) -> StreamResponse[SIPInboundRoutingRuleResponse]: - json = build_body_dict( + json = SIPInboundRoutingRuleRequest( name=name, trunk_ids=trunk_ids, caller_configs=caller_configs, @@ -1147,9 +1214,11 @@ def create_sip_inbound_routing_rule( direct_routing_configs=direct_routing_configs, pin_protection_configs=pin_protection_configs, pin_routing_configs=pin_routing_configs, - ) + ).to_dict() return self.post( - "/api/v2/video/sip/routing_rules", SIPInboundRoutingRuleResponse, json=json + "/api/v2/video/sip/inbound_routing_rules", + SIPInboundRoutingRuleResponse, + json=json, ) @telemetry.operation_name("getstream.api.video.delete_sip_inbound_routing_rule") @@ -1160,7 +1229,7 @@ def delete_sip_inbound_routing_rule( "id": id, } return self.delete( - "/api/v2/video/sip/routing_rules/{id}", + "/api/v2/video/sip/inbound_routing_rules/{id}", DeleteSIPInboundRoutingRuleResponse, path_params=path_params, ) @@ -1182,7 +1251,7 @@ def update_sip_inbound_routing_rule( path_params = { "id": id, } - json = build_body_dict( + json = UpdateSIPInboundRoutingRuleRequest( name=name, called_numbers=called_numbers, trunk_ids=trunk_ids, @@ -1192,9 +1261,9 @@ def update_sip_inbound_routing_rule( direct_routing_configs=direct_routing_configs, pin_protection_configs=pin_protection_configs, pin_routing_configs=pin_routing_configs, - ) + ).to_dict() return self.put( - "/api/v2/video/sip/routing_rules/{id}", + "/api/v2/video/sip/inbound_routing_rules/{id}", UpdateSIPInboundRoutingRuleResponse, path_params=path_params, json=json, @@ -1202,14 +1271,16 @@ def update_sip_inbound_routing_rule( @telemetry.operation_name("getstream.api.video.list_sip_trunks") def list_sip_trunks(self) -> StreamResponse[ListSIPTrunksResponse]: - return self.get("/api/v2/video/sip/trunks", ListSIPTrunksResponse) + return self.get("/api/v2/video/sip/inbound_trunks", ListSIPTrunksResponse) @telemetry.operation_name("getstream.api.video.create_sip_trunk") def create_sip_trunk( self, name: str, numbers: List[str] ) -> StreamResponse[CreateSIPTrunkResponse]: - json = build_body_dict(name=name, numbers=numbers) - return self.post("/api/v2/video/sip/trunks", CreateSIPTrunkResponse, json=json) + json = CreateSIPTrunkRequest(name=name, numbers=numbers).to_dict() + return self.post( + "/api/v2/video/sip/inbound_trunks", CreateSIPTrunkResponse, json=json + ) @telemetry.operation_name("getstream.api.video.delete_sip_trunk") def delete_sip_trunk(self, id: str) -> StreamResponse[DeleteSIPTrunkResponse]: @@ -1217,7 +1288,7 @@ def delete_sip_trunk(self, id: str) -> StreamResponse[DeleteSIPTrunkResponse]: "id": id, } return self.delete( - "/api/v2/video/sip/trunks/{id}", + "/api/v2/video/sip/inbound_trunks/{id}", DeleteSIPTrunkResponse, path_params=path_params, ) @@ -1229,14 +1300,34 @@ def update_sip_trunk( path_params = { "id": id, } - json = build_body_dict(name=name, numbers=numbers) + json = UpdateSIPTrunkRequest(name=name, numbers=numbers).to_dict() return self.put( - "/api/v2/video/sip/trunks/{id}", + "/api/v2/video/sip/inbound_trunks/{id}", UpdateSIPTrunkResponse, path_params=path_params, json=json, ) + @telemetry.operation_name("getstream.api.video.resolve_sip_inbound") + def resolve_sip_inbound( + self, + sip_caller_number: str, + sip_trunk_number: str, + challenge: SIPChallengeRequest, + routing_number: Optional[str] = None, + sip_headers: Optional[Dict[str, str]] = None, + ) -> StreamResponse[ResolveSipInboundResponse]: + json = ResolveSipInboundRequest( + sip_caller_number=sip_caller_number, + sip_trunk_number=sip_trunk_number, + challenge=challenge, + routing_number=routing_number, + sip_headers=sip_headers, + ).to_dict() + return self.post( + "/api/v2/video/sip/resolve", ResolveSipInboundResponse, json=json + ) + @telemetry.operation_name("getstream.api.video.query_aggregate_call_stats") def query_aggregate_call_stats( self, @@ -1244,7 +1335,9 @@ def query_aggregate_call_stats( to: Optional[str] = None, report_types: Optional[List[str]] = None, ) -> StreamResponse[QueryAggregateCallStatsResponse]: - json = build_body_dict(_from=_from, to=to, report_types=report_types) + json = QueryAggregateCallStatsRequest( + _from=_from, to=to, report_types=report_types + ).to_dict() return self.post( "/api/v2/video/stats", QueryAggregateCallStatsResponse, json=json ) diff --git a/getstream/video/rtc/coordinator/ws.py b/getstream/video/rtc/coordinator/ws.py index 4b666d40..01b82099 100644 --- a/getstream/video/rtc/coordinator/ws.py +++ b/getstream/video/rtc/coordinator/ws.py @@ -9,12 +9,11 @@ import json import logging import time -from typing import TYPE_CHECKING, Optional +from typing import Optional import websockets -if TYPE_CHECKING: - from websockets.legacy.client import WebSocketClientProtocol +from websockets import ClientConnection from getstream import AsyncStream from getstream.utils import StreamAsyncIOEventEmitter @@ -88,7 +87,7 @@ def __init__( self._logger = logger or globals()["logger"] # Connection state - self._websocket: Optional["WebSocketClientProtocol"] = None + self._websocket: Optional[ClientConnection] = None self._connected = False self._client_id: Optional[str] = None @@ -152,6 +151,7 @@ async def _open_socket(self) -> dict: self._build_auth_payload(), ) self._logger.debug("WebSocket connection established") + assert self._websocket is not None # Send authentication payload immediately self._logger.debug("Sending auth payload") diff --git a/getstream/video/rtc/track_util.py b/getstream/video/rtc/track_util.py index 6f23f66d..3c7c9f48 100644 --- a/getstream/video/rtc/track_util.py +++ b/getstream/video/rtc/track_util.py @@ -17,6 +17,7 @@ Literal, Optional, Union, + cast, ) import aiortc @@ -61,7 +62,7 @@ class AudioFormat(str, Enum): F32 = "f32" # 32-bit float @staticmethod - def validate(fmt: str) -> str: + def validate(fmt: str) -> "AudioFormatType": """ Validate that a format string is one of the supported audio formats. @@ -87,7 +88,7 @@ def validate(fmt: str) -> str: raise ValueError( f"Invalid audio format: {fmt!r}. Must be one of: {', '.join(sorted(valid_formats))}" ) - return fmt + return cast("AudioFormatType", fmt) # Type alias for audio format parameters diff --git a/getstream/webhook.py b/getstream/webhook.py new file mode 100644 index 00000000..c95099db --- /dev/null +++ b/getstream/webhook.py @@ -0,0 +1,596 @@ +# Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. + +import hmac +import hashlib +import json +from typing import Union, Any, Dict +from .models import ( + CustomEvent, + AppealAcceptedEvent, + AppealCreatedEvent, + AppealRejectedEvent, + CallAcceptedEvent, + BlockedUserEvent, + ClosedCaptionEvent, + CallClosedCaptionsFailedEvent, + CallClosedCaptionsStartedEvent, + CallClosedCaptionsStoppedEvent, + CallCreatedEvent, + CallDeletedEvent, + CallDTMFEvent, + CallEndedEvent, + CallFrameRecordingFailedEvent, + CallFrameRecordingFrameReadyEvent, + CallFrameRecordingStartedEvent, + CallFrameRecordingStoppedEvent, + CallHLSBroadcastingFailedEvent, + CallHLSBroadcastingStartedEvent, + CallHLSBroadcastingStoppedEvent, + KickedUserEvent, + CallLiveStartedEvent, + CallMemberAddedEvent, + CallMemberRemovedEvent, + CallMemberUpdatedEvent, + CallMemberUpdatedPermissionEvent, + CallMissedEvent, + CallModerationBlurEvent, + CallModerationWarningEvent, + CallNotificationEvent, + PermissionRequestEvent, + UpdatedCallPermissionsEvent, + CallReactionEvent, + CallRecordingFailedEvent, + CallRecordingReadyEvent, + CallRecordingStartedEvent, + CallRecordingStoppedEvent, + CallRejectedEvent, + CallRingEvent, + CallRtmpBroadcastFailedEvent, + CallRtmpBroadcastStartedEvent, + CallRtmpBroadcastStoppedEvent, + CallSessionEndedEvent, + CallSessionParticipantCountsUpdatedEvent, + CallSessionParticipantJoinedEvent, + CallSessionParticipantLeftEvent, + CallSessionStartedEvent, + CallStatsReportReadyEvent, + CallTranscriptionFailedEvent, + CallTranscriptionReadyEvent, + CallTranscriptionStartedEvent, + CallTranscriptionStoppedEvent, + UnblockedUserEvent, + CallUpdatedEvent, + CallUserFeedbackSubmittedEvent, + CallUserMutedEvent, + CampaignCompletedEvent, + CampaignStartedEvent, + ChannelCreatedEvent, + ChannelDeletedEvent, + ChannelFrozenEvent, + ChannelHiddenEvent, + MaxStreakChangedEvent, + ChannelMutedEvent, + ChannelTruncatedEvent, + ChannelUnFrozenEvent, + ChannelUnmutedEvent, + ChannelUpdatedEvent, + ChannelVisibleEvent, + ChannelBatchCompletedEvent, + ChannelBatchStartedEvent, + CustomVideoEvent, + AsyncBulkImageModerationEvent, + AsyncExportChannelsEvent, + AsyncExportModerationLogsEvent, + AsyncExportErrorEvent, + AsyncExportUsersEvent, + ActivityAddedEvent, + ActivityDeletedEvent, + ActivityFeedbackEvent, + ActivityMarkEvent, + ActivityPinnedEvent, + ActivityReactionAddedEvent, + ActivityReactionDeletedEvent, + ActivityReactionUpdatedEvent, + ActivityRemovedFromFeedEvent, + ActivityRestoredEvent, + ActivityUnpinnedEvent, + ActivityUpdatedEvent, + BookmarkAddedEvent, + BookmarkDeletedEvent, + BookmarkUpdatedEvent, + BookmarkFolderDeletedEvent, + BookmarkFolderUpdatedEvent, + CommentAddedEvent, + CommentDeletedEvent, + CommentReactionAddedEvent, + CommentReactionDeletedEvent, + CommentReactionUpdatedEvent, + CommentUpdatedEvent, + FeedCreatedEvent, + FeedDeletedEvent, + FeedUpdatedEvent, + FeedGroupChangedEvent, + FeedGroupDeletedEvent, + FeedGroupRestoredEvent, + FeedMemberAddedEvent, + FeedMemberRemovedEvent, + FeedMemberUpdatedEvent, + FollowCreatedEvent, + FollowDeletedEvent, + FollowUpdatedEvent, + NotificationFeedUpdatedEvent, + StoriesFeedUpdatedEvent, + FlagUpdatedEvent, + IngressErrorEvent, + IngressStartedEvent, + IngressStoppedEvent, + MemberAddedEvent, + MemberRemovedEvent, + MemberUpdatedEvent, + MessageDeletedEvent, + MessageFlaggedEvent, + MessageNewEvent, + PendingMessageEvent, + MessageReadEvent, + MessageUnblockedEvent, + MessageUndeletedEvent, + MessageUpdatedEvent, + ModerationCustomActionEvent, + ModerationFlaggedEvent, + ModerationMarkReviewedEvent, + ModerationCheckCompletedEvent, + ModerationRulesTriggeredEvent, + NotificationMarkUnreadEvent, + ReminderNotificationEvent, + NotificationThreadMessageNewEvent, + ReactionDeletedEvent, + ReactionNewEvent, + ReactionUpdatedEvent, + ReminderCreatedEvent, + ReminderDeletedEvent, + ReminderUpdatedEvent, + ReviewQueueItemNewEvent, + ReviewQueueItemUpdatedEvent, + ThreadUpdatedEvent, + UserBannedEvent, + UserDeactivatedEvent, + UserDeletedEvent, + UserFlaggedEvent, + UserMessagesDeletedEvent, + UserMutedEvent, + UserReactivatedEvent, + UserUnbannedEvent, + UserUnmutedEvent, + UserUnreadReminderEvent, + UserUpdatedEvent, + UserGroupCreatedEvent, + UserGroupDeletedEvent, + UserGroupMemberAddedEvent, + UserGroupMemberRemovedEvent, + UserGroupUpdatedEvent, +) + + +# Webhook event type constants +EVENT_TYPE_WILDCARD = "*" +EVENT_TYPE_APPEAL_ACCEPTED = "appeal.accepted" +EVENT_TYPE_APPEAL_CREATED = "appeal.created" +EVENT_TYPE_APPEAL_REJECTED = "appeal.rejected" +EVENT_TYPE_CALL_ACCEPTED = "call.accepted" +EVENT_TYPE_CALL_BLOCKED_USER = "call.blocked_user" +EVENT_TYPE_CALL_CLOSED_CAPTION = "call.closed_caption" +EVENT_TYPE_CALL_CLOSED_CAPTIONS_FAILED = "call.closed_captions_failed" +EVENT_TYPE_CALL_CLOSED_CAPTIONS_STARTED = "call.closed_captions_started" +EVENT_TYPE_CALL_CLOSED_CAPTIONS_STOPPED = "call.closed_captions_stopped" +EVENT_TYPE_CALL_CREATED = "call.created" +EVENT_TYPE_CALL_DELETED = "call.deleted" +EVENT_TYPE_CALL_DTMF = "call.dtmf" +EVENT_TYPE_CALL_ENDED = "call.ended" +EVENT_TYPE_CALL_FRAME_RECORDING_FAILED = "call.frame_recording_failed" +EVENT_TYPE_CALL_FRAME_RECORDING_READY = "call.frame_recording_ready" +EVENT_TYPE_CALL_FRAME_RECORDING_STARTED = "call.frame_recording_started" +EVENT_TYPE_CALL_FRAME_RECORDING_STOPPED = "call.frame_recording_stopped" +EVENT_TYPE_CALL_HLS_BROADCASTING_FAILED = "call.hls_broadcasting_failed" +EVENT_TYPE_CALL_HLS_BROADCASTING_STARTED = "call.hls_broadcasting_started" +EVENT_TYPE_CALL_HLS_BROADCASTING_STOPPED = "call.hls_broadcasting_stopped" +EVENT_TYPE_CALL_KICKED_USER = "call.kicked_user" +EVENT_TYPE_CALL_LIVE_STARTED = "call.live_started" +EVENT_TYPE_CALL_MEMBER_ADDED = "call.member_added" +EVENT_TYPE_CALL_MEMBER_REMOVED = "call.member_removed" +EVENT_TYPE_CALL_MEMBER_UPDATED = "call.member_updated" +EVENT_TYPE_CALL_MEMBER_UPDATED_PERMISSION = "call.member_updated_permission" +EVENT_TYPE_CALL_MISSED = "call.missed" +EVENT_TYPE_CALL_MODERATION_BLUR = "call.moderation_blur" +EVENT_TYPE_CALL_MODERATION_WARNING = "call.moderation_warning" +EVENT_TYPE_CALL_NOTIFICATION = "call.notification" +EVENT_TYPE_CALL_PERMISSION_REQUEST = "call.permission_request" +EVENT_TYPE_CALL_PERMISSIONS_UPDATED = "call.permissions_updated" +EVENT_TYPE_CALL_REACTION_NEW = "call.reaction_new" +EVENT_TYPE_CALL_RECORDING_FAILED = "call.recording_failed" +EVENT_TYPE_CALL_RECORDING_READY = "call.recording_ready" +EVENT_TYPE_CALL_RECORDING_STARTED = "call.recording_started" +EVENT_TYPE_CALL_RECORDING_STOPPED = "call.recording_stopped" +EVENT_TYPE_CALL_REJECTED = "call.rejected" +EVENT_TYPE_CALL_RING = "call.ring" +EVENT_TYPE_CALL_RTMP_BROADCAST_FAILED = "call.rtmp_broadcast_failed" +EVENT_TYPE_CALL_RTMP_BROADCAST_STARTED = "call.rtmp_broadcast_started" +EVENT_TYPE_CALL_RTMP_BROADCAST_STOPPED = "call.rtmp_broadcast_stopped" +EVENT_TYPE_CALL_SESSION_ENDED = "call.session_ended" +EVENT_TYPE_CALL_SESSION_PARTICIPANT_COUNT_UPDATED = ( + "call.session_participant_count_updated" +) +EVENT_TYPE_CALL_SESSION_PARTICIPANT_JOINED = "call.session_participant_joined" +EVENT_TYPE_CALL_SESSION_PARTICIPANT_LEFT = "call.session_participant_left" +EVENT_TYPE_CALL_SESSION_STARTED = "call.session_started" +EVENT_TYPE_CALL_STATS_REPORT_READY = "call.stats_report_ready" +EVENT_TYPE_CALL_TRANSCRIPTION_FAILED = "call.transcription_failed" +EVENT_TYPE_CALL_TRANSCRIPTION_READY = "call.transcription_ready" +EVENT_TYPE_CALL_TRANSCRIPTION_STARTED = "call.transcription_started" +EVENT_TYPE_CALL_TRANSCRIPTION_STOPPED = "call.transcription_stopped" +EVENT_TYPE_CALL_UNBLOCKED_USER = "call.unblocked_user" +EVENT_TYPE_CALL_UPDATED = "call.updated" +EVENT_TYPE_CALL_USER_FEEDBACK_SUBMITTED = "call.user_feedback_submitted" +EVENT_TYPE_CALL_USER_MUTED = "call.user_muted" +EVENT_TYPE_CAMPAIGN_COMPLETED = "campaign.completed" +EVENT_TYPE_CAMPAIGN_STARTED = "campaign.started" +EVENT_TYPE_CHANNEL_CREATED = "channel.created" +EVENT_TYPE_CHANNEL_DELETED = "channel.deleted" +EVENT_TYPE_CHANNEL_FROZEN = "channel.frozen" +EVENT_TYPE_CHANNEL_HIDDEN = "channel.hidden" +EVENT_TYPE_CHANNEL_MAX_STREAK_CHANGED = "channel.max_streak_changed" +EVENT_TYPE_CHANNEL_MUTED = "channel.muted" +EVENT_TYPE_CHANNEL_TRUNCATED = "channel.truncated" +EVENT_TYPE_CHANNEL_UNFROZEN = "channel.unfrozen" +EVENT_TYPE_CHANNEL_UNMUTED = "channel.unmuted" +EVENT_TYPE_CHANNEL_UPDATED = "channel.updated" +EVENT_TYPE_CHANNEL_VISIBLE = "channel.visible" +EVENT_TYPE_CHANNEL_BATCH_UPDATE_COMPLETED = "channel_batch_update.completed" +EVENT_TYPE_CHANNEL_BATCH_UPDATE_STARTED = "channel_batch_update.started" +EVENT_TYPE_CUSTOM = "custom" +EVENT_TYPE_EXPORT_BULK_IMAGE_MODERATION_ERROR = "export.bulk_image_moderation.error" +EVENT_TYPE_EXPORT_BULK_IMAGE_MODERATION_SUCCESS = "export.bulk_image_moderation.success" +EVENT_TYPE_EXPORT_CHANNELS_ERROR = "export.channels.error" +EVENT_TYPE_EXPORT_CHANNELS_SUCCESS = "export.channels.success" +EVENT_TYPE_EXPORT_MODERATION_LOGS_ERROR = "export.moderation_logs.error" +EVENT_TYPE_EXPORT_MODERATION_LOGS_SUCCESS = "export.moderation_logs.success" +EVENT_TYPE_EXPORT_USERS_ERROR = "export.users.error" +EVENT_TYPE_EXPORT_USERS_SUCCESS = "export.users.success" +EVENT_TYPE_FEEDS_ACTIVITY_ADDED = "feeds.activity.added" +EVENT_TYPE_FEEDS_ACTIVITY_DELETED = "feeds.activity.deleted" +EVENT_TYPE_FEEDS_ACTIVITY_FEEDBACK = "feeds.activity.feedback" +EVENT_TYPE_FEEDS_ACTIVITY_MARKED = "feeds.activity.marked" +EVENT_TYPE_FEEDS_ACTIVITY_PINNED = "feeds.activity.pinned" +EVENT_TYPE_FEEDS_ACTIVITY_REACTION_ADDED = "feeds.activity.reaction.added" +EVENT_TYPE_FEEDS_ACTIVITY_REACTION_DELETED = "feeds.activity.reaction.deleted" +EVENT_TYPE_FEEDS_ACTIVITY_REACTION_UPDATED = "feeds.activity.reaction.updated" +EVENT_TYPE_FEEDS_ACTIVITY_REMOVED_FROM_FEED = "feeds.activity.removed_from_feed" +EVENT_TYPE_FEEDS_ACTIVITY_RESTORED = "feeds.activity.restored" +EVENT_TYPE_FEEDS_ACTIVITY_UNPINNED = "feeds.activity.unpinned" +EVENT_TYPE_FEEDS_ACTIVITY_UPDATED = "feeds.activity.updated" +EVENT_TYPE_FEEDS_BOOKMARK_ADDED = "feeds.bookmark.added" +EVENT_TYPE_FEEDS_BOOKMARK_DELETED = "feeds.bookmark.deleted" +EVENT_TYPE_FEEDS_BOOKMARK_UPDATED = "feeds.bookmark.updated" +EVENT_TYPE_FEEDS_BOOKMARK_FOLDER_DELETED = "feeds.bookmark_folder.deleted" +EVENT_TYPE_FEEDS_BOOKMARK_FOLDER_UPDATED = "feeds.bookmark_folder.updated" +EVENT_TYPE_FEEDS_COMMENT_ADDED = "feeds.comment.added" +EVENT_TYPE_FEEDS_COMMENT_DELETED = "feeds.comment.deleted" +EVENT_TYPE_FEEDS_COMMENT_REACTION_ADDED = "feeds.comment.reaction.added" +EVENT_TYPE_FEEDS_COMMENT_REACTION_DELETED = "feeds.comment.reaction.deleted" +EVENT_TYPE_FEEDS_COMMENT_REACTION_UPDATED = "feeds.comment.reaction.updated" +EVENT_TYPE_FEEDS_COMMENT_UPDATED = "feeds.comment.updated" +EVENT_TYPE_FEEDS_FEED_CREATED = "feeds.feed.created" +EVENT_TYPE_FEEDS_FEED_DELETED = "feeds.feed.deleted" +EVENT_TYPE_FEEDS_FEED_UPDATED = "feeds.feed.updated" +EVENT_TYPE_FEEDS_FEED_GROUP_CHANGED = "feeds.feed_group.changed" +EVENT_TYPE_FEEDS_FEED_GROUP_DELETED = "feeds.feed_group.deleted" +EVENT_TYPE_FEEDS_FEED_GROUP_RESTORED = "feeds.feed_group.restored" +EVENT_TYPE_FEEDS_FEED_MEMBER_ADDED = "feeds.feed_member.added" +EVENT_TYPE_FEEDS_FEED_MEMBER_REMOVED = "feeds.feed_member.removed" +EVENT_TYPE_FEEDS_FEED_MEMBER_UPDATED = "feeds.feed_member.updated" +EVENT_TYPE_FEEDS_FOLLOW_CREATED = "feeds.follow.created" +EVENT_TYPE_FEEDS_FOLLOW_DELETED = "feeds.follow.deleted" +EVENT_TYPE_FEEDS_FOLLOW_UPDATED = "feeds.follow.updated" +EVENT_TYPE_FEEDS_NOTIFICATION_FEED_UPDATED = "feeds.notification_feed.updated" +EVENT_TYPE_FEEDS_STORIES_FEED_UPDATED = "feeds.stories_feed.updated" +EVENT_TYPE_FLAG_UPDATED = "flag.updated" +EVENT_TYPE_INGRESS_ERROR = "ingress.error" +EVENT_TYPE_INGRESS_STARTED = "ingress.started" +EVENT_TYPE_INGRESS_STOPPED = "ingress.stopped" +EVENT_TYPE_MEMBER_ADDED = "member.added" +EVENT_TYPE_MEMBER_REMOVED = "member.removed" +EVENT_TYPE_MEMBER_UPDATED = "member.updated" +EVENT_TYPE_MESSAGE_DELETED = "message.deleted" +EVENT_TYPE_MESSAGE_FLAGGED = "message.flagged" +EVENT_TYPE_MESSAGE_NEW = "message.new" +EVENT_TYPE_MESSAGE_PENDING = "message.pending" +EVENT_TYPE_MESSAGE_READ = "message.read" +EVENT_TYPE_MESSAGE_UNBLOCKED = "message.unblocked" +EVENT_TYPE_MESSAGE_UNDELETED = "message.undeleted" +EVENT_TYPE_MESSAGE_UPDATED = "message.updated" +EVENT_TYPE_MODERATION_CUSTOM_ACTION = "moderation.custom_action" +EVENT_TYPE_MODERATION_FLAGGED = "moderation.flagged" +EVENT_TYPE_MODERATION_MARK_REVIEWED = "moderation.mark_reviewed" +EVENT_TYPE_MODERATION_CHECK_COMPLETED = "moderation_check.completed" +EVENT_TYPE_MODERATION_RULE_TRIGGERED = "moderation_rule.triggered" +EVENT_TYPE_NOTIFICATION_MARK_UNREAD = "notification.mark_unread" +EVENT_TYPE_NOTIFICATION_REMINDER_DUE = "notification.reminder_due" +EVENT_TYPE_NOTIFICATION_THREAD_MESSAGE_NEW = "notification.thread_message_new" +EVENT_TYPE_REACTION_DELETED = "reaction.deleted" +EVENT_TYPE_REACTION_NEW = "reaction.new" +EVENT_TYPE_REACTION_UPDATED = "reaction.updated" +EVENT_TYPE_REMINDER_CREATED = "reminder.created" +EVENT_TYPE_REMINDER_DELETED = "reminder.deleted" +EVENT_TYPE_REMINDER_UPDATED = "reminder.updated" +EVENT_TYPE_REVIEW_QUEUE_ITEM_NEW = "review_queue_item.new" +EVENT_TYPE_REVIEW_QUEUE_ITEM_UPDATED = "review_queue_item.updated" +EVENT_TYPE_THREAD_UPDATED = "thread.updated" +EVENT_TYPE_USER_BANNED = "user.banned" +EVENT_TYPE_USER_DEACTIVATED = "user.deactivated" +EVENT_TYPE_USER_DELETED = "user.deleted" +EVENT_TYPE_USER_FLAGGED = "user.flagged" +EVENT_TYPE_USER_MESSAGES_DELETED = "user.messages.deleted" +EVENT_TYPE_USER_MUTED = "user.muted" +EVENT_TYPE_USER_REACTIVATED = "user.reactivated" +EVENT_TYPE_USER_UNBANNED = "user.unbanned" +EVENT_TYPE_USER_UNMUTED = "user.unmuted" +EVENT_TYPE_USER_UNREAD_MESSAGE_REMINDER = "user.unread_message_reminder" +EVENT_TYPE_USER_UPDATED = "user.updated" +EVENT_TYPE_USER_GROUP_CREATED = "user_group.created" +EVENT_TYPE_USER_GROUP_DELETED = "user_group.deleted" +EVENT_TYPE_USER_GROUP_MEMBER_ADDED = "user_group.member_added" +EVENT_TYPE_USER_GROUP_MEMBER_REMOVED = "user_group.member_removed" +EVENT_TYPE_USER_GROUP_UPDATED = "user_group.updated" + + +def get_event_type(raw_event: Union[bytes, str, Dict[str, Any]]) -> str: + """ + Extract the event type from a raw webhook payload. + + Args: + raw_event: The raw webhook payload + + Returns: + The event type string (e.g., "message.new"), or empty string if parsing fails + """ + if isinstance(raw_event, dict): + return raw_event.get("type", "") + + try: + if isinstance(raw_event, bytes): + raw_event = raw_event.decode("utf-8") + data = json.loads(raw_event) + return data.get("type", "") + except (json.JSONDecodeError, UnicodeDecodeError, AttributeError): + return "" + + +def parse_webhook_event(raw_event: Union[bytes, str, Dict[str, Any]]) -> Any: + """ + Deserialize a raw webhook payload into a typed event object. + + Args: + raw_event: The raw webhook payload + + Returns: + A typed event object corresponding to the event type + + Raises: + ValueError: If the event type is unknown or deserialization fails + """ + try: + if isinstance(raw_event, dict): + data = raw_event + elif isinstance(raw_event, bytes): + data = json.loads(raw_event.decode("utf-8")) + else: + data = json.loads(raw_event) + except (json.JSONDecodeError, UnicodeDecodeError) as e: + raise ValueError(f"Failed to parse webhook payload: {e}") + + event_type = data.get("type") + if not event_type: + raise ValueError("Webhook payload missing 'type' field") + + event_class = _get_event_class(event_type) + if event_class is None: + raise ValueError(f"Unknown webhook event type: {event_type}") + + try: + return event_class.from_dict(data, infer_missing=True) + except Exception as e: + raise ValueError(f"Failed to deserialize webhook event: {e}") + + +def _get_event_class(event_type: str): + """Map event type to event class.""" + event_map = { + "*": CustomEvent, + "appeal.accepted": AppealAcceptedEvent, + "appeal.created": AppealCreatedEvent, + "appeal.rejected": AppealRejectedEvent, + "call.accepted": CallAcceptedEvent, + "call.blocked_user": BlockedUserEvent, + "call.closed_caption": ClosedCaptionEvent, + "call.closed_captions_failed": CallClosedCaptionsFailedEvent, + "call.closed_captions_started": CallClosedCaptionsStartedEvent, + "call.closed_captions_stopped": CallClosedCaptionsStoppedEvent, + "call.created": CallCreatedEvent, + "call.deleted": CallDeletedEvent, + "call.dtmf": CallDTMFEvent, + "call.ended": CallEndedEvent, + "call.frame_recording_failed": CallFrameRecordingFailedEvent, + "call.frame_recording_ready": CallFrameRecordingFrameReadyEvent, + "call.frame_recording_started": CallFrameRecordingStartedEvent, + "call.frame_recording_stopped": CallFrameRecordingStoppedEvent, + "call.hls_broadcasting_failed": CallHLSBroadcastingFailedEvent, + "call.hls_broadcasting_started": CallHLSBroadcastingStartedEvent, + "call.hls_broadcasting_stopped": CallHLSBroadcastingStoppedEvent, + "call.kicked_user": KickedUserEvent, + "call.live_started": CallLiveStartedEvent, + "call.member_added": CallMemberAddedEvent, + "call.member_removed": CallMemberRemovedEvent, + "call.member_updated": CallMemberUpdatedEvent, + "call.member_updated_permission": CallMemberUpdatedPermissionEvent, + "call.missed": CallMissedEvent, + "call.moderation_blur": CallModerationBlurEvent, + "call.moderation_warning": CallModerationWarningEvent, + "call.notification": CallNotificationEvent, + "call.permission_request": PermissionRequestEvent, + "call.permissions_updated": UpdatedCallPermissionsEvent, + "call.reaction_new": CallReactionEvent, + "call.recording_failed": CallRecordingFailedEvent, + "call.recording_ready": CallRecordingReadyEvent, + "call.recording_started": CallRecordingStartedEvent, + "call.recording_stopped": CallRecordingStoppedEvent, + "call.rejected": CallRejectedEvent, + "call.ring": CallRingEvent, + "call.rtmp_broadcast_failed": CallRtmpBroadcastFailedEvent, + "call.rtmp_broadcast_started": CallRtmpBroadcastStartedEvent, + "call.rtmp_broadcast_stopped": CallRtmpBroadcastStoppedEvent, + "call.session_ended": CallSessionEndedEvent, + "call.session_participant_count_updated": CallSessionParticipantCountsUpdatedEvent, + "call.session_participant_joined": CallSessionParticipantJoinedEvent, + "call.session_participant_left": CallSessionParticipantLeftEvent, + "call.session_started": CallSessionStartedEvent, + "call.stats_report_ready": CallStatsReportReadyEvent, + "call.transcription_failed": CallTranscriptionFailedEvent, + "call.transcription_ready": CallTranscriptionReadyEvent, + "call.transcription_started": CallTranscriptionStartedEvent, + "call.transcription_stopped": CallTranscriptionStoppedEvent, + "call.unblocked_user": UnblockedUserEvent, + "call.updated": CallUpdatedEvent, + "call.user_feedback_submitted": CallUserFeedbackSubmittedEvent, + "call.user_muted": CallUserMutedEvent, + "campaign.completed": CampaignCompletedEvent, + "campaign.started": CampaignStartedEvent, + "channel.created": ChannelCreatedEvent, + "channel.deleted": ChannelDeletedEvent, + "channel.frozen": ChannelFrozenEvent, + "channel.hidden": ChannelHiddenEvent, + "channel.max_streak_changed": MaxStreakChangedEvent, + "channel.muted": ChannelMutedEvent, + "channel.truncated": ChannelTruncatedEvent, + "channel.unfrozen": ChannelUnFrozenEvent, + "channel.unmuted": ChannelUnmutedEvent, + "channel.updated": ChannelUpdatedEvent, + "channel.visible": ChannelVisibleEvent, + "channel_batch_update.completed": ChannelBatchCompletedEvent, + "channel_batch_update.started": ChannelBatchStartedEvent, + "custom": CustomVideoEvent, + "export.bulk_image_moderation.error": AsyncExportErrorEvent, + "export.bulk_image_moderation.success": AsyncBulkImageModerationEvent, + "export.channels.error": AsyncExportErrorEvent, + "export.channels.success": AsyncExportChannelsEvent, + "export.moderation_logs.error": AsyncExportErrorEvent, + "export.moderation_logs.success": AsyncExportModerationLogsEvent, + "export.users.error": AsyncExportErrorEvent, + "export.users.success": AsyncExportUsersEvent, + "feeds.activity.added": ActivityAddedEvent, + "feeds.activity.deleted": ActivityDeletedEvent, + "feeds.activity.feedback": ActivityFeedbackEvent, + "feeds.activity.marked": ActivityMarkEvent, + "feeds.activity.pinned": ActivityPinnedEvent, + "feeds.activity.reaction.added": ActivityReactionAddedEvent, + "feeds.activity.reaction.deleted": ActivityReactionDeletedEvent, + "feeds.activity.reaction.updated": ActivityReactionUpdatedEvent, + "feeds.activity.removed_from_feed": ActivityRemovedFromFeedEvent, + "feeds.activity.restored": ActivityRestoredEvent, + "feeds.activity.unpinned": ActivityUnpinnedEvent, + "feeds.activity.updated": ActivityUpdatedEvent, + "feeds.bookmark.added": BookmarkAddedEvent, + "feeds.bookmark.deleted": BookmarkDeletedEvent, + "feeds.bookmark.updated": BookmarkUpdatedEvent, + "feeds.bookmark_folder.deleted": BookmarkFolderDeletedEvent, + "feeds.bookmark_folder.updated": BookmarkFolderUpdatedEvent, + "feeds.comment.added": CommentAddedEvent, + "feeds.comment.deleted": CommentDeletedEvent, + "feeds.comment.reaction.added": CommentReactionAddedEvent, + "feeds.comment.reaction.deleted": CommentReactionDeletedEvent, + "feeds.comment.reaction.updated": CommentReactionUpdatedEvent, + "feeds.comment.updated": CommentUpdatedEvent, + "feeds.feed.created": FeedCreatedEvent, + "feeds.feed.deleted": FeedDeletedEvent, + "feeds.feed.updated": FeedUpdatedEvent, + "feeds.feed_group.changed": FeedGroupChangedEvent, + "feeds.feed_group.deleted": FeedGroupDeletedEvent, + "feeds.feed_group.restored": FeedGroupRestoredEvent, + "feeds.feed_member.added": FeedMemberAddedEvent, + "feeds.feed_member.removed": FeedMemberRemovedEvent, + "feeds.feed_member.updated": FeedMemberUpdatedEvent, + "feeds.follow.created": FollowCreatedEvent, + "feeds.follow.deleted": FollowDeletedEvent, + "feeds.follow.updated": FollowUpdatedEvent, + "feeds.notification_feed.updated": NotificationFeedUpdatedEvent, + "feeds.stories_feed.updated": StoriesFeedUpdatedEvent, + "flag.updated": FlagUpdatedEvent, + "ingress.error": IngressErrorEvent, + "ingress.started": IngressStartedEvent, + "ingress.stopped": IngressStoppedEvent, + "member.added": MemberAddedEvent, + "member.removed": MemberRemovedEvent, + "member.updated": MemberUpdatedEvent, + "message.deleted": MessageDeletedEvent, + "message.flagged": MessageFlaggedEvent, + "message.new": MessageNewEvent, + "message.pending": PendingMessageEvent, + "message.read": MessageReadEvent, + "message.unblocked": MessageUnblockedEvent, + "message.undeleted": MessageUndeletedEvent, + "message.updated": MessageUpdatedEvent, + "moderation.custom_action": ModerationCustomActionEvent, + "moderation.flagged": ModerationFlaggedEvent, + "moderation.mark_reviewed": ModerationMarkReviewedEvent, + "moderation_check.completed": ModerationCheckCompletedEvent, + "moderation_rule.triggered": ModerationRulesTriggeredEvent, + "notification.mark_unread": NotificationMarkUnreadEvent, + "notification.reminder_due": ReminderNotificationEvent, + "notification.thread_message_new": NotificationThreadMessageNewEvent, + "reaction.deleted": ReactionDeletedEvent, + "reaction.new": ReactionNewEvent, + "reaction.updated": ReactionUpdatedEvent, + "reminder.created": ReminderCreatedEvent, + "reminder.deleted": ReminderDeletedEvent, + "reminder.updated": ReminderUpdatedEvent, + "review_queue_item.new": ReviewQueueItemNewEvent, + "review_queue_item.updated": ReviewQueueItemUpdatedEvent, + "thread.updated": ThreadUpdatedEvent, + "user.banned": UserBannedEvent, + "user.deactivated": UserDeactivatedEvent, + "user.deleted": UserDeletedEvent, + "user.flagged": UserFlaggedEvent, + "user.messages.deleted": UserMessagesDeletedEvent, + "user.muted": UserMutedEvent, + "user.reactivated": UserReactivatedEvent, + "user.unbanned": UserUnbannedEvent, + "user.unmuted": UserUnmutedEvent, + "user.unread_message_reminder": UserUnreadReminderEvent, + "user.updated": UserUpdatedEvent, + "user_group.created": UserGroupCreatedEvent, + "user_group.deleted": UserGroupDeletedEvent, + "user_group.member_added": UserGroupMemberAddedEvent, + "user_group.member_removed": UserGroupMemberRemovedEvent, + "user_group.updated": UserGroupUpdatedEvent, + } + return event_map.get(event_type) + + +def verify_webhook_signature( + body: Union[bytes, str], signature: str, secret: str +) -> bool: + """ + Verify the HMAC-SHA256 signature of a webhook payload. + + Args: + body: The raw request body (bytes or UTF-8 string) + signature: The signature from the X-Signature header + secret: Your webhook secret (found in the Stream Dashboard) + + Returns: + True if the signature is valid, False otherwise + """ + if isinstance(body, str): + body = body.encode("utf-8") + + h = hmac.new(secret.encode("utf-8"), body, hashlib.sha256) + expected = h.hexdigest() + return hmac.compare_digest(signature, expected) diff --git a/tests/assets/test_upload.jpg b/tests/assets/test_upload.jpg new file mode 100644 index 00000000..0280f1e3 Binary files /dev/null and b/tests/assets/test_upload.jpg differ diff --git a/tests/assets/test_upload.txt b/tests/assets/test_upload.txt new file mode 100644 index 00000000..1296ff7f --- /dev/null +++ b/tests/assets/test_upload.txt @@ -0,0 +1 @@ +hello world test file content diff --git a/tests/base.py b/tests/base.py index ce858b2a..e5f239ca 100644 --- a/tests/base.py +++ b/tests/base.py @@ -21,13 +21,14 @@ def wait_for_task(client, task_id, timeout_ms=10000, poll_interval_ms=1000): Args: client: The client used to make the API call. task_id: The ID of the task to wait for. - timeout: The maximum amount of time to wait (in ms). - poll_interval: The interval between poll attempts (in ms). + timeout_ms: The maximum amount of time to wait (in ms). + poll_interval_ms: The interval between poll attempts (in ms). Returns: The final response from the API. Raises: + RuntimeError: If the task failed. TimeoutError: If the task is not completed within the timeout period. """ start_time = time.time() * 1000 # Convert to milliseconds @@ -35,8 +36,8 @@ def wait_for_task(client, task_id, timeout_ms=10000, poll_interval_ms=1000): response = client.get_task(id=task_id) if response.data.status == "completed": return response + if response.data.status == "failed": + raise RuntimeError(f"Task {task_id} failed") if (time.time() * 1000) - start_time > timeout_ms: - raise TimeoutError( - f"Task {task_id} did not complete within {timeout_ms} seconds" - ) + raise TimeoutError(f"Task {task_id} did not complete within {timeout_ms}ms") time.sleep(poll_interval_ms / 1000.0) diff --git a/tests/conftest.py b/tests/conftest.py index ae608f64..bd037013 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import functools +import uuid import pytest import os from dotenv import load_dotenv @@ -12,6 +13,9 @@ async_client, ) +from getstream import Stream +from getstream.models import UserRequest, ChannelInput + __all__ = [ "client", "call", @@ -20,9 +24,80 @@ "test_feed", "get_feed", "async_client", + "channel", + "random_user", + "random_users", + "server_user", ] +@pytest.fixture +def random_user(client: Stream): + user_id = str(uuid.uuid4()) + response = client.update_users( + users={user_id: UserRequest(id=user_id, name=user_id)} + ) + assert user_id in response.data.users + yield response.data.users[user_id] + try: + client.delete_users( + user_ids=[user_id], user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +@pytest.fixture +def random_users(client: Stream): + users = [] + user_ids = [] + for _ in range(3): + uid = str(uuid.uuid4()) + user_ids.append(uid) + users.append(UserRequest(id=uid, name=uid)) + response = client.update_users(users={u.id: u for u in users}) + yield [response.data.users[uid] for uid in user_ids] + try: + client.delete_users( + user_ids=user_ids, user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +@pytest.fixture +def server_user(client: Stream): + user_id = str(uuid.uuid4()) + response = client.update_users( + users={user_id: UserRequest(id=user_id, name="server-admin")} + ) + assert user_id in response.data.users + yield response.data.users[user_id] + try: + client.delete_users( + user_ids=[user_id], user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +@pytest.fixture +def channel(client: Stream, random_user): + channel_id = str(uuid.uuid4()) + ch = client.chat.channel("messaging", channel_id) + ch.get_or_create( + data=ChannelInput( + created_by_id=random_user.id, + custom={"test": True, "language": "python"}, + ) + ) + yield ch + try: + client.chat.delete_channels(cids=[f"messaging:{channel_id}"], hard_delete=True) + except Exception: + pass + + @pytest.fixture(scope="session", autouse=True) def load_env(): load_dotenv() diff --git a/tests/test_chat_channel.py b/tests/test_chat_channel.py new file mode 100644 index 00000000..f95bfc7e --- /dev/null +++ b/tests/test_chat_channel.py @@ -0,0 +1,746 @@ +import uuid +from pathlib import Path + +from getstream import Stream +from getstream.base import StreamAPIException +from getstream.chat.channel import Channel +from getstream.models import ( + ChannelExport, + ChannelInput, + ChannelInputRequest, + ChannelMemberRequest, + MessageRequest, + OnlyUserID, + QueryMembersPayload, + SortParamRequest, + UserRequest, +) +from tests.base import wait_for_task + +ASSETS_DIR = Path(__file__).parent / "assets" + + +class TestChannelCRUD: + def test_create_channel(self, client: Stream, random_users): + """Create a channel without specifying an ID (distinct channel).""" + member_ids = [u.id for u in random_users] + channel = client.chat.channel("messaging", str(uuid.uuid4())) + response = channel.get_or_create( + data=ChannelInput( + created_by_id=member_ids[0], + members=[ChannelMemberRequest(user_id=uid) for uid in member_ids], + ) + ) + assert response.data.channel is not None + assert response.data.channel.type == "messaging" + + # cleanup + try: + client.chat.delete_channels( + cids=[f"{response.data.channel.type}:{response.data.channel.id}"], + hard_delete=True, + ) + except StreamAPIException: + pass + + def test_create_channel_with_options(self, client: Stream, random_users): + """Create a channel with hide_for_creator option.""" + member_ids = [u.id for u in random_users] + channel = client.chat.channel("messaging", str(uuid.uuid4())) + response = channel.get_or_create( + hide_for_creator=True, + data=ChannelInput( + created_by_id=member_ids[0], + members=[ChannelMemberRequest(user_id=uid) for uid in member_ids], + ), + ) + assert response.data.channel is not None + + try: + client.chat.delete_channels( + cids=[f"{response.data.channel.type}:{response.data.channel.id}"], + hard_delete=True, + ) + except StreamAPIException: + pass + + def test_create_distinct_channel(self, client: Stream, random_users): + """Create a distinct channel and verify idempotency.""" + member_ids = [u.id for u in random_users[:2]] + members = [ChannelMemberRequest(user_id=uid) for uid in member_ids] + + response = client.chat.get_or_create_distinct_channel( + type="messaging", + data=ChannelInput(created_by_id=member_ids[0], members=members), + ) + assert response.data.channel is not None + first_cid = response.data.channel.cid + + # calling again with same members should return same channel + response2 = client.chat.get_or_create_distinct_channel( + type="messaging", + data=ChannelInput(created_by_id=member_ids[0], members=members), + ) + assert response2.data.channel.cid == first_cid + + try: + client.chat.delete_channels(cids=[first_cid], hard_delete=True) + except StreamAPIException: + pass + + def test_update_channel(self, channel: Channel, random_user): + """Update channel data with custom fields.""" + response = channel.update( + data=ChannelInputRequest(custom={"motd": "one apple a day..."}) + ) + assert response.data.channel is not None + assert response.data.channel.custom.get("motd") == "one apple a day..." + + def test_update_channel_partial(self, channel: Channel): + """Partial update: set and unset fields.""" + channel.update_channel_partial(set={"color": "blue", "age": 30}) + response = channel.update_channel_partial(set={"color": "red"}, unset=["age"]) + assert response.data.channel is not None + assert response.data.channel.custom.get("color") == "red" + assert "age" not in (response.data.channel.custom or {}) + + def test_delete_channel(self, client: Stream, random_user): + """Delete a channel and verify deleted_at is set.""" + channel_id = str(uuid.uuid4()) + ch = client.chat.channel("messaging", channel_id) + ch.get_or_create(data=ChannelInput(created_by_id=random_user.id)) + response = ch.delete() + assert response.data.channel is not None + assert response.data.channel.deleted_at is not None + + def test_delete_channels(self, client: Stream, random_user): + """Delete channels and verify task_id is returned.""" + channel_id = str(uuid.uuid4()) + ch = client.chat.channel("messaging", channel_id) + ch.get_or_create(data=ChannelInput(created_by_id=random_user.id)) + + cid = f"messaging:{channel_id}" + response = client.chat.delete_channels(cids=[cid], hard_delete=True) + assert response.data.task_id is not None + + def test_truncate_channel(self, channel: Channel, random_user): + """Truncate a channel.""" + channel.send_message( + message=MessageRequest(text="hello", user_id=random_user.id) + ) + response = channel.truncate() + assert response.data.channel is not None + + def test_truncate_channel_with_options(self, channel: Channel, random_user): + """Truncate a channel with skip_push and system message.""" + channel.send_message( + message=MessageRequest(text="hello", user_id=random_user.id) + ) + response = channel.truncate( + skip_push=True, + message=MessageRequest(text="Truncating channel.", user_id=random_user.id), + ) + assert response.data.channel is not None + + def test_freeze_unfreeze_channel(self, channel: Channel): + """Freeze and unfreeze a channel.""" + response = channel.update_channel_partial(set={"frozen": True}) + assert response.data.channel.frozen is True + + response = channel.update_channel_partial(set={"frozen": False}) + assert response.data.channel.frozen is False + + def test_query_channels(self, client: Stream, random_users): + """Query channels by member filter.""" + user_id = random_users[0].id + channel_id = str(uuid.uuid4()) + ch = client.chat.channel("messaging", channel_id) + ch.get_or_create( + data=ChannelInput( + created_by_id=user_id, + members=[ChannelMemberRequest(user_id=user_id)], + ) + ) + + response = client.chat.query_channels( + filter_conditions={"members": {"$in": [user_id]}} + ) + assert len(response.data.channels) >= 1 + + try: + client.chat.delete_channels( + cids=[f"messaging:{channel_id}"], hard_delete=True + ) + except StreamAPIException: + pass + + def test_query_channels_members_in(self, client: Stream, random_users): + """Query channels by $in member filter and verify result.""" + user_id = random_users[0].id + other_id = random_users[1].id + channel_id = str(uuid.uuid4()) + ch = client.chat.channel("messaging", channel_id) + ch.get_or_create( + data=ChannelInput( + created_by_id=user_id, + members=[ + ChannelMemberRequest(user_id=uid) for uid in [user_id, other_id] + ], + ) + ) + + response = client.chat.query_channels( + filter_conditions={"members": {"$in": [user_id]}} + ) + assert len(response.data.channels) >= 1 + channel_ids = [c.channel.id for c in response.data.channels] + assert channel_id in channel_ids + + # verify member count + matched = [c for c in response.data.channels if c.channel.id == channel_id] + assert len(matched) == 1 + assert matched[0].channel.member_count >= 2 + + try: + client.chat.delete_channels( + cids=[f"messaging:{channel_id}"], hard_delete=True + ) + except StreamAPIException: + pass + + def test_filter_tags(self, channel: Channel, random_user): + """Add and remove filter tags on a channel.""" + # add two tags + response = channel.update(add_filter_tags=["vip", "premium"]) + assert response.data.channel is not None + assert "vip" in response.data.channel.filter_tags + assert "premium" in response.data.channel.filter_tags + + # remove one tag + response = channel.update(remove_filter_tags=["premium"]) + assert response.data.channel is not None + assert "vip" in response.data.channel.filter_tags + assert "premium" not in response.data.channel.filter_tags + + # cleanup remaining tag + channel.update(remove_filter_tags=["vip"]) + + +class TestChannelMembers: + def test_add_members(self, channel: Channel, random_users): + """Add members to a channel.""" + user_id = random_users[0].id + # Remove first to ensure clean state + channel.update(remove_members=[user_id]) + response = channel.update(add_members=[ChannelMemberRequest(user_id=user_id)]) + assert response.data.members is not None + member_ids = [m.user_id for m in response.data.members] + assert user_id in member_ids + + def test_add_members_hide_history(self, channel: Channel, random_users): + """Add members with hide_history option.""" + user_id = random_users[0].id + channel.update(remove_members=[user_id]) + response = channel.update( + add_members=[ChannelMemberRequest(user_id=user_id)], + hide_history=True, + ) + assert response.data.members is not None + member_ids = [m.user_id for m in response.data.members] + assert user_id in member_ids + + def test_add_members_with_roles(self, client: Stream, channel: Channel): + """Add members with specific channel roles.""" + rand = str(uuid.uuid4())[:8] + mod_id = f"mod-{rand}" + member_id = f"member-{rand}" + user_ids = [mod_id, member_id] + client.update_users( + users={uid: UserRequest(id=uid, name=uid) for uid in user_ids} + ) + + channel.update( + add_members=[ + ChannelMemberRequest(user_id=mod_id, channel_role="channel_moderator"), + ChannelMemberRequest(user_id=member_id, channel_role="channel_member"), + ] + ) + + members_resp = client.chat.query_members( + payload=QueryMembersPayload( + type=channel.channel_type, + id=channel.channel_id, + filter_conditions={"id": {"$in": user_ids}}, + ) + ) + role_map = {m.user_id: m.channel_role for m in members_resp.data.members} + assert role_map[mod_id] == "channel_moderator" + assert role_map[member_id] == "channel_member" + + try: + client.delete_users( + user_ids=user_ids, + user="hard", + conversations="hard", + messages="hard", + ) + except StreamAPIException: + pass + + def test_invite_members(self, channel: Channel, random_users): + """Invite members to a channel.""" + user_id = random_users[0].id + channel.update(remove_members=[user_id]) + response = channel.update(invites=[ChannelMemberRequest(user_id=user_id)]) + assert response.data.members is not None + member_ids = [m.user_id for m in response.data.members] + assert user_id in member_ids + + def test_invites_accept_reject(self, client: Stream, random_users): + """Accept and reject channel invites, and verify non-invited user errors.""" + john = random_users[0].id + ringo = random_users[1].id + eric = random_users[2].id + + channel_id = "beatles-" + str(uuid.uuid4()) + ch = client.chat.channel("team", channel_id) + ch.get_or_create( + data=ChannelInput( + created_by_id=john, + members=[ + ChannelMemberRequest(user_id=uid) for uid in [john, ringo, eric] + ], + invites=[ChannelMemberRequest(user_id=uid) for uid in [ringo, eric]], + ) + ) + + # accept invite + accept = ch.update(accept_invite=True, user_id=ringo) + for m in accept.data.members: + if m.user_id == ringo: + assert m.invited is True + assert m.invite_accepted_at is not None + + # reject invite + reject = ch.update(reject_invite=True, user_id=eric) + for m in reject.data.members: + if m.user_id == eric: + assert m.invited is True + assert m.invite_rejected_at is not None + + # non-member accepting should raise an error + import pytest + + non_member = "brian-" + str(uuid.uuid4()) + client.update_users( + users={non_member: UserRequest(id=non_member, name=non_member)} + ) + with pytest.raises(StreamAPIException): + ch.update(accept_invite=True, user_id=non_member) + + try: + client.chat.delete_channels(cids=[f"team:{channel_id}"], hard_delete=True) + except StreamAPIException: + pass + + def test_add_moderators(self, channel: Channel, random_user): + """Add and demote moderators.""" + response = channel.update( + add_members=[ChannelMemberRequest(user_id=random_user.id)] + ) + response = channel.update(add_moderators=[random_user.id]) + mod = [m for m in response.data.members if m.user_id == random_user.id] + assert len(mod) == 1 + assert mod[0].is_moderator is True + + response = channel.update(demote_moderators=[random_user.id]) + mod = [m for m in response.data.members if m.user_id == random_user.id] + assert len(mod) == 1 + assert mod[0].is_moderator is not True + + def test_assign_roles(self, channel: Channel, random_user): + """Assign roles to channel members.""" + channel.update( + add_members=[ + ChannelMemberRequest( + user_id=random_user.id, channel_role="channel_moderator" + ) + ] + ) + mod = None + resp = channel.update( + assign_roles=[ + ChannelMemberRequest( + user_id=random_user.id, channel_role="channel_member" + ) + ] + ) + for m in resp.data.members: + if m.user_id == random_user.id: + mod = m + assert mod is not None + assert mod.channel_role == "channel_member" + + def test_query_members(self, client: Stream, channel: Channel): + """Query channel members with autocomplete filter.""" + rand = str(uuid.uuid4())[:8] + user_ids = [ + f"{n}-{rand}" for n in ["paul", "george", "john", "jessica", "john2"] + ] + client.update_users( + users={uid: UserRequest(id=uid, name=uid) for uid in user_ids} + ) + for uid in user_ids: + channel.update(add_members=[ChannelMemberRequest(user_id=uid)]) + + response = client.chat.query_members( + payload=QueryMembersPayload( + type=channel.channel_type, + id=channel.channel_id, + filter_conditions={"name": {"$autocomplete": "j"}}, + sort=[SortParamRequest(field="created_at", direction=1)], + offset=1, + limit=10, + ) + ) + assert response.data.members is not None + assert len(response.data.members) == 2 + + try: + client.delete_users( + user_ids=user_ids, + user="hard", + conversations="hard", + messages="hard", + ) + except StreamAPIException: + pass + + def test_update_member_partial(self, channel: Channel, random_users): + """Partial update of a channel member's custom fields.""" + user_id = random_users[0].id + channel.update(add_members=[ChannelMemberRequest(user_id=user_id)]) + + response = channel.update_member_partial(user_id=user_id, set={"hat": "blue"}) + assert response.data.channel_member is not None + assert response.data.channel_member.custom.get("hat") == "blue" + + response = channel.update_member_partial( + user_id=user_id, set={"color": "red"}, unset=["hat"] + ) + assert response.data.channel_member.custom.get("color") == "red" + assert "hat" not in (response.data.channel_member.custom or {}) + + +class TestChannelState: + def test_channel_hide_show(self, client: Stream, channel: Channel, random_users): + """Hide and show a channel for a user, including hidden filter queries.""" + user_id = random_users[0].id + channel.update( + add_members=[ + ChannelMemberRequest(user_id=uid) + for uid in [u.id for u in random_users] + ] + ) + cid = f"{channel.channel_type}:{channel.channel_id}" + + # verify channel is visible + response = client.chat.query_channels( + filter_conditions={"id": channel.channel_id}, user_id=user_id + ) + assert len(response.data.channels) == 1 + + # hide + channel.hide(user_id=user_id) + response = client.chat.query_channels( + filter_conditions={"id": channel.channel_id}, user_id=user_id + ) + assert len(response.data.channels) == 0 + + # verify hidden channel appears in hidden=True query + response = client.chat.query_channels( + filter_conditions={"hidden": True, "cid": cid}, user_id=user_id + ) + assert len(response.data.channels) == 1 + + # show + channel.show(user_id=user_id) + response = client.chat.query_channels( + filter_conditions={"id": channel.channel_id}, user_id=user_id + ) + assert len(response.data.channels) == 1 + + # verify channel no longer appears in hidden query + response = client.chat.query_channels( + filter_conditions={"hidden": True, "cid": cid}, user_id=user_id + ) + assert len(response.data.channels) == 0 + + def test_mute_unmute_channel(self, client: Stream, channel: Channel, random_users): + """Mute and unmute a channel.""" + user_id = random_users[0].id + channel.update(add_members=[ChannelMemberRequest(user_id=user_id)]) + cid = f"{channel.channel_type}:{channel.channel_id}" + + response = client.chat.mute_channel( + user_id=user_id, channel_cids=[cid], expiration=30000 + ) + assert response.data.channel_mute is not None + assert response.data.channel_mute.expires is not None + + # verify muted channel appears in query + response = client.chat.query_channels( + filter_conditions={"muted": True, "cid": cid}, user_id=user_id + ) + assert len(response.data.channels) == 1 + + # unmute + client.chat.unmute_channel(user_id=user_id, channel_cids=[cid]) + response = client.chat.query_channels( + filter_conditions={"muted": True, "cid": cid}, user_id=user_id + ) + assert len(response.data.channels) == 0 + + def test_pin_channel(self, client: Stream, channel: Channel, random_users): + """Pin and unpin a channel for a user.""" + user_id = random_users[0].id + channel.update(add_members=[ChannelMemberRequest(user_id=user_id)]) + cid = f"{channel.channel_type}:{channel.channel_id}" + + # Pin the channel + response = channel.update_member_partial(user_id=user_id, set={"pinned": True}) + assert response is not None + + # Query for pinned channels + response = client.chat.query_channels( + filter_conditions={"pinned": True, "cid": cid}, user_id=user_id + ) + assert len(response.data.channels) == 1 + assert response.data.channels[0].channel.cid == cid + + # Unpin the channel + response = channel.update_member_partial(user_id=user_id, set={"pinned": False}) + assert response is not None + + # Query for unpinned channels + response = client.chat.query_channels( + filter_conditions={"pinned": False, "cid": cid}, user_id=user_id + ) + assert len(response.data.channels) == 1 + + def test_archive_channel(self, client: Stream, channel: Channel, random_users): + """Archive and unarchive a channel for a user.""" + user_id = random_users[0].id + channel.update(add_members=[ChannelMemberRequest(user_id=user_id)]) + cid = f"{channel.channel_type}:{channel.channel_id}" + + # Archive the channel + response = channel.update_member_partial( + user_id=user_id, set={"archived": True} + ) + assert response is not None + + # Query for archived channels + response = client.chat.query_channels( + filter_conditions={"archived": True, "cid": cid}, user_id=user_id + ) + assert len(response.data.channels) == 1 + assert response.data.channels[0].channel.cid == cid + + # Unarchive the channel + response = channel.update_member_partial( + user_id=user_id, set={"archived": False} + ) + assert response is not None + + # Query for unarchived channels + response = client.chat.query_channels( + filter_conditions={"archived": False, "cid": cid}, user_id=user_id + ) + assert len(response.data.channels) == 1 + + def test_mark_read(self, channel: Channel, random_user): + """Mark a channel as read.""" + channel.update(add_members=[ChannelMemberRequest(user_id=random_user.id)]) + response = channel.mark_read(user_id=random_user.id) + assert response.data.event is not None + assert response.data.event.type == "message.read" + + def test_mark_unread(self, channel: Channel, random_user): + """Mark a channel as unread from a specific message.""" + msg_response = channel.send_message( + message=MessageRequest(text="helloworld", user_id=random_user.id) + ) + msg_id = msg_response.data.message.id + response = channel.mark_unread(user_id=random_user.id, message_id=msg_id) + assert response is not None + + def test_mark_unread_with_thread(self, channel: Channel, random_user): + """Mark unread from a specific thread.""" + channel.update(add_members=[ChannelMemberRequest(user_id=random_user.id)]) + parent = channel.send_message( + message=MessageRequest( + text="Parent for unread thread", user_id=random_user.id + ) + ) + parent_id = parent.data.message.id + + channel.send_message( + message=MessageRequest( + text="Reply in thread", + user_id=random_user.id, + parent_id=parent_id, + ) + ) + + response = channel.mark_unread( + user_id=random_user.id, + thread_id=parent_id, + ) + assert response is not None + + def test_mark_unread_with_timestamp(self, channel: Channel, random_user): + """Mark unread using a message timestamp.""" + channel.update(add_members=[ChannelMemberRequest(user_id=random_user.id)]) + send_resp = channel.send_message( + message=MessageRequest( + text="test message for timestamp", user_id=random_user.id + ) + ) + ts = send_resp.data.message.created_at + + response = channel.mark_unread( + user_id=random_user.id, + message_timestamp=ts, + ) + assert response is not None + + def test_message_count(self, client: Stream, channel: Channel, random_user): + """Verify message count on a channel.""" + channel.send_message( + message=MessageRequest(text="hello world", user_id=random_user.id) + ) + + q_resp = client.chat.query_channels( + filter_conditions={"cid": f"{channel.channel_type}:{channel.channel_id}"}, + user_id=random_user.id, + ) + assert len(q_resp.data.channels) == 1 + ch = q_resp.data.channels[0].channel + if ch.message_count is not None: + assert ch.message_count >= 1 + + def test_message_count_disabled( + self, client: Stream, channel: Channel, random_user + ): + """Verify message count is None when count_messages is disabled.""" + channel.update_channel_partial( + set={"config_overrides": {"count_messages": False}} + ) + + channel.send_message( + message=MessageRequest(text="hello world", user_id=random_user.id) + ) + + q_resp = client.chat.query_channels( + filter_conditions={"cid": f"{channel.channel_type}:{channel.channel_id}"}, + user_id=random_user.id, + ) + assert len(q_resp.data.channels) == 1 + assert q_resp.data.channels[0].channel.message_count is None + + +class TestChannelExportAndBan: + def test_export_channel(self, client: Stream, channel: Channel, random_users): + """Export a channel and poll the task until complete.""" + channel.send_message( + message=MessageRequest(text="Hey Joni", user_id=random_users[0].id) + ) + cid = f"{channel.channel_type}:{channel.channel_id}" + response = client.chat.export_channels(channels=[ChannelExport(cid=cid)]) + task_id = response.data.task_id + assert task_id is not None and task_id != "" + + task_response = wait_for_task(client, task_id, timeout_ms=30000) + assert task_response.data.status == "completed" + + def test_export_channel_status(self, client: Stream): + """Test error handling for export channel status with invalid task ID.""" + import pytest + + # Invalid task ID should raise an error + with pytest.raises(StreamAPIException): + client.get_task(id=str(uuid.uuid4())) + + def test_ban_user_in_channel( + self, client: Stream, channel: Channel, random_user, server_user + ): + """Ban and unban a user at channel level.""" + channel.update( + add_members=[ + ChannelMemberRequest(user_id=uid) + for uid in [random_user.id, server_user.id] + ] + ) + cid = f"{channel.channel_type}:{channel.channel_id}" + + client.moderation.ban( + target_user_id=random_user.id, + banned_by_id=server_user.id, + channel_cid=cid, + ) + client.moderation.ban( + target_user_id=random_user.id, + banned_by_id=server_user.id, + channel_cid=cid, + timeout=3600, + reason="offensive language is not allowed here", + ) + client.moderation.unban( + target_user_id=random_user.id, + channel_cid=cid, + ) + + +class TestChannelFileUpload: + def test_upload_and_delete_file(self, channel: Channel, random_user): + """Upload and delete a file.""" + file_path = str(ASSETS_DIR / "test_upload.txt") + + try: + upload_resp = channel.upload_channel_file( + file=file_path, + user=OnlyUserID(id=random_user.id), + ) + assert upload_resp.data.file is not None + file_url = upload_resp.data.file + assert "http" in file_url + + channel.delete_channel_file(url=file_url) + except Exception as e: + if "multipart" in str(e).lower(): + import pytest + + pytest.skip("File upload requires multipart/form-data support") + raise + + def test_upload_and_delete_image(self, channel: Channel, random_user): + """Upload and delete an image.""" + file_path = str(ASSETS_DIR / "test_upload.jpg") + + try: + upload_resp = channel.upload_channel_image( + file=file_path, + user=OnlyUserID(id=random_user.id), + ) + assert upload_resp.data.file is not None + image_url = upload_resp.data.file + assert "http" in image_url + + channel.delete_channel_image(url=image_url) + except Exception as e: + if "multipart" in str(e).lower(): + import pytest + + pytest.skip("Image upload requires multipart/form-data support") + raise diff --git a/tests/test_chat_draft.py b/tests/test_chat_draft.py new file mode 100644 index 00000000..c09f0ce3 --- /dev/null +++ b/tests/test_chat_draft.py @@ -0,0 +1,147 @@ +import uuid + +import pytest + +from getstream import Stream +from getstream.base import StreamAPIException +from getstream.chat.channel import Channel +from getstream.models import ( + ChannelInput, + ChannelMemberRequest, + MessageRequest, + Response, + SortParamRequest, +) + + +def _create_draft(channel, text, user_id, parent_id=None): + """Create a draft via raw HTTP (endpoint is client-side-only, not in generated SDK).""" + message = {"text": text, "user_id": user_id} + if parent_id: + message["parent_id"] = parent_id + return channel.client.post( + "/api/v2/chat/channels/{type}/{id}/draft", + Response, + path_params={"type": channel.channel_type, "id": channel.channel_id}, + json={"message": message}, + ) + + +class TestDrafts: + def test_create_and_get_draft(self, channel: Channel, random_user): + """Create a draft via raw HTTP and retrieve it via SDK.""" + text = f"draft-{uuid.uuid4()}" + channel.update(add_members=[ChannelMemberRequest(user_id=random_user.id)]) + + _create_draft(channel, text, random_user.id) + + response = channel.get_draft(user_id=random_user.id) + assert response.data.draft is not None + assert response.data.draft.message.text == text + assert response.data.draft.channel_cid == ( + f"{channel.channel_type}:{channel.channel_id}" + ) + + def test_delete_draft(self, channel: Channel, random_user): + """Create a draft, delete it, and verify get raises an error.""" + text = f"draft-to-delete-{uuid.uuid4()}" + channel.update(add_members=[ChannelMemberRequest(user_id=random_user.id)]) + + _create_draft(channel, text, random_user.id) + + # verify draft exists + response = channel.get_draft(user_id=random_user.id) + assert response.data.draft is not None + + # delete draft + channel.delete_draft(user_id=random_user.id) + + # verify draft is gone + with pytest.raises(StreamAPIException): + channel.get_draft(user_id=random_user.id) + + def test_thread_draft(self, channel: Channel, random_user): + """Create a draft on a thread (with parent_id), get and delete it.""" + channel.update(add_members=[ChannelMemberRequest(user_id=random_user.id)]) + + # send a parent message + parent = channel.send_message( + message=MessageRequest(text="thread parent", user_id=random_user.id) + ) + parent_id = parent.data.message.id + + # create draft with parent_id + text = f"thread-draft-{uuid.uuid4()}" + _create_draft(channel, text, random_user.id, parent_id=parent_id) + + # get draft with parent_id + response = channel.get_draft(user_id=random_user.id, parent_id=parent_id) + assert response.data.draft is not None + assert response.data.draft.message.text == text + assert response.data.draft.message.parent_id == parent_id + + # delete draft with parent_id + channel.delete_draft(user_id=random_user.id, parent_id=parent_id) + + with pytest.raises(StreamAPIException): + channel.get_draft(user_id=random_user.id, parent_id=parent_id) + + def test_query_drafts(self, client: Stream, random_users): + """Create drafts in 2 channels, query with various filters.""" + user_id = random_users[0].id + + # create 2 channels with the user as member + channel_ids = [str(uuid.uuid4()), str(uuid.uuid4())] + channels = [] + for cid in channel_ids: + ch = client.chat.channel("messaging", cid) + ch.get_or_create( + data=ChannelInput( + created_by_id=user_id, + members=[ChannelMemberRequest(user_id=user_id)], + ) + ) + channels.append(ch) + + # create a draft in each channel + for i, ch in enumerate(channels): + _create_draft(ch, f"draft-{i}-{uuid.uuid4()}", user_id) + + # query all drafts for user — should return at least 2 + response = client.chat.query_drafts(user_id=user_id) + assert response.data.drafts is not None + assert len(response.data.drafts) >= 2 + + # query with channel_cid filter — should return 1 + target_cid = f"messaging:{channel_ids[0]}" + response = client.chat.query_drafts( + user_id=user_id, + filter={"channel_cid": {"$eq": target_cid}}, + ) + assert len(response.data.drafts) == 1 + assert response.data.drafts[0].channel_cid == target_cid + + # query with sort by created_at descending + response = client.chat.query_drafts( + user_id=user_id, + sort=[SortParamRequest(field="created_at", direction=-1)], + ) + assert len(response.data.drafts) >= 2 + # verify descending order + for j in range(len(response.data.drafts) - 1): + assert ( + response.data.drafts[j].created_at + >= response.data.drafts[j + 1].created_at + ) + + # query with limit=1 pagination + response = client.chat.query_drafts(user_id=user_id, limit=1) + assert len(response.data.drafts) == 1 + + # cleanup + try: + client.chat.delete_channels( + cids=[f"messaging:{cid}" for cid in channel_ids], hard_delete=True + ) + except StreamAPIException: + pass diff --git a/tests/test_chat_message.py b/tests/test_chat_message.py new file mode 100644 index 00000000..edaa779b --- /dev/null +++ b/tests/test_chat_message.py @@ -0,0 +1,716 @@ +import time +import uuid + +import pytest + +from getstream import Stream +from getstream.base import StreamAPIException +from getstream.chat.channel import Channel +from getstream.models import ( + ChannelInput, + ChannelMemberRequest, + DeliveredMessagePayload, + EventRequest, + MessageRequest, + ReactionRequest, + SearchPayload, + SortParamRequest, +) + + +def test_send_message(channel: Channel, random_user): + """Send a message with skip_push option.""" + response = channel.send_message( + message=MessageRequest(text="hi", user_id=random_user.id), + skip_push=True, + ) + assert response.data.message is not None + assert response.data.message.text == "hi" + + +def test_send_pending_message(client: Stream, channel: Channel, random_user): + """Send a pending message and commit it.""" + response = channel.send_message( + message=MessageRequest(text="hi", user_id=random_user.id), + pending=True, + pending_message_metadata={"extra_data": "test"}, + ) + assert response.data.message is not None + assert response.data.message.text == "hi" + + commit_response = client.chat.commit_message(id=response.data.message.id) + assert commit_response.data.message is not None + assert commit_response.data.message.text == "hi" + + +def test_send_message_restricted_visibility(channel: Channel, random_users): + """Send a message with restricted visibility.""" + amy = random_users[0].id + paul = random_users[1].id + sender = random_users[2].id + + channel.update( + add_members=[ChannelMemberRequest(user_id=uid) for uid in [amy, paul, sender]] + ) + + response = channel.send_message( + message=MessageRequest( + text="hi", + user_id=sender, + restricted_visibility=[amy, paul], + ) + ) + assert response.data.message is not None + assert response.data.message.text == "hi" + assert response.data.message.restricted_visibility == [amy, paul] + + +def test_get_message(client: Stream, channel: Channel, random_user): + """Get a message by ID, including deleted messages.""" + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id, text="helloworld", user_id=random_user.id) + ) + + response = client.chat.get_message(id=msg_id) + assert response.data.message is not None + assert response.data.message.id == msg_id + assert response.data.message.text == "helloworld" + + # delete and then retrieve with show_deleted_message + client.chat.delete_message(id=msg_id) + response = client.chat.get_message(id=msg_id, show_deleted_message=True) + assert response.data.message is not None + assert response.data.message.text == "helloworld" + + +def test_get_many_messages(channel: Channel, random_user): + """Get multiple messages by IDs.""" + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id, text="helloworld", user_id=random_user.id) + ) + response = channel.get_many_messages(ids=[msg_id]) + assert response.data.messages is not None + assert len(response.data.messages) == 1 + + +def test_update_message(client: Stream, channel: Channel, random_user): + """Update a message's text.""" + msg_id = str(uuid.uuid4()) + response = channel.send_message( + message=MessageRequest(id=msg_id, text="hello world", user_id=random_user.id) + ) + assert response.data.message.text == "hello world" + + response = client.chat.update_message( + id=msg_id, + message=MessageRequest(text="helloworld", user_id=random_user.id), + ) + assert response.data.message.text == "helloworld" + + +def test_update_message_partial(client: Stream, channel: Channel, random_user): + """Partial update of a message.""" + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id, text="hello world", user_id=random_user.id) + ) + response = client.chat.update_message_partial( + id=msg_id, + set={"text": "helloworld"}, + user_id=random_user.id, + ) + assert response.data.message is not None + assert response.data.message.text == "helloworld" + + +def test_delete_message(client: Stream, channel: Channel, random_user): + """Delete a message (soft and hard).""" + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id, text="helloworld", user_id=random_user.id) + ) + response = client.chat.delete_message(id=msg_id) + assert response.data.message is not None + + # hard delete + msg_id2 = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id2, text="helloworld", user_id=random_user.id) + ) + response = client.chat.delete_message(id=msg_id2, hard=True) + assert response.data.message is not None + + +def test_pin_unpin_message(client: Stream, channel: Channel, random_user): + """Pin and unpin a message.""" + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id, text="hello world", user_id=random_user.id) + ) + + # pin + response = client.chat.update_message_partial( + id=msg_id, + set={"pinned": True, "pin_expires": None}, + user_id=random_user.id, + ) + assert response.data.message.pinned is True + assert response.data.message.pinned_at is not None + assert response.data.message.pinned_by is not None + assert response.data.message.pinned_by.id == random_user.id + + # unpin + response = client.chat.update_message_partial( + id=msg_id, + set={"pinned": False}, + user_id=random_user.id, + ) + assert response.data.message.pinned is False + + +def test_get_replies(client: Stream, channel: Channel, random_user): + """Send replies to a parent message and get them with pagination.""" + parent = channel.send_message( + message=MessageRequest(text="parent", user_id=random_user.id) + ) + parent_id = parent.data.message.id + + response = client.chat.get_replies(parent_id=parent_id) + assert response.data.messages is not None + assert len(response.data.messages) == 0 + + for i in range(5): + channel.send_message( + message=MessageRequest( + text=f"reply {i}", + user_id=random_user.id, + parent_id=parent_id, + ) + ) + + response = client.chat.get_replies(parent_id=parent_id) + assert len(response.data.messages) == 5 + + # test limit parameter + response = client.chat.get_replies(parent_id=parent_id, limit=2) + assert len(response.data.messages) == 2 + + +def test_send_reaction(client: Stream, channel: Channel, random_user): + """Send a reaction to a message.""" + msg = channel.send_message( + message=MessageRequest(text="hi", user_id=random_user.id) + ) + response = client.chat.send_reaction( + id=msg.data.message.id, + reaction=ReactionRequest(type="love", user_id=random_user.id), + ) + assert response.data.message is not None + assert len(response.data.message.latest_reactions) == 1 + assert response.data.message.latest_reactions[0].type == "love" + + +def test_delete_reaction(client: Stream, channel: Channel, random_user): + """Delete a reaction from a message.""" + msg = channel.send_message( + message=MessageRequest(text="hi", user_id=random_user.id) + ) + client.chat.send_reaction( + id=msg.data.message.id, + reaction=ReactionRequest(type="love", user_id=random_user.id), + ) + response = client.chat.delete_reaction( + id=msg.data.message.id, type="love", user_id=random_user.id + ) + assert response.data.message is not None + assert len(response.data.message.latest_reactions) == 0 + + +def test_get_reactions(client: Stream, channel: Channel, random_user): + """Get reactions on a message with offset pagination.""" + msg = channel.send_message( + message=MessageRequest(text="hi", user_id=random_user.id) + ) + msg_id = msg.data.message.id + + response = client.chat.get_reactions(id=msg_id) + assert response.data.reactions is not None + assert len(response.data.reactions) == 0 + + client.chat.send_reaction( + id=msg_id, + reaction=ReactionRequest(type="love", user_id=random_user.id), + ) + client.chat.send_reaction( + id=msg_id, + reaction=ReactionRequest(type="clap", user_id=random_user.id), + ) + + response = client.chat.get_reactions(id=msg_id) + assert len(response.data.reactions) == 2 + + # test offset pagination + response = client.chat.get_reactions(id=msg_id, offset=1) + assert len(response.data.reactions) == 1 + + +def test_send_event(channel: Channel, random_user): + """Send a typing event on a channel.""" + response = channel.send_event( + event=EventRequest(type="typing.start", user_id=random_user.id) + ) + assert response.data.event is not None + assert response.data.event.type == "typing.start" + + +def test_translate_message(client: Stream, channel: Channel, random_user): + """Translate a message.""" + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id, text="hello world", user_id=random_user.id) + ) + response = client.chat.translate_message(id=msg_id, language="hu") + assert response.data.message is not None + + +def test_run_message_action(client: Stream, channel: Channel, random_user): + """Run a message action (e.g. giphy shuffle).""" + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id, text="/giphy wave", user_id=random_user.id) + ) + try: + client.chat.run_message_action( + id=msg_id, + form_data={"image_action": "shuffle"}, + user_id=random_user.id, + ) + except Exception: + # giphy may not be configured on every test app + pass + + +def test_query_message_history(client: Stream, channel: Channel, random_user): + """Query message edit history.""" + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id, text="helloworld", user_id=random_user.id) + ) + for i in range(1, 4): + client.chat.update_message( + id=msg_id, + message=MessageRequest(text=f"helloworld-{i}", user_id=random_user.id), + ) + + response = client.chat.query_message_history( + filter={"message_id": {"$eq": msg_id}}, + sort=[SortParamRequest(field="message_updated_at", direction=-1)], + limit=1, + ) + assert response.data.message_history is not None + assert len(response.data.message_history) == 1 + assert response.data.message_history[0].text == "helloworld-2" + + +def test_search(client: Stream, channel: Channel, random_user): + """Search messages across channels.""" + query = f"supercalifragilisticexpialidocious-{uuid.uuid4()}" + channel.send_message( + message=MessageRequest( + text=f"How many syllables are there in {query}?", + user_id=random_user.id, + ) + ) + time.sleep(1) # wait for indexing + + response = client.chat.search( + payload=SearchPayload( + filter_conditions={"type": "messaging"}, + query=query, + limit=2, + offset=0, + ) + ) + assert response.data.results is not None + assert len(response.data.results) >= 1 + assert query in response.data.results[0].message.text + + +def test_search_with_sort(client: Stream, channel: Channel, random_user): + """Search messages with sort and cursor-based pagination.""" + text = f"searchsort-{uuid.uuid4()}" + ids = [f"0{text}", f"1{text}"] + channel.send_message( + message=MessageRequest(id=ids[0], text=text, user_id=random_user.id) + ) + channel.send_message( + message=MessageRequest(id=ids[1], text=text, user_id=random_user.id) + ) + time.sleep(1) # wait for indexing + + response = client.chat.search( + payload=SearchPayload( + filter_conditions={"type": "messaging"}, + query=text, + limit=1, + sort=[SortParamRequest(field="created_at", direction=-1)], + ) + ) + assert response.data.results is not None + assert len(response.data.results) >= 1 + assert response.data.results[0].message.id == ids[1] + assert response.data.next is not None + + # fetch next page + response2 = client.chat.search( + payload=SearchPayload( + filter_conditions={"type": "messaging"}, + query=text, + limit=1, + next=response.data.next, + sort=[SortParamRequest(field="created_at", direction=-1)], + ) + ) + assert response2.data.results is not None + assert len(response2.data.results) >= 1 + assert response2.data.results[0].message.id == ids[0] + + +def test_search_message_filters(client: Stream, channel: Channel, random_user): + """Search messages using message_filter_conditions.""" + query = f"supercalifragilisticexpialidocious-{uuid.uuid4()}" + channel.send_message( + message=MessageRequest( + text=f"How many syllables are there in {query}?", + user_id=random_user.id, + ) + ) + channel.send_message( + message=MessageRequest( + text="Does 'cious' count as one or two?", + user_id=random_user.id, + ) + ) + time.sleep(1) # wait for indexing + + response = client.chat.search( + payload=SearchPayload( + filter_conditions={"type": "messaging"}, + message_filter_conditions={"text": {"$q": query}}, + limit=2, + offset=0, + ) + ) + assert response.data.results is not None + assert len(response.data.results) >= 1 + assert query in response.data.results[0].message.text + + +def test_delete_message_for_me(client: Stream, channel: Channel, random_user): + """Delete a message for a specific user (delete for me).""" + channel.update(add_members=[ChannelMemberRequest(user_id=random_user.id)]) + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id, text="helloworld", user_id=random_user.id) + ) + response = client.chat.delete_message( + id=msg_id, delete_for_me=True, deleted_by=random_user.id + ) + assert response.data.message is not None + + +def test_mark_delivered(client: Stream, channel: Channel, random_user): + """Mark messages as delivered.""" + cid = f"{channel.channel_type}:{channel.channel_id}" + response = client.chat.mark_delivered( + user_id=random_user.id, + latest_delivered_messages=[ + DeliveredMessagePayload(cid=cid, id="test-message-id") + ], + ) + assert response is not None + + +def test_silent_message(channel: Channel, random_user): + """Send a silent message.""" + response = channel.send_message( + message=MessageRequest( + text="This is a silent message", user_id=random_user.id, silent=True + ), + ) + assert response.data.message is not None + assert response.data.message.silent is True + + +def test_skip_enrich_url(client: Stream, channel: Channel, random_user): + """Send a message with a URL but skip enrichment.""" + response = channel.send_message( + message=MessageRequest( + text="Check out https://getstream.io for more info", + user_id=random_user.id, + ), + skip_enrich_url=True, + ) + assert response.data.message is not None + assert len(response.data.message.attachments) == 0 + + +def test_keep_channel_hidden(client: Stream, channel: Channel, random_user): + """Send a message keeping the channel hidden.""" + channel.update(add_members=[ChannelMemberRequest(user_id=random_user.id)]) + + # hide the channel + channel.hide(user_id=random_user.id) + + # send message with keep_channel_hidden + channel.send_message( + message=MessageRequest(text="Hidden message", user_id=random_user.id), + keep_channel_hidden=True, + ) + + # channel should still be hidden + cid = f"{channel.channel_type}:{channel.channel_id}" + q_resp = client.chat.query_channels( + filter_conditions={"cid": cid}, user_id=random_user.id + ) + assert len(q_resp.data.channels) == 0 + + # show it back for cleanup + channel.show(user_id=random_user.id) + + +def test_undelete_message(client: Stream, channel: Channel, random_user): + """Soft delete and then undelete a message.""" + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest( + id=msg_id, text="Message to undelete", user_id=random_user.id + ) + ) + + # soft delete + client.chat.delete_message(id=msg_id) + get_resp = client.chat.get_message(id=msg_id) + assert get_resp.data.message.type == "deleted" + + # undelete + undelete_resp = client.chat.undelete_message(id=msg_id, undeleted_by=random_user.id) + assert undelete_resp.data.message is not None + assert undelete_resp.data.message.type != "deleted" + assert undelete_resp.data.message.text == "Message to undelete" + + +def test_pin_expiration(client: Stream, channel: Channel, random_user): + """Pin a message with expiration.""" + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest( + id=msg_id, text="Message to pin with expiry", user_id=random_user.id + ) + ) + + # pin with short expiry + from datetime import datetime, timedelta, timezone + + expiry = datetime.now(timezone.utc) + timedelta(seconds=3) + response = client.chat.update_message_partial( + id=msg_id, + set={"pinned": True, "pin_expires": expiry.isoformat()}, + user_id=random_user.id, + ) + assert response.data.message.pinned is True + + # wait for expiry + time.sleep(4) + + get_resp = client.chat.get_message(id=msg_id) + assert get_resp.data.message.pinned is False + + +def test_system_message(channel: Channel, random_user): + """Send a system message.""" + response = channel.send_message( + message=MessageRequest( + text="User joined the channel", + user_id=random_user.id, + type="system", + ), + ) + assert response.data.message is not None + assert response.data.message.type == "system" + + +def test_channel_role_in_member(client: Stream, random_users): + """Verify channel_role is present in message member.""" + member_id = random_users[0].id + mod_id = random_users[1].id + + channel_id = str(uuid.uuid4()) + ch = client.chat.channel("messaging", channel_id) + ch.get_or_create( + data=ChannelInput( + created_by_id=member_id, + members=[ + ChannelMemberRequest(user_id=member_id, channel_role="channel_member"), + ChannelMemberRequest(user_id=mod_id, channel_role="channel_moderator"), + ], + ) + ) + + resp_member = ch.send_message( + message=MessageRequest(text="message from member", user_id=member_id) + ) + assert resp_member.data.message.member is not None + assert resp_member.data.message.member.channel_role == "channel_member" + + resp_mod = ch.send_message( + message=MessageRequest(text="message from moderator", user_id=mod_id) + ) + assert resp_mod.data.message.member is not None + assert resp_mod.data.message.member.channel_role == "channel_moderator" + + try: + client.chat.delete_channels(cids=[f"messaging:{channel_id}"], hard_delete=True) + except Exception: + pass + + +def test_query_reactions(client: Stream, channel: Channel, random_users): + """Query reactions on a message.""" + msg = channel.send_message( + message=MessageRequest( + text="Message for query reactions", user_id=random_users[0].id + ) + ) + msg_id = msg.data.message.id + + client.chat.send_reaction( + id=msg_id, + reaction=ReactionRequest(type="like", user_id=random_users[0].id), + ) + client.chat.send_reaction( + id=msg_id, + reaction=ReactionRequest(type="wow", user_id=random_users[1].id), + ) + + response = client.chat.query_reactions(id=msg_id) + assert response.data.reactions is not None + assert len(response.data.reactions) >= 2 + + +def test_enforce_unique_reaction(client: Stream, channel: Channel, random_user): + """Enforce unique reaction per user.""" + msg = channel.send_message( + message=MessageRequest( + text="Message for unique reaction", user_id=random_user.id + ) + ) + msg_id = msg.data.message.id + + # send first reaction + client.chat.send_reaction( + id=msg_id, + reaction=ReactionRequest(type="like", user_id=random_user.id), + enforce_unique=True, + ) + + # send second reaction with enforce_unique — should replace + client.chat.send_reaction( + id=msg_id, + reaction=ReactionRequest(type="love", user_id=random_user.id), + enforce_unique=True, + ) + + # user should only have one reaction + response = client.chat.get_reactions(id=msg_id) + user_reactions = [r for r in response.data.reactions if r.user_id == random_user.id] + assert len(user_reactions) == 1 + + +def test_query_message_history_sort(client: Stream, channel: Channel, random_user): + """Query message history with ascending sort by message_updated_at.""" + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id, text="sort initial", user_id=random_user.id) + ) + + client.chat.update_message( + id=msg_id, + message=MessageRequest(text="sort updated 1", user_id=random_user.id), + ) + client.chat.update_message( + id=msg_id, + message=MessageRequest(text="sort updated 2", user_id=random_user.id), + ) + + # Query with ascending sort by message_updated_at + try: + response = client.chat.query_message_history( + filter={"message_id": msg_id}, + sort=[SortParamRequest(field="message_updated_at", direction=1)], + ) + except Exception as e: + if "feature flag" in str(e) or "not enabled" in str(e): + pytest.skip("QueryMessageHistory feature not enabled for this app") + raise + + assert response.data.message_history is not None + assert len(response.data.message_history) >= 2 + + # Ascending: oldest first + assert response.data.message_history[0].text == "sort initial" + assert response.data.message_history[0].message_updated_by_id == random_user.id + + +def test_pending_false(client: Stream, channel: Channel, random_user): + """Send a message with pending=False and verify it's immediately available.""" + response = channel.send_message( + message=MessageRequest(text="Non-pending message", user_id=random_user.id), + pending=False, + ) + assert response.data.message is not None + + # Get the message to verify it's immediately available (no commit needed) + get_response = client.chat.get_message(id=response.data.message.id) + assert get_response.data.message is not None + assert get_response.data.message.text == "Non-pending message" + + +def test_search_query_and_message_filters_error(client: Stream, random_user): + """Using both query and message_filter_conditions together should error.""" + with pytest.raises(StreamAPIException): + client.chat.search( + payload=SearchPayload( + filter_conditions={"members": {"$in": [random_user.id]}}, + query="test", + message_filter_conditions={"text": {"$q": "test"}}, + ) + ) + + +def test_search_offset_and_sort_error(client: Stream, random_user): + """Using offset with sort should error.""" + with pytest.raises(StreamAPIException): + client.chat.search( + payload=SearchPayload( + filter_conditions={"members": {"$in": [random_user.id]}}, + query="test", + offset=1, + sort=[SortParamRequest(field="created_at", direction=-1)], + ) + ) + + +def test_search_offset_and_next_error(client: Stream, random_user): + """Using offset with next should error.""" + with pytest.raises(StreamAPIException): + client.chat.search( + payload=SearchPayload( + filter_conditions={"members": {"$in": [random_user.id]}}, + query="test", + offset=1, + next="some_next_token", + ) + ) diff --git a/tests/test_chat_misc.py b/tests/test_chat_misc.py new file mode 100644 index 00000000..7342cfef --- /dev/null +++ b/tests/test_chat_misc.py @@ -0,0 +1,602 @@ +import time +import uuid + +import pytest + +from getstream import Stream +from getstream.base import StreamAPIException +from getstream.chat.channel import Channel +from getstream.models import ( + AsyncModerationCallbackConfig, + ChannelInput, + ChannelMemberRequest, + EventHook, + FileUploadConfig, + MessageRequest, + QueryFutureChannelBansPayload, + SortParamRequest, +) + + +def test_get_app_settings(client: Stream): + """Get application settings.""" + response = client.get_app() + assert response.data.app is not None + + +def test_update_app_settings(client: Stream): + """Update app settings and verify.""" + response = client.update_app() + assert response is not None + + +def test_update_app_settings_event_hooks(client: Stream): + """Update app settings with event hooks, then clear them.""" + response = client.update_app( + event_hooks=[ + EventHook( + hook_type="webhook", + webhook_url="https://example.com/webhook", + event_types=["message.new", "message.updated"], + ), + ] + ) + assert response is not None + + settings = client.get_app() + assert settings.data.app is not None + + # clear hooks + client.update_app(event_hooks=[]) + + +def test_blocklist_crud(client: Stream): + """Full CRUD cycle for blocklists.""" + name = f"test-blocklist-{uuid.uuid4().hex[:8]}" + + # create + client.create_block_list(name=name, words=["fudge", "heck"], type="word") + + # get + response = client.get_block_list(name=name) + assert response.data.blocklist is not None + assert response.data.blocklist.name == name + assert "fudge" in response.data.blocklist.words + + # list + response = client.list_block_lists() + assert response.data.blocklists is not None + names = [bl.name for bl in response.data.blocklists] + assert name in names + + # update + client.update_block_list(name=name, words=["dang"]) + response = client.get_block_list(name=name) + assert response.data.blocklist.words == ["dang"] + + # delete + client.delete_block_list(name=name) + + +def test_list_channel_types(client: Stream): + """List all channel types.""" + response = client.chat.list_channel_types() + assert response.data.channel_types is not None + assert len(response.data.channel_types) > 0 + + +def test_get_channel_type(client: Stream): + """Get a specific channel type.""" + response = client.chat.get_channel_type(name="team") + assert response.data.permissions is not None + + +def test_update_channel_type(client: Stream): + """Update a channel type's configuration.""" + # Get current config to know the required fields + current = client.chat.get_channel_type(name="team") + original_commands = [c.name for c in (current.data.commands or [])] + + try: + response = client.chat.update_channel_type( + name="team", + automod=current.data.automod, + automod_behavior=current.data.automod_behavior, + max_message_length=current.data.max_message_length, + commands=["ban", "unban"], + ) + assert response.data.commands is not None + assert "ban" in response.data.commands + assert "unban" in response.data.commands + finally: + client.chat.update_channel_type( + name="team", + automod=current.data.automod, + automod_behavior=current.data.automod_behavior, + max_message_length=current.data.max_message_length, + commands=original_commands, + ) + + +def test_command_crud(client: Stream): + """Full CRUD cycle for custom commands.""" + cmd_name = f"testcmd{uuid.uuid4().hex[:8]}" + + # create + response = client.chat.create_command(description="My test command", name=cmd_name) + assert response.data.command is not None + assert response.data.command.name == cmd_name + + # get + response = client.chat.get_command(name=cmd_name) + assert response.data.name == cmd_name + + # update + response = client.chat.update_command(name=cmd_name, description="Updated command") + assert response.data.command is not None + assert response.data.command.description == "Updated command" + + # list + response = client.chat.list_commands() + assert response.data.commands is not None + cmd_names = [c.name for c in response.data.commands] + assert cmd_name in cmd_names + + # delete + client.chat.delete_command(name=cmd_name) + + +def test_query_threads(client: Stream, channel: Channel, random_user): + """Create a thread and query threads.""" + parent = channel.send_message( + message=MessageRequest(text="thread parent", user_id=random_user.id) + ) + parent_id = parent.data.message.id + + channel.send_message( + message=MessageRequest( + text="thread reply", + user_id=random_user.id, + parent_id=parent_id, + ) + ) + + response = client.chat.query_threads(user_id=random_user.id) + assert response.data.threads is not None + assert len(response.data.threads) >= 1 + + +def test_query_threads_with_options(client: Stream, channel: Channel, random_user): + """Query threads with limit, filter, and sort options.""" + for i in range(3): + parent = channel.send_message( + message=MessageRequest(text=f"thread parent {i}", user_id=random_user.id) + ) + channel.send_message( + message=MessageRequest( + text=f"thread reply {i}", + user_id=random_user.id, + parent_id=parent.data.message.id, + ) + ) + + cid = f"{channel.channel_type}:{channel.channel_id}" + response = client.chat.query_threads( + filter={"channel_cid": cid}, + sort=[SortParamRequest(field="created_at", direction=-1)], + limit=1, + user_id=random_user.id, + ) + assert response.data.threads is not None + assert len(response.data.threads) == 1 + assert response.data.next is not None + + +@pytest.mark.skip(reason="slow and flaky due to waits") +def test_permissions_roles(client: Stream): + """Create and delete a custom role.""" + role_name = f"testrole{uuid.uuid4().hex[:8]}" + + client.create_role(name=role_name) + + # Poll until role appears (eventual consistency) + for _ in range(10): + response = client.list_roles() + assert response.data.roles is not None + role_names = [r.name for r in response.data.roles] + if role_name in role_names: + break + time.sleep(1) + else: + raise AssertionError(f"Role {role_name} did not appear within timeout") + + client.delete_role(name=role_name) + + # Poll until role disappears + for _ in range(10): + response = client.list_roles() + role_names = [r.name for r in response.data.roles] + if role_name not in role_names: + break + time.sleep(1) + else: + raise AssertionError(f"Role {role_name} was not deleted within timeout") + + +def test_list_get_permission(client: Stream): + """List permissions and get a specific one.""" + response = client.list_permissions() + assert response.data.permissions is not None + assert len(response.data.permissions) > 0 + + response = client.get_permission(id="create-channel") + assert response.data.permission is not None + assert response.data.permission.id == "create-channel" + + +def test_check_push(client: Stream, channel: Channel, random_user): + """Check push notification rendering.""" + msg = channel.send_message( + message=MessageRequest(text="/giphy wave", user_id=random_user.id) + ) + response = client.check_push( + message_id=msg.data.message.id, + skip_devices=True, + user_id=random_user.id, + ) + assert response.data.rendered_message is not None + + +def test_check_sqs(client: Stream): + """Check SQS configuration (expected to fail with invalid creds).""" + response = client.check_sqs( + sqs_key="key", sqs_secret="secret", sqs_url="https://foo.com/bar" + ) + assert response.data.status == "error" + + +def test_check_sns(client: Stream): + """Check SNS configuration (expected to fail with invalid creds).""" + response = client.check_sns( + sns_key="key", + sns_secret="secret", + sns_topic_arn="arn:aws:sns:us-east-1:123456789012:sns-topic", + ) + assert response.data.status == "error" + + +def test_get_rate_limits(client: Stream): + """Get rate limit information.""" + response = client.get_rate_limits() + assert response.data.server_side is not None + + response = client.get_rate_limits(server_side=True, android=True) + assert response.data.server_side is not None + assert response.data.android is not None + + +def test_response_metadata(client: Stream): + """Verify StreamResponse contains metadata (headers, status_code, rate_limit).""" + response = client.get_app() + assert response.status_code() == 200 + assert len(response.headers()) > 0 + rate_limit = response.rate_limit() + assert rate_limit is not None + assert rate_limit.limit > 0 + assert rate_limit.remaining >= 0 + + +def test_auth_exception(client: Stream): + """Verify authentication failure raises StreamAPIException.""" + bad_client = Stream(api_key="bad", api_secret="guy") + with pytest.raises(StreamAPIException): + bad_client.chat.get_channel_type(name="team") + + +def test_imports_end2end(client: Stream): + """End-to-end import: create URL, create import, get import, list imports.""" + import requests + + url_resp = client.create_import_url(filename=str(uuid.uuid4()) + ".json") + assert url_resp.data.upload_url is not None + assert url_resp.data.path is not None + + upload_resp = requests.put( + url_resp.data.upload_url, + data=b"{}", + headers={"Content-Type": "application/json"}, + ) + assert upload_resp.status_code == 200 + + create_resp = client.create_import(path=url_resp.data.path, mode="upsert") + assert create_resp.data.import_task is not None + assert create_resp.data.import_task.id is not None + + get_resp = client.get_import(id=create_resp.data.import_task.id) + assert get_resp.data.import_task is not None + assert get_resp.data.import_task.id == create_resp.data.import_task.id + + list_resp = client.list_imports() + assert list_resp.data.import_tasks is not None + assert len(list_resp.data.import_tasks) >= 1 + + +def test_file_upload_config(client: Stream): + """Set and verify file upload configuration.""" + # save original config + original = client.get_app() + original_config = original.data.app.file_upload_config + + try: + client.update_app( + file_upload_config=FileUploadConfig( + size_limit=10 * 1024 * 1024, + allowed_file_extensions=[".pdf", ".doc", ".txt"], + allowed_mime_types=["application/pdf", "text/plain"], + ) + ) + + verify = client.get_app() + cfg = verify.data.app.file_upload_config + assert cfg.size_limit == 10 * 1024 * 1024 + assert cfg.allowed_file_extensions == [".pdf", ".doc", ".txt"] + assert cfg.allowed_mime_types == ["application/pdf", "text/plain"] + finally: + # restore original config + if original_config is not None: + client.update_app(file_upload_config=original_config) + + +def test_query_future_channel_bans(client: Stream, random_users): + """Query future channel bans.""" + creator = random_users[0] + target = random_users[1] + + channel_id = str(uuid.uuid4()) + ch = client.chat.channel("messaging", channel_id) + ch.get_or_create( + data=ChannelInput( + created_by_id=creator.id, + members=[ + ChannelMemberRequest(user_id=creator.id), + ChannelMemberRequest(user_id=target.id), + ], + ) + ) + cid = f"messaging:{channel_id}" + + client.moderation.ban( + target_user_id=target.id, + banned_by_id=creator.id, + channel_cid=cid, + reason="test future ban query", + ) + + try: + response = client.chat.query_future_channel_bans( + payload=QueryFutureChannelBansPayload(user_id=creator.id) + ) + assert response.data.bans is not None + finally: + client.moderation.unban( + target_user_id=target.id, + channel_cid=cid, + ) + try: + client.chat.delete_channels(cids=[cid], hard_delete=True) + except Exception: + pass + + +def test_create_channel_type(client: Stream): + """Create a channel type with custom settings.""" + type_name = f"testtype{uuid.uuid4().hex[:8]}" + + try: + response = client.chat.create_channel_type( + name=type_name, + automod="disabled", + automod_behavior="flag", + max_message_length=5000, + ) + assert response.data.name == type_name + assert response.data.max_message_length == 5000 + + # Channel types are eventually consistent + time.sleep(6) + finally: + # Clean up + try: + client.chat.delete_channel_type(name=type_name) + except Exception: + pass + + +def test_update_channel_type_mark_messages_pending(client: Stream): + """Update a channel type with mark_messages_pending=True.""" + type_name = f"testtype{uuid.uuid4().hex[:8]}" + + try: + client.chat.create_channel_type( + name=type_name, + automod="disabled", + automod_behavior="flag", + max_message_length=5000, + ) + time.sleep(6) + + response = client.chat.update_channel_type( + name=type_name, + automod="disabled", + automod_behavior="flag", + max_message_length=5000, + mark_messages_pending=True, + ) + assert response.data.mark_messages_pending is True + + # Verify via get + get_response = client.chat.get_channel_type(name=type_name) + assert get_response.data.mark_messages_pending is True + finally: + try: + client.chat.delete_channel_type(name=type_name) + except Exception: + pass + + +def test_update_channel_type_push_notifications(client: Stream): + """Update a channel type with push_notifications=False.""" + type_name = f"testtype{uuid.uuid4().hex[:8]}" + + try: + client.chat.create_channel_type( + name=type_name, + automod="disabled", + automod_behavior="flag", + max_message_length=5000, + ) + time.sleep(6) + + response = client.chat.update_channel_type( + name=type_name, + automod="disabled", + automod_behavior="flag", + max_message_length=5000, + push_notifications=False, + ) + assert response.data.push_notifications is False + + # Verify via get + get_response = client.chat.get_channel_type(name=type_name) + assert get_response.data.push_notifications is False + finally: + try: + client.chat.delete_channel_type(name=type_name) + except Exception: + pass + + +def test_delete_channel_type(client: Stream): + """Create and delete a channel type with retry.""" + type_name = f"testdeltype{uuid.uuid4().hex[:8]}" + + client.chat.create_channel_type( + name=type_name, + automod="disabled", + automod_behavior="flag", + max_message_length=5000, + ) + time.sleep(6) + + # Retry delete up to 5 times (eventual consistency) + delete_err = None + for _ in range(5): + try: + client.chat.delete_channel_type(name=type_name) + delete_err = None + break + except Exception as e: + delete_err = e + time.sleep(1) + + assert delete_err is None, f"Failed to delete channel type: {delete_err}" + + +def test_get_thread(client: Stream, channel: Channel, random_user): + """Get a thread with reply_limit and verify replies.""" + parent = channel.send_message( + message=MessageRequest(text="thread parent", user_id=random_user.id) + ) + parent_id = parent.data.message.id + + # Send 2 replies + for i in range(2): + channel.send_message( + message=MessageRequest( + text=f"thread reply {i}", + user_id=random_user.id, + parent_id=parent_id, + ) + ) + + response = client.chat.get_thread(message_id=parent_id, reply_limit=10) + assert response.data.thread.parent_message_id == parent_id + assert len(response.data.thread.latest_replies) >= 2 + + +def test_get_rate_limits_specific_endpoints(client: Stream): + """Get rate limits for specific endpoints.""" + response = client.get_rate_limits( + server_side=True, + android=True, + endpoints="GetRateLimits,SendMessage", + ) + assert len(response.data.android) == 2 + assert len(response.data.server_side) == 2 + + for info in response.data.server_side.values(): + assert info.limit > 0 + assert info.remaining >= 0 + + +def test_event_hooks_sqs_sns(client: Stream): + """Test setting SQS, SNS, and pending_message event hooks.""" + # Save original hooks to restore later + original = client.get_app() + original_hooks = original.data.app.event_hooks + + try: + # SQS event hook + client.update_app( + event_hooks=[ + EventHook( + hook_type="sqs", + enabled=True, + event_types=["message.new"], + sqs_queue_url="https://sqs.us-east-1.amazonaws.com/123456789012/my-queue", + sqs_region="us-east-1", + sqs_auth_type="keys", + sqs_key="some key", + sqs_secret="some secret", + ), + ] + ) + + # SNS event hook + client.update_app( + event_hooks=[ + EventHook( + hook_type="sns", + enabled=True, + event_types=["message.new"], + sns_topic_arn="arn:aws:sns:us-east-1:123456789012:my-topic", + sns_region="us-east-1", + sns_auth_type="keys", + sns_key="some key", + sns_secret="some secret", + ), + ] + ) + + # Pending message event hook with async moderation callback + client.update_app( + event_hooks=[ + EventHook( + hook_type="pending_message", + enabled=True, + webhook_url="https://example.com/pending", + timeout_ms=10000, + callback=AsyncModerationCallbackConfig( + mode="CALLBACK_MODE_REST", + ), + ), + ] + ) + + # Clear all hooks + client.update_app(event_hooks=[]) + verify = client.get_app() + assert len(verify.data.app.event_hooks or []) == 0 + finally: + # Restore original hooks + client.update_app(event_hooks=original_hooks or []) diff --git a/tests/test_chat_moderation.py b/tests/test_chat_moderation.py new file mode 100644 index 00000000..87c31de7 --- /dev/null +++ b/tests/test_chat_moderation.py @@ -0,0 +1,288 @@ +import uuid + + +from getstream import Stream +from getstream.chat.channel import Channel +from getstream.models import ( + ChannelMemberRequest, + MessageRequest, + ModerationPayload, + QueryBannedUsersPayload, + QueryMessageFlagsPayload, +) + + +def test_ban_user(client: Stream, random_user, server_user): + """Ban a user.""" + response = client.moderation.ban( + target_user_id=random_user.id, + banned_by_id=server_user.id, + ) + assert response is not None + + +def test_unban_user(client: Stream, random_user, server_user): + """Ban then unban a user.""" + client.moderation.ban( + target_user_id=random_user.id, + banned_by_id=server_user.id, + ) + response = client.moderation.unban( + target_user_id=random_user.id, + unbanned_by_id=server_user.id, + ) + assert response is not None + + +def test_shadow_ban(client: Stream, random_user, server_user, channel: Channel): + """Shadow ban a user and verify messages are shadowed.""" + channel.update( + add_members=[ + ChannelMemberRequest(user_id=uid) + for uid in [random_user.id, server_user.id] + ] + ) + + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id, text="hello world", user_id=random_user.id) + ) + response = client.chat.get_message(id=msg_id) + assert response.data.message.shadowed is not True + + # shadow ban + client.moderation.ban( + target_user_id=random_user.id, + banned_by_id=server_user.id, + shadow=True, + ) + + msg_id2 = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id2, text="hello world", user_id=random_user.id) + ) + response = client.chat.get_message(id=msg_id2) + assert response.data.message.shadowed is True + + # remove shadow ban + client.moderation.unban( + target_user_id=random_user.id, + unbanned_by_id=server_user.id, + ) + + msg_id3 = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id3, text="hello world", user_id=random_user.id) + ) + response = client.chat.get_message(id=msg_id3) + assert response.data.message.shadowed is not True + + +def test_query_banned_users(client: Stream, random_user, server_user): + """Ban a user and query banned users.""" + client.moderation.ban( + target_user_id=random_user.id, + banned_by_id=server_user.id, + reason="because", + ) + response = client.chat.query_banned_users( + payload=QueryBannedUsersPayload( + filter_conditions={"reason": "because"}, + limit=1, + ) + ) + assert response.data.bans is not None + assert len(response.data.bans) >= 1 + + # cleanup + client.moderation.unban( + target_user_id=random_user.id, + unbanned_by_id=server_user.id, + ) + + +def test_mute_user(client: Stream, random_users): + """Mute a user.""" + response = client.moderation.mute( + target_ids=[random_users[0].id], + user_id=random_users[1].id, + ) + assert response.data.mutes is not None + assert len(response.data.mutes) >= 1 + assert response.data.mutes[0].target.id == random_users[0].id + assert response.data.mutes[0].user.id == random_users[1].id + + # cleanup + client.moderation.unmute( + target_ids=[random_users[0].id], + user_id=random_users[1].id, + ) + + +def test_mute_users(client: Stream, random_users): + """Mute multiple users at once.""" + muter = random_users[0].id + targets = [random_users[1].id, random_users[2].id] + + response = client.moderation.mute( + target_ids=targets, + user_id=muter, + ) + assert response.data.mutes is not None + muted_target_ids = [m.target.id for m in response.data.mutes] + for tid in targets: + assert tid in muted_target_ids + + # cleanup + client.moderation.unmute( + target_ids=targets, + user_id=muter, + ) + + +def test_unmute_user(client: Stream, random_users): + """Mute then unmute a user.""" + client.moderation.mute( + target_ids=[random_users[0].id], + user_id=random_users[1].id, + ) + response = client.moderation.unmute( + target_ids=[random_users[0].id], + user_id=random_users[1].id, + ) + assert response is not None + + +def test_mute_with_timeout(client: Stream, random_users): + """Mute a user with a timeout.""" + response = client.moderation.mute( + target_ids=[random_users[0].id], + user_id=random_users[1].id, + timeout=10, + ) + assert response.data.mutes is not None + assert len(response.data.mutes) >= 1 + assert response.data.mutes[0].expires is not None + + # cleanup + client.moderation.unmute( + target_ids=[random_users[0].id], + user_id=random_users[1].id, + ) + + +def test_flag_user(client: Stream, random_user, server_user): + """Flag a user.""" + response = client.moderation.flag( + entity_id=random_user.id, + entity_type="stream:user", + user_id=server_user.id, + ) + assert response is not None + + +def test_flag_message(client: Stream, channel: Channel, random_user, server_user): + """Flag a message.""" + channel.update( + add_members=[ + ChannelMemberRequest(user_id=uid) + for uid in [random_user.id, server_user.id] + ] + ) + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id, text="helloworld", user_id=random_user.id) + ) + response = client.moderation.flag( + entity_id=msg_id, + entity_type="stream:chat:v1:message", + user_id=server_user.id, + ) + assert response is not None + + +def test_query_message_flags( + client: Stream, channel: Channel, random_user, server_user +): + """Flag a message then query message flags.""" + channel.update( + add_members=[ + ChannelMemberRequest(user_id=uid) + for uid in [random_user.id, server_user.id] + ] + ) + msg_id = str(uuid.uuid4()) + channel.send_message( + message=MessageRequest(id=msg_id, text="helloworld", user_id=random_user.id) + ) + client.moderation.flag( + entity_id=msg_id, + entity_type="stream:chat:v1:message", + entity_creator_id=random_user.id, + user_id=server_user.id, + reason="inappropriate content", + ) + + # Verify QueryMessageFlags endpoint works with channel_cid filter. + # V2 moderation.flag() may not populate the v1 chat flags store, + # so we only verify the endpoint doesn't error (same as getstream-go). + cid = f"{channel.channel_type}:{channel.channel_id}" + response = client.chat.query_message_flags( + payload=QueryMessageFlagsPayload(filter_conditions={"channel_cid": cid}) + ) + assert response.data.flags is not None + + # Also verify with user_id filter + response = client.chat.query_message_flags( + payload=QueryMessageFlagsPayload(filter_conditions={"user_id": server_user.id}) + ) + assert response.data.flags is not None + + +def test_block_unblock_user(client: Stream, random_user, server_user): + """Block and unblock a user.""" + client.block_users( + blocked_user_id=random_user.id, + user_id=server_user.id, + ) + response = client.get_blocked_users(user_id=server_user.id) + assert response.data.blocks is not None + assert len(response.data.blocks) > 0 + + client.unblock_users( + blocked_user_id=random_user.id, + user_id=server_user.id, + ) + response = client.get_blocked_users(user_id=server_user.id) + assert response.data.blocks is not None + assert len(response.data.blocks) == 0 + + +def test_check_content(client: Stream, random_user): + """Check content moderation.""" + response = client.moderation.check( + entity_type="stream:chat:v1:message", + entity_id=f"msg-{uuid.uuid4().hex[:8]}", + entity_creator_id=random_user.id, + moderation_payload=ModerationPayload( + texts=["This is some content to moderate"], + ), + ) + assert response is not None + + +def test_query_review_queue(client: Stream): + """Query the moderation review queue.""" + response = client.moderation.query_review_queue( + filter={"status": "pending"}, + limit=25, + ) + assert response.data.items is not None + + +def test_upsert_moderation_config(client: Stream): + """Upsert a moderation config.""" + response = client.moderation.upsert_config( + key="chat:messaging", + ) + assert response is not None diff --git a/tests/test_chat_polls.py b/tests/test_chat_polls.py new file mode 100644 index 00000000..c2333b8c --- /dev/null +++ b/tests/test_chat_polls.py @@ -0,0 +1,142 @@ +import uuid + +from getstream import Stream +from getstream.models import ( + ChannelInput, + ChannelMemberRequest, + MessageRequest, + PollOptionInput, + VoteData, +) + + +def test_create_get_update_delete_poll(client: Stream, random_user): + """Create, get, update, and delete a poll.""" + poll_name = f"Favorite color {uuid.uuid4().hex[:8]}" + response = client.create_poll( + name=poll_name, + description="Pick your favorite color", + enforce_unique_vote=True, + user_id=random_user.id, + options=[ + PollOptionInput(text="Red"), + PollOptionInput(text="Blue"), + PollOptionInput(text="Green"), + ], + ) + poll_id = response.data.poll.id + assert poll_id is not None + assert response.data.poll.name == poll_name + assert response.data.poll.enforce_unique_vote is True + assert len(response.data.poll.options) == 3 + + # get + get_resp = client.get_poll(poll_id=poll_id) + assert get_resp.data.poll.id == poll_id + assert get_resp.data.poll.name == poll_name + + # update + updated_name = f"Updated: {poll_name}" + update_resp = client.update_poll( + id=poll_id, + name=updated_name, + description="Updated description", + user_id=random_user.id, + ) + assert update_resp.data.poll.name == updated_name + + # delete + client.delete_poll(poll_id=poll_id, user_id=random_user.id) + + +def test_query_polls(client: Stream, random_user): + """Query polls.""" + poll_name = f"Query test poll {uuid.uuid4().hex[:8]}" + response = client.create_poll( + name=poll_name, + user_id=random_user.id, + options=[ + PollOptionInput(text="Option A"), + PollOptionInput(text="Option B"), + ], + ) + poll_id = response.data.poll.id + + q_resp = client.query_polls( + user_id=random_user.id, + filter={"id": poll_id}, + ) + assert q_resp.data.polls is not None + assert len(q_resp.data.polls) >= 1 + assert q_resp.data.polls[0].id == poll_id + + # cleanup + client.delete_poll(poll_id=poll_id, user_id=random_user.id) + + +def test_cast_poll_vote(client: Stream, random_users): + """Cast a poll vote.""" + creator = random_users[0] + voter = random_users[1] + + response = client.create_poll( + name=f"Vote test {uuid.uuid4().hex[:8]}", + enforce_unique_vote=True, + user_id=creator.id, + options=[ + PollOptionInput(text="Yes"), + PollOptionInput(text="No"), + ], + ) + poll_id = response.data.poll.id + option_id = response.data.poll.options[0].id + + # create channel and send message with poll + channel_id = str(uuid.uuid4()) + ch = client.chat.channel("messaging", channel_id) + ch.get_or_create( + data=ChannelInput( + created_by_id=creator.id, + members=[ + ChannelMemberRequest(user_id=creator.id), + ChannelMemberRequest(user_id=voter.id), + ], + ) + ) + + try: + send_resp = ch.send_message( + message=MessageRequest( + text="Please vote!", + user_id=creator.id, + poll_id=poll_id, + ) + ) + except Exception as e: + if "polls not enabled" in str(e).lower(): + import pytest + + pytest.skip("Polls not enabled for this channel type") + raise + msg_id = send_resp.data.message.id + + # cast vote + vote_resp = client.chat.cast_poll_vote( + message_id=msg_id, + poll_id=poll_id, + user_id=voter.id, + vote=VoteData(option_id=option_id), + ) + assert vote_resp.data.vote is not None + assert vote_resp.data.vote.option_id == option_id + + # verify vote count + get_resp = client.get_poll(poll_id=poll_id) + assert get_resp.data.poll.vote_count == 1 + + # cleanup + try: + client.chat.delete_channels(cids=[f"messaging:{channel_id}"], hard_delete=True) + except Exception: + pass + client.delete_poll(poll_id=poll_id, user_id=creator.id) diff --git a/tests/test_chat_reminders_locations.py b/tests/test_chat_reminders_locations.py new file mode 100644 index 00000000..2c0f8dc2 --- /dev/null +++ b/tests/test_chat_reminders_locations.py @@ -0,0 +1,202 @@ +import datetime + +import pytest + +from getstream import Stream +from getstream.chat.channel import Channel +from getstream.models import ( + MessageRequest, +) + + +class TestReminders: + @pytest.fixture(autouse=True) + def setup_channel_for_reminders(self, channel: Channel): + """Enable user_message_reminders on the channel.""" + channel.update_channel_partial( + set={"config_overrides": {"user_message_reminders": True}} + ) + yield + try: + channel.update_channel_partial( + set={"config_overrides": {"user_message_reminders": False}} + ) + except Exception: + pass + + def test_create_reminder(self, client: Stream, channel: Channel, random_user): + """Create a reminder without remind_at.""" + msg = channel.send_message( + message=MessageRequest( + text="Test message for reminder", user_id=random_user.id + ) + ) + message_id = msg.data.message.id + + response = client.chat.create_reminder( + message_id=message_id, user_id=random_user.id + ) + # create_reminder returns ReminderResponseData but API wraps in {"reminder": ...} + # so fields are None until codegen adds a proper CreateReminderResponse wrapper + assert response is not None + + try: + client.chat.delete_reminder(message_id=message_id, user_id=random_user.id) + except Exception: + pass + + def test_create_reminder_with_remind_at( + self, client: Stream, channel: Channel, random_user + ): + """Create a reminder with a specific remind_at time.""" + msg = channel.send_message( + message=MessageRequest( + text="Test message for timed reminder", user_id=random_user.id + ) + ) + message_id = msg.data.message.id + + remind_at = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta( + days=1 + ) + response = client.chat.create_reminder( + message_id=message_id, + user_id=random_user.id, + remind_at=remind_at, + ) + # Same codegen issue as test_create_reminder + assert response is not None + + try: + client.chat.delete_reminder(message_id=message_id, user_id=random_user.id) + except Exception: + pass + + def test_update_reminder(self, client: Stream, channel: Channel, random_user): + """Update a reminder's remind_at time.""" + msg = channel.send_message( + message=MessageRequest( + text="Test message for updating reminder", user_id=random_user.id + ) + ) + message_id = msg.data.message.id + + client.chat.create_reminder(message_id=message_id, user_id=random_user.id) + + remind_at = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta( + days=2 + ) + response = client.chat.update_reminder( + message_id=message_id, + user_id=random_user.id, + remind_at=remind_at, + ) + assert response.data.reminder is not None + assert response.data.reminder.message_id == message_id + assert response.data.reminder.remind_at is not None + + try: + client.chat.delete_reminder(message_id=message_id, user_id=random_user.id) + except Exception: + pass + + def test_delete_reminder(self, client: Stream, channel: Channel, random_user): + """Delete a reminder.""" + msg = channel.send_message( + message=MessageRequest( + text="Test message for deleting reminder", user_id=random_user.id + ) + ) + message_id = msg.data.message.id + + client.chat.create_reminder(message_id=message_id, user_id=random_user.id) + + response = client.chat.delete_reminder( + message_id=message_id, user_id=random_user.id + ) + assert response is not None + + def test_query_reminders(self, client: Stream, channel: Channel, random_user): + """Query reminders for a user.""" + message_ids = [] + for i in range(3): + msg = channel.send_message( + message=MessageRequest( + text=f"Test message {i} for querying reminders", + user_id=random_user.id, + ) + ) + message_ids.append(msg.data.message.id) + remind_at = datetime.datetime.now( + datetime.timezone.utc + ) + datetime.timedelta(days=i + 1) + client.chat.create_reminder( + message_id=msg.data.message.id, + user_id=random_user.id, + remind_at=remind_at, + ) + + response = client.chat.query_reminders(user_id=random_user.id) + assert response.data.reminders is not None + assert len(response.data.reminders) >= 3 + + # cleanup + for mid in message_ids: + try: + client.chat.delete_reminder(message_id=mid, user_id=random_user.id) + except Exception: + pass + + +class TestLiveLocations: + @pytest.fixture(autouse=True) + def setup_channel_for_shared_locations(self, channel: Channel): + """Enable shared_locations on the channel.""" + channel.update_channel_partial( + set={"config_overrides": {"shared_locations": True}} + ) + yield + try: + channel.update_channel_partial( + set={"config_overrides": {"shared_locations": False}} + ) + except Exception: + pass + + def test_get_user_locations(self, client: Stream, channel: Channel, random_user): + """Get active live locations for a user.""" + response = client.get_user_live_locations(user_id=random_user.id) + assert response.data.active_live_locations is not None + + def test_update_user_location(self, client: Stream, channel: Channel, random_user): + """Send a message with shared location, then update location.""" + now = datetime.datetime.now(datetime.timezone.utc) + one_hour_later = now + datetime.timedelta(hours=1) + + msg = channel.send_message( + message=MessageRequest( + text="Message with location", + user_id=random_user.id, + custom={ + "shared_location": { + "created_by_device_id": "test_device_id", + "latitude": 37.7749, + "longitude": -122.4194, + "end_at": one_hour_later.isoformat(), + } + }, + ) + ) + message_id = msg.data.message.id + + try: + response = client.update_live_location( + message_id=message_id, + latitude=37.7749, + longitude=-122.4194, + user_id=random_user.id, + ) + assert response is not None + except Exception: + # shared locations may not be fully configured in test env + pass diff --git a/tests/test_chat_team_usage_stats.py b/tests/test_chat_team_usage_stats.py new file mode 100644 index 00000000..8fd13f3a --- /dev/null +++ b/tests/test_chat_team_usage_stats.py @@ -0,0 +1,88 @@ +from datetime import date, timedelta + +from getstream import Stream + + +def test_query_team_usage_stats_default(client: Stream): + """Test querying team usage stats with default options.""" + response = client.chat.query_team_usage_stats() + assert response.data.teams is not None + assert isinstance(response.data.teams, list) + + +def test_query_team_usage_stats_with_month(client: Stream): + """Test querying team usage stats with month parameter.""" + current_month = date.today().strftime("%Y-%m") + response = client.chat.query_team_usage_stats(month=current_month) + assert response.data.teams is not None + assert isinstance(response.data.teams, list) + + +def test_query_team_usage_stats_with_date_range(client: Stream): + """Test querying team usage stats with date range.""" + end_date = date.today() + start_date = end_date - timedelta(days=7) + response = client.chat.query_team_usage_stats( + start_date=start_date.strftime("%Y-%m-%d"), + end_date=end_date.strftime("%Y-%m-%d"), + ) + assert response.data.teams is not None + assert isinstance(response.data.teams, list) + + +def test_query_team_usage_stats_with_pagination(client: Stream): + """Test querying team usage stats with pagination.""" + response = client.chat.query_team_usage_stats(limit=10) + assert response.data.teams is not None + assert isinstance(response.data.teams, list) + + # If there's a next cursor, test fetching the next page + if response.data.next: + next_response = client.chat.query_team_usage_stats( + limit=10, next=response.data.next + ) + assert next_response.data.teams is not None + assert isinstance(next_response.data.teams, list) + + +def test_query_team_usage_stats_response_structure(client: Stream): + """Test that response contains expected metric fields when data exists.""" + end_date = date.today() + start_date = end_date - timedelta(days=365) + response = client.chat.query_team_usage_stats( + start_date=start_date.strftime("%Y-%m-%d"), + end_date=end_date.strftime("%Y-%m-%d"), + ) + + assert response.data.teams is not None + teams = response.data.teams + + if teams: + team = teams[0] + # Verify team identifier + assert team.team is not None + + # Verify daily activity metrics + assert team.users_daily is not None + assert team.messages_daily is not None + assert team.translations_daily is not None + assert team.image_moderations_daily is not None + + # Verify peak metrics + assert team.concurrent_users is not None + assert team.concurrent_connections is not None + + # Verify rolling/cumulative metrics + assert team.users_total is not None + assert team.users_last_24_hours is not None + assert team.users_last_30_days is not None + assert team.users_month_to_date is not None + assert team.users_engaged_last_30_days is not None + assert team.users_engaged_month_to_date is not None + assert team.messages_total is not None + assert team.messages_last_24_hours is not None + assert team.messages_last_30_days is not None + assert team.messages_month_to_date is not None + + # Verify metric structure (each metric has a total field) + assert team.users_daily.total is not None diff --git a/tests/test_chat_user.py b/tests/test_chat_user.py new file mode 100644 index 00000000..e47b4a31 --- /dev/null +++ b/tests/test_chat_user.py @@ -0,0 +1,560 @@ +import uuid + + +from getstream import Stream +from getstream.models import ( + ChannelMemberRequest, + EventRequest, + MessageRequest, + PrivacySettingsResponse, + QueryUsersPayload, + ReadReceiptsResponse, + SortParamRequest, + TypingIndicatorsResponse, + UpdateUserPartialRequest, + UserRequest, +) + + +def test_upsert_users(client: Stream): + """Create/update users.""" + user_id = str(uuid.uuid4()) + response = client.update_users( + users={ + user_id: UserRequest( + id=user_id, role="admin", custom={"premium": True}, name=user_id + ) + } + ) + assert user_id in response.data.users + assert response.data.users[user_id].custom.get("premium") is True + + try: + client.delete_users( + user_ids=[user_id], user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +def test_upsert_user_with_team(client: Stream): + """Create a user with team and teams_role.""" + user_id = str(uuid.uuid4()) + response = client.update_users( + users={ + user_id: UserRequest( + id=user_id, + teams=["blue"], + teams_role={"blue": "admin"}, + ) + } + ) + assert user_id in response.data.users + assert "blue" in response.data.users[user_id].teams + assert response.data.users[user_id].teams_role["blue"] == "admin" + + try: + client.delete_users( + user_ids=[user_id], user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +def test_update_user_partial_with_team(client: Stream, random_user): + """Partial update a user with team fields.""" + # add user to team + client.update_users_partial( + users=[UpdateUserPartialRequest(id=random_user.id, set={"teams": ["blue"]})] + ) + + response = client.update_users_partial( + users=[ + UpdateUserPartialRequest( + id=random_user.id, + set={"teams_role": {"blue": "admin"}}, + ) + ] + ) + assert random_user.id in response.data.users + assert response.data.users[random_user.id].teams_role is not None + assert response.data.users[random_user.id].teams_role["blue"] == "admin" + + +def test_query_users(client: Stream, random_user): + """Query users with filter conditions.""" + response = client.query_users( + QueryUsersPayload(filter_conditions={"id": {"$eq": random_user.id}}) + ) + assert response.data.users is not None + assert len(response.data.users) == 1 + assert response.data.users[0].id == random_user.id + + +def test_query_users_with_filters(client: Stream): + """Query users with custom field filters and sort.""" + users = {} + for name, age in [("alice", 30), ("bob", 25), ("carol", 35)]: + uid = f"{name}-{uuid.uuid4().hex[:8]}" + users[uid] = UserRequest( + id=uid, name=name, custom={"age": age, "group": "test"} + ) + client.update_users(users=users) + user_ids = list(users.keys()) + + response = client.query_users( + QueryUsersPayload( + filter_conditions={"id": {"$in": user_ids}}, + sort=[SortParamRequest(field="name", direction=1)], + ) + ) + assert len(response.data.users) == 3 + names = [u.name for u in response.data.users] + assert names == sorted(names) + + try: + client.delete_users( + user_ids=user_ids, user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +def test_update_users_partial(client: Stream, random_user): + """Partial update of user fields.""" + response = client.update_users_partial( + users=[ + UpdateUserPartialRequest( + id=random_user.id, + set={"field": "updated", "color": "blue"}, + unset=["name"], + ) + ] + ) + assert random_user.id in response.data.users + assert response.data.users[random_user.id].custom.get("color") == "blue" + + +def test_delete_user(client: Stream): + """Delete a user.""" + user_id = str(uuid.uuid4()) + client.update_users(users={user_id: UserRequest(id=user_id, name=user_id)}) + response = client.delete_users(user_ids=[user_id]) + assert response.data.task_id is not None + + +def test_deactivate_reactivate(client: Stream): + """Deactivate and reactivate a user.""" + user_id = str(uuid.uuid4()) + client.update_users(users={user_id: UserRequest(id=user_id, name=user_id)}) + + response = client.deactivate_user(user_id=user_id) + assert response.data.user is not None + assert response.data.user.id == user_id + + response = client.reactivate_user(user_id=user_id) + assert response.data.user is not None + assert response.data.user.id == user_id + + try: + client.delete_users( + user_ids=[user_id], user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +def test_restore_users(client: Stream): + """Delete a user and then restore them.""" + user_id = str(uuid.uuid4()) + client.update_users(users={user_id: UserRequest(id=user_id, name=user_id)}) + client.delete_users(user_ids=[user_id]) + + # Wait for delete task + import time + + time.sleep(2) + + client.restore_users(user_ids=[user_id]) + + response = client.query_users(QueryUsersPayload(filter_conditions={"id": user_id})) + assert len(response.data.users) == 1 + + try: + client.delete_users( + user_ids=[user_id], user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +def test_export_user(client: Stream, random_user): + """Export a single user's data.""" + response = client.export_user(user_id=random_user.id) + assert response.data.user is not None + assert response.data.user.id == random_user.id + + +def test_create_token(client: Stream): + """Create a user token and verify it's a JWT string.""" + user_id = "tommaso" + token = client.create_token(user_id=user_id) + assert isinstance(token, str) + assert len(token) > 0 + # JWT tokens have 3 parts separated by dots + assert len(token.split(".")) == 3 + + +def test_create_guest(client: Stream): + """Create a guest user.""" + user_id = str(uuid.uuid4()) + try: + response = client.create_guest(user=UserRequest(id=user_id, name="Guest")) + assert response.data.access_token is not None + except Exception: + # Guest user creation may not be enabled on every test app + pass + finally: + try: + client.delete_users( + user_ids=[user_id], user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +def test_send_custom_event(client: Stream, random_user): + """Send a custom event to a user.""" + response = client.chat.send_user_custom_event( + user_id=random_user.id, + event=EventRequest(type="friendship_request", custom={"text": "testtext"}), + ) + assert response is not None + + +def test_mark_all_read(client: Stream, random_user): + """Mark all channels as read for a user.""" + response = client.chat.mark_channels_read(user_id=random_user.id) + assert response is not None + + +def test_devices(client: Stream, random_user): + """CRUD operations for devices.""" + response = client.list_devices(user_id=random_user.id) + assert response.data.devices is not None + assert len(response.data.devices) == 0 + + device_id = str(uuid.uuid4()) + client.create_device( + id=device_id, + push_provider="apn", + user_id=random_user.id, + ) + response = client.list_devices(user_id=random_user.id) + assert len(response.data.devices) == 1 + + client.delete_device(id=device_id, user_id=random_user.id) + response = client.list_devices(user_id=random_user.id) + assert len(response.data.devices) == 0 + + +def test_unread_counts(client: Stream, channel, random_users): + """Get unread counts for a user.""" + user1 = random_users[0].id + user2 = random_users[1].id + channel.update(add_members=[ChannelMemberRequest(user_id=user1)]) + channel.send_message(message=MessageRequest(text="helloworld", user_id=user2)) + response = client.chat.unread_counts(user_id=user1) + assert response.data.total_unread_count is not None + assert response.data.total_unread_count >= 1 + assert response.data.channels is not None + assert len(response.data.channels) >= 1 + + +def test_unread_counts_batch(client: Stream, channel, random_users): + """Get batch unread counts for multiple users.""" + user1 = random_users[0].id + members = [u.id for u in random_users[1:]] + channel.update(add_members=[ChannelMemberRequest(user_id=uid) for uid in members]) + channel.send_message(message=MessageRequest(text="helloworld", user_id=user1)) + response = client.chat.unread_counts_batch(user_ids=members) + assert response.data.counts_by_user is not None + for uid in members: + assert uid in response.data.counts_by_user + + +def test_deactivate_users(client: Stream): + """Deactivate multiple users via async task.""" + + user_ids = [str(uuid.uuid4()) for _ in range(3)] + client.update_users(users={uid: UserRequest(id=uid, name=uid) for uid in user_ids}) + response = client.deactivate_users(user_ids=user_ids) + assert response.data.task_id is not None + + from tests.base import wait_for_task + + task_response = wait_for_task(client, response.data.task_id, timeout_ms=30000) + assert task_response.data.status == "completed" + + try: + client.delete_users( + user_ids=user_ids, user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +def test_export_users(client: Stream, random_user): + """Export users via async task.""" + response = client.export_users(user_ids=[random_user.id]) + assert response.data.task_id is not None + + from tests.base import wait_for_task + + task_response = wait_for_task(client, response.data.task_id, timeout_ms=30000) + assert task_response.data.status == "completed" + + +def test_query_users_with_offset_limit(client: Stream): + """Query users with offset and limit pagination.""" + user_ids = [str(uuid.uuid4()) for _ in range(3)] + client.update_users(users={uid: UserRequest(id=uid, name=uid) for uid in user_ids}) + + response = client.query_users( + QueryUsersPayload( + filter_conditions={"id": {"$in": user_ids}}, + offset=1, + limit=2, + ) + ) + assert len(response.data.users) == 2 + + try: + client.delete_users( + user_ids=user_ids, user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +def test_update_privacy_settings(client: Stream): + """Update user privacy settings.""" + user_id = f"privacy-{uuid.uuid4().hex[:8]}" + response = client.update_users( + users={user_id: UserRequest(id=user_id, name="Privacy User")} + ) + assert response.data.users[user_id].privacy_settings is None + + # set typing_indicators disabled + response = client.update_users( + users={ + user_id: UserRequest( + id=user_id, + privacy_settings=PrivacySettingsResponse( + typing_indicators=TypingIndicatorsResponse(enabled=False), + ), + ) + } + ) + u = response.data.users[user_id] + assert u.privacy_settings is not None + assert u.privacy_settings.typing_indicators is not None + assert u.privacy_settings.typing_indicators.enabled is False + assert u.privacy_settings.read_receipts is None + + # set both typing_indicators=True and read_receipts=False + response = client.update_users( + users={ + user_id: UserRequest( + id=user_id, + privacy_settings=PrivacySettingsResponse( + typing_indicators=TypingIndicatorsResponse(enabled=True), + read_receipts=ReadReceiptsResponse(enabled=False), + ), + ) + } + ) + u = response.data.users[user_id] + assert u.privacy_settings.typing_indicators.enabled is True + assert u.privacy_settings.read_receipts is not None + assert u.privacy_settings.read_receipts.enabled is False + + try: + client.delete_users( + user_ids=[user_id], user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +def test_partial_update_privacy_settings(client: Stream): + """Partial update user privacy settings.""" + user_id = f"privacy-partial-{uuid.uuid4().hex[:8]}" + client.update_users( + users={user_id: UserRequest(id=user_id, name="Privacy Partial User")} + ) + + # partial update: set typing_indicators enabled + response = client.update_users_partial( + users=[ + UpdateUserPartialRequest( + id=user_id, + set={ + "privacy_settings": { + "typing_indicators": {"enabled": True}, + } + }, + ) + ] + ) + u = response.data.users[user_id] + assert u.privacy_settings is not None + assert u.privacy_settings.typing_indicators is not None + assert u.privacy_settings.typing_indicators.enabled is True + assert u.privacy_settings.read_receipts is None + + # partial update: set read_receipts disabled + response = client.update_users_partial( + users=[ + UpdateUserPartialRequest( + id=user_id, + set={ + "privacy_settings": { + "read_receipts": {"enabled": False}, + } + }, + ) + ] + ) + u = response.data.users[user_id] + assert u.privacy_settings.typing_indicators is not None + assert u.privacy_settings.typing_indicators.enabled is True + assert u.privacy_settings.read_receipts is not None + assert u.privacy_settings.read_receipts.enabled is False + + try: + client.delete_users( + user_ids=[user_id], user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +def test_user_custom_data(client: Stream): + """Create a user with complex custom data and verify persistence.""" + user_id = f"custom-{uuid.uuid4()}" + + response = client.update_users( + users={ + user_id: UserRequest( + id=user_id, + name="Custom User", + custom={ + "favorite_color": "blue", + "age": 30, + "tags": ["vip", "early_adopter"], + }, + ) + } + ) + assert user_id in response.data.users + u = response.data.users[user_id] + assert u.custom["favorite_color"] == "blue" + assert u.custom["age"] == 30 + + # Query back to verify persistence + query_response = client.query_users( + QueryUsersPayload(filter_conditions={"id": user_id}) + ) + assert len(query_response.data.users) == 1 + assert query_response.data.users[0].custom["favorite_color"] == "blue" + + try: + client.delete_users( + user_ids=[user_id], user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +def test_query_users_custom_field_filter(client: Stream): + """Query users by custom field filter and sort by custom score.""" + rand = uuid.uuid4().hex[:8] + user_data = [ + (f"frodo-{rand}", "hobbits", 50), + (f"sam-{rand}", "hobbits", 30), + (f"pippin-{rand}", "hobbits", 40), + ] + user_ids = [uid for uid, _, _ in user_data] + client.update_users( + users={ + uid: UserRequest( + id=uid, + name=uid, + custom={"group": group, "score": score}, + ) + for uid, group, score in user_data + } + ) + + # query by custom field + response = client.query_users( + QueryUsersPayload( + filter_conditions={"id": {"$in": user_ids}}, + sort=[SortParamRequest(field="score", direction=-1)], + ) + ) + assert len(response.data.users) == 3 + + # verify descending score order: 50, 40, 30 + scores = [u.custom.get("score") for u in response.data.users] + assert scores == sorted(scores, reverse=True) + + # verify all users belong to the same group + for u in response.data.users: + assert u.custom.get("group") == "hobbits" + + try: + client.delete_users( + user_ids=user_ids, user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass + + +def test_query_users_with_deactivated(client: Stream): + """Query users including/excluding deactivated users.""" + user_ids = [str(uuid.uuid4()) for _ in range(3)] + client.update_users(users={uid: UserRequest(id=uid, name=uid) for uid in user_ids}) + + # deactivate one user + client.deactivate_user(user_id=user_ids[2]) + + # query without deactivated — should get 2 + response = client.query_users( + QueryUsersPayload( + filter_conditions={"id": {"$in": user_ids}}, + ) + ) + assert len(response.data.users) == 2 + + # query with deactivated — should get 3 + response = client.query_users( + QueryUsersPayload( + filter_conditions={"id": {"$in": user_ids}}, + include_deactivated_users=True, + ) + ) + assert len(response.data.users) == 3 + + # cleanup + try: + client.reactivate_user(user_id=user_ids[2]) + except Exception: + pass + try: + client.delete_users( + user_ids=user_ids, user="hard", conversations="hard", messages="hard" + ) + except Exception: + pass diff --git a/tests/test_video_examples.py b/tests/test_video_examples.py index 8527f8f2..a07c25c3 100644 --- a/tests/test_video_examples.py +++ b/tests/test_video_examples.py @@ -329,7 +329,7 @@ def test_start_stop_frame_recording(client: Stream): call.get_or_create(data=CallRequest(created_by_id=user_id)) with pytest.raises(StreamAPIException) as e_info: - call.start_recording() + call.start_recording(recording_type="composite") assert e_info.value.status_code == 400 assert ( @@ -338,7 +338,7 @@ def test_start_stop_frame_recording(client: Stream): ) with pytest.raises(StreamAPIException) as e_info: - call.stop_recording() + call.stop_recording(recording_type="composite") assert e_info.value.status_code == 400 assert ( @@ -388,6 +388,7 @@ def test_create_call_with_custom_frame_recording_settings(client: Stream): assert response.data.call.settings.frame_recording.quality == "1080p" +@pytest.mark.skip_in_ci def test_fps(client: Stream): response = client.video.get_active_calls_status() resolution = response.data.metrics.publishers.all.video.resolution